- Add core VRM addon infrastructure with manifest and registration - Add common utilities module with file system, logging, and conversion helpers - Add human bone mapper with support for multiple rigging standards (Mixamo, MMD, Unreal, Rigify, etc.) - Add VRM 0.x and 1.x format support with property groups and handlers - Add editor UI panels for VRM metadata, spring bones, and MToon materials - Add exporter with glTF2 extension support for VRM format serialization - Add importer with scene reconstruction and armature generation - Add MToon shader support with auto-setup and material migration - Add spring bone physics simulation with constraint handling - Add node constraint editor for advanced rigging control - Add comprehensive validation and error handling with user dialogs - Add scene watcher for real-time property synchronization - Add workspace management and preference system - Include Python cache files and Blender manifest configuration - This is the initial commit establishing the complete VRM addon ecosystem for Blender
3781 lines
132 KiB
Python
3781 lines
132 KiB
Python
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
|
import re
|
|
import sys
|
|
from collections.abc import Mapping, Sequence
|
|
from dataclasses import dataclass
|
|
from typing import TYPE_CHECKING, Callable, Final, Optional, Protocol, Union
|
|
|
|
import bpy
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
EnumProperty,
|
|
FloatProperty,
|
|
FloatVectorProperty,
|
|
IntProperty,
|
|
IntVectorProperty,
|
|
PointerProperty,
|
|
)
|
|
from bpy.types import (
|
|
Context,
|
|
Driver,
|
|
FCurve,
|
|
Image,
|
|
Material,
|
|
Node,
|
|
NodeReroute,
|
|
NodeSocketColor,
|
|
NodeSocketFloat,
|
|
PropertyGroup,
|
|
ShaderNodeBsdfPrincipled,
|
|
ShaderNodeEmission,
|
|
ShaderNodeGroup,
|
|
ShaderNodeMath,
|
|
ShaderNodeNormalMap,
|
|
ShaderNodeOutputMaterial,
|
|
ShaderNodeTexImage,
|
|
)
|
|
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
|
from mathutils import Vector
|
|
|
|
from ...common import convert, ops, shader
|
|
from ...common.gl import (
|
|
GL_CLAMP_TO_EDGE,
|
|
GL_LINEAR,
|
|
GL_LINEAR_MIPMAP_LINEAR,
|
|
GL_LINEAR_MIPMAP_NEAREST,
|
|
GL_MIRRORED_REPEAT,
|
|
GL_NEAREST,
|
|
GL_NEAREST_MIPMAP_LINEAR,
|
|
GL_NEAREST_MIPMAP_NEAREST,
|
|
GL_REPEAT,
|
|
)
|
|
from ...common.logger import get_logger
|
|
from ...common.preferences import VrmAddonPreferences
|
|
from ...common.version import get_addon_version
|
|
from ..property_group import property_group_enum
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
MTOON1_OUTPUT_NODE_GROUP_NAME: Final = "Mtoon1Material.Mtoon1Output"
|
|
|
|
PRINCIPLED_BSDF_BASE_COLOR_INPUT_KEY: Final = "Base Color"
|
|
PRINCIPLED_BSDF_ALPHA_INPUT_KEY: Final = "Alpha"
|
|
PRINCIPLED_BSDF_EMISSION_INPUT_KEY: Final = (
|
|
"Emission Color" if bpy.app.version >= (4,) else "Emission"
|
|
)
|
|
PRINCIPLED_BSDF_EMISSION_STRENGTH_INPUT_KEY: Final = "Emission Strength"
|
|
PRINCIPLED_BSDF_NORMAL_INPUT_KEY: Final = "Normal"
|
|
NORMAL_MAP_COLOR_INPUT_KEY: Final = "Color"
|
|
EMISSION_COLOR_INPUT_KEY: Final = "Color"
|
|
EMISSION_STRENGTH_INPUT_KEY: Final = "Strength"
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: Final = "Color"
|
|
TEX_IMAGE_ALPHA_OUTPUT_KEY: Final = "Alpha"
|
|
|
|
IMAGE_INTERPOLATION_CLOSEST: Final = "Closest"
|
|
IMAGE_INTERPOLATION_LINEAR: Final = "Linear"
|
|
IMAGE_INTERPOLATION_CUBIC: Final = "Cubic"
|
|
IMAGE_INTERPOLATION_SMART: Final = "Smart"
|
|
GL_LINEAR_IMAGE_INTERPOLATIONS: Final = (
|
|
IMAGE_INTERPOLATION_LINEAR,
|
|
IMAGE_INTERPOLATION_CUBIC,
|
|
IMAGE_INTERPOLATION_SMART,
|
|
)
|
|
|
|
ALPHA_CLIP_INPUT_NODE_NAME: Final = "Mtoon1Material.AlphaClip.Input"
|
|
ALPHA_CLIP_INPUT_NODE_SOCKET_NAME: Final = "Value"
|
|
ALPHA_CLIP_OUTPUT_NODE_NAME: Final = "Mtoon1Material.AlphaClip.Output"
|
|
ALPHA_CLIP_OUTPUT_NODE_SOCKET_NAME: Final = "Value"
|
|
|
|
|
|
def get_gltf_emissive_node(material: Material) -> Optional[ShaderNodeEmission]:
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return None
|
|
return next(
|
|
(
|
|
node
|
|
for node in node_tree.nodes
|
|
if isinstance(node, ShaderNodeEmission)
|
|
and node.name == "Mtoon1Material.GltfEmissive"
|
|
),
|
|
None,
|
|
)
|
|
|
|
|
|
class NodeSocketTarget(Protocol):
|
|
def get_in_socket_name(self) -> str: ...
|
|
|
|
def create_node_selector(self, material: Material) -> Callable[[Node], bool]: ...
|
|
|
|
|
|
class PrincipledBsdfNodeSocketTarget(NodeSocketTarget):
|
|
def __init__(self, *, in_socket_name: str) -> None:
|
|
self.in_socket_name = in_socket_name
|
|
|
|
def get_in_socket_name(self) -> str:
|
|
return self.in_socket_name
|
|
|
|
@staticmethod
|
|
def get_node_name(material: Material) -> Optional[str]:
|
|
# node has a short lifetime on the native side and is dangerous, so avoid
|
|
# exposing it outside the function
|
|
node = PrincipledBSDFWrapper(material).node_principled_bsdf
|
|
if node is None:
|
|
return None
|
|
# Use intern to avoid using str directly referenced from node that will be
|
|
# destroyed in the future
|
|
# Previously, using str referenced from destroyed Bone caused problems,
|
|
# so this is a precautionary measure, though it might be overly cautious
|
|
return sys.intern(node.name)
|
|
|
|
def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
|
|
name = self.get_node_name(material)
|
|
if name is None:
|
|
return lambda _: False
|
|
return (
|
|
lambda node: isinstance(node, ShaderNodeBsdfPrincipled)
|
|
and node.name == name
|
|
)
|
|
|
|
|
|
class StaticNodeSocketTarget(NodeSocketTarget):
|
|
def __init__(
|
|
self, *, in_node_name: str, in_node_type: type[Node], in_socket_name: str
|
|
) -> None:
|
|
self.in_node_name = in_node_name
|
|
self.in_node_type = in_node_type
|
|
self.in_socket_name = in_socket_name
|
|
|
|
def get_in_socket_name(self) -> str:
|
|
return self.in_socket_name
|
|
|
|
def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
|
|
_ = material
|
|
return (
|
|
lambda node: isinstance(node, self.in_node_type)
|
|
and node.name == self.in_node_name
|
|
)
|
|
|
|
|
|
class PrincipledBsdfNormalMapNodeSocketTarget(NodeSocketTarget):
|
|
def get_in_socket_name(self) -> str:
|
|
return NORMAL_MAP_COLOR_INPUT_KEY
|
|
|
|
@staticmethod
|
|
def get_node_name(material: Material) -> Optional[str]:
|
|
# node has a short lifetime on the native side and is dangerous, so avoid
|
|
# exposing it outside the function
|
|
node = PrincipledBSDFWrapper(material).node_normalmap
|
|
if node is None:
|
|
return None
|
|
# Use intern to avoid using str directly referenced from node that will be
|
|
# destroyed in the future
|
|
# Previously, using str referenced from destroyed Bone caused problems,
|
|
# so this is a precautionary measure, though it might be overly cautious
|
|
return sys.intern(node.name)
|
|
|
|
def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
|
|
name = self.get_node_name(material)
|
|
if name is None:
|
|
return lambda _: False
|
|
return lambda node: isinstance(node, ShaderNodeNormalMap) and node.name == name
|
|
|
|
|
|
class GltfEmissionNodeSocketTarget(NodeSocketTarget):
|
|
def get_in_socket_name(self) -> str:
|
|
return EMISSION_COLOR_INPUT_KEY
|
|
|
|
def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
|
|
_ = material
|
|
# https://github.com/KhronosGroup/glTF-Blender-IO/pull/740
|
|
return lambda node: isinstance(node, ShaderNodeEmission)
|
|
|
|
|
|
class NodeGroupSocketTarget(NodeSocketTarget):
|
|
def __init__(self, *, node_group_node_tree_name: str, in_socket_name: str) -> None:
|
|
self.node_group_node_tree_name = node_group_node_tree_name
|
|
self.in_socket_name = in_socket_name
|
|
|
|
def get_in_socket_name(self) -> str:
|
|
return self.in_socket_name
|
|
|
|
def select_node(self, node: Node) -> bool:
|
|
if not isinstance(node, ShaderNodeGroup):
|
|
return False
|
|
node_tree = node.node_tree
|
|
if not node_tree:
|
|
return False
|
|
return node_tree.name == self.node_group_node_tree_name
|
|
|
|
def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
|
|
_ = material
|
|
return self.select_node
|
|
|
|
|
|
class MaterialTraceablePropertyGroup(PropertyGroup):
|
|
def find_material(self) -> Material:
|
|
id_data = self.id_data
|
|
if isinstance(id_data, Material):
|
|
return id_data
|
|
|
|
message = (
|
|
f"{type(self)}/{self}.id_data is not a {Material}"
|
|
+ f" but {type(id_data)}/{id_data}"
|
|
)
|
|
raise AssertionError(message)
|
|
|
|
def find_outline_property_group(
|
|
self, material: Material
|
|
) -> Optional["MaterialTraceablePropertyGroup"]:
|
|
if get_material_mtoon1_extension(material).is_outline_material:
|
|
return None
|
|
outline_material = get_material_mtoon1_extension(material).outline_material
|
|
if not outline_material:
|
|
return None
|
|
if material.name == outline_material.name:
|
|
logger.error(
|
|
"Base material and outline material are same. name={material.name}"
|
|
)
|
|
return None
|
|
path_from_id = self.path_from_id()
|
|
property_group = outline_material.path_resolve(path_from_id, False)
|
|
if isinstance(property_group, MaterialTraceablePropertyGroup):
|
|
return property_group
|
|
message = f"No matching property group: {type(self)}/{self} {path_from_id}"
|
|
raise AssertionError(message)
|
|
|
|
def get_bool(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
*,
|
|
default_value: bool,
|
|
) -> bool:
|
|
value = self.get_value(
|
|
node_group_name, group_label, default_value=int(default_value)
|
|
)
|
|
if isinstance(value, float):
|
|
return abs(value) > 0.000001
|
|
return bool(value)
|
|
|
|
def get_float(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
*,
|
|
default_value: float,
|
|
) -> float:
|
|
value = self.get_value(
|
|
node_group_name, group_label, default_value=default_value
|
|
)
|
|
return float(value)
|
|
|
|
def get_int(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
*,
|
|
default_value: int,
|
|
) -> int:
|
|
value = self.get_value(
|
|
node_group_name, group_label, default_value=default_value
|
|
)
|
|
if isinstance(value, bool):
|
|
return int(value)
|
|
if isinstance(value, int):
|
|
return value
|
|
return round(value)
|
|
|
|
def get_value(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
*,
|
|
default_value: float,
|
|
) -> Union[float, int, bool]:
|
|
material = self.find_material()
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return default_value
|
|
|
|
node = next(
|
|
(
|
|
node
|
|
for node in node_tree.nodes
|
|
if isinstance(node, ShaderNodeGroup)
|
|
and node.node_tree
|
|
and node.node_tree.name == node_group_name
|
|
),
|
|
None,
|
|
)
|
|
if not node:
|
|
return default_value
|
|
|
|
socket = node.inputs.get(group_label)
|
|
if isinstance(
|
|
socket,
|
|
(
|
|
*shader.BOOL_SOCKET_CLASSES,
|
|
*shader.FLOAT_SOCKET_CLASSES,
|
|
*shader.INT_SOCKET_CLASSES,
|
|
),
|
|
):
|
|
return socket.default_value
|
|
|
|
return default_value
|
|
|
|
def get_rgb(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
*,
|
|
default_value: tuple[float, float, float],
|
|
) -> tuple[float, float, float]:
|
|
material = self.find_material()
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return default_value
|
|
|
|
node = next(
|
|
(
|
|
node
|
|
for node in node_tree.nodes
|
|
if isinstance(node, ShaderNodeGroup)
|
|
and node.node_tree
|
|
and node.node_tree.name == node_group_name
|
|
),
|
|
None,
|
|
)
|
|
if not node:
|
|
return default_value
|
|
|
|
socket = node.inputs.get(group_label)
|
|
if isinstance(socket, shader.COLOR_SOCKET_CLASSES):
|
|
return (
|
|
socket.default_value[0],
|
|
socket.default_value[1],
|
|
socket.default_value[2],
|
|
)
|
|
|
|
return default_value
|
|
|
|
def set_value(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
value_obj: object,
|
|
) -> None:
|
|
if isinstance(value_obj, float):
|
|
value = convert.float_or_none(value_obj)
|
|
if value is None:
|
|
return
|
|
elif isinstance(value_obj, int):
|
|
value = value_obj
|
|
else:
|
|
return
|
|
|
|
material = self.find_material()
|
|
outline = self.find_outline_property_group(material)
|
|
if outline:
|
|
outline.set_value(node_group_name, group_label, value)
|
|
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return
|
|
|
|
node = next(
|
|
(
|
|
node
|
|
for node in node_tree.nodes
|
|
if isinstance(node, ShaderNodeGroup)
|
|
and node.node_tree
|
|
and node.node_tree.name == node_group_name
|
|
),
|
|
None,
|
|
)
|
|
if not node:
|
|
logger.warning('No group node "%s"', node_group_name)
|
|
return
|
|
|
|
socket = node.inputs.get(group_label)
|
|
if isinstance(socket, shader.BOOL_SOCKET_CLASSES):
|
|
socket.default_value = bool(value)
|
|
elif isinstance(socket, shader.FLOAT_SOCKET_CLASSES):
|
|
socket.default_value = float(value)
|
|
elif isinstance(socket, shader.INT_SOCKET_CLASSES):
|
|
socket.default_value = int(value)
|
|
else:
|
|
logger.warning(
|
|
'No "%s" in shader node group "%s"', group_label, node_group_name
|
|
)
|
|
|
|
def set_bool(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
value: object,
|
|
) -> None:
|
|
self.set_value(node_group_name, group_label, 1 if value else 0)
|
|
|
|
def set_int(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
value: object,
|
|
) -> None:
|
|
self.set_value(node_group_name, group_label, value)
|
|
|
|
def set_rgba(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
value: object,
|
|
default_value: Optional[tuple[float, float, float, float]] = None,
|
|
) -> None:
|
|
material = self.find_material()
|
|
|
|
outline = self.find_outline_property_group(material)
|
|
if outline:
|
|
outline.set_rgba(node_group_name, group_label, value, default_value)
|
|
|
|
if not default_value:
|
|
default_value = (0.0, 0.0, 0.0, 0.0)
|
|
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return
|
|
|
|
rgba = shader.rgba_or_none(value) or default_value
|
|
|
|
node = next(
|
|
(
|
|
node
|
|
for node in node_tree.nodes
|
|
if isinstance(node, ShaderNodeGroup)
|
|
and node.node_tree
|
|
and node.node_tree.name == node_group_name
|
|
),
|
|
None,
|
|
)
|
|
if not node:
|
|
logger.warning('No group node "%s"', node_group_name)
|
|
return
|
|
|
|
socket = node.inputs.get(group_label)
|
|
if not isinstance(socket, shader.COLOR_SOCKET_CLASSES):
|
|
logger.warning(
|
|
'No "%s" in shader node group "%s"', group_label, node_group_name
|
|
)
|
|
return
|
|
|
|
socket.default_value = rgba
|
|
|
|
def set_rgb(
|
|
self,
|
|
node_group_name: str,
|
|
group_label: str,
|
|
value: object,
|
|
default_value: Optional[tuple[float, float, float]] = None,
|
|
) -> None:
|
|
material = self.find_material()
|
|
|
|
outline = self.find_outline_property_group(material)
|
|
if outline:
|
|
outline.set_rgb(node_group_name, group_label, value, default_value)
|
|
|
|
if not default_value:
|
|
default_value = (0.0, 0.0, 0.0)
|
|
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return
|
|
|
|
rgb = shader.rgb_or_none(value) or default_value
|
|
|
|
node = next(
|
|
(
|
|
node
|
|
for node in node_tree.nodes
|
|
if isinstance(node, ShaderNodeGroup)
|
|
and node.node_tree
|
|
and node.node_tree.name == node_group_name
|
|
),
|
|
None,
|
|
)
|
|
if not node:
|
|
logger.warning('No group node "%s"', node_group_name)
|
|
return
|
|
|
|
socket = node.inputs.get(group_label)
|
|
if not isinstance(socket, shader.COLOR_SOCKET_CLASSES):
|
|
logger.warning(
|
|
'No "%s" in shader node group "%s"', group_label, node_group_name
|
|
)
|
|
return
|
|
|
|
socket.default_value = (*rgb, 1.0)
|
|
|
|
|
|
class TextureTraceablePropertyGroup(MaterialTraceablePropertyGroup):
|
|
def get_texture_info_property_group(self) -> "Mtoon1TextureInfoPropertyGroup":
|
|
chain = self.path_from_id().split(".")
|
|
if chain[-1:] == ["sampler"]:
|
|
chain = chain[:-1]
|
|
if chain[-1:] == ["index"]:
|
|
chain = chain[:-1]
|
|
if chain[-1:] == ["khr_texture_transform"]:
|
|
chain = chain[:-1]
|
|
if chain[-1:] == ["extensions"]:
|
|
chain = chain[:-1]
|
|
material = self.find_material()
|
|
property_group = material.path_resolve(".".join(chain), False)
|
|
if not isinstance(property_group, Mtoon1TextureInfoPropertyGroup):
|
|
message = f"{property_group} is not a Mtoon1TextureInfoPropertyGroup"
|
|
raise TypeError(message)
|
|
return property_group
|
|
|
|
def get_texture_node_name(self, extra: str) -> str:
|
|
texture_info = self.get_texture_info_property_group()
|
|
name = type(texture_info.index).__name__
|
|
return re.sub("PropertyGroup$", "", name) + "." + extra
|
|
|
|
def get_image_texture_node_name(self) -> str:
|
|
return self.get_texture_node_name("Image")
|
|
|
|
def get_image_texture_uv_node_name(self) -> str:
|
|
return self.get_texture_node_name("Uv")
|
|
|
|
@classmethod
|
|
def link_nodes(
|
|
cls,
|
|
material: Material,
|
|
out_node_name: str,
|
|
out_node_type: type[Node],
|
|
out_node_socket_name: str,
|
|
node_socket_target: NodeSocketTarget,
|
|
) -> None:
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return
|
|
|
|
select_in_node = node_socket_target.create_node_selector(material)
|
|
in_socket_name = node_socket_target.get_in_socket_name()
|
|
|
|
# Check if already connected
|
|
connection_check_node = next(
|
|
(
|
|
link.from_node
|
|
for link in node_tree.links
|
|
if select_in_node(link.to_node)
|
|
and link.to_socket
|
|
and link.to_socket.name == in_socket_name
|
|
),
|
|
None,
|
|
)
|
|
while connection_check_node:
|
|
if (
|
|
isinstance(connection_check_node, out_node_type)
|
|
and connection_check_node.name == out_node_name
|
|
):
|
|
return
|
|
if isinstance(connection_check_node, NodeReroute):
|
|
connection_check_inputs = connection_check_node.inputs
|
|
if len(connection_check_inputs) == 0:
|
|
break
|
|
connection_check_links = connection_check_inputs[0].links
|
|
if len(connection_check_links) == 0:
|
|
break
|
|
connection_check_link = connection_check_links[0]
|
|
connection_check_node = connection_check_link.from_node
|
|
continue
|
|
break
|
|
|
|
# If connected to unrelated node, disconnect the link
|
|
cls.unlink_nodes(material, node_socket_target)
|
|
|
|
# Search for output node and socket
|
|
out_node = node_tree.nodes.get(out_node_name)
|
|
if not isinstance(out_node, out_node_type):
|
|
logger.error("No output node: %s", out_node_name)
|
|
return
|
|
out_socket = out_node.outputs.get(out_node_socket_name)
|
|
if not out_socket:
|
|
logger.error("No output node socket: %s", out_node_socket_name)
|
|
return
|
|
traversing_sockets = [out_socket]
|
|
while traversing_sockets:
|
|
traversing_socket = traversing_sockets.pop()
|
|
for connection_check_link in traversing_socket.links:
|
|
connection_check_node = connection_check_link.to_node
|
|
if not isinstance(connection_check_node, NodeReroute):
|
|
continue
|
|
if connection_check_node.label != in_socket_name:
|
|
traversing_sockets.extend(connection_check_node.outputs)
|
|
continue
|
|
traversing_outputs = connection_check_node.outputs
|
|
if len(traversing_outputs) == 0:
|
|
continue
|
|
out_socket = traversing_outputs[0]
|
|
out_node = connection_check_node
|
|
break
|
|
|
|
# Search for input node and socket
|
|
in_node = next(
|
|
(n for n in node_tree.nodes if select_in_node(n)),
|
|
None,
|
|
)
|
|
if not in_node:
|
|
logger.error("No input node")
|
|
return
|
|
|
|
in_socket = in_node.inputs.get(in_socket_name)
|
|
if not in_socket:
|
|
logger.error("No input socket: %s", in_socket_name)
|
|
return
|
|
|
|
node_tree.links.new(in_socket, out_socket)
|
|
|
|
@classmethod
|
|
def unlink_nodes(
|
|
cls,
|
|
material: Material,
|
|
node_socket_target: NodeSocketTarget,
|
|
) -> None:
|
|
while True:
|
|
# Refresh in_node/out_node. These nodes may be invalidated.
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return
|
|
|
|
select_in_node = node_socket_target.create_node_selector(material)
|
|
in_socket_name = node_socket_target.get_in_socket_name()
|
|
disconnecting_link = next(
|
|
(
|
|
link
|
|
for link in node_tree.links
|
|
if select_in_node(link.to_node)
|
|
and link.to_socket
|
|
and link.to_socket.name == in_socket_name
|
|
),
|
|
None,
|
|
)
|
|
if not disconnecting_link:
|
|
return
|
|
|
|
node_tree.links.remove(disconnecting_link)
|
|
|
|
@classmethod
|
|
def link_or_unlink_nodes(
|
|
cls,
|
|
material: Material,
|
|
out_node_name: str,
|
|
out_node_type: type[Node],
|
|
out_node_socket_name: str,
|
|
node_socket_target: NodeSocketTarget,
|
|
*,
|
|
link: bool,
|
|
) -> None:
|
|
if link:
|
|
cls.link_nodes(
|
|
material,
|
|
out_node_name,
|
|
out_node_type,
|
|
out_node_socket_name,
|
|
node_socket_target,
|
|
)
|
|
else:
|
|
cls.unlink_nodes(
|
|
material,
|
|
node_socket_target,
|
|
)
|
|
|
|
def update_image(self, image: Optional[Image]) -> None:
|
|
material = self.find_material()
|
|
|
|
outline = self.find_outline_property_group(material)
|
|
if outline and isinstance(outline, TextureTraceablePropertyGroup):
|
|
outline.update_image(image)
|
|
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return
|
|
|
|
node_name = self.get_image_texture_node_name()
|
|
|
|
node = node_tree.nodes.get(node_name)
|
|
if not isinstance(node, ShaderNodeTexImage):
|
|
logger.warning('No shader node tex image "%s"', node_name)
|
|
return
|
|
|
|
node.image = image
|
|
|
|
texture_info = self.get_texture_info_property_group()
|
|
|
|
for (
|
|
output_socket_name,
|
|
node_socket_targets,
|
|
) in texture_info.node_socket_targets.items():
|
|
for node_socket_target in node_socket_targets:
|
|
self.link_or_unlink_nodes(
|
|
material,
|
|
node_name,
|
|
ShaderNodeTexImage,
|
|
output_socket_name,
|
|
node_socket_target,
|
|
link=bool(image),
|
|
)
|
|
|
|
texture_info.setup_drivers(material)
|
|
|
|
def get_connected_node_image(self) -> Optional[Image]:
|
|
material = self.find_material()
|
|
if bpy.app.version < (5, 0, 0) and not material.use_nodes:
|
|
return None
|
|
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return None
|
|
|
|
texture_info = self.get_texture_info_property_group()
|
|
node_socket_targets = next(
|
|
iter(texture_info.node_socket_targets.values()), None
|
|
)
|
|
if not node_socket_targets:
|
|
return None
|
|
|
|
node_socket_target = next(iter(node_socket_targets), None)
|
|
if not node_socket_target:
|
|
return None
|
|
|
|
node_selector = node_socket_target.create_node_selector(material)
|
|
node = next((node for node in node_tree.nodes if node_selector(node)), None)
|
|
if not node:
|
|
return None
|
|
|
|
traversing_input_socket = None
|
|
for input_socket in node.inputs:
|
|
if input_socket.name == node_socket_target.get_in_socket_name():
|
|
traversing_input_socket = input_socket
|
|
break
|
|
|
|
while True:
|
|
if not traversing_input_socket:
|
|
return None
|
|
|
|
links_to_input_socket = traversing_input_socket.links
|
|
traversing_input_socket = None
|
|
for link_to_input_socket in links_to_input_socket:
|
|
from_node = link_to_input_socket.from_node
|
|
if isinstance(from_node, ShaderNodeTexImage):
|
|
return from_node.image
|
|
if isinstance(from_node, NodeReroute):
|
|
traversing_input_socket = from_node.inputs[0]
|
|
break
|
|
|
|
def get_texture_uv_int(self, name: str, default_value: int) -> int:
|
|
node_name = self.get_image_texture_uv_node_name()
|
|
material = self.find_material()
|
|
if not material.node_tree:
|
|
return default_value
|
|
node_tree = material.node_tree
|
|
node = node_tree.nodes.get(node_name)
|
|
if not isinstance(node, ShaderNodeGroup):
|
|
return default_value
|
|
socket = node.inputs.get(name)
|
|
if not socket:
|
|
logger.warning('No "%s" in shader node group "%s"', name, node_name)
|
|
return default_value
|
|
|
|
if isinstance(socket, shader.FLOAT_SOCKET_CLASSES):
|
|
return round(socket.default_value)
|
|
if isinstance(socket, shader.INT_SOCKET_CLASSES):
|
|
return socket.default_value
|
|
|
|
return default_value
|
|
|
|
def get_texture_uv_float(self, name: str, default_value: float) -> float:
|
|
node_name = self.get_image_texture_uv_node_name()
|
|
material = self.find_material()
|
|
if not material.node_tree:
|
|
return default_value
|
|
node_tree = material.node_tree
|
|
node = node_tree.nodes.get(node_name)
|
|
if not isinstance(node, ShaderNodeGroup):
|
|
return default_value
|
|
socket = node.inputs.get(name)
|
|
if not socket:
|
|
logger.warning('No "%s" in shader node group "%s"', name, node_name)
|
|
return default_value
|
|
|
|
if isinstance(socket, shader.FLOAT_SOCKET_CLASSES):
|
|
return socket.default_value
|
|
if isinstance(socket, shader.INT_SOCKET_CLASSES):
|
|
return float(socket.default_value)
|
|
|
|
return default_value
|
|
|
|
def set_texture_uv(self, name: str, value: object) -> None:
|
|
node_name = self.get_image_texture_uv_node_name()
|
|
material = self.find_material()
|
|
if not material.node_tree:
|
|
return
|
|
node_tree = material.node_tree
|
|
node = node_tree.nodes.get(node_name)
|
|
if not isinstance(node, ShaderNodeGroup):
|
|
return
|
|
socket = node.inputs.get(name)
|
|
if not socket:
|
|
logger.warning('No "%s" in shader node group "%s"', name, node_name)
|
|
return
|
|
|
|
if isinstance(value, (float, int)):
|
|
if isinstance(socket, shader.FLOAT_SOCKET_CLASSES):
|
|
socket.default_value = float(value)
|
|
if isinstance(socket, shader.INT_SOCKET_CLASSES):
|
|
socket.default_value = int(value)
|
|
|
|
outline = self.find_outline_property_group(material)
|
|
if not outline:
|
|
return
|
|
if not isinstance(outline, TextureTraceablePropertyGroup):
|
|
return
|
|
outline.set_texture_uv(name, value)
|
|
|
|
|
|
class Mtoon1KhrTextureTransformPropertyGroup(TextureTraceablePropertyGroup):
|
|
def get_texture_offset(self) -> tuple[float, float]:
|
|
x = self.get_texture_uv_float(
|
|
shader.UV_GROUP_UV_OFFSET_X_LABEL,
|
|
default_value=shader.UV_GROUP_UV_OFFSET_X_DEFAULT,
|
|
)
|
|
y = self.get_texture_uv_float(
|
|
shader.UV_GROUP_UV_OFFSET_Y_LABEL,
|
|
default_value=shader.UV_GROUP_UV_OFFSET_Y_DEFAULT,
|
|
)
|
|
return x, y
|
|
|
|
def set_texture_offset(self, value: object) -> None:
|
|
offset = convert.float2_or_none(value)
|
|
if offset is None:
|
|
return
|
|
|
|
self.set_texture_uv(shader.UV_GROUP_UV_OFFSET_X_LABEL, offset[0])
|
|
self.set_texture_uv(shader.UV_GROUP_UV_OFFSET_Y_LABEL, offset[1])
|
|
|
|
node_name = self.get_image_texture_node_name()
|
|
material = self.find_material()
|
|
if not material.node_tree:
|
|
return
|
|
node = material.node_tree.nodes.get(node_name)
|
|
if not isinstance(node, ShaderNodeTexImage):
|
|
logger.warning('No shader node tex image "%s"', node_name)
|
|
return
|
|
node.texture_mapping.translation = Vector((0, 0, 0))
|
|
|
|
outline = self.find_outline_property_group(material)
|
|
if not outline:
|
|
return
|
|
if not isinstance(outline, Mtoon1KhrTextureTransformPropertyGroup):
|
|
return
|
|
outline.set_texture_offset(offset)
|
|
|
|
def get_texture_scale(self) -> tuple[float, float]:
|
|
x = self.get_texture_uv_float(
|
|
shader.UV_GROUP_UV_SCALE_X_LABEL,
|
|
default_value=shader.UV_GROUP_UV_SCALE_X_DEFAULT,
|
|
)
|
|
y = self.get_texture_uv_float(
|
|
shader.UV_GROUP_UV_SCALE_Y_LABEL,
|
|
default_value=shader.UV_GROUP_UV_SCALE_Y_DEFAULT,
|
|
)
|
|
return x, y
|
|
|
|
def set_texture_scale(self, value: object) -> None:
|
|
scale = convert.float2_or_none(value)
|
|
if scale is None:
|
|
return
|
|
|
|
self.set_texture_uv(shader.UV_GROUP_UV_SCALE_X_LABEL, scale[0])
|
|
self.set_texture_uv(shader.UV_GROUP_UV_SCALE_Y_LABEL, scale[1])
|
|
|
|
node_name = self.get_image_texture_node_name()
|
|
material = self.find_material()
|
|
if not material.node_tree:
|
|
return
|
|
node = material.node_tree.nodes.get(node_name)
|
|
if not isinstance(node, ShaderNodeTexImage):
|
|
logger.warning('No shader node tex image "%s"', node_name)
|
|
return
|
|
node.texture_mapping.scale = Vector((1, 1, 1))
|
|
|
|
outline = self.find_outline_property_group(material)
|
|
if not outline:
|
|
return
|
|
if not isinstance(outline, Mtoon1KhrTextureTransformPropertyGroup):
|
|
return
|
|
outline.set_texture_scale(scale)
|
|
|
|
offset: FloatVectorProperty( # type: ignore[valid-type]
|
|
name="Offset",
|
|
size=2,
|
|
default=(
|
|
shader.UV_GROUP_UV_OFFSET_X_DEFAULT,
|
|
shader.UV_GROUP_UV_OFFSET_Y_DEFAULT,
|
|
),
|
|
get=get_texture_offset,
|
|
set=set_texture_offset,
|
|
)
|
|
|
|
scale: FloatVectorProperty( # type: ignore[valid-type]
|
|
name="Scale",
|
|
size=2,
|
|
default=(
|
|
shader.UV_GROUP_UV_SCALE_X_DEFAULT,
|
|
shader.UV_GROUP_UV_SCALE_Y_DEFAULT,
|
|
),
|
|
get=get_texture_scale,
|
|
set=set_texture_scale,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
offset: Sequence[float] # type: ignore[no-redef]
|
|
scale: Sequence[float] # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1BaseColorKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
pass
|
|
|
|
|
|
class Mtoon1ShadeMultiplyKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
pass
|
|
|
|
|
|
class Mtoon1NormalKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
pass
|
|
|
|
|
|
class Mtoon1ShadingShiftKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
pass
|
|
|
|
|
|
class Mtoon1EmissiveKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
pass
|
|
|
|
|
|
class Mtoon1RimMultiplyKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
pass
|
|
|
|
|
|
class Mtoon1MatcapKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
pass
|
|
|
|
|
|
class Mtoon1OutlineWidthMultiplyKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
def set_texture_offset_and_outline(self, value: object) -> None:
|
|
offset = convert.float2_or_none(value)
|
|
if offset is None:
|
|
return
|
|
material = self.find_material()
|
|
if get_material_mtoon1_extension(material).is_outline_material:
|
|
return
|
|
self.set_texture_offset(offset)
|
|
ops.vrm.refresh_mtoon1_outline(material_name=material.name)
|
|
|
|
def set_texture_scale_and_outline(self, value: object) -> None:
|
|
scale = convert.float2_or_none(value)
|
|
if scale is None:
|
|
return
|
|
material = self.find_material()
|
|
if get_material_mtoon1_extension(material).is_outline_material:
|
|
return
|
|
self.set_texture_scale(scale)
|
|
ops.vrm.refresh_mtoon1_outline(material_name=material.name)
|
|
|
|
offset: FloatVectorProperty( # type: ignore[valid-type]
|
|
name="Offset",
|
|
size=2,
|
|
default=(
|
|
shader.UV_GROUP_UV_OFFSET_X_DEFAULT,
|
|
shader.UV_GROUP_UV_OFFSET_Y_DEFAULT,
|
|
),
|
|
get=Mtoon1KhrTextureTransformPropertyGroup.get_texture_offset,
|
|
set=set_texture_offset_and_outline,
|
|
)
|
|
|
|
scale: FloatVectorProperty( # type: ignore[valid-type]
|
|
name="Scale",
|
|
size=2,
|
|
default=(
|
|
shader.UV_GROUP_UV_SCALE_X_DEFAULT,
|
|
shader.UV_GROUP_UV_SCALE_Y_DEFAULT,
|
|
),
|
|
get=Mtoon1KhrTextureTransformPropertyGroup.get_texture_scale,
|
|
set=set_texture_scale_and_outline,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
offset: Sequence[float] # type: ignore[no-redef]
|
|
scale: Sequence[float] # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1UvAnimationMaskKhrTextureTransformPropertyGroup(
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
):
|
|
pass
|
|
|
|
|
|
class Mtoon1TextureInfoExtensionsPropertyGroup(PropertyGroup):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1KhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1KhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1BaseColorTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1BaseColorKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1BaseColorKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1ShadeMultiplyTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadeMultiplyKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1ShadeMultiplyKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1NormalTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1NormalKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1NormalKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1ShadingShiftTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadingShiftKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1ShadingShiftKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1EmissiveTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1EmissiveKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1EmissiveKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1RimMultiplyTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1RimMultiplyKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1RimMultiplyKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1MatcapTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1MatcapKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1MatcapKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1OutlineWidthMultiplyTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1OutlineWidthMultiplyKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1OutlineWidthMultiplyKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1UvAnimationMaskTextureInfoExtensionsPropertyGroup(
|
|
Mtoon1TextureInfoExtensionsPropertyGroup
|
|
):
|
|
khr_texture_transform: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1UvAnimationMaskKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
khr_texture_transform: ( # type: ignore[no-redef]
|
|
Mtoon1UvAnimationMaskKhrTextureTransformPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1SamplerPropertyGroup(TextureTraceablePropertyGroup):
|
|
(
|
|
mag_filter_enum,
|
|
(
|
|
MAG_FILTER_NEAREST,
|
|
MAG_FILTER_LINEAR,
|
|
),
|
|
) = property_group_enum(
|
|
("NEAREST", "Nearest", "", "NONE", GL_NEAREST),
|
|
("LINEAR", "Linear", "", "NONE", GL_LINEAR),
|
|
)
|
|
MAG_FILTER_DEFAULT = MAG_FILTER_LINEAR
|
|
|
|
def get_mag_filter(self) -> int:
|
|
default_value = self.MAG_FILTER_DEFAULT.value
|
|
|
|
material = self.find_material()
|
|
node_name = self.get_image_texture_node_name()
|
|
node_tree = material.node_tree
|
|
if node_tree is None:
|
|
return default_value
|
|
|
|
node = node_tree.nodes.get(node_name)
|
|
if not isinstance(node, ShaderNodeTexImage):
|
|
return default_value
|
|
|
|
if node.interpolation == IMAGE_INTERPOLATION_CLOSEST:
|
|
return GL_NEAREST
|
|
|
|
if node.interpolation in GL_LINEAR_IMAGE_INTERPOLATIONS:
|
|
return GL_LINEAR
|
|
|
|
value = self.get("mag_filter")
|
|
if isinstance(value, int) and value in self.mag_filter_enum.values():
|
|
return value
|
|
|
|
return default_value
|
|
|
|
def set_mag_filter(self, value: int) -> None:
|
|
# If input value conflicts with TexImage value, change TexImage value
|
|
# If input value is GL_NEAREST and TexImage is Closest, delete internal value
|
|
# If input value is GL_LINEAR and TexImage is Linear/Cubic/Smart,
|
|
# delete internal value
|
|
|
|
if value not in self.mag_filter_enum.values():
|
|
self.pop("mag_filter", None)
|
|
return
|
|
|
|
material = self.find_material()
|
|
node_name = self.get_image_texture_node_name()
|
|
node_tree = material.node_tree
|
|
if node_tree is not None:
|
|
node = node_tree.nodes.get(node_name)
|
|
if isinstance(node, ShaderNodeTexImage):
|
|
if value == GL_NEAREST:
|
|
if node.interpolation == IMAGE_INTERPOLATION_CLOSEST:
|
|
self.pop("mag_filter", None)
|
|
return
|
|
node.interpolation = IMAGE_INTERPOLATION_CLOSEST
|
|
if value == GL_LINEAR:
|
|
if node.interpolation in GL_LINEAR_IMAGE_INTERPOLATIONS:
|
|
self.pop("mag_filter", None)
|
|
return
|
|
node.interpolation = IMAGE_INTERPOLATION_LINEAR
|
|
|
|
self["mag_filter"] = value
|
|
|
|
def get_min_filter(self) -> int:
|
|
value = self.get("min_filter")
|
|
if isinstance(value, int) and value in self.min_filter_enum.values():
|
|
return value
|
|
|
|
default_value = self.MIN_FILTER_DEFAULT.value
|
|
|
|
material = self.find_material()
|
|
node_name = self.get_image_texture_node_name()
|
|
node_tree = material.node_tree
|
|
if node_tree is None:
|
|
return default_value
|
|
|
|
node = node_tree.nodes.get(node_name)
|
|
if not isinstance(node, ShaderNodeTexImage):
|
|
return default_value
|
|
|
|
if node.interpolation == IMAGE_INTERPOLATION_CLOSEST:
|
|
return GL_NEAREST
|
|
|
|
if node.interpolation in GL_LINEAR_IMAGE_INTERPOLATIONS:
|
|
return GL_LINEAR
|
|
|
|
return default_value
|
|
|
|
def set_min_filter(self, value: int) -> None:
|
|
# If input value is GL_NEAREST and TexImage is Closest, delete internal value
|
|
# If input value is GL_LINEAR and TexImage is Linear/Cubic/Smart,
|
|
# delete internal value
|
|
|
|
if value not in self.min_filter_enum.values():
|
|
self.pop("min_filter", None)
|
|
return
|
|
|
|
material = self.find_material()
|
|
node_name = self.get_image_texture_node_name()
|
|
node_tree = material.node_tree
|
|
if node_tree is not None:
|
|
node = node_tree.nodes.get(node_name)
|
|
if isinstance(node, ShaderNodeTexImage):
|
|
if (
|
|
value == GL_NEAREST
|
|
and node.interpolation == IMAGE_INTERPOLATION_CLOSEST
|
|
):
|
|
self.pop("min_filter", None)
|
|
return
|
|
if (
|
|
value == GL_LINEAR
|
|
and node.interpolation in GL_LINEAR_IMAGE_INTERPOLATIONS
|
|
):
|
|
self.pop("min_filter", None)
|
|
return
|
|
|
|
self["min_filter"] = value
|
|
|
|
(
|
|
min_filter_enum,
|
|
(
|
|
MIN_FILTER_NEAREST,
|
|
MIN_FILTER_LINEAR,
|
|
MIN_FILTER_NEAREST_MIPMAP_NEAREST,
|
|
MIN_FILTER_LINEAR_MIPMAP_NEAREST,
|
|
MIN_FILTER_NEAREST_MIPMAP_LINEAR,
|
|
MIN_FILTER_LINEAR_MIPMAP_LINEAR,
|
|
),
|
|
) = property_group_enum(
|
|
("NEAREST", "Nearest", "", "NONE", GL_NEAREST),
|
|
("LINEAR", "Linear", "", "NONE", GL_LINEAR),
|
|
(
|
|
"NEAREST_MIPMAP_NEAREST",
|
|
"Nearest Mipmap Nearest",
|
|
"",
|
|
"NONE",
|
|
GL_NEAREST_MIPMAP_NEAREST,
|
|
),
|
|
(
|
|
"LINEAR_MIPMAP_NEAREST",
|
|
"Linear Mipmap Nearest",
|
|
"",
|
|
"NONE",
|
|
GL_LINEAR_MIPMAP_NEAREST,
|
|
),
|
|
(
|
|
"NEAREST_MIPMAP_LINEAR",
|
|
"Nearest Mipmap Linear",
|
|
"",
|
|
"NONE",
|
|
GL_NEAREST_MIPMAP_LINEAR,
|
|
),
|
|
(
|
|
"LINEAR_MIPMAP_LINEAR",
|
|
"Linear Mipmap Linear",
|
|
"",
|
|
"NONE",
|
|
GL_LINEAR_MIPMAP_LINEAR,
|
|
),
|
|
)
|
|
MIN_FILTER_DEFAULT = MIN_FILTER_LINEAR
|
|
|
|
# https://github.com/KhronosGroup/glTF/blob/2a9996a2ea66ab712590eaf62f39f1115996f5a3/specification/2.0/schema/sampler.schema.json#L67-L117
|
|
(
|
|
wrap_enum,
|
|
(
|
|
WRAP_CLAMP_TO_EDGE,
|
|
WRAP_MIRRORED_REPEAT,
|
|
WRAP_REPEAT,
|
|
),
|
|
) = property_group_enum(
|
|
("CLAMP_TO_EDGE", "Clamp to Edge", "", "NONE", GL_CLAMP_TO_EDGE),
|
|
("MIRRORED_REPEAT", "Mirrored Repeat", "", "NONE", GL_MIRRORED_REPEAT),
|
|
("REPEAT", "Repeat", "", "NONE", GL_REPEAT),
|
|
)
|
|
|
|
WRAP_DEFAULT = WRAP_REPEAT
|
|
|
|
def get_wrap_s(self) -> int:
|
|
wrap_s = self.get_texture_uv_int(
|
|
shader.UV_GROUP_WRAP_S_LABEL, shader.UV_GROUP_WRAP_S_DEFAULT
|
|
)
|
|
if wrap_s in self.wrap_enum.values():
|
|
return wrap_s
|
|
return shader.UV_GROUP_WRAP_S_DEFAULT
|
|
|
|
def set_wrap_s(self, value: object) -> None:
|
|
self.set_texture_uv(shader.UV_GROUP_WRAP_S_LABEL, value)
|
|
|
|
def get_wrap_t(self) -> int:
|
|
wrap_t = self.get_texture_uv_int(
|
|
shader.UV_GROUP_WRAP_T_LABEL, shader.UV_GROUP_WRAP_T_DEFAULT
|
|
)
|
|
if wrap_t in self.wrap_enum.values():
|
|
return wrap_t
|
|
return shader.UV_GROUP_WRAP_T_DEFAULT
|
|
|
|
def set_wrap_t(self, value: object) -> None:
|
|
self.set_texture_uv(shader.UV_GROUP_WRAP_T_LABEL, value)
|
|
|
|
mag_filter: EnumProperty( # type: ignore[valid-type]
|
|
items=mag_filter_enum.items(),
|
|
get=get_mag_filter,
|
|
set=set_mag_filter,
|
|
name="Mag Filter",
|
|
)
|
|
|
|
min_filter: EnumProperty( # type: ignore[valid-type]
|
|
items=min_filter_enum.items(),
|
|
get=get_min_filter,
|
|
set=set_min_filter,
|
|
name="Min Filter",
|
|
)
|
|
|
|
wrap_s: EnumProperty( # type: ignore[valid-type]
|
|
items=wrap_enum.items(),
|
|
name="Wrap S",
|
|
default=WRAP_DEFAULT.value,
|
|
get=get_wrap_s,
|
|
set=set_wrap_s,
|
|
)
|
|
|
|
wrap_t: EnumProperty( # type: ignore[valid-type]
|
|
items=wrap_enum.items(),
|
|
name="Wrap T",
|
|
default=WRAP_DEFAULT.value,
|
|
get=get_wrap_t,
|
|
set=set_wrap_t,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
mag_filter: str # type: ignore[no-redef]
|
|
min_filter: str # type: ignore[no-redef]
|
|
wrap_s: str # type: ignore[no-redef]
|
|
wrap_t: str # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1BaseColorSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1ShadeMultiplySamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1NormalSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1ShadingShiftSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1EmissiveSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1RimMultiplySamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1MatcapSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1OutlineWidthMultiplySamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1UvAnimationMaskSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
|
|
pass
|
|
|
|
|
|
class Mtoon1TexturePropertyGroup(TextureTraceablePropertyGroup):
|
|
label = ""
|
|
panel_label = label
|
|
colorspace = "sRGB"
|
|
|
|
def update_source(self, _context: Context) -> None:
|
|
self.update_image(self.source)
|
|
|
|
source: PointerProperty( # type: ignore[valid-type]
|
|
type=Image,
|
|
update=update_source,
|
|
)
|
|
|
|
def update_source_for_desynced_node_tree(self, context: Context) -> None:
|
|
"""Update PointerProperty() passed to prop() when not synced with NodeTree.
|
|
|
|
When not synced with NodeTree, want to display the correct Image name on
|
|
the Placeholder side of prop().
|
|
Therefore, it operates as follows:
|
|
- Always display Placeholder, so the value always returns None
|
|
- When a value is input from outside, reset the value to None and
|
|
transfer to self.source
|
|
"""
|
|
original_syncing_source_name: Optional[str] = None
|
|
if self.source_for_desynced_node_tree:
|
|
original_syncing_source_name = self.source_for_desynced_node_tree.name
|
|
|
|
if self.source_for_desynced_node_tree is not None:
|
|
self.source_for_desynced_node_tree = None # trigger recursive assignment
|
|
|
|
if original_syncing_source_name is not None:
|
|
image = context.blend_data.images.get(original_syncing_source_name)
|
|
if image:
|
|
self.source = image
|
|
else:
|
|
self.source = None
|
|
|
|
source_for_desynced_node_tree: PointerProperty( # type: ignore[valid-type]
|
|
type=Image,
|
|
update=update_source_for_desynced_node_tree,
|
|
)
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1SamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
source: Optional[Image] # type: ignore[no-redef]
|
|
source_for_desynced_node_tree: Optional[Image] # type: ignore[no-redef]
|
|
sampler: Mtoon1SamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1BaseColorTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "Lit Color, Alpha"
|
|
panel_label = label
|
|
colorspace = "sRGB"
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1BaseColorSamplerPropertyGroup
|
|
)
|
|
|
|
def update_image(self, image: Optional[Image]) -> None:
|
|
super().update_image(image)
|
|
material = self.find_material()
|
|
mtoon1 = get_material_mtoon1_extension(material)
|
|
mtoon1.update_alpha_nodes(
|
|
material,
|
|
mtoon1.get_alpha_mode(),
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: Mtoon1BaseColorSamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1ShadeMultiplyTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "Shade Color"
|
|
panel_label = label
|
|
colorspace = "sRGB"
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadeMultiplySamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: Mtoon1ShadeMultiplySamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1NormalTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "Normal Map"
|
|
panel_label = label
|
|
colorspace = "Non-Color"
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1NormalSamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: Mtoon1NormalSamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1ShadingShiftTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "Additive Shading Shift"
|
|
panel_label = label
|
|
colorspace = "Non-Color"
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadingShiftSamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: Mtoon1ShadingShiftSamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1EmissiveTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "Emission"
|
|
panel_label = label
|
|
colorspace = "sRGB"
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1EmissiveSamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: Mtoon1EmissiveSamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1RimMultiplyTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "Rim Color"
|
|
panel_label = label
|
|
colorspace = "sRGB"
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1RimMultiplySamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: Mtoon1RimMultiplySamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1MatcapTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "Matcap Rim"
|
|
panel_label = label
|
|
colorspace = "sRGB"
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1MatcapSamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: Mtoon1MatcapSamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1OutlineWidthMultiplyTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "Outline Width"
|
|
panel_label = label
|
|
colorspace = "Non-Color"
|
|
|
|
def update_source(self, context: Context) -> None:
|
|
mtoon1 = get_material_mtoon1_extension(self.find_material())
|
|
mtoon1.extensions.vrmc_materials_mtoon.update_outline_geometry()
|
|
super().update_source(context)
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1OutlineWidthMultiplySamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: ( # type: ignore[no-redef]
|
|
Mtoon1OutlineWidthMultiplySamplerPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1UvAnimationMaskTexturePropertyGroup(Mtoon1TexturePropertyGroup):
|
|
label = "UV Animation Mask"
|
|
panel_label = "Mask"
|
|
colorspace = "Non-Color"
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1UvAnimationMaskSamplerPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
sampler: Mtoon1UvAnimationMaskSamplerPropertyGroup # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1TextureInfoPropertyGroup(MaterialTraceablePropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1TexturePropertyGroup
|
|
)
|
|
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1TextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
@dataclass(frozen=True)
|
|
class TextureInfoBackup:
|
|
source: Optional[Image]
|
|
mag_filter: Optional[str]
|
|
min_filter: Optional[str]
|
|
wrap_s: str
|
|
wrap_t: str
|
|
offset: tuple[float, float]
|
|
scale: tuple[float, float]
|
|
|
|
def backup(self) -> TextureInfoBackup:
|
|
return Mtoon1TextureInfoPropertyGroup.TextureInfoBackup(
|
|
source=self.index.get_connected_node_image(),
|
|
mag_filter=self.index.sampler.mag_filter,
|
|
min_filter=self.index.sampler.min_filter,
|
|
wrap_s=self.index.sampler.wrap_s,
|
|
wrap_t=self.index.sampler.wrap_t,
|
|
offset=(
|
|
self.extensions.khr_texture_transform.offset[0],
|
|
self.extensions.khr_texture_transform.offset[1],
|
|
),
|
|
scale=(
|
|
self.extensions.khr_texture_transform.scale[0],
|
|
self.extensions.khr_texture_transform.scale[1],
|
|
),
|
|
)
|
|
|
|
def restore(self, backup: TextureInfoBackup) -> None:
|
|
self.index.source = backup.source
|
|
|
|
if (
|
|
backup.mag_filter
|
|
in Mtoon1SamplerPropertyGroup.mag_filter_enum.identifiers()
|
|
):
|
|
self.index.sampler.mag_filter = backup.mag_filter
|
|
elif backup.mag_filter is not None:
|
|
logger.warning("invalid mag filter: %s", backup.mag_filter)
|
|
self.index.sampler.mag_filter = (
|
|
Mtoon1SamplerPropertyGroup.MAG_FILTER_DEFAULT.identifier
|
|
)
|
|
|
|
if (
|
|
backup.min_filter
|
|
in Mtoon1SamplerPropertyGroup.min_filter_enum.identifiers()
|
|
):
|
|
self.index.sampler.min_filter = backup.min_filter
|
|
elif backup.min_filter is not None:
|
|
logger.warning("invalid min filter: %s", backup.min_filter)
|
|
self.index.sampler.min_filter = (
|
|
Mtoon1SamplerPropertyGroup.MIN_FILTER_DEFAULT.identifier
|
|
)
|
|
|
|
if backup.wrap_s in Mtoon1SamplerPropertyGroup.wrap_enum.identifiers():
|
|
self.index.sampler.wrap_s = backup.wrap_s
|
|
else:
|
|
logger.warning("invalid wrap s: %s", backup.wrap_s)
|
|
self.index.sampler.wrap_s = (
|
|
Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
|
|
)
|
|
|
|
if backup.wrap_t in Mtoon1SamplerPropertyGroup.wrap_enum.identifiers():
|
|
self.index.sampler.wrap_t = backup.wrap_t
|
|
else:
|
|
logger.warning("invalid wrap t: %s", backup.wrap_t)
|
|
self.index.sampler.wrap_t = (
|
|
Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
|
|
)
|
|
|
|
self.extensions.khr_texture_transform.offset = backup.offset
|
|
self.extensions.khr_texture_transform.scale = backup.scale
|
|
|
|
show_expanded: BoolProperty() # type: ignore[valid-type]
|
|
|
|
def setup_drivers(self, material: Material) -> None:
|
|
mtoon1 = get_material_mtoon1_extension(material)
|
|
if not mtoon1.enabled:
|
|
return
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return
|
|
animation_data = node_tree.animation_data
|
|
if not animation_data:
|
|
animation_data = node_tree.animation_data_create()
|
|
if not animation_data:
|
|
logger.error(
|
|
'Failed to create anomation data for node tree "%s"', node_tree.name
|
|
)
|
|
return
|
|
uv_node_name = self.index.get_image_texture_uv_node_name()
|
|
uv_node = node_tree.nodes.get(uv_node_name)
|
|
if not uv_node or not isinstance(uv_node, ShaderNodeGroup):
|
|
logger.error('Failed to get uv node "%s"', uv_node_name)
|
|
return
|
|
image_node_name = self.index.get_image_texture_node_name()
|
|
image_node = node_tree.nodes.get(image_node_name)
|
|
image_exists = isinstance(image_node, ShaderNodeTexImage) and isinstance(
|
|
image_node.image, Image
|
|
)
|
|
for size_index, uv_input_label in (
|
|
(0, shader.UV_GROUP_IMAGE_WIDTH_LABEL),
|
|
(1, shader.UV_GROUP_IMAGE_HEIGHT_LABEL),
|
|
):
|
|
uv_input_index = next(
|
|
iter(
|
|
index
|
|
for index, socket in enumerate(uv_node.inputs)
|
|
if socket.name == uv_input_label
|
|
),
|
|
None,
|
|
)
|
|
if uv_input_index is None:
|
|
logger.error('Failed to get uv input index for "%s"', uv_input_label)
|
|
continue
|
|
data_path = (
|
|
f'nodes["{uv_node_name}"]'
|
|
+ f".inputs[{uv_input_index}]"
|
|
+ ".default_value"
|
|
)
|
|
fcurve: Optional[Union[FCurve, list[FCurve]]] = next(
|
|
iter(
|
|
fcurve
|
|
for fcurve in animation_data.drivers
|
|
if fcurve.data_path == data_path
|
|
),
|
|
None,
|
|
)
|
|
if fcurve is None:
|
|
try:
|
|
fcurve = node_tree.driver_add(data_path)
|
|
except TypeError:
|
|
continue
|
|
if not isinstance(fcurve, FCurve):
|
|
logger.error(
|
|
'Failed to get fcurve "%s" for node tree "%s"',
|
|
data_path,
|
|
node_tree.name,
|
|
)
|
|
continue
|
|
if fcurve.array_index != 0:
|
|
fcurve.array_index = 0
|
|
driver = fcurve.driver
|
|
if not isinstance(driver, Driver):
|
|
logger.error('Failed to get driver for fcurve "%s"', data_path)
|
|
continue
|
|
if driver.type != "SUM":
|
|
driver.type = "SUM"
|
|
if not driver.variables:
|
|
driver.variables.new()
|
|
while len(driver.variables) > 1:
|
|
driver.variables.remove(driver.variables[-1])
|
|
variable = driver.variables[0]
|
|
if variable.type != "SINGLE_PROP":
|
|
variable.type = "SINGLE_PROP"
|
|
if not variable.targets:
|
|
logger.error(
|
|
'No targets in variable for fcurve "%s" in node_tree "%s"',
|
|
data_path,
|
|
node_tree.name,
|
|
)
|
|
continue
|
|
target = variable.targets[0]
|
|
if target.id_type != "MATERIAL":
|
|
target.id_type = "MATERIAL"
|
|
if target.id != material:
|
|
target.id = material
|
|
target_data_path = (
|
|
"node_tree"
|
|
+ f'.nodes["{image_node_name}"]'
|
|
+ ".image"
|
|
+ f".size[{size_index}]"
|
|
)
|
|
if target.data_path != target_data_path or (
|
|
# Blender 2.93 sets is_valid to False once image is deleted.
|
|
# This is resolved by resetting data_path.
|
|
image_exists and (not fcurve.is_valid or not driver.is_valid)
|
|
):
|
|
target.data_path = target_data_path
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1TexturePropertyGroup # type: ignore[no-redef]
|
|
extensions: Mtoon1TextureInfoExtensionsPropertyGroup # type: ignore[no-redef]
|
|
show_expanded: bool # type: ignore[no-redef]
|
|
|
|
|
|
# https://github.com/KhronosGroup/glTF/blob/1ab49ec412e638f2e5af0289e9fbb60c7271e457/specification/2.0/schema/textureInfo.schema.json
|
|
class Mtoon1BaseColorTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
|
|
in_socket_name="Lit Color Texture Color",
|
|
),
|
|
PrincipledBsdfNodeSocketTarget(
|
|
in_socket_name=PRINCIPLED_BSDF_BASE_COLOR_INPUT_KEY
|
|
),
|
|
],
|
|
TEX_IMAGE_ALPHA_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
|
|
in_socket_name="Lit Color Texture Alpha",
|
|
),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1BaseColorTexturePropertyGroup
|
|
)
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1BaseColorTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1BaseColorTexturePropertyGroup # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1BaseColorTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1ShadeMultiplyTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
|
|
in_socket_name="Shade Color Texture",
|
|
),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadeMultiplyTexturePropertyGroup
|
|
)
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadeMultiplyTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1ShadeMultiplyTexturePropertyGroup # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1ShadeMultiplyTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
# https://github.com/KhronosGroup/glTF/blob/1ab49ec412e638f2e5af0289e9fbb60c7271e457/specification/2.0/schema/material.normalTextureInfo.schema.json
|
|
class Mtoon1NormalTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.NORMAL_GROUP_NAME,
|
|
in_socket_name="Normal Map Texture",
|
|
),
|
|
PrincipledBsdfNormalMapNodeSocketTarget(),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1NormalTexturePropertyGroup
|
|
)
|
|
|
|
def get_scale(self) -> float:
|
|
return self.get_float(
|
|
shader.NORMAL_GROUP_NAME,
|
|
shader.NORMAL_GROUP_SCALE_LABEL,
|
|
default_value=shader.NORMAL_GROUP_SCALE_DEFAULT,
|
|
)
|
|
|
|
def set_scale(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.NORMAL_GROUP_NAME,
|
|
shader.NORMAL_GROUP_SCALE_LABEL,
|
|
value,
|
|
)
|
|
material = self.find_material()
|
|
principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
|
|
principled_bsdf.normalmap_strength = self.scale
|
|
|
|
mtoon1 = get_material_mtoon1_extension(material)
|
|
if mtoon1.is_outline_material:
|
|
return
|
|
outline_material = mtoon1.outline_material
|
|
if not outline_material:
|
|
return
|
|
outline_principled_bsdf = PrincipledBSDFWrapper(
|
|
outline_material, is_readonly=False
|
|
)
|
|
outline_principled_bsdf.normalmap_strength = self.scale
|
|
|
|
scale: FloatProperty( # type: ignore[valid-type]
|
|
name="Scale",
|
|
default=shader.NORMAL_GROUP_SCALE_DEFAULT,
|
|
get=get_scale,
|
|
set=set_scale,
|
|
)
|
|
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1NormalTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1NormalTexturePropertyGroup # type: ignore[no-redef]
|
|
scale: float # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1NormalTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
# https://github.com/vrm-c/vrm-specification/blob/c5d1afdc4d59c292cb4fd6d54cad1dc0c4d19c60/specification/VRMC_materials_mtoon-1.0/schema/mtoon.shadingShiftTexture.schema.json
|
|
class Mtoon1ShadingShiftTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
|
|
in_socket_name="Shading Shift Texture",
|
|
),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadingShiftTexturePropertyGroup
|
|
)
|
|
|
|
def get_scale(self) -> float:
|
|
return self.get_float(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_DEFAULT,
|
|
)
|
|
|
|
def set_scale(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_LABEL,
|
|
value,
|
|
)
|
|
|
|
scale: FloatProperty( # type: ignore[valid-type]
|
|
name="Scale",
|
|
default=shader.OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_DEFAULT,
|
|
set=set_scale,
|
|
get=get_scale,
|
|
)
|
|
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadingShiftTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1ShadingShiftTexturePropertyGroup # type: ignore[no-redef]
|
|
scale: float # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1ShadingShiftTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
# https://github.com/KhronosGroup/glTF/blob/1ab49ec412e638f2e5af0289e9fbb60c7271e457/specification/2.0/schema/textureInfo.schema.json
|
|
class Mtoon1EmissiveTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
|
|
in_socket_name="Emissive Texture",
|
|
),
|
|
PrincipledBsdfNodeSocketTarget(
|
|
in_socket_name=PRINCIPLED_BSDF_EMISSION_INPUT_KEY,
|
|
),
|
|
GltfEmissionNodeSocketTarget(),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1EmissiveTexturePropertyGroup
|
|
)
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1EmissiveTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1EmissiveTexturePropertyGroup # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1EmissiveTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1RimMultiplyTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
|
|
in_socket_name="Rim Color Texture",
|
|
),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1RimMultiplyTexturePropertyGroup
|
|
)
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1RimMultiplyTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1RimMultiplyTexturePropertyGroup # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1RimMultiplyTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1MatcapTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
|
|
in_socket_name="MatCap Texture",
|
|
),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1MatcapTexturePropertyGroup
|
|
)
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1MatcapTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1MatcapTexturePropertyGroup # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1MatcapTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1OutlineWidthMultiplyTextureInfoPropertyGroup(
|
|
Mtoon1TextureInfoPropertyGroup
|
|
):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
|
|
in_socket_name="Outline Width Texture",
|
|
),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1OutlineWidthMultiplyTexturePropertyGroup
|
|
)
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1OutlineWidthMultiplyTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1OutlineWidthMultiplyTexturePropertyGroup # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1OutlineWidthMultiplyTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1UvAnimationMaskTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
|
|
node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
|
|
TEX_IMAGE_COLOR_OUTPUT_KEY: [
|
|
NodeGroupSocketTarget(
|
|
node_group_node_tree_name=shader.UV_ANIMATION_GROUP_NAME,
|
|
in_socket_name="Mask Texture",
|
|
),
|
|
],
|
|
}
|
|
|
|
index: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1UvAnimationMaskTexturePropertyGroup
|
|
)
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1UvAnimationMaskTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
index: Mtoon1UvAnimationMaskTexturePropertyGroup # type: ignore[no-redef]
|
|
extensions: ( # type: ignore[no-redef]
|
|
Mtoon1UvAnimationMaskTextureInfoExtensionsPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon0SamplerPropertyGroup(PropertyGroup):
|
|
mag_filter: EnumProperty( # type: ignore[valid-type]
|
|
items=Mtoon1SamplerPropertyGroup.mag_filter_enum.items(),
|
|
name="Mag Filter",
|
|
)
|
|
|
|
min_filter: EnumProperty( # type: ignore[valid-type]
|
|
items=Mtoon1SamplerPropertyGroup.min_filter_enum.items(),
|
|
name="Min Filter",
|
|
)
|
|
|
|
wrap_s: EnumProperty( # type: ignore[valid-type]
|
|
items=Mtoon1SamplerPropertyGroup.wrap_enum.items(),
|
|
name="Wrap S",
|
|
default=Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.value,
|
|
)
|
|
|
|
wrap_t: EnumProperty( # type: ignore[valid-type]
|
|
items=Mtoon1SamplerPropertyGroup.wrap_enum.items(),
|
|
name="Wrap T",
|
|
default=Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.value,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
mag_filter: str # type: ignore[no-redef]
|
|
min_filter: str # type: ignore[no-redef]
|
|
wrap_s: str # type: ignore[no-redef]
|
|
wrap_t: str # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon0TexturePropertyGroup(PropertyGroup):
|
|
label = ""
|
|
panel_label = ""
|
|
colorspace = "sRGB"
|
|
|
|
def get_connected_node_image(self) -> Optional[Image]:
|
|
return self.source if isinstance(self.source, Image) else None
|
|
|
|
source: PointerProperty( # type: ignore[valid-type]
|
|
type=Image,
|
|
)
|
|
|
|
sampler: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon0SamplerPropertyGroup,
|
|
)
|
|
|
|
show_expanded: BoolProperty() # type: ignore[valid-type]
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
source: Optional[Image] # type: ignore[no-redef]
|
|
sampler: Mtoon0SamplerPropertyGroup # type: ignore[no-redef]
|
|
show_expanded: bool # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon0ReceiveShadowTexturePropertyGroup(Mtoon0TexturePropertyGroup):
|
|
label = "Shadow Receive Multiplier"
|
|
panel_label = label
|
|
colorspace = "Non-Color"
|
|
|
|
|
|
class Mtoon0ShadingGradeTexturePropertyGroup(Mtoon0TexturePropertyGroup):
|
|
label = "Lit & Shade Mixing Multiplier"
|
|
panel_label = label
|
|
colorspace = "Non-Color"
|
|
|
|
|
|
# https://github.com/KhronosGroup/glTF/blob/1ab49ec412e638f2e5af0289e9fbb60c7271e457/specification/2.0/schema/material.pbrMetallicRoughness.schema.json#L9-L26
|
|
class Mtoon1PbrMetallicRoughnessPropertyGroup(MaterialTraceablePropertyGroup):
|
|
def get_base_color_factor(self) -> tuple[float, float, float, float]:
|
|
rgb = self.get_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_DEFAULT,
|
|
)
|
|
a = self.get_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_DEFAULT,
|
|
)
|
|
return (*rgb, a)
|
|
|
|
def set_base_color_factor(self, value: object) -> None:
|
|
color = convert.float4_or_none(value)
|
|
if color is None:
|
|
return
|
|
self.set_rgba(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_LABEL,
|
|
color,
|
|
)
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_LABEL,
|
|
color[3],
|
|
)
|
|
material = self.find_material()
|
|
principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
|
|
principled_bsdf.base_color = (
|
|
color[0],
|
|
color[1],
|
|
color[2],
|
|
)
|
|
mtoon1 = get_material_mtoon1_extension(material)
|
|
alpha_mode_value = mtoon1.get_alpha_mode()
|
|
mtoon1.update_alpha_nodes(material, alpha_mode_value)
|
|
|
|
if mtoon1.is_outline_material:
|
|
return
|
|
outline_material = mtoon1.outline_material
|
|
if not outline_material:
|
|
return
|
|
outline_mtoon1 = get_material_mtoon1_extension(outline_material)
|
|
outline_mtoon1.update_alpha_nodes(outline_material, alpha_mode_value)
|
|
|
|
base_color_factor: FloatVectorProperty( # type: ignore[valid-type]
|
|
size=4,
|
|
subtype="COLOR",
|
|
default=(
|
|
*shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_DEFAULT,
|
|
shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_DEFAULT,
|
|
),
|
|
min=0,
|
|
max=1,
|
|
get=get_base_color_factor,
|
|
set=set_base_color_factor,
|
|
)
|
|
|
|
base_color_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1BaseColorTextureInfoPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
base_color_factor: Sequence[float] # type: ignore[no-redef]
|
|
base_color_texture: ( # type: ignore[no-redef]
|
|
Mtoon1BaseColorTextureInfoPropertyGroup
|
|
)
|
|
|
|
|
|
class Mtoon1VrmcMaterialsMtoonPropertyGroup(MaterialTraceablePropertyGroup):
|
|
def get_transparent_with_z_write(self) -> bool:
|
|
return self.get_bool(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_DEFAULT,
|
|
)
|
|
|
|
def set_transparent_with_z_write(self, value: object) -> None:
|
|
self.set_bool(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_LABEL,
|
|
value,
|
|
)
|
|
|
|
mtoon1 = get_material_mtoon1_extension(self.find_material())
|
|
mtoon1.set_mtoon0_render_queue_and_clamp(mtoon1.mtoon0_render_queue)
|
|
|
|
transparent_with_z_write: BoolProperty( # type: ignore[valid-type]
|
|
name="Transparent With ZWrite Mode",
|
|
default=shader.OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_DEFAULT,
|
|
get=get_transparent_with_z_write,
|
|
set=set_transparent_with_z_write,
|
|
)
|
|
|
|
def get_render_queue_offset_number(self) -> int:
|
|
return self.get_int(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_DEFAULT,
|
|
)
|
|
|
|
def set_render_queue_offset_number(self, value: int) -> None:
|
|
self.set_int(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_LABEL,
|
|
value,
|
|
)
|
|
|
|
render_queue_offset_number: IntProperty( # type: ignore[valid-type]
|
|
name="RenderQueue Offset",
|
|
min=shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_MIN,
|
|
default=shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_DEFAULT,
|
|
max=shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_MAX,
|
|
get=get_render_queue_offset_number,
|
|
set=set_render_queue_offset_number,
|
|
)
|
|
|
|
shade_multiply_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadeMultiplyTextureInfoPropertyGroup
|
|
)
|
|
|
|
def get_shade_color_factor(self) -> tuple[float, float, float]:
|
|
return self.get_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_SHADE_COLOR_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_SHADE_COLOR_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_shade_color_factor(self, value: object) -> None:
|
|
self.set_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_SHADE_COLOR_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
|
|
shade_color_factor: FloatVectorProperty( # type: ignore[valid-type]
|
|
size=3,
|
|
subtype="COLOR",
|
|
default=shader.OUTPUT_GROUP_SHADE_COLOR_FACTOR_DEFAULT,
|
|
min=0.0,
|
|
max=1.0,
|
|
get=get_shade_color_factor,
|
|
set=set_shade_color_factor,
|
|
)
|
|
|
|
shading_shift_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1ShadingShiftTextureInfoPropertyGroup
|
|
)
|
|
|
|
def get_shading_shift_factor(self) -> float:
|
|
return self.get_float(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_shading_shift_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
|
|
shading_shift_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_LABEL,
|
|
soft_min=-1.0,
|
|
default=shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_DEFAULT,
|
|
soft_max=1.0,
|
|
get=get_shading_shift_factor,
|
|
set=set_shading_shift_factor,
|
|
)
|
|
|
|
def get_shading_toony_factor(self) -> float:
|
|
return self.get_float(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_shading_toony_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
|
|
shading_toony_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_LABEL,
|
|
min=0.0,
|
|
default=shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_DEFAULT,
|
|
max=1.0,
|
|
get=get_shading_toony_factor,
|
|
set=set_shading_toony_factor,
|
|
)
|
|
|
|
def get_gi_equalization_factor(self) -> float:
|
|
return self.get_float(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_gi_equalization_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
|
|
gi_equalization_factor: FloatProperty( # type: ignore[valid-type]
|
|
name="GI Equalization",
|
|
min=0.0,
|
|
default=shader.OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_DEFAULT,
|
|
max=1.0,
|
|
get=get_gi_equalization_factor,
|
|
set=set_gi_equalization_factor,
|
|
)
|
|
|
|
def get_matcap_factor(self) -> tuple[float, float, float]:
|
|
return self.get_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_MATCAP_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_MATCAP_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_matcap_factor(self, value: object) -> None:
|
|
self.set_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_MATCAP_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
|
|
matcap_factor: FloatVectorProperty( # type: ignore[valid-type]
|
|
size=3,
|
|
subtype="COLOR",
|
|
default=shader.OUTPUT_GROUP_MATCAP_FACTOR_DEFAULT,
|
|
min=0,
|
|
max=1,
|
|
get=get_matcap_factor,
|
|
set=set_matcap_factor,
|
|
)
|
|
|
|
matcap_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1MatcapTextureInfoPropertyGroup
|
|
)
|
|
|
|
def get_parametric_rim_color_factor(self) -> tuple[float, float, float]:
|
|
return self.get_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_parametric_rim_color_factor(self, value: object) -> None:
|
|
self.set_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
|
|
parametric_rim_color_factor: FloatVectorProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_LABEL,
|
|
size=3,
|
|
subtype="COLOR",
|
|
default=shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_DEFAULT,
|
|
min=0,
|
|
max=1,
|
|
get=get_parametric_rim_color_factor,
|
|
set=set_parametric_rim_color_factor,
|
|
)
|
|
|
|
rim_multiply_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1RimMultiplyTextureInfoPropertyGroup
|
|
)
|
|
|
|
def get_rim_lighting_mix_factor(self) -> float:
|
|
return self.get_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_rim_lighting_mix_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
|
|
rim_lighting_mix_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_LABEL,
|
|
default=shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_DEFAULT,
|
|
soft_min=0,
|
|
soft_max=1,
|
|
get=get_rim_lighting_mix_factor,
|
|
set=set_rim_lighting_mix_factor,
|
|
)
|
|
|
|
def get_parametric_rim_fresnel_power_factor(self) -> float:
|
|
return self.get_float(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_DEFAULT,
|
|
)
|
|
|
|
def set_parametric_rim_fresnel_power_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_LABEL,
|
|
value,
|
|
)
|
|
|
|
parametric_rim_fresnel_power_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_LABEL,
|
|
min=0.0,
|
|
default=shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_DEFAULT,
|
|
soft_max=100.0,
|
|
get=get_parametric_rim_fresnel_power_factor,
|
|
set=set_parametric_rim_fresnel_power_factor,
|
|
)
|
|
|
|
def get_parametric_rim_lift_factor(self) -> float:
|
|
return self.get_float(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_parametric_rim_lift_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
|
|
parametric_rim_lift_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_LABEL,
|
|
soft_min=0.0,
|
|
default=shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_DEFAULT,
|
|
soft_max=1.0,
|
|
get=get_parametric_rim_lift_factor,
|
|
set=set_parametric_rim_lift_factor,
|
|
)
|
|
|
|
(
|
|
outline_width_mode_enum,
|
|
(
|
|
OUTLINE_WIDTH_MODE_NONE,
|
|
OUTLINE_WIDTH_MODE_WORLD_COORDINATES,
|
|
OUTLINE_WIDTH_MODE_SCREEN_COORDINATES,
|
|
),
|
|
) = property_group_enum(
|
|
(
|
|
"none",
|
|
"None",
|
|
"",
|
|
"NONE",
|
|
shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_MIN,
|
|
),
|
|
("worldCoordinates", "World Coordinates", "", "NONE", 1),
|
|
(
|
|
"screenCoordinates",
|
|
"Screen Coordinates",
|
|
"",
|
|
"NONE",
|
|
shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_MAX,
|
|
),
|
|
)
|
|
|
|
def get_outline_width_mode(self) -> int:
|
|
return self.get_int(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_DEFAULT,
|
|
)
|
|
|
|
def set_outline_width_mode(self, value: object) -> None:
|
|
self.set_int(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_LABEL,
|
|
value,
|
|
)
|
|
self.update_outline_geometry()
|
|
|
|
def update_outline_geometry(self) -> None:
|
|
material = self.find_material()
|
|
if get_material_mtoon1_extension(material).is_outline_material:
|
|
return
|
|
ops.vrm.refresh_mtoon1_outline(
|
|
material_name=material.name, create_modifier=True
|
|
)
|
|
|
|
outline_width_mode: EnumProperty( # type: ignore[valid-type]
|
|
items=outline_width_mode_enum.items(),
|
|
name=shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_LABEL,
|
|
get=get_outline_width_mode,
|
|
set=set_outline_width_mode,
|
|
)
|
|
|
|
def get_outline_width_factor(self) -> float:
|
|
return self.get_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_outline_width_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
self.update_outline_geometry()
|
|
|
|
outline_width_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_LABEL,
|
|
default=shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_DEFAULT,
|
|
min=0.0,
|
|
soft_max=0.05,
|
|
get=get_outline_width_factor,
|
|
set=set_outline_width_factor,
|
|
)
|
|
|
|
outline_width_multiply_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1OutlineWidthMultiplyTextureInfoPropertyGroup
|
|
)
|
|
|
|
def get_outline_color_factor(self) -> tuple[float, float, float]:
|
|
return self.get_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_outline_color_factor(self, value: object) -> None:
|
|
self.set_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_LABEL,
|
|
value,
|
|
shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_DEFAULT,
|
|
)
|
|
self.update_outline_geometry()
|
|
|
|
material = self.find_material()
|
|
mtoon1 = get_material_mtoon1_extension(material)
|
|
if mtoon1.is_outline_material:
|
|
return
|
|
outline_material = mtoon1.outline_material
|
|
if not outline_material:
|
|
return
|
|
outline_principled_bsdf = PrincipledBSDFWrapper(
|
|
outline_material, is_readonly=False
|
|
)
|
|
|
|
outline_diffuse_alpha = outline_material.diffuse_color[3]
|
|
outline_principled_bsdf.base_color = (
|
|
self.outline_color_factor[0],
|
|
self.outline_color_factor[1],
|
|
self.outline_color_factor[2],
|
|
)
|
|
outline_material.diffuse_color[3] = outline_diffuse_alpha
|
|
|
|
outline_color_factor: FloatVectorProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_LABEL,
|
|
size=3,
|
|
subtype="COLOR",
|
|
default=shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_DEFAULT,
|
|
min=0,
|
|
max=1,
|
|
get=get_outline_color_factor,
|
|
set=set_outline_color_factor,
|
|
)
|
|
|
|
def get_outline_lighting_mix_factor(self) -> float:
|
|
return self.get_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_outline_lighting_mix_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_LABEL,
|
|
value,
|
|
)
|
|
self.update_outline_geometry()
|
|
|
|
outline_lighting_mix_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_LABEL,
|
|
min=0.0,
|
|
default=shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_DEFAULT,
|
|
max=1.0,
|
|
get=get_outline_lighting_mix_factor,
|
|
set=set_outline_lighting_mix_factor,
|
|
)
|
|
|
|
def update_enable_outline_preview(self, context: Context) -> None:
|
|
material_name = self.find_material().name
|
|
ops.vrm.refresh_mtoon1_outline(
|
|
material_name=material_name, create_modifier=True
|
|
)
|
|
for material in context.blend_data.materials:
|
|
if material.name == material_name:
|
|
continue
|
|
ext = get_material_mtoon1_extension(material)
|
|
if not ext.enabled:
|
|
continue
|
|
mtoon = ext.extensions.vrmc_materials_mtoon
|
|
if mtoon.enable_outline_preview != self.enable_outline_preview:
|
|
mtoon.enable_outline_preview = self.enable_outline_preview
|
|
|
|
enable_outline_preview: BoolProperty( # type: ignore[valid-type]
|
|
default=True,
|
|
update=update_enable_outline_preview,
|
|
)
|
|
|
|
uv_animation_mask_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1UvAnimationMaskTextureInfoPropertyGroup
|
|
)
|
|
|
|
def get_uv_animation_scroll_x_speed_factor(self) -> float:
|
|
return self.get_value(
|
|
shader.UV_ANIMATION_GROUP_NAME,
|
|
shader.UV_ANIMATION_GROUP_TRANSLATE_X_LABEL,
|
|
default_value=shader.UV_ANIMATION_GROUP_TRANSLATE_X_DEFAULT,
|
|
)
|
|
|
|
def set_uv_animation_scroll_x_speed_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.UV_ANIMATION_GROUP_NAME,
|
|
shader.UV_ANIMATION_GROUP_TRANSLATE_X_LABEL,
|
|
value,
|
|
)
|
|
|
|
uv_animation_scroll_x_speed_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.UV_ANIMATION_GROUP_TRANSLATE_X_LABEL,
|
|
default=shader.UV_ANIMATION_GROUP_TRANSLATE_X_DEFAULT,
|
|
get=get_uv_animation_scroll_x_speed_factor,
|
|
set=set_uv_animation_scroll_x_speed_factor,
|
|
)
|
|
|
|
def get_uv_animation_scroll_y_speed_factor(self) -> float:
|
|
return self.get_value(
|
|
shader.UV_ANIMATION_GROUP_NAME,
|
|
shader.UV_ANIMATION_GROUP_TRANSLATE_Y_LABEL,
|
|
default_value=shader.UV_ANIMATION_GROUP_TRANSLATE_Y_DEFAULT,
|
|
)
|
|
|
|
def set_uv_animation_scroll_y_speed_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.UV_ANIMATION_GROUP_NAME,
|
|
shader.UV_ANIMATION_GROUP_TRANSLATE_Y_LABEL,
|
|
value,
|
|
)
|
|
|
|
uv_animation_scroll_y_speed_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.UV_ANIMATION_GROUP_TRANSLATE_Y_LABEL,
|
|
default=shader.UV_ANIMATION_GROUP_TRANSLATE_Y_DEFAULT,
|
|
get=get_uv_animation_scroll_y_speed_factor,
|
|
set=set_uv_animation_scroll_y_speed_factor,
|
|
)
|
|
|
|
def get_uv_animation_rotation_speed_factor(self) -> float:
|
|
return self.get_value(
|
|
shader.UV_ANIMATION_GROUP_NAME,
|
|
shader.UV_ANIMATION_GROUP_ROTATION_LABEL,
|
|
default_value=shader.UV_ANIMATION_GROUP_ROTATION_DEFAULT,
|
|
)
|
|
|
|
def set_uv_animation_rotation_speed_factor(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.UV_ANIMATION_GROUP_NAME,
|
|
shader.UV_ANIMATION_GROUP_ROTATION_LABEL,
|
|
value,
|
|
)
|
|
|
|
uv_animation_rotation_speed_factor: FloatProperty( # type: ignore[valid-type]
|
|
name=shader.UV_ANIMATION_GROUP_ROTATION_LABEL,
|
|
get=get_uv_animation_rotation_speed_factor,
|
|
set=set_uv_animation_rotation_speed_factor,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
transparent_with_z_write: bool # type: ignore[no-redef]
|
|
render_queue_offset_number: int # type: ignore[no-redef]
|
|
shade_multiply_texture: ( # type: ignore[no-redef]
|
|
Mtoon1ShadeMultiplyTextureInfoPropertyGroup
|
|
)
|
|
shade_color_factor: Sequence[float] # type: ignore[no-redef]
|
|
shading_shift_texture: ( # type: ignore[no-redef]
|
|
Mtoon1ShadingShiftTextureInfoPropertyGroup
|
|
)
|
|
shading_shift_factor: float # type: ignore[no-redef]
|
|
shading_toony_factor: float # type: ignore[no-redef]
|
|
gi_equalization_factor: float # type: ignore[no-redef]
|
|
matcap_factor: Sequence[float] # type: ignore[no-redef]
|
|
matcap_texture: Mtoon1MatcapTextureInfoPropertyGroup # type: ignore[no-redef]
|
|
parametric_rim_color_factor: Sequence[float] # type: ignore[no-redef]
|
|
rim_multiply_texture: ( # type: ignore[no-redef]
|
|
Mtoon1RimMultiplyTextureInfoPropertyGroup
|
|
)
|
|
rim_lighting_mix_factor: float # type: ignore[no-redef]
|
|
parametric_rim_fresnel_power_factor: float # type: ignore[no-redef]
|
|
parametric_rim_lift_factor: float # type: ignore[no-redef]
|
|
outline_width_mode: str # type: ignore[no-redef]
|
|
outline_width_factor: float # type: ignore[no-redef]
|
|
outline_width_multiply_texture: ( # type: ignore[no-redef]
|
|
Mtoon1OutlineWidthMultiplyTextureInfoPropertyGroup
|
|
)
|
|
outline_color_factor: Sequence[float] # type: ignore[no-redef]
|
|
outline_lighting_mix_factor: float # type: ignore[no-redef]
|
|
enable_outline_preview: bool # type: ignore[no-redef]
|
|
uv_animation_mask_texture: ( # type: ignore[no-redef]
|
|
Mtoon1UvAnimationMaskTextureInfoPropertyGroup
|
|
)
|
|
uv_animation_scroll_x_speed_factor: float # type: ignore[no-redef]
|
|
uv_animation_scroll_y_speed_factor: float # type: ignore[no-redef]
|
|
uv_animation_rotation_speed_factor: float # type: ignore[no-redef]
|
|
|
|
|
|
# https://github.com/KhronosGroup/glTF/blob/d997b7dc7e426bc791f5613475f5b4490da0b099/extensions/2.0/Khronos/KHR_materials_emissive_strength/schema/glTF.KHR_materials_emissive_strength.schema.json
|
|
class Mtoon1KhrMaterialsEmissiveStrengthPropertyGroup(MaterialTraceablePropertyGroup):
|
|
def get_emissive_strength(self) -> float:
|
|
return self.get_float(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_EMISSIVE_STRENGTH_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_EMISSIVE_STRENGTH_DEFAULT,
|
|
)
|
|
|
|
def set_emissive_strength(self, value: object) -> None:
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_EMISSIVE_STRENGTH_LABEL,
|
|
value,
|
|
)
|
|
|
|
material = self.find_material()
|
|
principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
|
|
principled_bsdf.emission_strength = self.emissive_strength
|
|
|
|
emissive_node = get_gltf_emissive_node(material)
|
|
if emissive_node is not None:
|
|
socket = emissive_node.inputs.get(EMISSION_STRENGTH_INPUT_KEY)
|
|
if isinstance(socket, NodeSocketFloat):
|
|
socket.default_value = self.emissive_strength
|
|
|
|
mtoon1 = get_material_mtoon1_extension(material)
|
|
if mtoon1.is_outline_material:
|
|
return
|
|
outline_material = mtoon1.outline_material
|
|
if not outline_material:
|
|
return
|
|
outline_principled_bsdf = PrincipledBSDFWrapper(
|
|
outline_material, is_readonly=False
|
|
)
|
|
outline_principled_bsdf.emission_strength = self.emissive_strength
|
|
|
|
outline_emissive_node = get_gltf_emissive_node(outline_material)
|
|
if outline_emissive_node is not None:
|
|
outline_socket = outline_emissive_node.inputs.get(
|
|
EMISSION_STRENGTH_INPUT_KEY
|
|
)
|
|
if isinstance(outline_socket, NodeSocketFloat):
|
|
outline_socket.default_value = self.emissive_strength
|
|
|
|
emissive_strength: FloatProperty( # type: ignore[valid-type]
|
|
name="Strength",
|
|
min=0.0,
|
|
default=shader.OUTPUT_GROUP_EMISSIVE_STRENGTH_DEFAULT,
|
|
get=get_emissive_strength,
|
|
set=set_emissive_strength,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
emissive_strength: float # type: ignore[no-redef]
|
|
|
|
|
|
class Mtoon1MaterialExtensionsPropertyGroup(PropertyGroup):
|
|
vrmc_materials_mtoon: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1VrmcMaterialsMtoonPropertyGroup
|
|
)
|
|
khr_materials_emissive_strength: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1KhrMaterialsEmissiveStrengthPropertyGroup
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
vrmc_materials_mtoon: ( # type: ignore[no-redef]
|
|
Mtoon1VrmcMaterialsMtoonPropertyGroup
|
|
)
|
|
khr_materials_emissive_strength: ( # type: ignore[no-redef]
|
|
Mtoon1KhrMaterialsEmissiveStrengthPropertyGroup
|
|
)
|
|
|
|
|
|
# https://github.com/vrm-c/vrm-specification/blob/8dc51ec7241be27ee95f159cefc0190a0e41967b/specification/VRMC_materials_mtoon-1.0-beta/schema/VRMC_materials_mtoon.schema.json
|
|
class Mtoon1MaterialPropertyGroup(MaterialTraceablePropertyGroup):
|
|
INITIAL_ADDON_VERSION = VrmAddonPreferences.INITIAL_ADDON_VERSION
|
|
|
|
addon_version: IntVectorProperty( # type: ignore[valid-type]
|
|
size=3,
|
|
default=INITIAL_ADDON_VERSION,
|
|
)
|
|
|
|
pbr_metallic_roughness: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1PbrMetallicRoughnessPropertyGroup
|
|
)
|
|
|
|
(
|
|
alpha_mode_enum,
|
|
(
|
|
ALPHA_MODE_OPAQUE,
|
|
ALPHA_MODE_MASK,
|
|
ALPHA_MODE_BLEND,
|
|
),
|
|
) = property_group_enum(
|
|
("OPAQUE", "Opaque", "", "NONE", 0),
|
|
("MASK", "Cutout", "", "NONE", 1),
|
|
("BLEND", "Transparent", "", "NONE", 2),
|
|
)
|
|
|
|
alpha_mode_blend_method_hashed: BoolProperty() # type: ignore[valid-type]
|
|
|
|
def get_alpha_mode(self) -> int:
|
|
alpha_mode_value = self.get_int(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_ALPHA_MODE_LABEL,
|
|
default_value=self.ALPHA_MODE_OPAQUE.value,
|
|
)
|
|
if alpha_mode_value in self.alpha_mode_enum.values():
|
|
return alpha_mode_value
|
|
return self.ALPHA_MODE_OPAQUE.value
|
|
|
|
@staticmethod
|
|
def update_alpha_nodes(material: Material, alpha_mode_value: int) -> None:
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return
|
|
|
|
# Align with glTF nodes
|
|
# https://docs.blender.org/manual/en/4.2/addons/import_export/scene_gltf2.html#alpha-modes
|
|
mtoon1 = get_material_mtoon1_extension(material)
|
|
texture = mtoon1.pbr_metallic_roughness.base_color_texture.index
|
|
tex_image_node_name = texture.get_image_texture_node_name()
|
|
|
|
if alpha_mode_value != mtoon1.ALPHA_MODE_MASK.value:
|
|
TextureTraceablePropertyGroup.unlink_nodes(
|
|
material,
|
|
StaticNodeSocketTarget(
|
|
in_node_name=ALPHA_CLIP_INPUT_NODE_NAME,
|
|
in_node_type=ShaderNodeMath,
|
|
in_socket_name=ALPHA_CLIP_INPUT_NODE_SOCKET_NAME,
|
|
),
|
|
)
|
|
TextureTraceablePropertyGroup.unlink_nodes(
|
|
material,
|
|
PrincipledBsdfNodeSocketTarget(
|
|
in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
|
|
),
|
|
)
|
|
if alpha_mode_value != mtoon1.ALPHA_MODE_BLEND.value or bpy.app.version < (
|
|
4,
|
|
2,
|
|
):
|
|
TextureTraceablePropertyGroup.unlink_nodes(
|
|
material,
|
|
PrincipledBsdfNodeSocketTarget(
|
|
in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
|
|
),
|
|
)
|
|
|
|
principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
|
|
if bpy.app.version < (4, 2):
|
|
principled_bsdf.alpha = mtoon1.pbr_metallic_roughness.base_color_factor[3]
|
|
tex_image_node = node_tree.nodes.get(tex_image_node_name)
|
|
if isinstance(tex_image_node, ShaderNodeTexImage):
|
|
TextureTraceablePropertyGroup.link_or_unlink_nodes(
|
|
material,
|
|
tex_image_node_name,
|
|
ShaderNodeTexImage,
|
|
TEX_IMAGE_ALPHA_OUTPUT_KEY,
|
|
PrincipledBsdfNodeSocketTarget(
|
|
in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
|
|
),
|
|
link=bool(tex_image_node.image),
|
|
)
|
|
elif alpha_mode_value == mtoon1.ALPHA_MODE_OPAQUE.value:
|
|
principled_bsdf.alpha = 1.0
|
|
elif alpha_mode_value == mtoon1.ALPHA_MODE_MASK.value:
|
|
tex_image_node = node_tree.nodes.get(tex_image_node_name)
|
|
image_exists = False
|
|
if isinstance(tex_image_node, ShaderNodeTexImage):
|
|
image_exists = bool(tex_image_node.image)
|
|
TextureTraceablePropertyGroup.link_or_unlink_nodes(
|
|
material,
|
|
tex_image_node_name,
|
|
ShaderNodeTexImage,
|
|
TEX_IMAGE_ALPHA_OUTPUT_KEY,
|
|
StaticNodeSocketTarget(
|
|
in_node_name=ALPHA_CLIP_INPUT_NODE_NAME,
|
|
in_node_type=ShaderNodeMath,
|
|
in_socket_name=ALPHA_CLIP_INPUT_NODE_SOCKET_NAME,
|
|
),
|
|
link=image_exists,
|
|
)
|
|
TextureTraceablePropertyGroup.link_or_unlink_nodes(
|
|
material,
|
|
ALPHA_CLIP_OUTPUT_NODE_NAME,
|
|
ShaderNodeMath,
|
|
ALPHA_CLIP_OUTPUT_NODE_SOCKET_NAME,
|
|
PrincipledBsdfNodeSocketTarget(
|
|
in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
|
|
),
|
|
link=image_exists,
|
|
)
|
|
if not image_exists:
|
|
alpha = mtoon1.pbr_metallic_roughness.base_color_factor[3]
|
|
if alpha >= mtoon1.alpha_cutoff:
|
|
principled_bsdf.alpha = 1.0
|
|
else:
|
|
principled_bsdf.alpha = 0.0
|
|
elif alpha_mode_value == mtoon1.ALPHA_MODE_BLEND.value:
|
|
principled_bsdf.alpha = mtoon1.pbr_metallic_roughness.base_color_factor[3]
|
|
tex_image_node = node_tree.nodes.get(tex_image_node_name)
|
|
if isinstance(tex_image_node, ShaderNodeTexImage):
|
|
TextureTraceablePropertyGroup.link_or_unlink_nodes(
|
|
material,
|
|
tex_image_node_name,
|
|
ShaderNodeTexImage,
|
|
TEX_IMAGE_ALPHA_OUTPUT_KEY,
|
|
PrincipledBsdfNodeSocketTarget(
|
|
in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
|
|
),
|
|
link=bool(tex_image_node.image),
|
|
)
|
|
|
|
alpha_clip_input_node = node_tree.nodes.get(ALPHA_CLIP_INPUT_NODE_NAME)
|
|
if (
|
|
isinstance(alpha_clip_input_node, ShaderNodeMath)
|
|
and alpha_clip_input_node.operation == "MINIMUM"
|
|
):
|
|
inputs = alpha_clip_input_node.inputs
|
|
if len(inputs) >= 2:
|
|
alpha_input = inputs[0]
|
|
if isinstance(alpha_input, shader.FLOAT_SOCKET_CLASSES):
|
|
alpha_input.default_value = (
|
|
mtoon1.pbr_metallic_roughness.base_color_factor[3]
|
|
)
|
|
else:
|
|
logger.error(
|
|
"Unexpected alpha input node input type: %s", type(alpha_input)
|
|
)
|
|
|
|
alpha_cutoff_input = inputs[1]
|
|
if isinstance(alpha_cutoff_input, shader.FLOAT_SOCKET_CLASSES):
|
|
alpha_cutoff_input.default_value = mtoon1.alpha_cutoff
|
|
else:
|
|
logger.error(
|
|
"Unexpected alpha clip input node input type: %s",
|
|
type(alpha_cutoff_input),
|
|
)
|
|
else:
|
|
logger.error(
|
|
"Unexpected alpha clip input node input length: %s", len(inputs)
|
|
)
|
|
else:
|
|
logger.error(
|
|
"Unexpected alpha clip input node type: %s",
|
|
type(alpha_clip_input_node),
|
|
)
|
|
|
|
def set_alpha_mode(self, value: int) -> None:
|
|
changed = self.get_alpha_mode() != value
|
|
|
|
material = self.find_material()
|
|
|
|
shadow_method = None
|
|
if bpy.app.version < (4, 2):
|
|
if material.blend_method == "HASHED":
|
|
self.alpha_mode_blend_method_hashed = True
|
|
if material.blend_method == "BLEND":
|
|
self.alpha_mode_blend_method_hashed = False
|
|
|
|
if value == self.ALPHA_MODE_OPAQUE.value:
|
|
material.blend_method = "OPAQUE"
|
|
shadow_method = "OPAQUE"
|
|
elif value == self.ALPHA_MODE_MASK.value:
|
|
material.blend_method = "CLIP"
|
|
shadow_method = "CLIP"
|
|
elif value == self.ALPHA_MODE_BLEND.value:
|
|
material.blend_method = "HASHED"
|
|
shadow_method = "HASHED"
|
|
else:
|
|
logger.error("Unexpected alpha mode: {value}")
|
|
material.blend_method = "OPAQUE"
|
|
shadow_method = "OPAQUE"
|
|
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_ALPHA_MODE_LABEL,
|
|
value,
|
|
)
|
|
|
|
if changed:
|
|
self.set_mtoon0_render_queue_and_clamp(self.mtoon0_render_queue)
|
|
|
|
self.update_alpha_nodes(material, value)
|
|
|
|
if self.is_outline_material:
|
|
if bpy.app.version < (4, 2):
|
|
material.shadow_method = "NONE"
|
|
return
|
|
|
|
if bpy.app.version < (4, 3) and shadow_method is not None:
|
|
material.shadow_method = shadow_method
|
|
|
|
outline_material = self.outline_material
|
|
if not outline_material:
|
|
return
|
|
get_material_mtoon1_extension(outline_material).set_alpha_mode(value)
|
|
|
|
alpha_mode: EnumProperty( # type: ignore[valid-type]
|
|
items=alpha_mode_enum.items(),
|
|
name="Alpha Mode",
|
|
get=get_alpha_mode,
|
|
set=set_alpha_mode,
|
|
)
|
|
|
|
def get_double_sided(self) -> bool:
|
|
return not self.find_material().use_backface_culling
|
|
|
|
def set_double_sided(self, value: object) -> None:
|
|
material = self.find_material()
|
|
material.use_backface_culling = not value
|
|
self.set_bool(
|
|
shader.OUTPUT_GROUP_NAME, shader.OUTPUT_GROUP_DOUBLE_SIDED_LABEL, value
|
|
)
|
|
if get_material_mtoon1_extension(material).is_outline_material:
|
|
return
|
|
outline_material = get_material_mtoon1_extension(material).outline_material
|
|
if not outline_material:
|
|
return
|
|
get_material_mtoon1_extension(outline_material).double_sided = False
|
|
|
|
double_sided: BoolProperty( # type: ignore[valid-type]
|
|
name=shader.OUTPUT_GROUP_DOUBLE_SIDED_LABEL,
|
|
get=get_double_sided,
|
|
set=set_double_sided,
|
|
)
|
|
|
|
def get_alpha_cutoff(self) -> float:
|
|
return self.get_float(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_ALPHA_CUTOFF_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_ALPHA_CUTOFF_DEFAULT,
|
|
)
|
|
|
|
def set_alpha_cutoff(self, value: float) -> None:
|
|
material = self.find_material()
|
|
if bpy.app.version < (4, 2):
|
|
material.alpha_threshold = max(0, min(1, value - 0.00001)) # TODO: ...
|
|
self.set_value(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_ALPHA_CUTOFF_LABEL,
|
|
max(0, value),
|
|
)
|
|
|
|
self.update_alpha_nodes(material, self.get_alpha_mode())
|
|
|
|
if self.is_outline_material:
|
|
return
|
|
outline_material = self.outline_material
|
|
if not outline_material:
|
|
return
|
|
get_material_mtoon1_extension(outline_material).set_alpha_cutoff(value)
|
|
|
|
alpha_cutoff: FloatProperty( # type: ignore[valid-type]
|
|
name="Cutoff",
|
|
min=0,
|
|
soft_max=1,
|
|
get=get_alpha_cutoff,
|
|
set=set_alpha_cutoff,
|
|
)
|
|
|
|
normal_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1NormalTextureInfoPropertyGroup
|
|
)
|
|
|
|
emissive_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1EmissiveTextureInfoPropertyGroup
|
|
)
|
|
|
|
def get_emissive_factor(self) -> tuple[float, float, float]:
|
|
return self.get_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_EMISSIVE_FACTOR_LABEL,
|
|
default_value=shader.OUTPUT_GROUP_EMISSIVE_FACTOR_DEFAULT,
|
|
)
|
|
|
|
def set_emissive_factor(self, value: object) -> None:
|
|
self.set_rgb(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_EMISSIVE_FACTOR_LABEL,
|
|
value,
|
|
shader.OUTPUT_GROUP_EMISSIVE_FACTOR_DEFAULT,
|
|
)
|
|
|
|
material = self.find_material()
|
|
principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
|
|
principled_bsdf.emission_color = (
|
|
self.emissive_factor[0],
|
|
self.emissive_factor[1],
|
|
self.emissive_factor[2],
|
|
)
|
|
|
|
emissive_node = get_gltf_emissive_node(material)
|
|
if emissive_node is not None:
|
|
socket = emissive_node.inputs.get(EMISSION_COLOR_INPUT_KEY)
|
|
if isinstance(socket, NodeSocketColor):
|
|
socket.default_value = (
|
|
self.emissive_factor[0],
|
|
self.emissive_factor[1],
|
|
self.emissive_factor[2],
|
|
1,
|
|
)
|
|
|
|
mtoon1 = get_material_mtoon1_extension(material)
|
|
if mtoon1.is_outline_material:
|
|
return
|
|
outline_material = mtoon1.outline_material
|
|
if not outline_material:
|
|
return
|
|
outline_principled_bsdf = PrincipledBSDFWrapper(
|
|
outline_material, is_readonly=False
|
|
)
|
|
outline_principled_bsdf.emission_color = (
|
|
self.emissive_factor[0],
|
|
self.emissive_factor[1],
|
|
self.emissive_factor[2],
|
|
)
|
|
|
|
outline_emissive_node = get_gltf_emissive_node(material)
|
|
if outline_emissive_node is not None:
|
|
outline_socket = outline_emissive_node.inputs.get(EMISSION_COLOR_INPUT_KEY)
|
|
if isinstance(outline_socket, NodeSocketColor):
|
|
outline_socket.default_value = (
|
|
self.emissive_factor[0],
|
|
self.emissive_factor[1],
|
|
self.emissive_factor[2],
|
|
1,
|
|
)
|
|
|
|
emissive_factor: FloatVectorProperty( # type: ignore[valid-type]
|
|
size=3,
|
|
subtype="COLOR",
|
|
default=shader.OUTPUT_GROUP_EMISSIVE_FACTOR_DEFAULT,
|
|
min=0,
|
|
max=1,
|
|
get=get_emissive_factor,
|
|
set=set_emissive_factor,
|
|
)
|
|
|
|
extensions: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon1MaterialExtensionsPropertyGroup
|
|
)
|
|
|
|
def get_enabled(self) -> bool:
|
|
if self.is_outline_material:
|
|
return False
|
|
|
|
material = self.find_material()
|
|
if bpy.app.version < (5, 0, 0) and not material.use_nodes:
|
|
return False
|
|
|
|
node_tree = material.node_tree
|
|
if not node_tree:
|
|
return False
|
|
|
|
group_node = node_tree.nodes.get("Mtoon1Material.Mtoon1Output")
|
|
if not isinstance(group_node, ShaderNodeGroup):
|
|
return False
|
|
|
|
group_node_tree = group_node.node_tree
|
|
if not group_node_tree:
|
|
return False
|
|
|
|
if group_node_tree.name != shader.OUTPUT_GROUP_NAME:
|
|
return False
|
|
|
|
return bool(self.get("enabled"))
|
|
|
|
def set_enabled(self, value: object) -> None:
|
|
material = self.find_material()
|
|
|
|
if not value:
|
|
if self.get("enabled") and (
|
|
bpy.app.version >= (5, 0, 0) or material.use_nodes
|
|
):
|
|
ops.vrm.convert_mtoon1_to_bsdf_principled(material_name=material.name)
|
|
self["enabled"] = False
|
|
return
|
|
|
|
if bpy.app.version < (5, 0, 0) and not material.use_nodes:
|
|
material.use_nodes = True
|
|
if self.enabled:
|
|
return
|
|
|
|
ops.vrm.convert_material_to_mtoon1(material_name=material.name)
|
|
self["enabled"] = True
|
|
self.setup_drivers()
|
|
|
|
enabled: BoolProperty( # type: ignore[valid-type]
|
|
name="Enable VRM MToon Material",
|
|
get=get_enabled,
|
|
set=set_enabled,
|
|
)
|
|
|
|
export_shape_key_normals: BoolProperty( # type: ignore[valid-type]
|
|
name="Export Shape Key Normals",
|
|
)
|
|
|
|
def update_is_outline_material(self, _context: Context) -> None:
|
|
self.set_bool(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_IS_OUTLINE_LABEL,
|
|
self.is_outline_material,
|
|
)
|
|
self.set_bool(
|
|
shader.NORMAL_GROUP_NAME,
|
|
shader.NORMAL_GROUP_IS_OUTLINE_LABEL,
|
|
self.is_outline_material,
|
|
)
|
|
self.set_bool(
|
|
shader.OUTPUT_GROUP_NAME,
|
|
shader.OUTPUT_GROUP_DOUBLE_SIDED_LABEL,
|
|
False if self.is_outline_material else self.double_sided,
|
|
)
|
|
|
|
is_outline_material: BoolProperty( # type: ignore[valid-type]
|
|
update=update_is_outline_material,
|
|
)
|
|
|
|
outline_material: PointerProperty( # type: ignore[valid-type]
|
|
type=Material,
|
|
)
|
|
|
|
def all_texture_info(self) -> list[Mtoon1TextureInfoPropertyGroup]:
|
|
return [
|
|
self.pbr_metallic_roughness.base_color_texture,
|
|
self.normal_texture,
|
|
self.emissive_texture,
|
|
self.extensions.vrmc_materials_mtoon.shade_multiply_texture,
|
|
self.extensions.vrmc_materials_mtoon.shading_shift_texture,
|
|
self.extensions.vrmc_materials_mtoon.matcap_texture,
|
|
self.extensions.vrmc_materials_mtoon.rim_multiply_texture,
|
|
self.extensions.vrmc_materials_mtoon.outline_width_multiply_texture,
|
|
self.extensions.vrmc_materials_mtoon.uv_animation_mask_texture,
|
|
]
|
|
|
|
def all_textures(
|
|
self, *, downgrade_to_mtoon0: bool
|
|
) -> list[Union[Mtoon0TexturePropertyGroup, Mtoon1TexturePropertyGroup]]:
|
|
# TODO: remove code duplication
|
|
result: list[Union[Mtoon0TexturePropertyGroup, Mtoon1TexturePropertyGroup]] = []
|
|
result.extend(
|
|
[
|
|
self.pbr_metallic_roughness.base_color_texture.index,
|
|
self.extensions.vrmc_materials_mtoon.shade_multiply_texture.index,
|
|
self.normal_texture.index,
|
|
]
|
|
)
|
|
if downgrade_to_mtoon0:
|
|
result.extend(
|
|
[
|
|
self.mtoon0_receive_shadow_texture,
|
|
self.mtoon0_shading_grade_texture,
|
|
]
|
|
)
|
|
result.append(self.emissive_texture.index)
|
|
if not downgrade_to_mtoon0:
|
|
result.append(
|
|
self.extensions.vrmc_materials_mtoon.shading_shift_texture.index
|
|
)
|
|
result.extend(
|
|
[
|
|
self.extensions.vrmc_materials_mtoon.matcap_texture.index,
|
|
self.extensions.vrmc_materials_mtoon.rim_multiply_texture.index,
|
|
self.extensions.vrmc_materials_mtoon.outline_width_multiply_texture.index,
|
|
self.extensions.vrmc_materials_mtoon.uv_animation_mask_texture.index,
|
|
]
|
|
)
|
|
return result
|
|
|
|
show_expanded_mtoon0: BoolProperty( # type: ignore[valid-type]
|
|
name="Show MToon 0.0 Options",
|
|
)
|
|
|
|
mtoon0_front_cull_mode: BoolProperty( # type: ignore[valid-type]
|
|
name="Front Face Culling",
|
|
)
|
|
|
|
mtoon0_outline_scaled_max_distance: FloatProperty( # type: ignore[valid-type]
|
|
name="Outline Width Scaled Max Distance",
|
|
min=1,
|
|
default=1,
|
|
max=10,
|
|
)
|
|
|
|
mtoon0_light_color_attenuation: FloatProperty( # type: ignore[valid-type]
|
|
name="LightColor Attenuation",
|
|
min=0,
|
|
default=0,
|
|
max=1,
|
|
)
|
|
|
|
mtoon0_receive_shadow_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon0ReceiveShadowTexturePropertyGroup
|
|
)
|
|
|
|
mtoon0_receive_shadow_rate: FloatProperty( # type: ignore[valid-type]
|
|
min=0,
|
|
default=1,
|
|
max=1,
|
|
)
|
|
|
|
mtoon0_shading_grade_texture: PointerProperty( # type: ignore[valid-type]
|
|
type=Mtoon0ShadingGradeTexturePropertyGroup
|
|
)
|
|
|
|
mtoon0_shading_grade_rate: FloatProperty( # type: ignore[valid-type]
|
|
min=0,
|
|
default=1,
|
|
max=1,
|
|
)
|
|
|
|
mtoon0_rim_lighting_mix: FloatProperty( # type: ignore[valid-type]
|
|
name="Rim LightingMix (MToon 0.0)",
|
|
min=0,
|
|
default=0,
|
|
max=1,
|
|
)
|
|
|
|
def get_mtoon0_render_queue_and_clamp(self) -> int:
|
|
return int(self.mtoon0_render_queue)
|
|
|
|
def set_mtoon0_render_queue_and_clamp(self, value: int) -> None:
|
|
# https://github.com/Santarh/MToon/blob/42b03163459ac8e6b7aee08070d0f4f912035069/MToon/Scripts/Utils.cs#L74-L113
|
|
if self.alpha_mode == self.ALPHA_MODE_OPAQUE.identifier:
|
|
mtoon0_render_queue = 2000
|
|
elif self.alpha_mode == self.ALPHA_MODE_MASK.identifier:
|
|
mtoon0_render_queue = 2450
|
|
elif not self.extensions.vrmc_materials_mtoon.transparent_with_z_write:
|
|
mtoon0_render_queue = max(2951, min(3000, value))
|
|
else:
|
|
mtoon0_render_queue = max(2501, min(2550, value))
|
|
|
|
if self.mtoon0_render_queue != mtoon0_render_queue:
|
|
self.mtoon0_render_queue = mtoon0_render_queue
|
|
|
|
# Set the Render Queue value for MToon0. Performs clamping when assigning values.
|
|
# UniVRM performs clamping when setting values from UI or when changing
|
|
# Alpha Mode etc., so use this to match that behavior.
|
|
mtoon0_render_queue_and_clamp: IntProperty( # type: ignore[valid-type]
|
|
name="Render Queue",
|
|
get=get_mtoon0_render_queue_and_clamp,
|
|
set=set_mtoon0_render_queue_and_clamp,
|
|
)
|
|
|
|
# Set the Render Queue value for MToon0. Does not perform clamping when
|
|
# assigning values.
|
|
# UniVRM does not perform clamping during VRM0 import or export,
|
|
# so use this during import or export to match that behavior.
|
|
mtoon0_render_queue: IntProperty( # type: ignore[valid-type]
|
|
name="Render Queue",
|
|
default=2000,
|
|
)
|
|
|
|
def setup_drivers(self) -> None:
|
|
material = self.find_material()
|
|
for texture_info in self.all_texture_info():
|
|
texture_info.setup_drivers(material)
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
addon_version: Sequence[int] # type: ignore[no-redef]
|
|
pbr_metallic_roughness: ( # type: ignore[no-redef]
|
|
Mtoon1PbrMetallicRoughnessPropertyGroup
|
|
)
|
|
alpha_mode_blend_method_hashed: bool # type: ignore[no-redef]
|
|
alpha_mode: str # type: ignore[no-redef]
|
|
double_sided: bool # type: ignore[no-redef]
|
|
alpha_cutoff: float # type: ignore[no-redef]
|
|
normal_texture: Mtoon1NormalTextureInfoPropertyGroup # type: ignore[no-redef]
|
|
emissive_texture: ( # type: ignore[no-redef]
|
|
Mtoon1EmissiveTextureInfoPropertyGroup
|
|
)
|
|
emissive_factor: Sequence[float] # type: ignore[no-redef]
|
|
extensions: Mtoon1MaterialExtensionsPropertyGroup # type: ignore[no-redef]
|
|
enabled: bool # type: ignore[no-redef]
|
|
export_shape_key_normals: bool # type: ignore[no-redef]
|
|
is_outline_material: bool # type: ignore[no-redef]
|
|
outline_material: Optional[Material] # type: ignore[no-redef]
|
|
show_expanded_mtoon0: bool # type: ignore[no-redef]
|
|
mtoon0_front_cull_mode: bool # type: ignore[no-redef]
|
|
mtoon0_outline_scaled_max_distance: float # type: ignore[no-redef]
|
|
mtoon0_light_color_attenuation: float # type: ignore[no-redef]
|
|
mtoon0_receive_shadow_texture: ( # type: ignore[no-redef]
|
|
Mtoon0ReceiveShadowTexturePropertyGroup
|
|
)
|
|
mtoon0_receive_shadow_rate: float # type: ignore[no-redef]
|
|
mtoon0_shading_grade_texture: ( # type: ignore[no-redef]
|
|
Mtoon0ShadingGradeTexturePropertyGroup
|
|
)
|
|
mtoon0_shading_grade_rate: float # type: ignore[no-redef]
|
|
mtoon0_rim_lighting_mix: float # type: ignore[no-redef]
|
|
mtoon0_render_queue_and_clamp: int # type: ignore[no-redef]
|
|
mtoon0_render_queue: int # type: ignore[no-redef]
|
|
|
|
|
|
def reset_shader_node_group(
|
|
context: Context,
|
|
material: Material,
|
|
*,
|
|
reset_material_node_tree: bool,
|
|
reset_node_groups: bool,
|
|
) -> None:
|
|
gltf = get_material_mtoon1_extension(material)
|
|
mtoon = gltf.extensions.vrmc_materials_mtoon
|
|
|
|
base_color_factor = list(gltf.pbr_metallic_roughness.base_color_factor)
|
|
base_color_texture = gltf.pbr_metallic_roughness.base_color_texture.backup()
|
|
alpha_mode_blend_method_hashed = gltf.alpha_mode_blend_method_hashed
|
|
alpha_mode = gltf.alpha_mode
|
|
double_sided = gltf.double_sided
|
|
alpha_cutoff = gltf.alpha_cutoff
|
|
normal_texture = gltf.normal_texture.backup()
|
|
normal_texture_scale = gltf.normal_texture.scale
|
|
emissive_texture = gltf.emissive_texture.backup()
|
|
emissive_factor = list(gltf.emissive_factor)
|
|
export_shape_key_normals = gltf.export_shape_key_normals
|
|
emissive_strength = (
|
|
gltf.extensions.khr_materials_emissive_strength.emissive_strength
|
|
)
|
|
|
|
transparent_with_z_write = mtoon.transparent_with_z_write
|
|
render_queue_offset_number = mtoon.render_queue_offset_number
|
|
shade_multiply_texture = mtoon.shade_multiply_texture.backup()
|
|
shade_color_factor = list(mtoon.shade_color_factor)
|
|
shading_shift_texture = mtoon.shading_shift_texture.backup()
|
|
shading_shift_texture_scale = mtoon.shading_shift_texture.scale
|
|
shading_shift_factor = mtoon.shading_shift_factor
|
|
shading_toony_factor = mtoon.shading_toony_factor
|
|
gi_equalization_factor = mtoon.gi_equalization_factor
|
|
matcap_factor = list(mtoon.matcap_factor)
|
|
matcap_texture = mtoon.matcap_texture.backup()
|
|
parametric_rim_color_factor = list(mtoon.parametric_rim_color_factor)
|
|
rim_multiply_texture = mtoon.rim_multiply_texture.backup()
|
|
rim_lighting_mix_factor = mtoon.rim_lighting_mix_factor
|
|
parametric_rim_fresnel_power_factor = mtoon.parametric_rim_fresnel_power_factor
|
|
parametric_rim_lift_factor = mtoon.parametric_rim_lift_factor
|
|
enable_outline_preview = mtoon.enable_outline_preview
|
|
outline_width_mode = mtoon.outline_width_mode
|
|
outline_width_factor = mtoon.outline_width_factor
|
|
outline_width_multiply_texture = mtoon.outline_width_multiply_texture.backup()
|
|
outline_color_factor = list(mtoon.outline_color_factor)
|
|
outline_lighting_mix_factor = mtoon.outline_lighting_mix_factor
|
|
uv_animation_mask_texture = mtoon.uv_animation_mask_texture.backup()
|
|
uv_animation_scroll_x_speed_factor = mtoon.uv_animation_scroll_x_speed_factor
|
|
uv_animation_scroll_y_speed_factor = mtoon.uv_animation_scroll_y_speed_factor
|
|
uv_animation_rotation_speed_factor = mtoon.uv_animation_rotation_speed_factor
|
|
|
|
if reset_material_node_tree:
|
|
shader.load_mtoon1_shader(
|
|
context,
|
|
material,
|
|
reset_node_groups=reset_node_groups,
|
|
)
|
|
outline_material = gltf.outline_material
|
|
if outline_material:
|
|
shader.load_mtoon1_shader(
|
|
context,
|
|
outline_material,
|
|
reset_node_groups=reset_node_groups,
|
|
)
|
|
|
|
gltf.is_outline_material = False
|
|
outline_material = gltf.outline_material
|
|
if outline_material:
|
|
get_material_mtoon1_extension(outline_material).is_outline_material = True
|
|
|
|
gltf.pbr_metallic_roughness.base_color_factor = base_color_factor
|
|
gltf.pbr_metallic_roughness.base_color_texture.restore(base_color_texture)
|
|
gltf.alpha_mode_blend_method_hashed = alpha_mode_blend_method_hashed
|
|
gltf.alpha_mode = alpha_mode
|
|
gltf.double_sided = double_sided
|
|
gltf.alpha_cutoff = alpha_cutoff
|
|
gltf.normal_texture.restore(normal_texture)
|
|
gltf.normal_texture.scale = normal_texture_scale
|
|
gltf.emissive_texture.restore(emissive_texture)
|
|
gltf.emissive_factor = emissive_factor
|
|
gltf.export_shape_key_normals = export_shape_key_normals
|
|
gltf.extensions.khr_materials_emissive_strength.emissive_strength = (
|
|
emissive_strength
|
|
)
|
|
|
|
mtoon.transparent_with_z_write = transparent_with_z_write
|
|
mtoon.render_queue_offset_number = render_queue_offset_number
|
|
mtoon.shade_multiply_texture.restore(shade_multiply_texture)
|
|
mtoon.shade_color_factor = shade_color_factor
|
|
mtoon.shading_shift_texture.restore(shading_shift_texture)
|
|
mtoon.shading_shift_texture.scale = shading_shift_texture_scale
|
|
mtoon.shading_shift_factor = shading_shift_factor
|
|
mtoon.shading_toony_factor = shading_toony_factor
|
|
mtoon.gi_equalization_factor = gi_equalization_factor
|
|
mtoon.matcap_factor = matcap_factor
|
|
mtoon.matcap_texture.restore(matcap_texture)
|
|
mtoon.parametric_rim_color_factor = parametric_rim_color_factor
|
|
mtoon.rim_multiply_texture.restore(rim_multiply_texture)
|
|
mtoon.rim_lighting_mix_factor = rim_lighting_mix_factor
|
|
mtoon.parametric_rim_fresnel_power_factor = parametric_rim_fresnel_power_factor
|
|
mtoon.parametric_rim_lift_factor = parametric_rim_lift_factor
|
|
mtoon.enable_outline_preview = enable_outline_preview
|
|
mtoon.outline_width_mode = outline_width_mode
|
|
mtoon.outline_width_factor = outline_width_factor
|
|
mtoon.outline_width_multiply_texture.restore(outline_width_multiply_texture)
|
|
mtoon.outline_color_factor = outline_color_factor
|
|
mtoon.outline_lighting_mix_factor = outline_lighting_mix_factor
|
|
mtoon.uv_animation_mask_texture.restore(uv_animation_mask_texture)
|
|
mtoon.uv_animation_scroll_x_speed_factor = uv_animation_scroll_x_speed_factor
|
|
mtoon.uv_animation_scroll_y_speed_factor = uv_animation_scroll_y_speed_factor
|
|
mtoon.uv_animation_rotation_speed_factor = uv_animation_rotation_speed_factor
|
|
|
|
gltf.setup_drivers()
|
|
gltf.addon_version = get_addon_version()
|
|
|
|
|
|
def get_material_mtoon1_extension(material: Material) -> Mtoon1MaterialPropertyGroup:
|
|
from ..extension import get_material_extension
|
|
|
|
mtoon1: Mtoon1MaterialPropertyGroup = get_material_extension(material).mtoon1
|
|
return mtoon1
|
|
|
|
|
|
def setup_drivers(context: Context) -> None:
|
|
for material in context.blend_data.materials:
|
|
get_material_mtoon1_extension(material).setup_drivers()
|
|
shader.setup_frame_count_driver(context)
|
|
|
|
|
|
def link_or_unlink_gltf_material_nodes(
|
|
context: Context, material: Material, *, link: bool
|
|
) -> None:
|
|
TextureTraceablePropertyGroup.link_or_unlink_nodes(
|
|
material,
|
|
"Mtoon1Material.CompatibleShader",
|
|
ShaderNodeBsdfPrincipled,
|
|
"BSDF",
|
|
StaticNodeSocketTarget(
|
|
in_node_name="Mtoon1Material.CompatibleMaterialOutput",
|
|
in_node_type=ShaderNodeOutputMaterial,
|
|
in_socket_name="Surface",
|
|
),
|
|
link=link,
|
|
)
|
|
|
|
output_group = context.blend_data.node_groups.get(shader.OUTPUT_GROUP_NAME)
|
|
if output_group is None:
|
|
return
|
|
|
|
for from_node_name, to_node_name in [
|
|
(
|
|
"EeveeMaterialOutputSwitch",
|
|
"EeveeMaterialOutput",
|
|
),
|
|
(
|
|
"CyclesMaterialOutputSwitch",
|
|
"CyclesMaterialOutput",
|
|
),
|
|
]:
|
|
from_node = output_group.nodes.get(from_node_name)
|
|
if not from_node or not isinstance(from_node, NodeReroute):
|
|
continue
|
|
from_socket = from_node.outputs[0]
|
|
|
|
to_node = output_group.nodes.get(to_node_name)
|
|
if not to_node or not isinstance(to_node, ShaderNodeOutputMaterial):
|
|
continue
|
|
to_socket = to_node.inputs["Surface"]
|
|
|
|
existing_link = next(
|
|
(
|
|
link
|
|
for link in output_group.links
|
|
if link.from_node == from_node
|
|
and link.from_socket == from_socket
|
|
and link.to_node == to_node
|
|
and link.to_socket == to_socket
|
|
),
|
|
None,
|
|
)
|
|
if existing_link:
|
|
if not link:
|
|
output_group.links.remove(existing_link)
|
|
elif link:
|
|
output_group.links.new(
|
|
from_socket,
|
|
to_socket,
|
|
)
|