3414ad8917
- 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.
698 lines
30 KiB
Python
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"])
|