Files
blender-vrm/editor/mtoon1/property_group.py
小煜 091ad6a49a feat: Add VRM Blender addon with complete import/export functionality
- 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
2026-01-01 14:21:56 +08:00

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,
)