Holy shit this was a pain

- Truly fixes PMX Import lol, i messed up completely
- Updated MMD Tools to use Cats One
This commit is contained in:
Yusarina
2025-11-19 06:35:06 +00:00
parent f0bda259d3
commit a929f68ad4
38 changed files with 4479 additions and 2709 deletions
+48 -63
View File
@@ -1,48 +1,39 @@
# -*- coding: utf-8 -*-
# Copyright 2014 MMD Tools authors
# This file was originally part of the MMD Tools add-on for Blender
# You can find MMD Tools here: https://github.com/MMD-Blender/blender_mmd_tools
# Neoneko has modified this file to work with Avatar Toolkit and may of made changes or improvements.
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
# Copyright 2012 MMD Tools authors
# This file is part of MMD Tools.
from typing import Iterable, Optional, Any, List, Tuple, Union
from typing import Iterable, Optional
import bpy
from bpy.types import Material, NodeTree, Node, NodeSocket, ShaderNodeGroup, ShaderNodeOutputMaterial, NodeLink
from ..logging_setup import logger
from .core.shader import _NodeGroupUtils
from .core.material import FnMaterial
from .core.shader import _NodeGroupUtils
def __switchToCyclesRenderEngine() -> None:
def __switchToCyclesRenderEngine():
if bpy.context.scene.render.engine != "CYCLES":
logger.debug("Switching render engine to Cycles")
bpy.context.scene.render.engine = "CYCLES"
def __exposeNodeTreeInput(in_socket: NodeSocket, name: str, default_value: Any, node_input: Node, shader: NodeTree) -> None:
def __exposeNodeTreeInput(in_socket, name, default_value, node_input, shader):
_NodeGroupUtils(shader).new_input_socket(name, in_socket, default_value)
def __exposeNodeTreeOutput(out_socket: NodeSocket, name: str, node_output: Node, shader: NodeTree) -> None:
def __exposeNodeTreeOutput(out_socket, name, node_output, shader):
_NodeGroupUtils(shader).new_output_socket(name, out_socket)
def __getMaterialOutput(nodes: bpy.types.Nodes, bl_idname: str) -> Node:
def __getMaterialOutput(nodes, bl_idname):
o = next((n for n in nodes if n.bl_idname == bl_idname and n.is_active_output), None) or nodes.new(bl_idname)
o.is_active_output = True
return o
def create_MMDAlphaShader() -> NodeTree:
def create_MMDAlphaShader():
__switchToCyclesRenderEngine()
if "MMDAlphaShader" in bpy.data.node_groups:
logger.debug("Using existing MMDAlphaShader node group")
return bpy.data.node_groups["MMDAlphaShader"]
logger.info("Creating new MMDAlphaShader node group")
shader = bpy.data.node_groups.new(name="MMDAlphaShader", type="ShaderNodeTree")
node_input = shader.nodes.new("NodeGroupInput")
@@ -64,28 +55,26 @@ def create_MMDAlphaShader() -> NodeTree:
return shader
def create_MMDBasicShader() -> NodeTree:
def create_MMDBasicShader():
__switchToCyclesRenderEngine()
if "MMDBasicShader" in bpy.data.node_groups:
logger.debug("Using existing MMDBasicShader node group")
return bpy.data.node_groups["MMDBasicShader"]
logger.info("Creating new MMDBasicShader node group")
shader: NodeTree = bpy.data.node_groups.new(name="MMDBasicShader", type="ShaderNodeTree")
shader: bpy.types.ShaderNodeTree = bpy.data.node_groups.new(name="MMDBasicShader", type="ShaderNodeTree")
node_input: Node = shader.nodes.new("NodeGroupInput")
node_output: Node = 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: Node = shader.nodes.new("ShaderNodeBsdfDiffuse")
dif: bpy.types.ShaderNodeBsdfDiffuse = shader.nodes.new("ShaderNodeBsdfDiffuse")
dif.location.x -= 250
dif.location.y += 150
glo: Node = shader.nodes.new("ShaderNodeBsdfAnisotropic")
glo: bpy.types.ShaderNodeBsdfAnisotropic = shader.nodes.new("ShaderNodeBsdfAnisotropic")
glo.location.x -= 250
glo.location.y -= 150
mix: Node = 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"])
@@ -98,63 +87,62 @@ def create_MMDBasicShader() -> NodeTree:
return shader
def __enum_linked_nodes(node: Node) -> Iterable[Node]:
def __enum_linked_nodes(node: bpy.types.Node) -> Iterable[bpy.types.Node]:
yield node
if node.parent:
yield node.parent
for n in set(l.from_node for i in node.inputs for l in i.links):
for n in {link.from_node for i in node.inputs for link in i.links}:
yield from __enum_linked_nodes(n)
def __cleanNodeTree(material: Material) -> None:
logger.debug(f"Cleaning node tree for material: {material.name}")
def __cleanNodeTree(material: bpy.types.Material):
nodes = material.node_tree.nodes
node_names = set(n.name for n in nodes)
node_names = {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):
node_names -= set(linked.name for linked in __enum_linked_nodes(o))
node_names -= {linked.name for linked in __enum_linked_nodes(o)}
for name in node_names:
nodes.remove(nodes[name])
def convertToCyclesShader(obj: bpy.types.Object, use_principled: bool = False, clean_nodes: bool = False, subsurface: float = 0.001) -> None:
logger.info(f"Converting {obj.name} to Cycles shader (use_principled={use_principled}, clean_nodes={clean_nodes})")
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: bpy.types.Object, use_principled: bool = False, clean_nodes: bool = False, subsurface: float = 0.001) -> None:
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
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
if not i.material.node_tree or len(i.material.node_tree.nodes) == 0:
logger.debug(f"Setting up node tree for material: {i.material.name}")
# use_nodes is deprecated in 5.0 but always returns True and setting it is safe
if not i.material.use_nodes:
i.material.use_nodes = True
__convertToMMDBasicShader(i.material)
if use_principled:
logger.debug(f"Converting material to Principled BSDF: {i.material.name}")
__convertToPrincipledBsdf(i.material, subsurface)
if clean_nodes:
__cleanNodeTree(i.material)
def convertToMMDShader(obj: bpy.types.Object) -> None:
def convertToMMDShader(obj):
"""BSDF -> MMDShaderDev conversion."""
logger.info(f"Converting {obj.name} to MMD shader")
for i in obj.material_slots:
if not i.material:
continue
# Note: material.use_nodes is deprecated in Blender 5.0 - materials always use nodes
# use_nodes is deprecated in 5.0 but always returns True and setting it is safe
if not i.material.use_nodes:
i.material.use_nodes = True
FnMaterial.convert_to_mmd_material(i.material)
def __convertToMMDBasicShader(material: Material) -> None:
logger.debug(f"Converting material to MMD Basic Shader: {material.name}")
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, ShaderNodeGroup) and x.node_tree.name in {"MMDBasicShader", "MMDAlphaShader"}, material.node_tree.nodes)):
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: ShaderNodeGroup = 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]
@@ -169,8 +157,7 @@ def __convertToMMDBasicShader(material: Material) -> None:
alpha_value = material.diffuse_color[3]
if alpha_value < 1.0:
logger.debug(f"Material has alpha: {material.name}, alpha={alpha_value}")
alpha_shader: ShaderNodeGroup = material.node_tree.nodes.new("ShaderNodeGroup")
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
@@ -178,22 +165,21 @@ def __convertToMMDBasicShader(material: Material) -> None:
material.node_tree.links.new(alpha_shader.inputs[0], outplug)
outplug = alpha_shader.outputs[0]
material_output: ShaderNodeOutputMaterial = __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
def __convertToPrincipledBsdf(material: Material, subsurface: float) -> None:
logger.debug(f"Converting material to Principled BSDF: {material.name}")
def __convertToPrincipledBsdf(material: bpy.types.Material, subsurface: float):
node_names = set()
for s in (n for n in material.node_tree.nodes if isinstance(n, 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: NodeLink
for l in s.outputs[0].links:
to_node = l.to_node
link: bpy.types.NodeLink
for link in s.outputs[0].links:
to_node = link.to_node
# assuming there is no bpy.types.NodeReroute between MMDBasicShader and MMDAlphaShader
if isinstance(to_node, ShaderNodeGroup) and to_node.node_tree.name == "MMDAlphaShader":
if isinstance(to_node, bpy.types.ShaderNodeGroup) and to_node.node_tree.name == "MMDAlphaShader":
__switchToPrincipledBsdf(material.node_tree, s, subsurface, node_alpha=to_node)
node_names.add(to_node.name)
else:
@@ -208,9 +194,8 @@ def __convertToPrincipledBsdf(material: Material, subsurface: float) -> None:
nodes.remove(nodes[name])
def __switchToPrincipledBsdf(node_tree: NodeTree, node_basic: ShaderNodeGroup, subsurface: float, node_alpha: Optional[ShaderNodeGroup] = None) -> None:
logger.debug(f"Switching to Principled BSDF: {node_basic.name}")
shader: Node = node_tree.nodes.new("ShaderNodeBsdfPrincipled")
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
shader.location.y = node_basic.location.y
@@ -238,7 +223,7 @@ def __switchToPrincipledBsdf(node_tree: NodeTree, node_basic: ShaderNodeGroup, s
if alpha_socket_name in node_alpha.inputs:
if "Alpha" in shader.inputs:
shader.inputs["Alpha"].default_value = node_alpha.inputs[alpha_socket_name].default_value
shader.inputs["Alpha"].default_value = node_alpha.inputs["Alpha"].default_value
if node_alpha.inputs[alpha_socket_name].is_linked:
node_tree.links.new(node_alpha.inputs[alpha_socket_name].links[0].from_socket, shader.inputs["Alpha"])
else:
@@ -254,5 +239,5 @@ def __switchToPrincipledBsdf(node_tree: NodeTree, node_basic: ShaderNodeGroup, s
node_tree.links.new(node_alpha.inputs[alpha_socket_name].links[0].from_socket, node_invert.inputs[1])
node_tree.links.new(node_invert.outputs[0], shader.inputs["Transmission"])
for l in output_links:
node_tree.links.new(shader.outputs[0], l.to_socket)
for link in output_links:
node_tree.links.new(shader.outputs[0], link.to_socket)