diff --git a/mmd_tools/core/material.py b/mmd_tools/core/material.py index 5c29e326..6b0f3c4c 100644 --- a/mmd_tools/core/material.py +++ b/mmd_tools/core/material.py @@ -4,14 +4,18 @@ import logging import os -from typing import Optional, Tuple, cast +from typing import TYPE_CHECKING, Callable, Iterable, Optional, Tuple, cast import bpy +from mathutils import Vector from mmd_tools.bpyutils import addon_preferences from mmd_tools.core.exceptions import MaterialNotFoundError from mmd_tools.core.shader import _NodeGroupUtils +if TYPE_CHECKING: + from mmd_tools.properties.material import MMDMaterial + SPHERE_MODE_OFF = 0 SPHERE_MODE_MULT = 1 SPHERE_MODE_ADD = 2 @@ -33,25 +37,25 @@ def __init__(self, image): class FnMaterial: - __NODES_ARE_READONLY = False + __NODES_ARE_READONLY: bool = False - def __init__(self, material=None): + def __init__(self, material: bpy.types.Material): self.__material = material self._nodes_are_readonly = FnMaterial.__NODES_ARE_READONLY @staticmethod - def set_nodes_are_readonly(nodes_are_readonly): + def set_nodes_are_readonly(nodes_are_readonly: bool): FnMaterial.__NODES_ARE_READONLY = nodes_are_readonly @classmethod - def from_material_id(cls, material_id): + def from_material_id(cls, material_id: str): for material in bpy.data.materials: if material.mmd_material.material_id == material_id: return cls(material) return None @staticmethod - def clean_materials(obj, can_remove): + def clean_materials(obj, can_remove: Callable[[bpy.types.Material], bool]): materials = obj.data.materials materials_pop = materials.pop for i in sorted((x for x, m in enumerate(materials) if can_remove(m)), reverse=True): @@ -105,20 +109,21 @@ def swap_materials(mesh_object: bpy.types.Object, mat1_ref: str | int, mat2_ref: return mat1, mat2 @staticmethod - def fixMaterialOrder(meshObj, material_names): + def fixMaterialOrder(meshObj: bpy.types.Object, material_names: Iterable[str]): """ This method will fix the material order. Which is lost after joining meshes. """ + materials = cast(bpy.types.Mesh, meshObj.data).materials for new_idx, mat in enumerate(material_names): # Get the material that is currently on this index - other_mat = meshObj.data.materials[new_idx] + other_mat = materials[new_idx] if other_mat.name == mat: continue # This is already in place FnMaterial.swap_materials(meshObj, mat, new_idx, reverse=True, swap_slots=True) @property def material_id(self): - mmd_mat = self.__material.mmd_material + mmd_mat: MMDMaterial = self.__material.mmd_material if mmd_mat.material_id < 0: max_id = -1 for mat in bpy.data.materials: @@ -164,7 +169,7 @@ def _load_image(self, filepath): def update_toon_texture(self): if self._nodes_are_readonly: return - mmd_mat = self.__material.mmd_material + mmd_mat: MMDMaterial = self.__material.mmd_material if mmd_mat.is_shared_toon_texture: shared_toon_folder = addon_preferences("shared_toon_folder", "") toon_path = os.path.join(shared_toon_folder, "toon%02d.bmp" % (mmd_mat.shared_toon_texture + 1)) @@ -174,7 +179,7 @@ def update_toon_texture(self): else: self.remove_toon_texture() - def _mixDiffuseAndAmbient(self, mmd_mat): + def _mix_diffuse_and_ambient(self, mmd_mat): r, g, b = mmd_mat.diffuse_color ar, ag, ab = mmd_mat.ambient_color return [min(1.0, 0.5 * r + ar), min(1.0, 0.5 * g + ag), min(1.0, 0.5 * b + ab)] @@ -191,13 +196,13 @@ def update_edge_color(self): if self._nodes_are_readonly: return mat = self.__material - mmd_mat = mat.mmd_material + mmd_mat: MMDMaterial = mat.mmd_material color, alpha = mmd_mat.edge_color[:3], mmd_mat.edge_color[3] line_color = color + (min(alpha, int(mmd_mat.enabled_toon_edge)),) if hasattr(mat, "line_color"): # freestyle line color mat.line_color = line_color - mat_edge = bpy.data.materials.get("mmd_edge." + mat.name, None) + mat_edge: bpy.types.Material = bpy.data.materials.get("mmd_edge." + mat.name, None) if mat_edge: mat_edge.mmd_material.edge_color = line_color @@ -330,7 +335,7 @@ def update_ambient_color(self): return mat = self.material mmd_mat = mat.mmd_material - mat.diffuse_color[:3] = self._mixDiffuseAndAmbient(mmd_mat) + mat.diffuse_color[:3] = self._mix_diffuse_and_ambient(mmd_mat) self.__update_shader_input("Ambient Color", mmd_mat.ambient_color[:] + (1,)) def update_diffuse_color(self): @@ -338,7 +343,7 @@ def update_diffuse_color(self): return mat = self.material mmd_mat = mat.mmd_material - mat.diffuse_color[:3] = self._mixDiffuseAndAmbient(mmd_mat) + mat.diffuse_color[:3] = self._mix_diffuse_and_ambient(mmd_mat) self.__update_shader_input("Diffuse Color", mmd_mat.diffuse_color[:] + (1,)) def update_alpha(self): @@ -488,8 +493,6 @@ def __update_shader_nodes(self): mat.use_nodes = True mat.node_tree.nodes.clear() - from mathutils import Vector - nodes, links = mat.node_tree.nodes, mat.node_tree.links class _Dummy: @@ -497,13 +500,13 @@ class _Dummy: node_shader = nodes.get("mmd_shader", None) if node_shader is None: - node_shader = nodes.new("ShaderNodeGroup") + node_shader: bpy.types.ShaderNodeGroup = nodes.new("ShaderNodeGroup") node_shader.name = "mmd_shader" node_shader.location = (0, 1500) node_shader.width = 200 node_shader.node_tree = self.__get_shader() - mmd_mat = mat.mmd_material + mmd_mat: MMDMaterial = mat.mmd_material node_shader.inputs.get("Ambient Color", _Dummy).default_value = mmd_mat.ambient_color[:] + (1,) node_shader.inputs.get("Diffuse Color", _Dummy).default_value = mmd_mat.diffuse_color[:] + (1,) node_shader.inputs.get("Specular Color", _Dummy).default_value = mmd_mat.specular_color[:] + (1,) @@ -515,7 +518,7 @@ class _Dummy: node_uv = nodes.get("mmd_tex_uv", None) if node_uv is None: - node_uv = nodes.new("ShaderNodeGroup") + node_uv: bpy.types.ShaderNodeGroup = nodes.new("ShaderNodeGroup") node_uv.name = "mmd_tex_uv" node_uv.location = node_shader.location + Vector((-5 * 210, -2.5 * 220)) node_uv.node_tree = self.__get_shader_uv() @@ -523,7 +526,7 @@ class _Dummy: if not (node_shader.outputs["Shader"].is_linked or node_shader.outputs["Color"].is_linked or node_shader.outputs["Alpha"].is_linked): node_output = next((n for n in nodes if isinstance(n, bpy.types.ShaderNodeOutputMaterial) and n.is_active_output), None) if node_output is None: - node_output = nodes.new("ShaderNodeOutputMaterial") + node_output: bpy.types.ShaderNodeOutputMaterial = nodes.new("ShaderNodeOutputMaterial") node_output.is_active_output = True node_output.location = node_shader.location + Vector((400, 0)) links.new(node_shader.outputs["Shader"], node_output.inputs["Surface"]) @@ -541,30 +544,26 @@ class _Dummy: def __get_shader_uv(self): group_name = "MMDTexUV" - shader = bpy.data.node_groups.get(group_name, None) or bpy.data.node_groups.new(name=group_name, type="ShaderNodeTree") + shader: bpy.types.ShaderNodeTree = bpy.data.node_groups.get(group_name, None) or bpy.data.node_groups.new(name=group_name, type="ShaderNodeTree") if len(shader.nodes): return shader ng = _NodeGroupUtils(shader) ############################################################################ - _node_output = ng.new_node("NodeGroupOutput", (6, 0)) + _node_output: bpy.types.NodeGroupOutput = ng.new_node("NodeGroupOutput", (6, 0)) - tex_coord = ng.new_node("ShaderNodeTexCoord", (0, 0)) + tex_coord: bpy.types.ShaderNodeTexCoord = ng.new_node("ShaderNodeTexCoord", (0, 0)) - if hasattr(bpy.types, "ShaderNodeUVMap"): - tex_coord1 = ng.new_node("ShaderNodeUVMap", (4, -2)) - tex_coord1.uv_map, socketUV1 = "UV1", "UV" - else: - tex_coord1 = ng.new_node("ShaderNodeAttribute", (4, -2)) - tex_coord1.attribute_name, socketUV1 = "UV1", "Vector" + tex_coord1: bpy.types.ShaderNodeUVMap = ng.new_node("ShaderNodeUVMap", (4, -2)) + tex_coord1.uv_map = "UV1" - vec_trans = ng.new_node("ShaderNodeVectorTransform", (1, -1)) + vec_trans: bpy.types.ShaderNodeVectorTransform = ng.new_node("ShaderNodeVectorTransform", (1, -1)) vec_trans.vector_type = "NORMAL" vec_trans.convert_from = "OBJECT" vec_trans.convert_to = "CAMERA" - node_vector = ng.new_node("ShaderNodeMapping", (2, -1)) + node_vector: bpy.types.ShaderNodeMapping = ng.new_node("ShaderNodeMapping", (2, -1)) node_vector.vector_type = "POINT" node_vector.inputs["Location"].default_value = (0.5, 0.5, 0.0) node_vector.inputs["Scale"].default_value = (0.5, 0.5, 1.0) @@ -576,49 +575,49 @@ def __get_shader_uv(self): ng.new_output_socket("Base UV", tex_coord.outputs["UV"]) ng.new_output_socket("Toon UV", node_vector.outputs["Vector"]) ng.new_output_socket("Sphere UV", node_vector.outputs["Vector"]) - ng.new_output_socket("SubTex UV", tex_coord1.outputs[socketUV1]) + ng.new_output_socket("SubTex UV", tex_coord1.outputs["UV"]) return shader def __get_shader(self): group_name = "MMDShaderDev" - shader = bpy.data.node_groups.get(group_name, None) or bpy.data.node_groups.new(name=group_name, type="ShaderNodeTree") + shader: bpy.types.ShaderNodeTree = bpy.data.node_groups.get(group_name, None) or bpy.data.node_groups.new(name=group_name, type="ShaderNodeTree") if len(shader.nodes): return shader ng = _NodeGroupUtils(shader) ############################################################################ - node_input = ng.new_node("NodeGroupInput", (-5, -1)) - _node_output = ng.new_node("NodeGroupOutput", (11, 1)) + node_input: bpy.types.NodeGroupInput = ng.new_node("NodeGroupInput", (-5, -1)) + _node_output: bpy.types.NodeGroupOutput = ng.new_node("NodeGroupOutput", (11, 1)) - node_diffuse = ng.new_mix_node("ADD", (-3, 4), fac=0.6) + node_diffuse: bpy.types.ShaderNodeMath = ng.new_mix_node("ADD", (-3, 4), fac=0.6) node_diffuse.use_clamp = True - node_tex = ng.new_mix_node("MULTIPLY", (-2, 3.5)) - node_toon = ng.new_mix_node("MULTIPLY", (-1, 3)) - node_sph = ng.new_mix_node("MULTIPLY", (0, 2.5)) - node_spa = ng.new_mix_node("ADD", (0, 1.5)) - node_sphere = ng.new_mix_node("MIX", (1, 1)) + node_tex: bpy.types.ShaderNodeMath = ng.new_mix_node("MULTIPLY", (-2, 3.5)) + node_toon: bpy.types.ShaderNodeMath = ng.new_mix_node("MULTIPLY", (-1, 3)) + node_sph: bpy.types.ShaderNodeMath = ng.new_mix_node("MULTIPLY", (0, 2.5)) + node_spa: bpy.types.ShaderNodeMath = ng.new_mix_node("ADD", (0, 1.5)) + node_sphere: bpy.types.ShaderNodeMath = ng.new_mix_node("MIX", (1, 1)) - node_geo = ng.new_node("ShaderNodeNewGeometry", (6, 3.5)) - node_invert = ng.new_math_node("LESS_THAN", (7, 3)) - node_cull = ng.new_math_node("MAXIMUM", (8, 2.5)) - node_alpha = ng.new_math_node("MINIMUM", (9, 2)) + node_geo: bpy.types.ShaderNodeNewGeometry = ng.new_node("ShaderNodeNewGeometry", (6, 3.5)) + node_invert: bpy.types.ShaderNodeMath = ng.new_math_node("LESS_THAN", (7, 3)) + node_cull: bpy.types.ShaderNodeMath = ng.new_math_node("MAXIMUM", (8, 2.5)) + node_alpha: bpy.types.ShaderNodeMath = ng.new_math_node("MINIMUM", (9, 2)) node_alpha.use_clamp = True - node_alpha_tex = ng.new_math_node("MULTIPLY", (-1, -2)) - node_alpha_toon = ng.new_math_node("MULTIPLY", (0, -2.5)) - node_alpha_sph = ng.new_math_node("MULTIPLY", (1, -3)) + node_alpha_tex: bpy.types.ShaderNodeMath = ng.new_math_node("MULTIPLY", (-1, -2)) + node_alpha_toon: bpy.types.ShaderNodeMath = ng.new_math_node("MULTIPLY", (0, -2.5)) + node_alpha_sph: bpy.types.ShaderNodeMath = ng.new_math_node("MULTIPLY", (1, -3)) - node_reflect = ng.new_math_node("DIVIDE", (7, -1.5), value1=1) + node_reflect: bpy.types.ShaderNodeMath = ng.new_math_node("DIVIDE", (7, -1.5), value1=1) node_reflect.use_clamp = True - shader_diffuse = ng.new_node("ShaderNodeBsdfDiffuse", (8, 0)) - shader_glossy = ng.new_node("ShaderNodeBsdfGlossy", (8, -1)) - shader_base_mix = ng.new_node("ShaderNodeMixShader", (9, 0)) + shader_diffuse: bpy.types.ShaderNodeBsdfDiffuse = ng.new_node("ShaderNodeBsdfDiffuse", (8, 0)) + shader_glossy: bpy.types.ShaderNodeBsdfAnisotropic = ng.new_node("ShaderNodeBsdfAnisotropic", (8, -1)) + shader_base_mix: bpy.types.ShaderNodeMixShader = ng.new_node("ShaderNodeMixShader", (9, 0)) shader_base_mix.inputs["Fac"].default_value = 0.02 - shader_trans = ng.new_node("ShaderNodeBsdfTransparent", (9, 1)) - shader_alpha_mix = ng.new_node("ShaderNodeMixShader", (10, 1)) + shader_trans: bpy.types.ShaderNodeBsdfTransparent = ng.new_node("ShaderNodeBsdfTransparent", (9, 1)) + shader_alpha_mix: bpy.types.ShaderNodeMixShader = ng.new_node("ShaderNodeMixShader", (10, 1)) links = ng.links links.new(node_reflect.outputs["Value"], shader_glossy.inputs["Roughness"]) @@ -679,10 +678,10 @@ def update_mmd_shader(): if mmd_shader_node_tree is None: return - if "Color" in mmd_shader_node_tree.outputs: + ng = _NodeGroupUtils(mmd_shader_node_tree) + if "Color" in ng.node_output.inputs: return - ng = _NodeGroupUtils(mmd_shader_node_tree) shader_diffuse: bpy.types.ShaderNodeBsdfDiffuse = [n for n in mmd_shader_node_tree.nodes if n.type == "BSDF_DIFFUSE"][0] node_sphere: bpy.types.ShaderNodeMixRGB = shader_diffuse.inputs["Color"].links[0].from_node node_output: bpy.types.NodeGroupOutput = ng.node_output diff --git a/mmd_tools/core/model.py b/mmd_tools/core/model.py index 69fa0c64..b5e53845 100644 --- a/mmd_tools/core/model.py +++ b/mmd_tools/core/model.py @@ -645,18 +645,19 @@ def allObjects(self, obj: Optional[bpy.types.Object] = None) -> Iterator[bpy.typ yield obj yield from FnModel.iterate_child_objects(obj) - def rootObject(self): + def rootObject(self) -> bpy.types.Object: return self.__root - def armature(self): + def armature(self) -> bpy.types.Object: if self.__arm is None: self.__arm = FnModel.find_armature_object(self.__root) + assert self.__arm is not None return self.__arm - def hasRigidGroupObject(self): + def hasRigidGroupObject(self) -> bool: return FnModel.find_rigid_group_object(self.__root) is not None - def rigidGroupObject(self): + def rigidGroupObject(self) -> bpy.types.Object: if self.__rigid_grp is None: self.__rigid_grp = FnModel.find_rigid_group_object(self.__root) if self.__rigid_grp is None: @@ -670,10 +671,10 @@ def rigidGroupObject(self): self.__rigid_grp = rigids return self.__rigid_grp - def hasJointGroupObject(self): + def hasJointGroupObject(self) -> bool: return FnModel.find_joint_group_object(self.__root) is not None - def jointGroupObject(self): + def jointGroupObject(self) -> bpy.types.Object: if self.__joint_grp is None: self.__joint_grp = FnModel.find_joint_group_object(self.__root) if self.__joint_grp is None: @@ -687,10 +688,10 @@ def jointGroupObject(self): self.__joint_grp = joints return self.__joint_grp - def hasTemporaryGroupObject(self): + def hasTemporaryGroupObject(self) -> bool: return FnModel.find_temporary_group_object(self.__root) is not None - def temporaryGroupObject(self): + def temporaryGroupObject(self) -> bpy.types.Object: if self.__temporary_grp is None: self.__temporary_grp = FnModel.find_temporary_group_object(self.__root) if self.__temporary_grp is None: @@ -704,18 +705,18 @@ def temporaryGroupObject(self): self.__temporary_grp = temporarys return self.__temporary_grp - def meshes(self): + def meshes(self) -> Iterator[bpy.types.Object]: return FnModel.iterate_mesh_objects(self.__root) def attachMeshes(self, meshes: Iterator[bpy.types.Object], add_armature_modifier: bool = True): FnModel.attach_mesh_objects(self.rootObject(), meshes, add_armature_modifier) - def firstMesh(self): + def firstMesh(self) -> Optional[bpy.types.Object]: for i in self.meshes(): return i return None - def findMesh(self, mesh_name): + def findMesh(self, mesh_name) -> Optional[bpy.types.Object]: """ Helper method to find a mesh by name """ @@ -726,7 +727,7 @@ def findMesh(self, mesh_name): return mesh return None - def findMeshByIndex(self, index): + def findMeshByIndex(self, index: int) -> Optional[bpy.types.Object]: """ Helper method to find the mesh by index """ @@ -737,7 +738,7 @@ def findMeshByIndex(self, index): return mesh return None - def getMeshIndex(self, mesh_name): + def getMeshIndex(self, mesh_name: str) -> int: """ Helper method to get the index of a mesh. Returns -1 if not found """ @@ -748,23 +749,23 @@ def getMeshIndex(self, mesh_name): return i return -1 - def rigidBodies(self): + def rigidBodies(self) -> Iterator[bpy.types.Object]: return FnModel.iterate_rigid_body_objects(self.__root) - def joints(self): + def joints(self) -> Iterator[bpy.types.Object]: return FnModel.iterate_joint_objects(self.__root) - def temporaryObjects(self, rigid_track_only=False): + def temporaryObjects(self, rigid_track_only=False) -> Iterator[bpy.types.Object]: return FnModel.iterate_temporary_objects(self.__root, rigid_track_only) - def materials(self): + def materials(self) -> Iterator[bpy.types.Material]: """ Helper method to list all materials in all meshes """ materials = {} # Use dict instead of set to guarantee preserve order for mesh in self.meshes(): materials.update((slot.material, 0) for slot in mesh.material_slots if slot.material is not None) - return list(materials.keys()) + return iter(materials.keys()) def renameBone(self, old_bone_name, new_bone_name): if old_bone_name == new_bone_name: diff --git a/mmd_tools/core/shader.py b/mmd_tools/core/shader.py index cab9dba3..4455ed63 100644 --- a/mmd_tools/core/shader.py +++ b/mmd_tools/core/shader.py @@ -2,18 +2,21 @@ # Copyright 2019 MMD Tools authors # This file is part of MMD Tools. +from typing import Optional, Tuple, cast import bpy class _NodeTreeUtils: - def __init__(self, shader: bpy.types.NodeTree): - self.shader, self.nodes, self.links = shader, shader.nodes, shader.links + def __init__(self, shader: bpy.types.ShaderNodeTree): + self.shader = shader + self.nodes: bpy.types.bpy_prop_collection[bpy.types.ShaderNode] = shader.nodes # type: ignore + self.links = shader.links - def _find_node(self, node_type): + def _find_node(self, node_type: str) -> Optional[bpy.types.ShaderNode]: return next((n for n in self.nodes if n.bl_idname == node_type), None) - def new_node(self, idname, pos): - node = self.nodes.new(idname) + def new_node(self, idname: str, pos: Tuple[int, int]) -> bpy.types.ShaderNode: + node: bpy.types.ShaderNode = self.nodes.new(idname) node.location = (pos[0] * 210, pos[1] * 220) return node @@ -53,31 +56,33 @@ def new_mix_node(self, blend_type, pos, fac=None, color1=None, color2=None): class _NodeGroupUtils(_NodeTreeUtils): - def __init__(self, shader: bpy.types.NodeTree): + def __init__(self, shader: bpy.types.ShaderNodeTree): super().__init__(shader) - self.__node_input = self.__node_output = None + self.__node_input: Optional[bpy.types.NodeGroupInput] = None + self.__node_output: Optional[bpy.types.NodeGroupOutput] = None @property - def node_input(self): + def node_input(self) -> bpy.types.NodeGroupInput: if not self.__node_input: - self.__node_input = self._find_node("NodeGroupInput") or self.new_node("NodeGroupInput", (-2, 0)) + self.__node_input = cast(bpy.types.NodeGroupInput, self._find_node("NodeGroupInput") or self.new_node("NodeGroupInput", (-2, 0))) return self.__node_input @property - def node_output(self): + def node_output(self) -> bpy.types.NodeGroupOutput: if not self.__node_output: - self.__node_output = self._find_node("NodeGroupOutput") or self.new_node("NodeGroupOutput", (2, 0)) + self.__node_output = cast(bpy.types.NodeGroupOutput, self._find_node("NodeGroupOutput") or self.new_node("NodeGroupOutput", (2, 0))) return self.__node_output def hide_nodes(self, hide_sockets=True): skip_nodes = {self.__node_input, self.__node_output} for n in (x for x in self.nodes if x not in skip_nodes): n.hide = True - if hide_sockets: - for s in n.inputs: - s.hide = not s.is_linked - for s in n.outputs: - s.hide = not s.is_linked + if not hide_sockets: + continue + for s in n.inputs: + s.hide = not s.is_linked + for s in n.outputs: + s.hide = not s.is_linked def new_input_socket(self, io_name, socket, default_val=None, min_max=None, socket_type=None): self.__new_io("INPUT", self.node_input.outputs, io_name, socket, default_val, min_max, socket_type) diff --git a/mmd_tools/cycles_converter.py b/mmd_tools/cycles_converter.py index a74206f8..50c19bd5 100644 --- a/mmd_tools/cycles_converter.py +++ b/mmd_tools/cycles_converter.py @@ -2,7 +2,7 @@ # Copyright 2012 MMD Tools authors # This file is part of MMD Tools. -import logging +from typing import Iterable, Optional import bpy @@ -61,20 +61,20 @@ def create_MMDBasicShader(): if "MMDBasicShader" in bpy.data.node_groups: return bpy.data.node_groups["MMDBasicShader"] - shader = bpy.data.node_groups.new(name="MMDBasicShader", type="ShaderNodeTree") + shader: bpy.types.ShaderNodeTree = bpy.data.node_groups.new(name="MMDBasicShader", type="ShaderNodeTree") - node_input = shader.nodes.new("NodeGroupInput") - node_output = shader.nodes.new("NodeGroupOutput") + node_input: bpy.types.NodeGroupInput = shader.nodes.new("NodeGroupInput") + node_output: bpy.types.NodeGroupOutput = shader.nodes.new("NodeGroupOutput") node_output.location.x += 250 node_input.location.x -= 500 - dif = shader.nodes.new("ShaderNodeBsdfDiffuse") + dif: bpy.types.ShaderNodeBsdfDiffuse = shader.nodes.new("ShaderNodeBsdfDiffuse") dif.location.x -= 250 dif.location.y += 150 - glo = shader.nodes.new("ShaderNodeBsdfGlossy") + glo: bpy.types.ShaderNodeBsdfAnisotropic = shader.nodes.new("ShaderNodeBsdfAnisotropic") glo.location.x -= 250 glo.location.y -= 150 - mix = shader.nodes.new("ShaderNodeMixShader") + mix: bpy.types.ShaderNodeMixShader = shader.nodes.new("ShaderNodeMixShader") shader.links.new(mix.inputs[1], dif.outputs["BSDF"]) shader.links.new(mix.inputs[2], glo.outputs["BSDF"]) @@ -87,7 +87,7 @@ def create_MMDBasicShader(): return shader -def __enum_linked_nodes(node): +def __enum_linked_nodes(node: bpy.types.Node) -> Iterable[bpy.types.Node]: yield node if node.parent: yield node.parent @@ -95,8 +95,8 @@ def __enum_linked_nodes(node): yield from __enum_linked_nodes(n) -def __cleanNodeTree(material): - nodes = getattr(material.node_tree, "nodes", ()) +def __cleanNodeTree(material: bpy.types.Material): + nodes = material.node_tree.nodes node_names = set(n.name for n in nodes) for o in (n for n in nodes if n.bl_idname in {"ShaderNodeOutput", "ShaderNodeOutputMaterial"}): if any(i.is_linked for i in o.inputs): @@ -105,17 +105,12 @@ def __cleanNodeTree(material): nodes.remove(nodes[name]) -def is_principled_bsdf_supported(): - return hasattr(bpy.types, "ShaderNodeBsdfPrincipled") - - -def convertToCyclesShader(obj, use_principled=False, clean_nodes=False, subsurface=0.001): +def convertToCyclesShader(obj: bpy.types.Object, use_principled=False, clean_nodes=False, subsurface=0.001): __switchToCyclesRenderEngine() convertToBlenderShader(obj, use_principled, clean_nodes, subsurface) -def convertToBlenderShader(obj, use_principled=False, clean_nodes=False, subsurface=0.001): - use_principled = use_principled and is_principled_bsdf_supported() +def convertToBlenderShader(obj: bpy.types.Object, use_principled=False, clean_nodes=False, subsurface=0.001): for i in obj.material_slots: if not i.material: continue @@ -128,177 +123,58 @@ def convertToBlenderShader(obj, use_principled=False, clean_nodes=False, subsurf __cleanNodeTree(i.material) -def __convertToMMDBasicShader(material): +def __convertToMMDBasicShader(material: bpy.types.Material): + # TODO: test me mmd_basic_shader_grp = create_MMDBasicShader() mmd_alpha_shader_grp = create_MMDAlphaShader() if not any(filter(lambda x: isinstance(x, bpy.types.ShaderNodeGroup) and x.node_tree.name in {"MMDBasicShader", "MMDAlphaShader"}, material.node_tree.nodes)): # Add nodes for Cycles Render - shader = material.node_tree.nodes.new("ShaderNodeGroup") + shader: bpy.types.ShaderNodeGroup = material.node_tree.nodes.new("ShaderNodeGroup") shader.node_tree = mmd_basic_shader_grp shader.inputs[0].default_value[:3] = material.diffuse_color[:3] shader.inputs[1].default_value[:3] = material.specular_color[:3] shader.inputs["glossy_rough"].default_value = 1.0 / getattr(material, "specular_hardness", 50) outplug = shader.outputs[0] - node_tex, node_alpha = None, None location = shader.location.copy() location.x -= 1000 - reuse_nodes = {} - for j in getattr(material, "texture_slots", ()): - if j and j.use and isinstance(j.texture, bpy.types.ImageTexture) and getattr(j.texture.image, "depth", 0): - if not (j.use_map_color_diffuse or j.use_map_alpha): - continue - if j.texture_coords not in {"UV", "NORMAL"}: - continue - - uv_tag = j.uv_layer if j.texture_coords == "UV" else "" - key_node_tex = j.texture.name + j.texture_coords + uv_tag - tex_img = reuse_nodes.get(key_node_tex) - if tex_img is None: - tex_img = material.node_tree.nodes.new("ShaderNodeTexImage") - tex_img.location = location - tex_img.image = j.texture.image - if j.texture_coords == "NORMAL" and j.blend_type == "ADD": - tex_img.color_space = "NONE" - reuse_nodes[key_node_tex] = tex_img - location.x += 250 - location.y -= 150 - - key_node_vec = j.texture_coords + uv_tag - plug_vector = reuse_nodes.get(key_node_vec) - if plug_vector is None: - plug_vector = 0 - if j.texture_coords == "UV": - if j.uv_layer and hasattr(bpy.types, "ShaderNodeUVMap"): - node_vector = material.node_tree.nodes.new("ShaderNodeUVMap") - node_vector.uv_map = j.uv_layer - node_vector.location.x = shader.location.x - 1500 - node_vector.location.y = tex_img.location.y - 50 - plug_vector = node_vector.outputs[0] - elif j.uv_layer: - node_vector = material.node_tree.nodes.new("ShaderNodeAttribute") - node_vector.attribute_name = j.uv_layer - node_vector.location.x = shader.location.x - 1500 - node_vector.location.y = tex_img.location.y - 50 - plug_vector = node_vector.outputs[1] - - elif j.texture_coords == "NORMAL": - tex_coord = material.node_tree.nodes.new("ShaderNodeTexCoord") - tex_coord.location.x = shader.location.x - 1900 - tex_coord.location.y = shader.location.y - 600 - - vec_trans = material.node_tree.nodes.new("ShaderNodeVectorTransform") - vec_trans.vector_type = "NORMAL" - vec_trans.convert_from = "OBJECT" - vec_trans.convert_to = "CAMERA" - vec_trans.location.x = tex_coord.location.x + 200 - vec_trans.location.y = tex_coord.location.y - material.node_tree.links.new(vec_trans.inputs[0], tex_coord.outputs["Normal"]) - - node_vector = material.node_tree.nodes.new("ShaderNodeMapping") - node_vector.vector_type = "POINT" - node_vector.translation = (0.5, 0.5, 0.0) - node_vector.scale = (0.5, 0.5, 1.0) - node_vector.location.x = vec_trans.location.x + 200 - node_vector.location.y = vec_trans.location.y - material.node_tree.links.new(node_vector.inputs[0], vec_trans.outputs[0]) - plug_vector = node_vector.outputs[0] - - reuse_nodes[key_node_vec] = plug_vector - - if plug_vector: - material.node_tree.links.new(tex_img.inputs[0], plug_vector) - - if j.use_map_color_diffuse: - if node_tex is None and tuple(material.diffuse_color) == (1.0, 1.0, 1.0): - node_tex = tex_img - else: - node_tex_last = node_tex - node_tex = material.node_tree.nodes.new("ShaderNodeMixRGB") - try: - node_tex.blend_type = j.blend_type - except TypeError as ex: - logging.exception(node_tex) - node_tex.inputs[0].default_value = 1.0 - node_tex.inputs[1].default_value = shader.inputs[0].default_value - node_tex.location.x = tex_img.location.x + 250 - node_tex.location.y = tex_img.location.y + 150 - material.node_tree.links.new(node_tex.inputs[2], tex_img.outputs["Color"]) - if node_tex_last: - material.node_tree.links.new(node_tex.inputs[1], node_tex_last.outputs[0]) - - if j.use_map_alpha: - if node_alpha is None and material.alpha == 1.0: - node_alpha = tex_img - else: - node_alpha_last = node_alpha - node_alpha = material.node_tree.nodes.new("ShaderNodeMath") - try: - node_alpha.operation = j.blend_type - except TypeError as ex: - logging.exception(node_alpha) - node_alpha.inputs[0].default_value = material.alpha - node_alpha.location.x = tex_img.location.x + 250 - node_alpha.location.y = tex_img.location.y - 500 - material.node_tree.links.new(node_alpha.inputs[1], tex_img.outputs["Alpha"]) - if node_alpha_last: - material.node_tree.links.new(node_alpha.inputs[0], node_alpha_last.outputs[len(node_alpha_last.outputs) - 1]) - - if node_tex: - material.node_tree.links.new(shader.inputs[0], node_tex.outputs[0]) alpha_value = 1.0 - if hasattr(material, "alpha"): - alpha_value = material.alpha - elif len(material.diffuse_color) > 3: + if len(material.diffuse_color) > 3: alpha_value = material.diffuse_color[3] - if node_alpha or alpha_value < 1.0: - alpha_shader = material.node_tree.nodes.new("ShaderNodeGroup") + if alpha_value < 1.0: + alpha_shader: bpy.types.ShaderNodeGroup = material.node_tree.nodes.new("ShaderNodeGroup") alpha_shader.location.x = shader.location.x + 250 alpha_shader.location.y = shader.location.y - 150 alpha_shader.node_tree = mmd_alpha_shader_grp alpha_shader.inputs[1].default_value = alpha_value material.node_tree.links.new(alpha_shader.inputs[0], outplug) outplug = alpha_shader.outputs[0] - if node_alpha: - material.node_tree.links.new(alpha_shader.inputs[1], node_alpha.outputs[len(node_alpha.outputs) - 1]) - material_output = __getMaterialOutput(material.node_tree.nodes, "ShaderNodeOutputMaterial") + material_output: bpy.types.ShaderNodeOutputMaterial = __getMaterialOutput(material.node_tree.nodes, "ShaderNodeOutputMaterial") material.node_tree.links.new(material_output.inputs["Surface"], outplug) material_output.location.x = shader.location.x + 500 material_output.location.y = shader.location.y - 150 - if not hasattr(bpy.types, "ShaderNodeMaterial"): - return - # Add necessary nodes to retain Blender Render functionality - out_node = __getMaterialOutput(material.node_tree.nodes, "ShaderNodeOutput") - mat_node = material.node_tree.nodes.new("ShaderNodeMaterial") - mat_node.material = material - mat_node.location.x = shader.location.x - 250 - mat_node.location.y = shader.location.y + 500 - out_node.location.x = mat_node.location.x + 750 - out_node.location.y = mat_node.location.y - material.node_tree.links.new(out_node.inputs["Color"], mat_node.outputs["Color"]) - material.node_tree.links.new(out_node.inputs["Alpha"], mat_node.outputs["Alpha"]) - - -def __convertToPrincipledBsdf(material, subsurface): + +def __convertToPrincipledBsdf(material: bpy.types.Material, subsurface: float): node_names = set() - for s in tuple(n for n in material.node_tree.nodes if isinstance(n, bpy.types.ShaderNodeGroup)): + for s in (n for n in material.node_tree.nodes if isinstance(n, bpy.types.ShaderNodeGroup)): if s.node_tree.name == "MMDBasicShader": + l: bpy.types.NodeLink for l in s.outputs[0].links: to_node = l.to_node # assuming there is no bpy.types.NodeReroute between MMDBasicShader and MMDAlphaShader if isinstance(to_node, bpy.types.ShaderNodeGroup) and to_node.node_tree.name == "MMDAlphaShader": - __switchToPrincipledBsdf(material.node_tree, s, to_node, subsurface=subsurface) + __switchToPrincipledBsdf(material.node_tree, s, subsurface, node_alpha=to_node) node_names.add(to_node.name) else: - __switchToPrincipledBsdf(material.node_tree, s, subsurface=subsurface) + __switchToPrincipledBsdf(material.node_tree, s, subsurface) node_names.add(s.name) elif s.node_tree.name == "MMDShaderDev": - __switchToPrincipledBsdf(material.node_tree, s, subsurface=subsurface) + __switchToPrincipledBsdf(material.node_tree, s, subsurface) node_names.add(s.name) # remove MMD shader nodes nodes = material.node_tree.nodes @@ -306,7 +182,7 @@ def __convertToPrincipledBsdf(material, subsurface): nodes.remove(nodes[name]) -def __switchToPrincipledBsdf(node_tree, node_basic, node_alpha=None, subsurface=0): +def __switchToPrincipledBsdf(node_tree: bpy.types.NodeTree, node_basic: bpy.types.ShaderNodeGroup, subsurface: float, node_alpha: Optional[bpy.types.ShaderNodeGroup] = None): shader: bpy.types.ShaderNodeBsdfPrincipled = node_tree.nodes.new("ShaderNodeBsdfPrincipled") shader.parent = node_basic.parent shader.location.x = node_basic.location.x @@ -325,7 +201,7 @@ def __switchToPrincipledBsdf(node_tree, node_basic, node_alpha=None, subsurface= node_tree.links.new(node_basic.inputs["diffuse"].links[0].from_socket, shader.inputs["Base Color"]) shader.inputs["IOR"].default_value = 1.0 - shader.inputs["Subsurface"].default_value = subsurface + shader.inputs["Subsurface Weight"].default_value = subsurface output_links = node_basic.outputs[0].links if node_alpha: diff --git a/mmd_tools/operators/material.py b/mmd_tools/operators/material.py index 316c5298..68a0435f 100644 --- a/mmd_tools/operators/material.py +++ b/mmd_tools/operators/material.py @@ -38,8 +38,7 @@ def poll(cls, context): def draw(self, context): layout = self.layout - if cycles_converter.is_principled_bsdf_supported(): - layout.prop(self, "use_principled") + layout.prop(self, "use_principled") layout.prop(self, "clean_nodes") def execute(self, context): diff --git a/mmd_tools/operators/translations.py b/mmd_tools/operators/translations.py index f28a04a4..b6574540 100644 --- a/mmd_tools/operators/translations.py +++ b/mmd_tools/operators/translations.py @@ -2,7 +2,7 @@ # Copyright 2021 MMD Tools authors # This file is part of MMD Tools. -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast import bpy @@ -106,7 +106,7 @@ def translate(self, name_j, name_e): name_e = None return self.__translator.translate(name_j, name_e) - def translate_blender_names(self, rig): + def translate_blender_names(self, rig: Model): if "BONE" in self.types: for b in rig.armature().pose.bones: rig.renameBone(b.name, self.translate(b.name, b.name)) @@ -121,7 +121,8 @@ def translate_blender_names(self, rig): m.name = self.translate(m.name, m.name) if "DISPLAY" in self.types: - for g in rig.armature().pose.bone_groups: + g: bpy.types.BoneCollection + for g in cast(bpy.types.Armature, rig.armature().data).collections: g.name = self.translate(g.name, g.name) if "PHYSICS" in self.types: diff --git a/mmd_tools/typings/mmd_tools/properties/material.pyi b/mmd_tools/typings/mmd_tools/properties/material.pyi new file mode 100644 index 00000000..6c2bc700 --- /dev/null +++ b/mmd_tools/typings/mmd_tools/properties/material.pyi @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 MMD Tools authors +# This file is part of MMD Tools. + +from typing import List + +class MMDMaterial(bpy.types.PropertyGroup): + name_j: str + name_e: str + + material_id: int + + ambient_color: List[float] + diffuse_color: List[float] + + alpha: float + + specular_color: List[float] + + shininess: float + + is_double_sided: bool + + enabled_drop_shadow: bool + + enabled_self_shadow_map: bool + + enabled_self_shadow: bool + + enabled_toon_edge: bool + + edge_color: List[float] + + edge_weight: float + + sphere_texture_type: str + + is_shared_toon_texture: bool + toon_texture: str + + shared_toon_texture: int + + comment: int + + def is_id_unique(self) -> bool: ... diff --git a/mmd_tools/typings/mmd_tools/properties/root.pyi b/mmd_tools/typings/mmd_tools/properties/root.pyi index 1072f800..5597d02c 100644 --- a/mmd_tools/typings/mmd_tools/properties/root.pyi +++ b/mmd_tools/typings/mmd_tools/properties/root.pyi @@ -2,13 +2,27 @@ # Copyright 2024 MMD Tools authors # This file is part of MMD Tools. -from typing import List +from typing import Literal +import bpy from mmd_tools.properties.morph import BoneMorph, GroupMorph, MaterialMorph, UVMorph, VertexMorph from mmd_tools.properties.translations import MMDTranslation +class MMDDisplayItem: + name: str + type: Literal["BONE"] | Literal["MORPH"] + + morph_type: Literal["material_morphs"] | Literal["uv_morphs"] | Literal["bone_morphs"] | Literal["vertex_morphs"] | Literal["group_morphs"] + class MMDDisplayItemFrame: - pass + name: str + name_e: str + + is_special: bool + + data: bpy.types.bpy_prop_collection[MMDDisplayItem] + + active_item: int class MMDRoot: name: str @@ -31,13 +45,13 @@ class MMDRoot: is_built: bool active_rigidbody_index: int active_joint_index: int - display_item_frames: List[MMDDisplayItemFrame] + display_item_frames: bpy.types.bpy_prop_collection[MMDDisplayItemFrame] active_display_item_frame: int - material_morphs: List[MaterialMorph] - uv_morphs: List[UVMorph] - bone_morphs: List[BoneMorph] - vertex_morphs: List[VertexMorph] - group_morphs: List[GroupMorph] + material_morphs: bpy.types.bpy_prop_collection[MaterialMorph] + uv_morphs: bpy.types.bpy_prop_collection[UVMorph] + bone_morphs: bpy.types.bpy_prop_collection[BoneMorph] + vertex_morphs: bpy.types.bpy_prop_collection[VertexMorph] + group_morphs: bpy.types.bpy_prop_collection[GroupMorph] active_morph_type: str # TODO: Replace with StrEnum active_morph: int diff --git a/mmd_tools/utils.py b/mmd_tools/utils.py index c5370db9..e57c7835 100644 --- a/mmd_tools/utils.py +++ b/mmd_tools/utils.py @@ -107,7 +107,7 @@ def mergeVertexGroup(meshObj, src_vertex_group_name, dest_vertex_group_name): pass -def separateByMaterials(meshObj): +def separateByMaterials(meshObj: bpy.types.Object): if len(meshObj.data.materials) < 2: selectAObject(meshObj) return @@ -122,14 +122,12 @@ def separateByMaterials(meshObj): bpy.ops.mesh.separate(type="MATERIAL") finally: bpy.ops.object.mode_set(mode="OBJECT") - for i in dummy_parent.children: - if custom_normal_keeper: - custom_normal_keeper.restore_custom_normals(i.data) - materials = i.data.materials - i.name = getattr(materials[0], "name", "None") if len(materials) else "None" - i.parent = prev_parent - i.matrix_parent_inverse = matrix_parent_inverse - bpy.data.objects.remove(dummy_parent) + for i in dummy_parent.children: + materials = i.data.materials + i.name = getattr(materials[0], "name", "None") if len(materials) else "None" + i.parent = prev_parent + i.matrix_parent_inverse = matrix_parent_inverse + bpy.data.objects.remove(dummy_parent) def clearUnusedMeshes(): @@ -233,7 +231,7 @@ def get_by_index(items, index): return None @staticmethod - def resize(items, length): + def resize(items: bpy.types.bpy_prop_collection, length: int): count = length - len(items) if count > 0: for i in range(count):