Update Files and Fixes
This commit is contained in:
+124
-64
@@ -7,7 +7,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Callable, Iterable, Optional, Tuple, cast
|
||||
from typing import TYPE_CHECKING, Callable, Iterable, Optional, Tuple, cast, Dict, List, Any, Union, Set
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
@@ -15,6 +15,7 @@ from mathutils import Vector
|
||||
from ..bpyutils import FnContext
|
||||
from .exceptions import MaterialNotFoundError
|
||||
from .shader import _NodeGroupUtils
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..properties.material import MMDMaterial
|
||||
@@ -27,48 +28,53 @@ SPHERE_MODE_SUBTEX = 3
|
||||
|
||||
|
||||
class _DummyTexture:
|
||||
def __init__(self, image):
|
||||
self.type = "IMAGE"
|
||||
self.image = image
|
||||
self.use_mipmap = True
|
||||
def __init__(self, image: bpy.types.Image):
|
||||
self.type: str = "IMAGE"
|
||||
self.image: bpy.types.Image = image
|
||||
self.use_mipmap: bool = True
|
||||
|
||||
|
||||
class _DummyTextureSlot:
|
||||
def __init__(self, image):
|
||||
self.diffuse_color_factor = 1
|
||||
self.uv_layer = ""
|
||||
self.texture = _DummyTexture(image)
|
||||
def __init__(self, image: bpy.types.Image):
|
||||
self.diffuse_color_factor: float = 1
|
||||
self.uv_layer: str = ""
|
||||
self.texture: _DummyTexture = _DummyTexture(image)
|
||||
|
||||
|
||||
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
|
||||
self.__material: bpy.types.Material = material
|
||||
self._nodes_are_readonly: bool = FnMaterial.__NODES_ARE_READONLY
|
||||
|
||||
@staticmethod
|
||||
def set_nodes_are_readonly(nodes_are_readonly: bool):
|
||||
def set_nodes_are_readonly(nodes_are_readonly: bool) -> None:
|
||||
FnMaterial.__NODES_ARE_READONLY = nodes_are_readonly
|
||||
|
||||
@classmethod
|
||||
def from_material_id(cls, material_id: str):
|
||||
def from_material_id(cls, material_id: str) -> Optional['FnMaterial']:
|
||||
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]):
|
||||
def clean_materials(obj: bpy.types.Object, can_remove: Callable[[bpy.types.Material], bool]) -> None:
|
||||
materials = obj.data.materials
|
||||
materials_pop = materials.pop
|
||||
removed_count = 0
|
||||
for i in sorted((x for x, m in enumerate(materials) if can_remove(m)), reverse=True):
|
||||
m = materials_pop(index=i)
|
||||
removed_count += 1
|
||||
if m.users < 1:
|
||||
bpy.data.materials.remove(m)
|
||||
|
||||
if removed_count > 0:
|
||||
logger.debug(f"Removed {removed_count} materials from {obj.name}")
|
||||
|
||||
@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]:
|
||||
def swap_materials(mesh_object: bpy.types.Object, mat1_ref: Union[str, int], mat2_ref: Union[str, int], reverse: bool = False, swap_slots: bool = 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.
|
||||
@@ -98,8 +104,12 @@ class FnMaterial:
|
||||
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)
|
||||
|
||||
logger.debug(f"Swapping materials: {mat1.name} (idx:{mat1_idx}) <-> {mat2.name} (idx:{mat2_idx}) in {mesh_object.name}")
|
||||
|
||||
# Swap polygons
|
||||
for poly in mesh.polygons:
|
||||
if poly.material_index == mat1_idx:
|
||||
@@ -113,33 +123,37 @@ class FnMaterial:
|
||||
return mat1, mat2
|
||||
|
||||
@staticmethod
|
||||
def fixMaterialOrder(meshObj: bpy.types.Object, material_names: Iterable[str]):
|
||||
def fixMaterialOrder(meshObj: bpy.types.Object, material_names: Iterable[str]) -> None:
|
||||
"""
|
||||
This method will fix the material order. Which is lost after joining meshes.
|
||||
"""
|
||||
materials = cast(bpy.types.Mesh, meshObj.data).materials
|
||||
logger.debug(f"Fixing material order for {meshObj.name}")
|
||||
|
||||
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
|
||||
logger.debug(f"Moving material {mat} to index {new_idx}")
|
||||
FnMaterial.swap_materials(meshObj, mat, new_idx, reverse=True, swap_slots=True)
|
||||
|
||||
@property
|
||||
def material_id(self):
|
||||
mmd_mat: MMDMaterial = self.__material.mmd_material
|
||||
def material_id(self) -> int:
|
||||
mmd_mat: 'MMDMaterial' = 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
|
||||
logger.debug(f"Assigned new material ID {mmd_mat.material_id} to {self.__material.name}")
|
||||
return mmd_mat.material_id
|
||||
|
||||
@property
|
||||
def material(self):
|
||||
def material(self) -> bpy.types.Material:
|
||||
return self.__material
|
||||
|
||||
def __same_image_file(self, image, filepath):
|
||||
def __same_image_file(self, image: Optional[bpy.types.Image], filepath: str) -> bool:
|
||||
if image and image.source == "FILE":
|
||||
# pylint: disable=assignment-from-no-return
|
||||
img_filepath = bpy.path.abspath(image.filepath) # image.filepath_from_user()
|
||||
@@ -152,14 +166,15 @@ class FnMaterial:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _load_image(self, filepath):
|
||||
def _load_image(self, filepath: str) -> bpy.types.Image:
|
||||
img = next((i for i in bpy.data.images if self.__same_image_file(i, filepath)), None)
|
||||
if img is None:
|
||||
# pylint: disable=bare-except
|
||||
try:
|
||||
logger.debug(f"Loading image: {filepath}")
|
||||
img = bpy.data.images.load(filepath)
|
||||
except:
|
||||
logging.warning("Cannot create a texture for %s. No such file.", filepath)
|
||||
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
|
||||
@@ -170,43 +185,46 @@ class FnMaterial:
|
||||
img.alpha_mode = "NONE"
|
||||
return img
|
||||
|
||||
def update_toon_texture(self):
|
||||
def update_toon_texture(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
mmd_mat: MMDMaterial = self.__material.mmd_material
|
||||
mmd_mat: 'MMDMaterial' = self.__material.mmd_material
|
||||
if mmd_mat.is_shared_toon_texture:
|
||||
shared_toon_folder = FnContext.get_addon_preferences_attribute(FnContext.ensure_context(), "shared_toon_folder", "")
|
||||
toon_path = os.path.join(shared_toon_folder, "toon%02d.bmp" % (mmd_mat.shared_toon_texture + 1))
|
||||
logger.debug(f"Using shared toon texture: {toon_path}")
|
||||
self.create_toon_texture(bpy.path.resolve_ncase(path=toon_path))
|
||||
elif mmd_mat.toon_texture != "":
|
||||
logger.debug(f"Using custom toon texture: {mmd_mat.toon_texture}")
|
||||
self.create_toon_texture(mmd_mat.toon_texture)
|
||||
else:
|
||||
logger.debug(f"Removing toon texture from {self.__material.name}")
|
||||
self.remove_toon_texture()
|
||||
|
||||
def _mix_diffuse_and_ambient(self, mmd_mat):
|
||||
def _mix_diffuse_and_ambient(self, mmd_mat: 'MMDMaterial') -> List[float]:
|
||||
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):
|
||||
def update_drop_shadow(self) -> None:
|
||||
pass
|
||||
|
||||
def update_enabled_toon_edge(self):
|
||||
def update_enabled_toon_edge(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
self.update_edge_color()
|
||||
|
||||
def update_edge_color(self):
|
||||
def update_edge_color(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
mat = self.__material
|
||||
mmd_mat: MMDMaterial = 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.types.Material = bpy.data.materials.get("mmd_edge." + mat.name, None)
|
||||
mat_edge: Optional[bpy.types.Material] = bpy.data.materials.get("mmd_edge." + mat.name, None)
|
||||
if mat_edge:
|
||||
mat_edge.mmd_material.edge_color = line_color
|
||||
|
||||
@@ -217,39 +235,46 @@ class FnMaterial:
|
||||
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
|
||||
|
||||
logger.debug(f"Updated edge color for {mat.name}")
|
||||
|
||||
def update_edge_weight(self):
|
||||
def update_edge_weight(self) -> None:
|
||||
pass
|
||||
|
||||
def get_texture(self):
|
||||
def get_texture(self) -> Optional[_DummyTexture]:
|
||||
return self.__get_texture_node("mmd_base_tex", use_dummy=True)
|
||||
|
||||
def create_texture(self, filepath):
|
||||
def create_texture(self, filepath: str) -> _DummyTextureSlot:
|
||||
texture = self.__create_texture_node("mmd_base_tex", filepath, (-4, -1))
|
||||
logger.debug(f"Created base texture for {self.__material.name}: {filepath}")
|
||||
return _DummyTextureSlot(texture.image)
|
||||
|
||||
def remove_texture(self):
|
||||
def remove_texture(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
logger.debug(f"Removing base texture from {self.__material.name}")
|
||||
self.__remove_texture_node("mmd_base_tex")
|
||||
|
||||
def get_sphere_texture(self):
|
||||
def get_sphere_texture(self) -> Optional[_DummyTexture]:
|
||||
return self.__get_texture_node("mmd_sphere_tex", use_dummy=True)
|
||||
|
||||
def use_sphere_texture(self, use_sphere, obj=None):
|
||||
def use_sphere_texture(self, use_sphere: bool, obj: Optional[bpy.types.Object] = None) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
if use_sphere:
|
||||
logger.debug(f"Enabling sphere texture for {self.__material.name}")
|
||||
self.update_sphere_texture_type(obj)
|
||||
else:
|
||||
logger.debug(f"Disabling sphere texture for {self.__material.name}")
|
||||
self.__update_shader_input("Sphere Tex Fac", 0)
|
||||
|
||||
def create_sphere_texture(self, filepath, obj=None):
|
||||
def create_sphere_texture(self, filepath: str, obj: Optional[bpy.types.Object] = None) -> _DummyTextureSlot:
|
||||
texture = self.__create_texture_node("mmd_sphere_tex", filepath, (-2, -2))
|
||||
logger.debug(f"Created sphere texture for {self.__material.name}: {filepath}")
|
||||
self.update_sphere_texture_type(obj)
|
||||
return _DummyTextureSlot(texture.image)
|
||||
|
||||
def update_sphere_texture_type(self, obj=None):
|
||||
def update_sphere_texture_type(self, obj: Optional[bpy.types.Object] = None) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
sphere_texture_type = int(self.material.mmd_material.sphere_texture_type)
|
||||
@@ -277,48 +302,54 @@ class FnMaterial:
|
||||
next(uv_layers, None) # skip base UV
|
||||
subtex_uv = getattr(next(uv_layers, None), "name", "")
|
||||
if subtex_uv != "UV1":
|
||||
logging.info(' * material(%s): object "%s" use UV "%s" for SubTex', mat.name, obj.name, subtex_uv)
|
||||
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"])
|
||||
|
||||
logger.debug(f"Updated sphere texture type for {self.material.name}: {sphere_texture_type}")
|
||||
|
||||
def remove_sphere_texture(self):
|
||||
def remove_sphere_texture(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
logger.debug(f"Removing sphere texture from {self.__material.name}")
|
||||
self.__remove_texture_node("mmd_sphere_tex")
|
||||
|
||||
def get_toon_texture(self):
|
||||
def get_toon_texture(self) -> Optional[_DummyTexture]:
|
||||
return self.__get_texture_node("mmd_toon_tex", use_dummy=True)
|
||||
|
||||
def use_toon_texture(self, use_toon):
|
||||
def use_toon_texture(self, use_toon: bool) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
logger.debug(f"{'Enabling' if use_toon else 'Disabling'} toon texture for {self.__material.name}")
|
||||
self.__update_shader_input("Toon Tex Fac", use_toon)
|
||||
|
||||
def create_toon_texture(self, filepath):
|
||||
def create_toon_texture(self, filepath: str) -> _DummyTextureSlot:
|
||||
texture = self.__create_texture_node("mmd_toon_tex", filepath, (-3, -1.5))
|
||||
logger.debug(f"Created toon texture for {self.__material.name}: {filepath}")
|
||||
return _DummyTextureSlot(texture.image)
|
||||
|
||||
def remove_toon_texture(self):
|
||||
def remove_toon_texture(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
logger.debug(f"Removing toon texture from {self.__material.name}")
|
||||
self.__remove_texture_node("mmd_toon_tex")
|
||||
|
||||
def __get_texture_node(self, node_name, use_dummy=False):
|
||||
def __get_texture_node(self, node_name: str, use_dummy: bool = False) -> Optional[Union[bpy.types.ShaderNodeTexImage, _DummyTexture]]:
|
||||
mat = self.material
|
||||
texture = getattr(mat.node_tree, "nodes", {}).get(node_name, None)
|
||||
if isinstance(texture, bpy.types.ShaderNodeTexImage):
|
||||
return _DummyTexture(texture.image) if use_dummy else texture
|
||||
return None
|
||||
|
||||
def __remove_texture_node(self, node_name):
|
||||
def __remove_texture_node(self, node_name: str) -> None:
|
||||
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):
|
||||
def __create_texture_node(self, node_name: str, filepath: str, pos: Tuple[float, float]) -> bpy.types.ShaderNodeTexImage:
|
||||
texture = self.__get_texture_node(node_name)
|
||||
if texture is None:
|
||||
from mathutils import Vector
|
||||
@@ -334,23 +365,25 @@ class FnMaterial:
|
||||
self.__update_shader_nodes()
|
||||
return texture
|
||||
|
||||
def update_ambient_color(self):
|
||||
def update_ambient_color(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
mat = self.material
|
||||
mmd_mat = mat.mmd_material
|
||||
mat.diffuse_color[:3] = self._mix_diffuse_and_ambient(mmd_mat)
|
||||
self.__update_shader_input("Ambient Color", mmd_mat.ambient_color[:] + (1,))
|
||||
logger.debug(f"Updated ambient color for {mat.name}")
|
||||
|
||||
def update_diffuse_color(self):
|
||||
def update_diffuse_color(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
mat = self.material
|
||||
mmd_mat = mat.mmd_material
|
||||
mat.diffuse_color[:3] = self._mix_diffuse_and_ambient(mmd_mat)
|
||||
self.__update_shader_input("Diffuse Color", mmd_mat.diffuse_color[:] + (1,))
|
||||
logger.debug(f"Updated diffuse color for {mat.name}")
|
||||
|
||||
def update_alpha(self):
|
||||
def update_alpha(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
mat = self.material
|
||||
@@ -368,16 +401,18 @@ class FnMaterial:
|
||||
mat.diffuse_color[3] = mmd_mat.alpha
|
||||
self.__update_shader_input("Alpha", mmd_mat.alpha)
|
||||
self.update_self_shadow_map()
|
||||
logger.debug(f"Updated alpha for {mat.name}: {mmd_mat.alpha}")
|
||||
|
||||
def update_specular_color(self):
|
||||
def update_specular_color(self) -> None:
|
||||
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,))
|
||||
logger.debug(f"Updated specular color for {mat.name}")
|
||||
|
||||
def update_shininess(self):
|
||||
def update_shininess(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
mat = self.material
|
||||
@@ -388,8 +423,9 @@ class FnMaterial:
|
||||
if hasattr(mat, "specular_hardness"):
|
||||
mat.specular_hardness = mmd_mat.shininess
|
||||
self.__update_shader_input("Reflect", mmd_mat.shininess)
|
||||
logger.debug(f"Updated shininess for {mat.name}: {mmd_mat.shininess}")
|
||||
|
||||
def update_is_double_sided(self):
|
||||
def update_is_double_sided(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
mat = self.material
|
||||
@@ -399,8 +435,9 @@ class FnMaterial:
|
||||
elif hasattr(mat, "use_backface_culling"):
|
||||
mat.use_backface_culling = not mmd_mat.is_double_sided
|
||||
self.__update_shader_input("Double Sided", mmd_mat.is_double_sided)
|
||||
logger.debug(f"Updated double-sided setting for {mat.name}: {mmd_mat.is_double_sided}")
|
||||
|
||||
def update_self_shadow_map(self):
|
||||
def update_self_shadow_map(self) -> None:
|
||||
if self._nodes_are_readonly:
|
||||
return
|
||||
mat = self.material
|
||||
@@ -408,21 +445,24 @@ class FnMaterial:
|
||||
cast_shadows = mmd_mat.enabled_self_shadow_map if mmd_mat.alpha > 1e-3 else False
|
||||
if hasattr(mat, "shadow_method"):
|
||||
mat.shadow_method = "HASHED" if cast_shadows else "NONE"
|
||||
logger.debug(f"Updated self shadow map for {mat.name}: {cast_shadows}")
|
||||
|
||||
def update_self_shadow(self):
|
||||
def update_self_shadow(self) -> None:
|
||||
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)
|
||||
logger.debug(f"Updated self shadow for {mat.name}: {mmd_mat.enabled_self_shadow}")
|
||||
|
||||
@staticmethod
|
||||
def convert_to_mmd_material(material, context=bpy.context):
|
||||
def convert_to_mmd_material(material: bpy.types.Material, context: bpy.types.Context = bpy.context) -> None:
|
||||
m, mmd_material = material, material.mmd_material
|
||||
logger.debug(f"Converting material to MMD material: {material.name}")
|
||||
|
||||
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):
|
||||
def search_tex_image_node(node: bpy.types.ShaderNode) -> Optional[bpy.types.ShaderNodeTexImage]:
|
||||
if node.type == "TEX_IMAGE":
|
||||
return node
|
||||
for node_input in node.inputs:
|
||||
@@ -459,6 +499,7 @@ class FnMaterial:
|
||||
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:
|
||||
logger.debug(f"Found texture node for {material.name}: {tex_node.name}")
|
||||
tex_node.name = "mmd_base_tex"
|
||||
else:
|
||||
# Take the Base Color from BSDF if there's no texture
|
||||
@@ -466,6 +507,7 @@ class FnMaterial:
|
||||
if bsdf_node:
|
||||
base_color_input = bsdf_node.inputs.get('Base Color') or bsdf_node.inputs.get('Color')
|
||||
if base_color_input:
|
||||
logger.debug(f"Using BSDF base color for {material.name}")
|
||||
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]
|
||||
@@ -498,9 +540,10 @@ class FnMaterial:
|
||||
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:
|
||||
logger.debug(f"Removing BSDF node from {material.name}: {n.name}")
|
||||
m.node_tree.nodes.remove(n)
|
||||
|
||||
def __update_shader_input(self, name, val):
|
||||
def __update_shader_input(self, name: str, val: Any) -> None:
|
||||
mat = self.material
|
||||
if mat.name.startswith("mmd_"): # skip mmd_edge.*
|
||||
return
|
||||
@@ -512,26 +555,29 @@ class FnMaterial:
|
||||
val = min(max(val, interface_socket.min_value), interface_socket.max_value)
|
||||
shader.inputs[name].default_value = val
|
||||
|
||||
def __update_shader_nodes(self):
|
||||
def __update_shader_nodes(self) -> None:
|
||||
mat = self.material
|
||||
if mat.node_tree is None:
|
||||
logger.debug(f"Creating node tree for {mat.name}")
|
||||
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
|
||||
default_value: Any = None
|
||||
is_linked: bool = True
|
||||
|
||||
node_shader = nodes.get("mmd_shader", None)
|
||||
if node_shader is None:
|
||||
logger.debug(f"Creating MMD shader node for {mat.name}")
|
||||
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: MMDMaterial = 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,)
|
||||
@@ -543,6 +589,7 @@ class FnMaterial:
|
||||
|
||||
node_uv = nodes.get("mmd_tex_uv", None)
|
||||
if node_uv is None:
|
||||
logger.debug(f"Creating MMD UV node for {mat.name}")
|
||||
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))
|
||||
@@ -567,12 +614,13 @@ class FnMaterial:
|
||||
if not texture.inputs["Vector"].is_linked:
|
||||
links.new(node_uv.outputs[name_uv_out], texture.inputs["Vector"])
|
||||
|
||||
def __get_shader_uv(self):
|
||||
def __get_shader_uv(self) -> bpy.types.ShaderNodeTree:
|
||||
group_name = "MMDTexUV"
|
||||
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
|
||||
|
||||
logger.debug(f"Creating MMD UV shader node group")
|
||||
ng = _NodeGroupUtils(shader)
|
||||
|
||||
############################################################################
|
||||
@@ -604,12 +652,13 @@ class FnMaterial:
|
||||
|
||||
return shader
|
||||
|
||||
def __get_shader(self):
|
||||
def __get_shader(self) -> bpy.types.ShaderNodeTree:
|
||||
group_name = "MMDShaderDev"
|
||||
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
|
||||
|
||||
logger.debug(f"Creating MMD shader node group")
|
||||
ng = _NodeGroupUtils(shader)
|
||||
|
||||
############################################################################
|
||||
@@ -699,15 +748,18 @@ class FnMaterial:
|
||||
|
||||
class MigrationFnMaterial:
|
||||
@staticmethod
|
||||
def update_mmd_shader():
|
||||
def update_mmd_shader() -> None:
|
||||
mmd_shader_node_tree: Optional[bpy.types.NodeTree] = bpy.data.node_groups.get("MMDShaderDev")
|
||||
if mmd_shader_node_tree is None:
|
||||
logger.debug("No MMD shader node tree found, skipping update")
|
||||
return
|
||||
|
||||
ng = _NodeGroupUtils(mmd_shader_node_tree)
|
||||
if "Color" in ng.node_output.inputs:
|
||||
logger.debug("MMD shader already has Color output, skipping update")
|
||||
return
|
||||
|
||||
logger.info("Updating 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
|
||||
@@ -716,3 +768,11 @@ class MigrationFnMaterial:
|
||||
|
||||
ng.new_output_socket("Color", node_sphere.outputs["Color"])
|
||||
ng.new_output_socket("Alpha", node_alpha.outputs["Value"])
|
||||
logger.info("MMD shader node tree updated successfully")
|
||||
|
||||
# Add Self Shadow input if it doesn't exist
|
||||
if "Self Shadow" not in ng.node_input.outputs:
|
||||
logger.info("Adding Self Shadow input to MMD shader")
|
||||
# Find shader_base_mix node to connect Self Shadow
|
||||
shader_base_mix = shader_alpha_mix.inputs[2].links[0].from_node
|
||||
ng.new_input_socket("Self Shadow", shader_base_mix.inputs["Fac"], 0, min_max=(0, 1))
|
||||
|
||||
Reference in New Issue
Block a user