Files
Avatar-Toolkit/core/mmd/material.py
T
Yusarina 3414ad8917 Initial MMD Importer Commit
- This is the initial commit I spent several hours trying to get it up two Avatar Toolkit standard, it does not work yet because there are files missing but I been doing this since 6am and it is 4pm almost, i need food.
- I have also removed as much legacy code as i could, MMD Tools contains so much of it even though there have a 4.2+ only version there have not removed any of the legacy code for pre 4.2.... this is going to take a while.

God I hope this works fine once I am done.
2025-04-03 15:39:03 +01:00

698 lines
30 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2013 MMD Tools authors
# This file was originally part of the MMD Tools project, However Neoneko has added it to Avatar Toolkit.
# All credit goes to the original authors.
# Please note that some code was modified to fit the needs of Avatar Toolkit and some code may of been removed.
# MMD Tools is licensed under the terms of the GPL-3.0 license which Avatar Toolkit is also licensed under.
# You can find MMD Tools at: https://github.com/MMD-Blender/blender_mmd_tools/
import logging
import os
from typing import TYPE_CHECKING, Callable, Iterable, Optional, Tuple, cast
import bpy
from mathutils import Vector
from ..logging_setup import logger
from .exceptions import MaterialNotFoundError
from .shader import _NodeGroupUtils
if TYPE_CHECKING:
from ..properties.material import MMDMaterial
# Constants for sphere modes
SPHERE_MODE_OFF = 0
SPHERE_MODE_MULT = 1
SPHERE_MODE_ADD = 2
SPHERE_MODE_SUBTEX = 3
class FnMaterial:
__NODES_ARE_READONLY: bool = False
def __init__(self, material: bpy.types.Material):
self.__material = material
self._nodes_are_readonly = FnMaterial.__NODES_ARE_READONLY
logger.debug(f"Initializing FnMaterial for {material.name}")
@staticmethod
def set_nodes_are_readonly(nodes_are_readonly: bool):
FnMaterial.__NODES_ARE_READONLY = nodes_are_readonly
@classmethod
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: 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):
m = materials_pop(index=i)
if m.users < 1:
bpy.data.materials.remove(m)
@staticmethod
def swap_materials(mesh_object: bpy.types.Object, mat1_ref: str | int, mat2_ref: str | int, reverse=False, swap_slots=False) -> Tuple[bpy.types.Material, bpy.types.Material]:
"""
This method will assign the polygons of mat1 to mat2.
If reverse is True it will also swap the polygons assigned to mat2 to mat1.
The reference to materials can be indexes or names
Finally it will also swap the material slots if the option is given.
Args:
mesh_object (bpy.types.Object): The mesh object
mat1_ref (str | int): The reference to the first material
mat2_ref (str | int): The reference to the second material
reverse (bool, optional): If true it will also swap the polygons assigned to mat2 to mat1. Defaults to False.
swap_slots (bool, optional): If true it will also swap the material slots. Defaults to False.
Retruns:
Tuple[bpy.types.Material, bpy.types.Material]: The swapped materials
Raises:
MaterialNotFoundError: If one of the materials is not found
"""
mesh = cast(bpy.types.Mesh, mesh_object.data)
try:
# Try to find the materials
mat1 = mesh.materials[mat1_ref]
mat2 = mesh.materials[mat2_ref]
if None in (mat1, mat2):
raise MaterialNotFoundError()
except (KeyError, IndexError) as exc:
# Wrap exceptions within our custom ones
raise MaterialNotFoundError() from exc
mat1_idx = mesh.materials.find(mat1.name)
mat2_idx = mesh.materials.find(mat2.name)
# Swap polygons
for poly in mesh.polygons:
if poly.material_index == mat1_idx:
poly.material_index = mat2_idx
elif reverse and poly.material_index == mat2_idx:
poly.material_index = mat1_idx
# Swap slots if specified
if swap_slots:
mesh_object.material_slots[mat1_idx].material = mat2
mesh_object.material_slots[mat2_idx].material = mat1
return mat1, mat2
@staticmethod
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 = 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
if mmd_mat.material_id < 0:
max_id = -1
for mat in bpy.data.materials:
max_id = max(max_id, mat.mmd_material.material_id)
mmd_mat.material_id = max_id + 1
return mmd_mat.material_id
@property
def material(self):
return self.__material
def __same_image_file(self, image, filepath):
if image and image.source == "FILE":
img_filepath = bpy.path.abspath(image.filepath)
if img_filepath == filepath:
return True
try:
return os.path.samefile(img_filepath, filepath)
except:
pass
return False
def _load_image(self, filepath):
img = next((i for i in bpy.data.images if self.__same_image_file(i, filepath)), None)
if img is None:
try:
img = bpy.data.images.load(filepath)
logger.debug(f"Loaded image from {filepath}")
except:
logger.warning(f"Cannot create a texture for {filepath}. No such file.")
img = bpy.data.images.new(os.path.basename(filepath), 1, 1)
img.source = "FILE"
img.filepath = filepath
# For Blender 4.4+
if img.depth == 32 and img.file_format != "BMP":
img.alpha_mode = "CHANNEL_PACKED"
else:
img.alpha_mode = "NONE"
return img
def update_toon_texture(self):
if self._nodes_are_readonly:
return
mmd_mat = self.__material.mmd_material
if mmd_mat.is_shared_toon_texture:
# Get shared toon folder from preferences
context = bpy.context
addon_prefs = context.preferences.addons.get("avatar_toolkit", None)
if addon_prefs:
shared_toon_folder = addon_prefs.preferences.shared_toon_folder
else:
shared_toon_folder = ""
toon_path = os.path.join(shared_toon_folder, f"toon{mmd_mat.shared_toon_texture + 1:02d}.bmp")
self.create_toon_texture(bpy.path.resolve_ncase(path=toon_path))
elif mmd_mat.toon_texture != "":
self.create_toon_texture(mmd_mat.toon_texture)
else:
self.remove_toon_texture()
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)]
def update_drop_shadow(self):
pass
def update_enabled_toon_edge(self):
if self._nodes_are_readonly:
return
self.update_edge_color()
def update_edge_color(self):
if self._nodes_are_readonly:
return
mat = self.__material
mmd_mat = 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)),)
# For Blender 4.4+
if hasattr(mat, "line_color"): # freestyle line color
mat.line_color = line_color
mat_edge = bpy.data.materials.get("mmd_edge." + mat.name, None)
if mat_edge:
mat_edge.mmd_material.edge_color = line_color
if mat.name.startswith("mmd_edge.") and mat.node_tree:
mmd_mat.ambient_color, mmd_mat.alpha = color, alpha
node_shader = mat.node_tree.nodes.get("mmd_edge_preview", None)
if node_shader and "Color" in node_shader.inputs:
node_shader.inputs["Color"].default_value = mmd_mat.edge_color
if node_shader and "Alpha" in node_shader.inputs:
node_shader.inputs["Alpha"].default_value = alpha
def update_edge_weight(self):
pass
def get_texture(self):
return self.__get_texture_node("mmd_base_tex")
def create_texture(self, filepath):
texture = self.__create_texture_node("mmd_base_tex", filepath, (-4, -1))
return texture
def remove_texture(self):
if self._nodes_are_readonly:
return
self.__remove_texture_node("mmd_base_tex")
def get_sphere_texture(self):
return self.__get_texture_node("mmd_sphere_tex")
def use_sphere_texture(self, use_sphere, obj=None):
if self._nodes_are_readonly:
return
if use_sphere:
self.update_sphere_texture_type(obj)
else:
self.__update_shader_input("Sphere Tex Fac", 0)
def create_sphere_texture(self, filepath, obj=None):
texture = self.__create_texture_node("mmd_sphere_tex", filepath, (-2, -2))
self.update_sphere_texture_type(obj)
return texture
def update_sphere_texture_type(self, obj=None):
if self._nodes_are_readonly:
return
sphere_texture_type = int(self.material.mmd_material.sphere_texture_type)
is_sph_add = sphere_texture_type == 2
if sphere_texture_type not in (1, 2, 3):
self.__update_shader_input("Sphere Tex Fac", 0)
else:
self.__update_shader_input("Sphere Tex Fac", 1)
self.__update_shader_input("Sphere Mul/Add", is_sph_add)
self.__update_shader_input("Sphere Tex", (0, 0, 0, 1) if is_sph_add else (1, 1, 1, 1))
texture = self.__get_texture_node("mmd_sphere_tex")
if texture and (not texture.inputs["Vector"].is_linked or texture.inputs["Vector"].links[0].from_node.name == "mmd_tex_uv"):
# For Blender 4.4+
texture.image.colorspace_settings.name = "Linear Rec.709" if is_sph_add else "sRGB"
mat = self.material
nodes, links = mat.node_tree.nodes, mat.node_tree.links
if sphere_texture_type == 3:
if obj and obj.type == "MESH" and mat in tuple(obj.data.materials):
uv_layers = (l for l in obj.data.uv_layers if not l.name.startswith("_"))
next(uv_layers, None) # skip base UV
subtex_uv = getattr(next(uv_layers, None), "name", "")
if subtex_uv != "UV1":
logger.info(f'Material({mat.name}): object "{obj.name}" use UV "{subtex_uv}" for SubTex')
links.new(nodes["mmd_tex_uv"].outputs["SubTex UV"], texture.inputs["Vector"])
else:
links.new(nodes["mmd_tex_uv"].outputs["Sphere UV"], texture.inputs["Vector"])
def remove_sphere_texture(self):
if self._nodes_are_readonly:
return
self.__remove_texture_node("mmd_sphere_tex")
def get_toon_texture(self):
return self.__get_texture_node("mmd_toon_tex")
def use_toon_texture(self, use_toon):
if self._nodes_are_readonly:
return
self.__update_shader_input("Toon Tex Fac", use_toon)
def create_toon_texture(self, filepath):
texture = self.__create_texture_node("mmd_toon_tex", filepath, (-3, -1.5))
return texture
def remove_toon_texture(self):
if self._nodes_are_readonly:
return
self.__remove_texture_node("mmd_toon_tex")
def __get_texture_node(self, node_name):
mat = self.material
texture = getattr(mat.node_tree, "nodes", {}).get(node_name, None)
if isinstance(texture, bpy.types.ShaderNodeTexImage):
return texture
return None
def __remove_texture_node(self, node_name):
mat = self.material
texture = getattr(mat.node_tree, "nodes", {}).get(node_name, None)
if isinstance(texture, bpy.types.ShaderNodeTexImage):
mat.node_tree.nodes.remove(texture)
mat.update_tag()
def __create_texture_node(self, node_name, filepath, pos):
texture = self.__get_texture_node(node_name)
if texture is None:
from mathutils import Vector
self.__update_shader_nodes()
nodes = self.material.node_tree.nodes
texture = nodes.new("ShaderNodeTexImage")
texture.label = bpy.path.display_name(node_name)
texture.name = node_name
texture.location = nodes["mmd_shader"].location + Vector((pos[0] * 210, pos[1] * 220))
texture.image = self._load_image(filepath)
self.__update_shader_nodes()
return texture
def update_ambient_color(self):
if self._nodes_are_readonly:
return
mat = self.material
mmd_mat = mat.mmd_material
# For Blender 4.4+
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):
if self._nodes_are_readonly:
return
mat = self.material
mmd_mat = mat.mmd_material
# For Blender 4.4+
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):
if self._nodes_are_readonly:
return
mat = self.material
mmd_mat = mat.mmd_material
# For Blender 4.4+
mat.blend_method = "HASHED"
# Update alpha in diffuse_color
if len(mat.diffuse_color) > 3:
mat.diffuse_color[3] = mmd_mat.alpha
self.__update_shader_input("Alpha", mmd_mat.alpha)
self.update_self_shadow_map()
def update_specular_color(self):
if self._nodes_are_readonly:
return
mat = self.material
mmd_mat = mat.mmd_material
mat.specular_color = mmd_mat.specular_color
self.__update_shader_input("Specular Color", mmd_mat.specular_color[:] + (1,))
def update_shininess(self):
if self._nodes_are_readonly:
return
mat = self.material
mmd_mat = mat.mmd_material
# For Blender 4.4+
mat.roughness = 1 / pow(max(mmd_mat.shininess, 1), 0.37)
mat.metallic = pow(1 - mat.roughness, 2.7)
self.__update_shader_input("Reflect", mmd_mat.shininess)
def update_is_double_sided(self):
if self._nodes_are_readonly:
return
mat = self.material
mmd_mat = mat.mmd_material
# For Blender 4.4+
mat.use_backface_culling = not mmd_mat.is_double_sided
self.__update_shader_input("Double Sided", mmd_mat.is_double_sided)
def update_self_shadow_map(self):
if self._nodes_are_readonly:
return
mat = self.material
mmd_mat = mat.mmd_material
cast_shadows = mmd_mat.enabled_self_shadow_map if mmd_mat.alpha > 1e-3 else False
# For Blender 4.4+
mat.shadow_method = "HASHED" if cast_shadows else "NONE"
def update_self_shadow(self):
if self._nodes_are_readonly:
return
mat = self.material
mmd_mat = mat.mmd_material
self.__update_shader_input("Self Shadow", mmd_mat.enabled_self_shadow)
@staticmethod
def convert_to_mmd_material(material, context=bpy.context):
m, mmd_material = material, material.mmd_material
if m.use_nodes and next((n for n in m.node_tree.nodes if n.name.startswith("mmd_")), None) is None:
def search_tex_image_node(node: bpy.types.ShaderNode):
if node.type == "TEX_IMAGE":
return node
for node_input in node.inputs:
if not node_input.is_linked:
continue
child = search_tex_image_node(node_input.links[0].from_node)
if child is not None:
return child
return None
# For Blender 4.4+
preferred_output_node_target = "EEVEE"
tex_node = None
for target in [preferred_output_node_target, "ALL"]:
output_node = m.node_tree.get_output_node(target)
if output_node is None:
continue
if not output_node.inputs[0].is_linked:
continue
tex_node = search_tex_image_node(output_node.inputs[0].links[0].from_node)
break
if tex_node is None:
tex_node = next((n for n in m.node_tree.nodes if n.bl_idname == "ShaderNodeTexImage"), None)
if tex_node:
tex_node.name = "mmd_base_tex"
else:
# Take the Base Color from BSDF if there's no texture
bsdf_node = next((n for n in m.node_tree.nodes if n.type.startswith('BSDF_')), None)
if bsdf_node:
base_color_input = bsdf_node.inputs.get('Base Color') or bsdf_node.inputs.get('Color')
if base_color_input:
mmd_material.diffuse_color = base_color_input.default_value[:3]
# ambient should be half the diffuse
mmd_material.ambient_color = [x * 0.5 for x in mmd_material.diffuse_color]
# For Blender 4.4+
shadow_method = getattr(m, "shadow_method", None)
if mmd_material.diffuse_color is None:
mmd_material.diffuse_color = m.diffuse_color[:3]
# For Blender 4.4+
if len(m.diffuse_color) > 3:
mmd_material.alpha = m.diffuse_color[3]
mmd_material.specular_color = m.specular_color
# For Blender 4.4+
mmd_material.shininess = pow(1 / max(m.roughness, 0.099), 1 / 0.37)
mmd_material.is_double_sided = not m.use_backface_culling
if shadow_method:
mmd_material.enabled_self_shadow_map = (shadow_method != "NONE") and mmd_material.alpha > 1e-3
mmd_material.enabled_self_shadow = shadow_method != "NONE"
# delete bsdf node if it's there
if m.use_nodes:
nodes_to_remove = [n for n in m.node_tree.nodes if n.type == 'BSDF_PRINCIPLED' or n.type.startswith('BSDF_')]
for n in nodes_to_remove:
m.node_tree.nodes.remove(n)
def __update_shader_input(self, name, val):
mat = self.material
if mat.name.startswith("mmd_"): # skip mmd_edge.*
return
self.__update_shader_nodes()
shader = mat.node_tree.nodes.get("mmd_shader", None)
if shader and name in shader.inputs:
interface_socket = shader.node_tree.interface.items_tree[name]
if hasattr(interface_socket, "min_value"):
val = min(max(val, interface_socket.min_value), interface_socket.max_value)
shader.inputs[name].default_value = val
def __update_shader_nodes(self):
mat = self.material
if mat.node_tree is None:
mat.use_nodes = True
mat.node_tree.nodes.clear()
nodes, links = mat.node_tree.nodes, mat.node_tree.links
class _Dummy:
default_value, is_linked = None, True
node_shader = nodes.get("mmd_shader", None)
if node_shader is None:
node_shader = 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
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,)
node_shader.inputs.get("Reflect", _Dummy).default_value = mmd_mat.shininess
node_shader.inputs.get("Alpha", _Dummy).default_value = mmd_mat.alpha
node_shader.inputs.get("Double Sided", _Dummy).default_value = mmd_mat.is_double_sided
node_shader.inputs.get("Self Shadow", _Dummy).default_value = mmd_mat.enabled_self_shadow
self.update_sphere_texture_type()
node_uv = nodes.get("mmd_tex_uv", None)
if node_uv is None:
node_uv = 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()
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.is_active_output = True
node_output.location = node_shader.location + Vector((400, 0))
links.new(node_shader.outputs["Shader"], node_output.inputs["Surface"])
for name_id in ("Base", "Toon", "Sphere"):
texture = self.__get_texture_node("mmd_%s_tex" % name_id.lower())
if texture:
name_tex_in, name_alpha_in, name_uv_out = (name_id + x for x in (" Tex", " Alpha", " UV"))
if not node_shader.inputs.get(name_tex_in, _Dummy).is_linked:
links.new(texture.outputs["Color"], node_shader.inputs[name_tex_in])
if not node_shader.inputs.get(name_alpha_in, _Dummy).is_linked:
links.new(texture.outputs["Alpha"], node_shader.inputs[name_alpha_in])
if not texture.inputs["Vector"].is_linked:
links.new(node_uv.outputs[name_uv_out], texture.inputs["Vector"])
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")
if len(shader.nodes):
return shader
ng = _NodeGroupUtils(shader)
############################################################################
_node_output = ng.new_node("NodeGroupOutput", (6, 0))
tex_coord = ng.new_node("ShaderNodeTexCoord", (0, 0))
tex_coord1 = ng.new_node("ShaderNodeUVMap", (4, -2))
tex_coord1.uv_map = "UV1"
vec_trans = 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.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)
links = ng.links
links.new(tex_coord.outputs["Normal"], vec_trans.inputs["Vector"])
links.new(vec_trans.outputs["Vector"], node_vector.inputs["Vector"])
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["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")
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_diffuse = 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_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_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_reflect = 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("ShaderNodeBsdfAnisotropic", (8, -1))
shader_base_mix = 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))
links = ng.links
links.new(node_reflect.outputs["Value"], shader_glossy.inputs["Roughness"])
links.new(shader_diffuse.outputs["BSDF"], shader_base_mix.inputs[1])
links.new(shader_glossy.outputs["BSDF"], shader_base_mix.inputs[2])
links.new(node_diffuse.outputs["Color"], node_tex.inputs["Color1"])
links.new(node_tex.outputs["Color"], node_toon.inputs["Color1"])
links.new(node_toon.outputs["Color"], node_sph.inputs["Color1"])
links.new(node_toon.outputs["Color"], node_spa.inputs["Color1"])
links.new(node_sph.outputs["Color"], node_sphere.inputs["Color1"])
links.new(node_spa.outputs["Color"], node_sphere.inputs["Color2"])
links.new(node_sphere.outputs["Color"], shader_diffuse.inputs["Color"])
links.new(node_geo.outputs["Backfacing"], node_invert.inputs[0])
links.new(node_invert.outputs["Value"], node_cull.inputs[0])
links.new(node_cull.outputs["Value"], node_alpha.inputs[0])
links.new(node_alpha_tex.outputs["Value"], node_alpha_toon.inputs[0])
links.new(node_alpha_toon.outputs["Value"], node_alpha_sph.inputs[0])
links.new(node_alpha_sph.outputs["Value"], node_alpha.inputs[1])
links.new(node_alpha.outputs["Value"], shader_alpha_mix.inputs["Fac"])
links.new(shader_trans.outputs["BSDF"], shader_alpha_mix.inputs[1])
links.new(shader_base_mix.outputs["Shader"], shader_alpha_mix.inputs[2])
############################################################################
ng.new_input_socket("Ambient Color", node_diffuse.inputs["Color1"], (0.4, 0.4, 0.4, 1))
ng.new_input_socket("Diffuse Color", node_diffuse.inputs["Color2"], (0.8, 0.8, 0.8, 1))
# ↓ specular should be disabled by default
ng.new_input_socket("Specular Color", shader_glossy.inputs["Color"], (0.0, 0.0, 0.0, 1))
ng.new_input_socket("Reflect", node_reflect.inputs[1], 50, min_max=(1, 512))
ng.new_input_socket("Base Tex Fac", node_tex.inputs["Fac"], 1)
ng.new_input_socket("Base Tex", node_tex.inputs["Color2"], (1, 1, 1, 1))
ng.new_input_socket("Toon Tex Fac", node_toon.inputs["Fac"], 1)
ng.new_input_socket("Toon Tex", node_toon.inputs["Color2"], (1, 1, 1, 1))
ng.new_input_socket("Sphere Tex Fac", node_sph.inputs["Fac"], 1)
ng.new_input_socket("Sphere Tex", node_sph.inputs["Color2"], (1, 1, 1, 1))
ng.new_input_socket("Sphere Mul/Add", node_sphere.inputs["Fac"], 0)
ng.new_input_socket("Double Sided", node_cull.inputs[1], 0, min_max=(0, 1))
ng.new_input_socket("Alpha", node_alpha_tex.inputs[0], 1, min_max=(0, 1))
ng.new_input_socket("Base Alpha", node_alpha_tex.inputs[1], 1, min_max=(0, 1))
ng.new_input_socket("Toon Alpha", node_alpha_toon.inputs[1], 1, min_max=(0, 1))
ng.new_input_socket("Sphere Alpha", node_alpha_sph.inputs[1], 1, min_max=(0, 1))
links.new(node_input.outputs["Sphere Tex Fac"], node_spa.inputs["Fac"])
links.new(node_input.outputs["Sphere Tex"], node_spa.inputs["Color2"])
ng.new_output_socket("Shader", shader_alpha_mix.outputs["Shader"])
ng.new_output_socket("Color", node_sphere.outputs["Color"])
ng.new_output_socket("Alpha", node_alpha.outputs["Value"])
return shader
class MigrationFnMaterial:
@staticmethod
def update_mmd_shader():
mmd_shader_node_tree = bpy.data.node_groups.get("MMDShaderDev")
if mmd_shader_node_tree is None:
return
ng = _NodeGroupUtils(mmd_shader_node_tree)
if "Color" in ng.node_output.inputs:
return
shader_diffuse = [n for n in mmd_shader_node_tree.nodes if n.type == "BSDF_DIFFUSE"][0]
node_sphere = shader_diffuse.inputs["Color"].links[0].from_node
node_output = ng.node_output
shader_alpha_mix = node_output.inputs["Shader"].links[0].from_node
node_alpha = shader_alpha_mix.inputs["Fac"].links[0].from_node
ng.new_output_socket("Color", node_sphere.outputs["Color"])
ng.new_output_socket("Alpha", node_alpha.outputs["Value"])