Bringing files in-line with Avatar Toolkit

- Adding better typing
- Update to use Avatar Toolkit's logging system.
- Removed some files which were in the wrong location (From my first attempt).
This commit is contained in:
Yusarina
2025-04-12 00:17:11 +01:00
parent d25c95fc73
commit bb5a314796
5 changed files with 153 additions and 429 deletions
-66
View File
@@ -1,66 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2014 MMD Tools authors
# This file is part of MMD Tools.
import bpy
from ..bpyutils import FnContext, Props
class MMDLamp:
def __init__(self, obj):
if MMDLamp.isLamp(obj):
obj = obj.parent
if obj and obj.type == "EMPTY" and obj.mmd_type == "LIGHT":
self.__emptyObj = obj
else:
raise ValueError("%s is not MMDLamp" % str(obj))
@staticmethod
def isLamp(obj):
return obj and obj.type in {"LIGHT", "LAMP"}
@staticmethod
def isMMDLamp(obj):
if MMDLamp.isLamp(obj):
obj = obj.parent
return obj and obj.type == "EMPTY" and obj.mmd_type == "LIGHT"
@staticmethod
def convertToMMDLamp(lampObj, scale=1.0):
if MMDLamp.isMMDLamp(lampObj):
return MMDLamp(lampObj)
empty = bpy.data.objects.new(name="MMD_Light", object_data=None)
FnContext.link_object(FnContext.ensure_context(), empty)
empty.rotation_mode = "XYZ"
empty.lock_rotation = (True, True, True)
setattr(empty, Props.empty_display_size, 0.4)
empty.scale = [10 * scale] * 3
empty.mmd_type = "LIGHT"
empty.location = (0, 0, 11 * scale)
lampObj.parent = empty
lampObj.data.color = (0.602, 0.602, 0.602)
lampObj.location = (0.5, -0.5, 1.0)
lampObj.rotation_mode = "XYZ"
lampObj.rotation_euler = (0, 0, 0)
lampObj.lock_rotation = (True, True, True)
constraint = lampObj.constraints.new(type="TRACK_TO")
constraint.name = "mmd_lamp_track"
constraint.target = empty
constraint.track_axis = "TRACK_NEGATIVE_Z"
constraint.up_axis = "UP_Y"
return MMDLamp(empty)
def object(self):
return self.__emptyObj
def lamp(self):
for i in self.__emptyObj.children:
if MMDLamp.isLamp(i):
return i
raise KeyError
+82 -67
View File
@@ -6,9 +6,13 @@
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit. # MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
import contextlib import contextlib
from typing import Generator, List, Optional, TypeVar from typing import Generator, List, Optional, TypeVar, Any, Set, Tuple, Dict, Union
import bpy import bpy
from bpy.types import Object, Context, ID, Key, ShapeKey, FCurve, LayerCollection, Collection
from bpy.types import AddonPreferences, Addon, WindowManager, Area, Region, Window
from ..logging_setup import logger
class Props: # For API changes of only name changed properties class Props: # For API changes of only name changed properties
@@ -20,7 +24,7 @@ class Props: # For API changes of only name changed properties
class __EditMode: class __EditMode:
def __init__(self, obj): def __init__(self, obj: Object):
if not isinstance(obj, bpy.types.Object): if not isinstance(obj, bpy.types.Object):
raise ValueError raise ValueError
self.__prevMode = obj.mode self.__prevMode = obj.mode
@@ -30,10 +34,10 @@ class __EditMode:
if obj.mode != "EDIT": if obj.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
def __enter__(self): def __enter__(self) -> Any:
return self.__obj.data return self.__obj.data
def __exit__(self, type, value, traceback): def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
if self.__prevMode == "EDIT": if self.__prevMode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT") # update edited data bpy.ops.object.mode_set(mode="OBJECT") # update edited data
bpy.ops.object.mode_set(mode=self.__prevMode) bpy.ops.object.mode_set(mode=self.__prevMode)
@@ -41,17 +45,18 @@ class __EditMode:
class __SelectObjects: class __SelectObjects:
def __init__(self, active_object: bpy.types.Object, selected_objects: Optional[List[bpy.types.Object]] = None): def __init__(self, active_object: Object, selected_objects: Optional[List[Object]] = None):
if not isinstance(active_object, bpy.types.Object): if not isinstance(active_object, bpy.types.Object):
raise ValueError raise ValueError
try: try:
bpy.ops.object.mode_set(mode="OBJECT") bpy.ops.object.mode_set(mode="OBJECT")
except Exception: except Exception:
logger.debug("Failed to set object mode")
pass pass
contenxt = FnContext.ensure_context() context = FnContext.ensure_context()
for i in contenxt.selected_objects: for i in context.selected_objects:
i.select_set(False) i.select_set(False)
self.__active_object = active_object self.__active_object = active_object
@@ -60,23 +65,23 @@ class __SelectObjects:
self.__hides: List[bool] = [] self.__hides: List[bool] = []
for i in self.__selected_objects: for i in self.__selected_objects:
self.__hides.append(i.hide_get()) self.__hides.append(i.hide_get())
FnContext.select_object(contenxt, i) FnContext.select_object(context, i)
FnContext.set_active_object(contenxt, active_object) FnContext.set_active_object(context, active_object)
def __enter__(self) -> bpy.types.Object: def __enter__(self) -> Object:
return self.__active_object return self.__active_object
def __exit__(self, type, value, traceback): def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
for i, j in zip(self.__selected_objects, self.__hides): for i, j in zip(self.__selected_objects, self.__hides):
i.hide_set(j) i.hide_set(j)
def setParent(obj, parent): def setParent(obj: Object, parent: Object) -> None:
with select_object(parent, objects=[parent, obj]): with select_object(parent, objects=[parent, obj]):
bpy.ops.object.parent_set(type="OBJECT", xmirror=False, keep_transform=False) bpy.ops.object.parent_set(type="OBJECT", xmirror=False, keep_transform=False)
def setParentToBone(obj, parent, bone_name): def setParentToBone(obj: Object, parent: Object, bone_name: str) -> None:
with select_object(parent, objects=[parent, obj]): with select_object(parent, objects=[parent, obj]):
bpy.ops.object.mode_set(mode="POSE") bpy.ops.object.mode_set(mode="POSE")
parent.data.bones.active = parent.data.bones[bone_name] parent.data.bones.active = parent.data.bones[bone_name]
@@ -84,7 +89,7 @@ def setParentToBone(obj, parent, bone_name):
bpy.ops.object.mode_set(mode="OBJECT") bpy.ops.object.mode_set(mode="OBJECT")
def edit_object(obj): def edit_object(obj: Object) -> __EditMode:
"""Set the object interaction mode to 'EDIT' """Set the object interaction mode to 'EDIT'
It is recommended to use 'edit_object' with 'with' statement like the following code. It is recommended to use 'edit_object' with 'with' statement like the following code.
@@ -95,7 +100,7 @@ def edit_object(obj):
return __EditMode(obj) return __EditMode(obj)
def select_object(obj: bpy.types.Object, objects: Optional[List[bpy.types.Object]] = None): def select_object(obj: Object, objects: Optional[List[Object]] = None) -> __SelectObjects:
"""Select objects. """Select objects.
It is recommended to use 'select_object' with 'with' statement like the following code. It is recommended to use 'select_object' with 'with' statement like the following code.
@@ -108,20 +113,23 @@ def select_object(obj: bpy.types.Object, objects: Optional[List[bpy.types.Object
return __SelectObjects(obj, objects) return __SelectObjects(obj, objects)
def duplicateObject(obj, total_len): def duplicateObject(obj: Object, total_len: int) -> List[Object]:
return FnContext.duplicate_object(FnContext.ensure_context(), obj, total_len) return FnContext.duplicate_object(FnContext.ensure_context(), obj, total_len)
def createObject(name="Object", object_data=None, target_scene=None): def createObject(name: str = "Object", object_data: Optional[ID] = None, target_scene: Optional[bpy.types.Scene] = None) -> Object:
context = FnContext.ensure_context(target_scene) context = FnContext.ensure_context(target_scene)
return FnContext.set_active_object(context, FnContext.new_and_link_object(context, name, object_data)) return FnContext.set_active_object(context, FnContext.new_and_link_object(context, name, object_data))
def makeSphere(segment=8, ring_count=5, radius=1.0, target_object=None): def makeSphere(segment: int = 8, ring_count: int = 5, radius: float = 1.0, target_object: Optional[Object] = None) -> Object:
import bmesh import bmesh
if target_object is None: if target_object is None:
target_object = createObject(name="Sphere") target_object = createObject(name="Sphere")
logger.debug(f"Created new sphere object: {target_object.name}")
else:
logger.debug(f"Using existing object for sphere: {target_object.name}")
mesh = target_object.data mesh = target_object.data
bm = bmesh.new() bm = bmesh.new()
@@ -138,12 +146,15 @@ def makeSphere(segment=8, ring_count=5, radius=1.0, target_object=None):
return target_object return target_object
def makeBox(size=(1, 1, 1), target_object=None): def makeBox(size: Tuple[float, float, float] = (1, 1, 1), target_object: Optional[Object] = None) -> Object:
import bmesh import bmesh
from mathutils import Matrix from mathutils import Matrix
if target_object is None: if target_object is None:
target_object = createObject(name="Box") target_object = createObject(name="Box")
logger.debug(f"Created new box object: {target_object.name}")
else:
logger.debug(f"Using existing object for box: {target_object.name}")
mesh = target_object.data mesh = target_object.data
bm = bmesh.new() bm = bmesh.new()
@@ -159,13 +170,16 @@ def makeBox(size=(1, 1, 1), target_object=None):
return target_object return target_object
def makeCapsule(segment=8, ring_count=2, radius=1.0, height=1.0, target_object=None): def makeCapsule(segment: int = 8, ring_count: int = 2, radius: float = 1.0, height: float = 1.0, target_object: Optional[Object] = None) -> Object:
import math import math
import bmesh import bmesh
if target_object is None: if target_object is None:
target_object = createObject(name="Capsule") target_object = createObject(name="Capsule")
logger.debug(f"Created new capsule object: {target_object.name}")
else:
logger.debug(f"Using existing object for capsule: {target_object.name}")
height = max(height, 1e-3) height = max(height, 1e-3)
mesh = target_object.data mesh = target_object.data
@@ -224,10 +238,10 @@ def makeCapsule(segment=8, ring_count=2, radius=1.0, height=1.0, target_object=N
class TransformConstraintOp: class TransformConstraintOp:
__MIN_MAX_MAP = {"ROTATION": "_rot", "SCALE": "_scale"} __MIN_MAX_MAP: Dict[Union[str, Tuple[str, str]], Union[str, Tuple[str, ...]]] = {"ROTATION": "_rot", "SCALE": "_scale"}
@staticmethod @staticmethod
def create(constraints, name, map_type): def create(constraints: bpy.types.ObjectConstraints, name: str, map_type: str) -> bpy.types.TransformConstraint:
c = constraints.get(name, None) c = constraints.get(name, None)
if c and c.type != "TRANSFORM": if c and c.type != "TRANSFORM":
constraints.remove(c) constraints.remove(c)
@@ -245,7 +259,7 @@ class TransformConstraintOp:
return c return c
@classmethod @classmethod
def min_max_attributes(cls, map_type, name_id=""): def min_max_attributes(cls, map_type: str, name_id: str = "") -> Tuple[str, ...]:
key = (map_type, name_id) key = (map_type, name_id)
ret = cls.__MIN_MAX_MAP.get(key, None) ret = cls.__MIN_MAX_MAP.get(key, None)
if ret is None: if ret is None:
@@ -255,7 +269,7 @@ class TransformConstraintOp:
return ret return ret
@classmethod @classmethod
def update_min_max(cls, constraint, value, influence=1): def update_min_max(cls, constraint: bpy.types.TransformConstraint, value: float, influence: Optional[float] = 1) -> None:
c = constraint c = constraint
if not c or c.type != "TRANSFORM": if not c or c.type != "TRANSFORM":
return return
@@ -279,14 +293,14 @@ class FnObject:
raise NotImplementedError("This class is not expected to be instantiated.") raise NotImplementedError("This class is not expected to be instantiated.")
@staticmethod @staticmethod
def mesh_remove_shape_key(mesh_object: bpy.types.Object, shape_key: bpy.types.ShapeKey): def mesh_remove_shape_key(mesh_object: Object, shape_key: ShapeKey) -> None:
assert isinstance(mesh_object.data, bpy.types.Mesh) assert isinstance(mesh_object.data, bpy.types.Mesh)
key: bpy.types.Key = shape_key.id_data key: Key = shape_key.id_data
assert key == mesh_object.data.shape_keys assert key == mesh_object.data.shape_keys
if mesh_object.animation_data is not None: if mesh_object.animation_data is not None:
fc_curve: bpy.types.FCurve fc_curve: FCurve
for fc_curve in mesh_object.animation_data.drivers: for fc_curve in mesh_object.animation_data.drivers:
if not fc_curve.data_path.startswith(shape_key.path_from_id()): if not fc_curve.data_path.startswith(shape_key.path_from_id()):
continue continue
@@ -310,35 +324,35 @@ class FnContext:
raise NotImplementedError("This class is not expected to be instantiated.") raise NotImplementedError("This class is not expected to be instantiated.")
@staticmethod @staticmethod
def ensure_context(context: Optional[bpy.types.Context] = None) -> bpy.types.Context: def ensure_context(context: Optional[Context] = None) -> Context:
return context or bpy.context return context or bpy.context
@staticmethod @staticmethod
def get_active_object(context: bpy.types.Context) -> Optional[bpy.types.Object]: def get_active_object(context: Context) -> Optional[Object]:
return context.active_object return context.active_object
@staticmethod @staticmethod
def set_active_object(context: bpy.types.Context, obj: bpy.types.Object) -> bpy.types.Object: def set_active_object(context: Context, obj: Object) -> Object:
context.view_layer.objects.active = obj context.view_layer.objects.active = obj
return obj return obj
@staticmethod @staticmethod
def set_active_and_select_single_object(context: bpy.types.Context, obj: bpy.types.Object) -> bpy.types.Object: def set_active_and_select_single_object(context: Context, obj: Object) -> Object:
return FnContext.set_active_object(context, FnContext.select_single_object(context, obj)) return FnContext.set_active_object(context, FnContext.select_single_object(context, obj))
@staticmethod @staticmethod
def get_scene_objects(context: bpy.types.Context) -> bpy.types.SceneObjects: def get_scene_objects(context: Context) -> bpy.types.SceneObjects:
return context.scene.objects return context.scene.objects
@staticmethod @staticmethod
def ensure_selectable(context: bpy.types.Context, obj: bpy.types.Object) -> bpy.types.Object: def ensure_selectable(context: Context, obj: Object) -> Object:
obj.hide_viewport = False obj.hide_viewport = False
obj.hide_select = False obj.hide_select = False
obj.hide_set(False) obj.hide_set(False)
if obj not in context.selectable_objects: if obj not in context.selectable_objects:
def __layer_check(layer_collection: bpy.types.LayerCollection) -> bool: def __layer_check(layer_collection: LayerCollection) -> bool:
for lc in layer_collection.children: for lc in layer_collection.children:
if __layer_check(lc): if __layer_check(lc):
lc.hide_viewport = False lc.hide_viewport = False
@@ -360,44 +374,44 @@ class FnContext:
return obj return obj
@staticmethod @staticmethod
def select_object(context: bpy.types.Context, obj: bpy.types.Object) -> bpy.types.Object: def select_object(context: Context, obj: Object) -> Object:
FnContext.ensure_selectable(context, obj).select_set(True) FnContext.ensure_selectable(context, obj).select_set(True)
return obj return obj
@staticmethod @staticmethod
def select_objects(context: bpy.types.Context, *objects: bpy.types.Object) -> List[bpy.types.Object]: def select_objects(context: Context, *objects: Object) -> List[Object]:
return [FnContext.select_object(context, obj) for obj in objects] return [FnContext.select_object(context, obj) for obj in objects]
@staticmethod @staticmethod
def select_single_object(context: bpy.types.Context, obj: bpy.types.Object) -> bpy.types.Object: def select_single_object(context: Context, obj: Object) -> Object:
for i in context.selected_objects: for i in context.selected_objects:
if i != obj: if i != obj:
i.select_set(False) i.select_set(False)
return FnContext.select_object(context, obj) return FnContext.select_object(context, obj)
@staticmethod @staticmethod
def link_object(context: bpy.types.Context, obj: bpy.types.Object) -> bpy.types.Object: def link_object(context: Context, obj: Object) -> Object:
context.collection.objects.link(obj) context.collection.objects.link(obj)
return obj return obj
@staticmethod @staticmethod
def new_and_link_object(context: bpy.types.Context, name: str, object_data: Optional[bpy.types.ID]) -> bpy.types.Object: def new_and_link_object(context: Context, name: str, object_data: Optional[ID]) -> Object:
return FnContext.link_object(context, bpy.data.objects.new(name=name, object_data=object_data)) return FnContext.link_object(context, bpy.data.objects.new(name=name, object_data=object_data))
@staticmethod @staticmethod
def duplicate_object(context: bpy.types.Context, object_to_duplicate: bpy.types.Object, target_count: int) -> List[bpy.types.Object]: def duplicate_object(context: Context, object_to_duplicate: Object, target_count: int) -> List[Object]:
""" """
Duplicate object. Duplicate object.
This function duplicates the given object and returns a list of duplicated objects. This function duplicates the given object and returns a list of duplicated objects.
Args: Args:
context (bpy.types.Context): The context in which the duplication is performed. context (Context): The context in which the duplication is performed.
object_to_duplicate (bpy.types.Object): The object to be duplicated. object_to_duplicate (Object): The object to be duplicated.
target_count (int): The desired count of duplicated objects. target_count (int): The desired count of duplicated objects.
Returns: Returns:
List[bpy.types.Object]: A list of duplicated objects. List[Object]: A list of duplicated objects.
Raises: Raises:
AssertionError: If the number of selected objects in the context is not equal to 1 or if the selected object is not the same as the object to be duplicated. AssertionError: If the number of selected objects in the context is not equal to 1 or if the selected object is not the same as the object to be duplicated.
@@ -421,27 +435,28 @@ class FnContext:
last_selected_objects[i].select_set(True) last_selected_objects[i].select_set(True)
last_selected_objects = context.selected_objects last_selected_objects = context.selected_objects
assert len(result_objects) == target_count assert len(result_objects) == target_count
logger.debug(f"Duplicated object {object_to_duplicate.name} to create {target_count} objects")
return result_objects return result_objects
@staticmethod @staticmethod
def find_user_layer_collection_by_object(context: bpy.types.Context, target_object: bpy.types.Object) -> Optional[bpy.types.LayerCollection]: def find_user_layer_collection_by_object(context: Context, target_object: Object) -> Optional[LayerCollection]:
""" """
Finds the layer collection that contains the given target_object in the user's collections. Finds the layer collection that contains the given target_object in the user's collections.
Args: Args:
context (bpy.types.Context): The Blender context. context (Context): The Blender context.
target_object (bpy.types.Object): The target object to find the layer collection for. target_object (Object): The target object to find the layer collection for.
Returns: Returns:
Optional[bpy.types.LayerCollection]: The layer collection that contains the target_object, or None if not found. Optional[LayerCollection]: The layer collection that contains the target_object, or None if not found.
""" """
scene_layer_collection: bpy.types.LayerCollection = context.view_layer.layer_collection scene_layer_collection: LayerCollection = context.view_layer.layer_collection
def find_layer_collection_by_name(layer_collection: bpy.types.LayerCollection, name: str) -> Optional[bpy.types.LayerCollection]: def find_layer_collection_by_name(layer_collection: LayerCollection, name: str) -> Optional[LayerCollection]:
if layer_collection.name == name: if layer_collection.name == name:
return layer_collection return layer_collection
child_layer_collection: bpy.types.LayerCollection child_layer_collection: LayerCollection
for child_layer_collection in layer_collection.children: for child_layer_collection in layer_collection.children:
found = find_layer_collection_by_name(child_layer_collection, name) found = find_layer_collection_by_name(child_layer_collection, name)
if found is not None: if found is not None:
@@ -449,7 +464,7 @@ class FnContext:
return None return None
user_collection: bpy.types.Collection user_collection: Collection
for user_collection in target_object.users_collection: for user_collection in target_object.users_collection:
found = find_layer_collection_by_name(scene_layer_collection, user_collection.name) found = find_layer_collection_by_name(scene_layer_collection, user_collection.name)
if found is not None: if found is not None:
@@ -459,7 +474,7 @@ class FnContext:
@staticmethod @staticmethod
@contextlib.contextmanager @contextlib.contextmanager
def temp_override_active_layer_collection(context: bpy.types.Context, target_object: bpy.types.Object) -> Generator[bpy.types.Context, None, None]: def temp_override_active_layer_collection(context: Context, target_object: Object) -> Generator[Context, None, None]:
""" """
Context manager to temporarily override the active_layer_collection that contains the target object. Context manager to temporarily override the active_layer_collection that contains the target object.
@@ -467,11 +482,11 @@ class FnContext:
It ensures that the original active_layer_collection is restored after the context is exited. It ensures that the original active_layer_collection is restored after the context is exited.
Args: Args:
context (bpy.types.Context): The context in which the active_layer_collection will be overridden. context (Context): The context in which the active_layer_collection will be overridden.
target_object (bpy.types.Object): The target object whose layer collection will be set as the active_layer_collection. target_object (Object): The target object whose layer collection will be set as the active_layer_collection.
Yields: Yields:
bpy.types.Context: The modified context with the active_layer_collection overridden. Context: The modified context with the active_layer_collection overridden.
Example: Example:
with FnContext.temp_override_active_layer_collection(context, target_object): with FnContext.temp_override_active_layer_collection(context, target_object):
@@ -492,24 +507,24 @@ class FnContext:
context.view_layer.active_layer_collection = original_layer_collection context.view_layer.active_layer_collection = original_layer_collection
@staticmethod @staticmethod
def __get_addon_preferences(context: bpy.types.Context) -> Optional[bpy.types.AddonPreferences]: def __get_addon_preferences(context: Context) -> Optional[AddonPreferences]:
addon: bpy.types.Addon = context.preferences.addons.get(__package__, None) addon: Addon = context.preferences.addons.get(__package__, None)
return addon.preferences if addon else None return addon.preferences if addon else None
@staticmethod @staticmethod
def get_addon_preferences_attribute(context: bpy.types.Context, attribute_name: str, default_value: ADDON_PREFERENCE_ATTRIBUTE_VALUE_TYPE = None) -> ADDON_PREFERENCE_ATTRIBUTE_VALUE_TYPE: def get_addon_preferences_attribute(context: Context, attribute_name: str, default_value: ADDON_PREFERENCE_ATTRIBUTE_VALUE_TYPE = None) -> ADDON_PREFERENCE_ATTRIBUTE_VALUE_TYPE:
return getattr(FnContext.__get_addon_preferences(context), attribute_name, default_value) return getattr(FnContext.__get_addon_preferences(context), attribute_name, default_value)
@staticmethod @staticmethod
def temp_override_objects( def temp_override_objects(
context: bpy.types.Context, context: Context,
window: Optional[bpy.types.Window] = None, window: Optional[Window] = None,
area: Optional[bpy.types.Area] = None, area: Optional[Area] = None,
region: Optional[bpy.types.Region] = None, region: Optional[Region] = None,
active_object: Optional[bpy.types.Object] = None, active_object: Optional[Object] = None,
selected_objects: Optional[List[bpy.types.Object]] = None, selected_objects: Optional[List[Object]] = None,
**keywords, **keywords: Any,
) -> Generator[bpy.types.Context, None, None]: ) -> Generator[Context, None, None]:
if active_object is not None: if active_object is not None:
keywords["active_object"] = active_object keywords["active_object"] = active_object
keywords["object"] = active_object keywords["object"] = active_object
+46 -29
View File
@@ -5,39 +5,44 @@
# Neoneko has modified this file to work with Avatar Toolkit and may of made changes or improvements. # 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. # MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
from typing import Iterable, Optional from typing import Iterable, Optional, Any, List, Tuple, Union
import bpy 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.shader import _NodeGroupUtils
from .core.material import FnMaterial from .core.material import FnMaterial
def __switchToCyclesRenderEngine(): def __switchToCyclesRenderEngine() -> None:
if bpy.context.scene.render.engine != "CYCLES": if bpy.context.scene.render.engine != "CYCLES":
logger.debug("Switching render engine to Cycles")
bpy.context.scene.render.engine = "CYCLES" bpy.context.scene.render.engine = "CYCLES"
def __exposeNodeTreeInput(in_socket, name, default_value, node_input, shader): def __exposeNodeTreeInput(in_socket: NodeSocket, name: str, default_value: Any, node_input: Node, shader: NodeTree) -> None:
_NodeGroupUtils(shader).new_input_socket(name, in_socket, default_value) _NodeGroupUtils(shader).new_input_socket(name, in_socket, default_value)
def __exposeNodeTreeOutput(out_socket, name, node_output, shader): def __exposeNodeTreeOutput(out_socket: NodeSocket, name: str, node_output: Node, shader: NodeTree) -> None:
_NodeGroupUtils(shader).new_output_socket(name, out_socket) _NodeGroupUtils(shader).new_output_socket(name, out_socket)
def __getMaterialOutput(nodes, bl_idname): def __getMaterialOutput(nodes: bpy.types.Nodes, bl_idname: str) -> Node:
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 = 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 o.is_active_output = True
return o return o
def create_MMDAlphaShader(): def create_MMDAlphaShader() -> NodeTree:
__switchToCyclesRenderEngine() __switchToCyclesRenderEngine()
if "MMDAlphaShader" in bpy.data.node_groups: if "MMDAlphaShader" in bpy.data.node_groups:
logger.debug("Using existing MMDAlphaShader node group")
return bpy.data.node_groups["MMDAlphaShader"] return bpy.data.node_groups["MMDAlphaShader"]
logger.info("Creating new MMDAlphaShader node group")
shader = bpy.data.node_groups.new(name="MMDAlphaShader", type="ShaderNodeTree") shader = bpy.data.node_groups.new(name="MMDAlphaShader", type="ShaderNodeTree")
node_input = shader.nodes.new("NodeGroupInput") node_input = shader.nodes.new("NodeGroupInput")
@@ -59,26 +64,28 @@ def create_MMDAlphaShader():
return shader return shader
def create_MMDBasicShader(): def create_MMDBasicShader() -> NodeTree:
__switchToCyclesRenderEngine() __switchToCyclesRenderEngine()
if "MMDBasicShader" in bpy.data.node_groups: if "MMDBasicShader" in bpy.data.node_groups:
logger.debug("Using existing MMDBasicShader node group")
return bpy.data.node_groups["MMDBasicShader"] return bpy.data.node_groups["MMDBasicShader"]
shader: bpy.types.ShaderNodeTree = bpy.data.node_groups.new(name="MMDBasicShader", type="ShaderNodeTree") logger.info("Creating new MMDBasicShader node group")
shader: NodeTree = bpy.data.node_groups.new(name="MMDBasicShader", type="ShaderNodeTree")
node_input: bpy.types.NodeGroupInput = shader.nodes.new("NodeGroupInput") node_input: Node = shader.nodes.new("NodeGroupInput")
node_output: bpy.types.NodeGroupOutput = shader.nodes.new("NodeGroupOutput") node_output: Node = shader.nodes.new("NodeGroupOutput")
node_output.location.x += 250 node_output.location.x += 250
node_input.location.x -= 500 node_input.location.x -= 500
dif: bpy.types.ShaderNodeBsdfDiffuse = shader.nodes.new("ShaderNodeBsdfDiffuse") dif: Node = shader.nodes.new("ShaderNodeBsdfDiffuse")
dif.location.x -= 250 dif.location.x -= 250
dif.location.y += 150 dif.location.y += 150
glo: bpy.types.ShaderNodeBsdfAnisotropic = shader.nodes.new("ShaderNodeBsdfAnisotropic") glo: Node = shader.nodes.new("ShaderNodeBsdfAnisotropic")
glo.location.x -= 250 glo.location.x -= 250
glo.location.y -= 150 glo.location.y -= 150
mix: bpy.types.ShaderNodeMixShader = shader.nodes.new("ShaderNodeMixShader") mix: Node = shader.nodes.new("ShaderNodeMixShader")
shader.links.new(mix.inputs[1], dif.outputs["BSDF"]) shader.links.new(mix.inputs[1], dif.outputs["BSDF"])
shader.links.new(mix.inputs[2], glo.outputs["BSDF"]) shader.links.new(mix.inputs[2], glo.outputs["BSDF"])
@@ -91,7 +98,7 @@ def create_MMDBasicShader():
return shader return shader
def __enum_linked_nodes(node: bpy.types.Node) -> Iterable[bpy.types.Node]: def __enum_linked_nodes(node: Node) -> Iterable[Node]:
yield node yield node
if node.parent: if node.parent:
yield node.parent yield node.parent
@@ -99,7 +106,8 @@ def __enum_linked_nodes(node: bpy.types.Node) -> Iterable[bpy.types.Node]:
yield from __enum_linked_nodes(n) yield from __enum_linked_nodes(n)
def __cleanNodeTree(material: bpy.types.Material): def __cleanNodeTree(material: Material) -> None:
logger.debug(f"Cleaning node tree for material: {material.name}")
nodes = material.node_tree.nodes nodes = material.node_tree.nodes
node_names = set(n.name for n in 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"}): for o in (n for n in nodes if n.bl_idname in {"ShaderNodeOutput", "ShaderNodeOutputMaterial"}):
@@ -109,40 +117,46 @@ def __cleanNodeTree(material: bpy.types.Material):
nodes.remove(nodes[name]) nodes.remove(nodes[name])
def convertToCyclesShader(obj: bpy.types.Object, use_principled=False, clean_nodes=False, subsurface=0.001): 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})")
__switchToCyclesRenderEngine() __switchToCyclesRenderEngine()
convertToBlenderShader(obj, use_principled, clean_nodes, subsurface) convertToBlenderShader(obj, use_principled, clean_nodes, subsurface)
def convertToBlenderShader(obj: bpy.types.Object, use_principled=False, clean_nodes=False, subsurface=0.001): def convertToBlenderShader(obj: bpy.types.Object, use_principled: bool = False, clean_nodes: bool = False, subsurface: float = 0.001) -> None:
for i in obj.material_slots: for i in obj.material_slots:
if not i.material: if not i.material:
continue continue
if not i.material.use_nodes: if not i.material.use_nodes:
logger.debug(f"Enabling nodes for material: {i.material.name}")
i.material.use_nodes = True i.material.use_nodes = True
__convertToMMDBasicShader(i.material) __convertToMMDBasicShader(i.material)
if use_principled: if use_principled:
logger.debug(f"Converting material to Principled BSDF: {i.material.name}")
__convertToPrincipledBsdf(i.material, subsurface) __convertToPrincipledBsdf(i.material, subsurface)
if clean_nodes: if clean_nodes:
__cleanNodeTree(i.material) __cleanNodeTree(i.material)
def convertToMMDShader(obj): def convertToMMDShader(obj: bpy.types.Object) -> None:
"""BSDF -> MMDShaderDev conversion.""" """BSDF -> MMDShaderDev conversion."""
logger.info(f"Converting {obj.name} to MMD shader")
for i in obj.material_slots: for i in obj.material_slots:
if not i.material: if not i.material:
continue continue
if not i.material.use_nodes: if not i.material.use_nodes:
logger.debug(f"Enabling nodes for material: {i.material.name}")
i.material.use_nodes = True i.material.use_nodes = True
FnMaterial.convert_to_mmd_material(i.material) FnMaterial.convert_to_mmd_material(i.material)
def __convertToMMDBasicShader(material: bpy.types.Material): def __convertToMMDBasicShader(material: Material) -> None:
logger.debug(f"Converting material to MMD Basic Shader: {material.name}")
# TODO: test me # TODO: test me
mmd_basic_shader_grp = create_MMDBasicShader() mmd_basic_shader_grp = create_MMDBasicShader()
mmd_alpha_shader_grp = create_MMDAlphaShader() 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)): if not any(filter(lambda x: isinstance(x, ShaderNodeGroup) and x.node_tree.name in {"MMDBasicShader", "MMDAlphaShader"}, material.node_tree.nodes)):
# Add nodes for Cycles Render # Add nodes for Cycles Render
shader: bpy.types.ShaderNodeGroup = material.node_tree.nodes.new("ShaderNodeGroup") shader: ShaderNodeGroup = material.node_tree.nodes.new("ShaderNodeGroup")
shader.node_tree = mmd_basic_shader_grp shader.node_tree = mmd_basic_shader_grp
shader.inputs[0].default_value[:3] = material.diffuse_color[:3] shader.inputs[0].default_value[:3] = material.diffuse_color[:3]
shader.inputs[1].default_value[:3] = material.specular_color[:3] shader.inputs[1].default_value[:3] = material.specular_color[:3]
@@ -157,7 +171,8 @@ def __convertToMMDBasicShader(material: bpy.types.Material):
alpha_value = material.diffuse_color[3] alpha_value = material.diffuse_color[3]
if alpha_value < 1.0: if alpha_value < 1.0:
alpha_shader: bpy.types.ShaderNodeGroup = material.node_tree.nodes.new("ShaderNodeGroup") logger.debug(f"Material has alpha: {material.name}, alpha={alpha_value}")
alpha_shader: ShaderNodeGroup = material.node_tree.nodes.new("ShaderNodeGroup")
alpha_shader.location.x = shader.location.x + 250 alpha_shader.location.x = shader.location.x + 250
alpha_shader.location.y = shader.location.y - 150 alpha_shader.location.y = shader.location.y - 150
alpha_shader.node_tree = mmd_alpha_shader_grp alpha_shader.node_tree = mmd_alpha_shader_grp
@@ -165,21 +180,22 @@ def __convertToMMDBasicShader(material: bpy.types.Material):
material.node_tree.links.new(alpha_shader.inputs[0], outplug) material.node_tree.links.new(alpha_shader.inputs[0], outplug)
outplug = alpha_shader.outputs[0] outplug = alpha_shader.outputs[0]
material_output: bpy.types.ShaderNodeOutputMaterial = __getMaterialOutput(material.node_tree.nodes, "ShaderNodeOutputMaterial") material_output: ShaderNodeOutputMaterial = __getMaterialOutput(material.node_tree.nodes, "ShaderNodeOutputMaterial")
material.node_tree.links.new(material_output.inputs["Surface"], outplug) material.node_tree.links.new(material_output.inputs["Surface"], outplug)
material_output.location.x = shader.location.x + 500 material_output.location.x = shader.location.x + 500
material_output.location.y = shader.location.y - 150 material_output.location.y = shader.location.y - 150
def __convertToPrincipledBsdf(material: bpy.types.Material, subsurface: float): def __convertToPrincipledBsdf(material: Material, subsurface: float) -> None:
logger.debug(f"Converting material to Principled BSDF: {material.name}")
node_names = set() node_names = set()
for s in (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, ShaderNodeGroup)):
if s.node_tree.name == "MMDBasicShader": if s.node_tree.name == "MMDBasicShader":
l: bpy.types.NodeLink l: NodeLink
for l in s.outputs[0].links: for l in s.outputs[0].links:
to_node = l.to_node to_node = l.to_node
# assuming there is no bpy.types.NodeReroute between MMDBasicShader and MMDAlphaShader # 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": if isinstance(to_node, ShaderNodeGroup) and to_node.node_tree.name == "MMDAlphaShader":
__switchToPrincipledBsdf(material.node_tree, s, subsurface, node_alpha=to_node) __switchToPrincipledBsdf(material.node_tree, s, subsurface, node_alpha=to_node)
node_names.add(to_node.name) node_names.add(to_node.name)
else: else:
@@ -194,8 +210,9 @@ def __convertToPrincipledBsdf(material: bpy.types.Material, subsurface: float):
nodes.remove(nodes[name]) nodes.remove(nodes[name])
def __switchToPrincipledBsdf(node_tree: bpy.types.NodeTree, node_basic: bpy.types.ShaderNodeGroup, subsurface: float, node_alpha: Optional[bpy.types.ShaderNodeGroup] = None): def __switchToPrincipledBsdf(node_tree: NodeTree, node_basic: ShaderNodeGroup, subsurface: float, node_alpha: Optional[ShaderNodeGroup] = None) -> None:
shader: bpy.types.ShaderNodeBsdfPrincipled = node_tree.nodes.new("ShaderNodeBsdfPrincipled") logger.debug(f"Switching to Principled BSDF: {node_basic.name}")
shader: Node = node_tree.nodes.new("ShaderNodeBsdfPrincipled")
shader.parent = node_basic.parent shader.parent = node_basic.parent
shader.location.x = node_basic.location.x shader.location.x = node_basic.location.x
shader.location.y = node_basic.location.y shader.location.y = node_basic.location.y
+25 -27
View File
@@ -5,18 +5,19 @@
# Neoneko has modified this file to work with Avatar Toolkit and may of made changes or improvements. # 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. # MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
import logging
import os import os
import re import re
from typing import Callable, Optional, Set from typing import Callable, Dict, List, Optional, Set, Tuple, Union, Any
import bpy import bpy
from bpy.types import Object, Bone, PoseBone, Mesh, VertexGroup
from ..logging_setup import logger
from .bpyutils import FnContext from .bpyutils import FnContext
## 指定したオブジェクトのみを選択状態かつアクティブにする ## 指定したオブジェクトのみを選択状態かつアクティブにする
def selectAObject(obj): def selectAObject(obj: Object) -> None:
try: try:
bpy.ops.object.mode_set(mode="OBJECT") bpy.ops.object.mode_set(mode="OBJECT")
except Exception: except Exception:
@@ -27,13 +28,13 @@ def selectAObject(obj):
## 現在のモードを指定したオブジェクトのEdit Modeに変更する ## 現在のモードを指定したオブジェクトのEdit Modeに変更する
def enterEditMode(obj): def enterEditMode(obj: Object) -> None:
selectAObject(obj) selectAObject(obj)
if obj.mode != "EDIT": if obj.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT") bpy.ops.object.mode_set(mode="EDIT")
def setParentToBone(obj, parent, bone_name): def setParentToBone(obj: Object, parent: Object, bone_name: str) -> None:
selectAObject(obj) selectAObject(obj)
FnContext.set_active_object(FnContext.ensure_context(), parent) FnContext.set_active_object(FnContext.ensure_context(), parent)
bpy.ops.object.mode_set(mode="POSE") bpy.ops.object.mode_set(mode="POSE")
@@ -42,7 +43,7 @@ def setParentToBone(obj, parent, bone_name):
bpy.ops.object.mode_set(mode="OBJECT") bpy.ops.object.mode_set(mode="OBJECT")
def selectSingleBone(context, armature, bone_name, reset_pose=False): def selectSingleBone(context: bpy.types.Context, armature: Object, bone_name: str, reset_pose: bool = False) -> None:
try: try:
bpy.ops.object.mode_set(mode="OBJECT") bpy.ops.object.mode_set(mode="OBJECT")
except: except:
@@ -55,7 +56,7 @@ def selectSingleBone(context, armature, bone_name, reset_pose=False):
for p_bone in armature.pose.bones: for p_bone in armature.pose.bones:
p_bone.matrix_basis.identity() p_bone.matrix_basis.identity()
armature_bones: bpy.types.ArmatureBones = armature.data.bones armature_bones: bpy.types.ArmatureBones = armature.data.bones
i: bpy.types.Bone i: Bone
for i in armature_bones: for i in armature_bones:
i.select = i.name == bone_name i.select = i.name == bone_name
i.select_head = i.select_tail = i.select i.select_head = i.select_tail = i.select
@@ -69,7 +70,7 @@ __CONVERT_NAME_TO_R_REGEXP = re.compile("^(.*)右(.*)$")
## 日本語で左右を命名されている名前をblender方式のL(R)に変更する ## 日本語で左右を命名されている名前をblender方式のL(R)に変更する
def convertNameToLR(name, use_underscore=False): def convertNameToLR(name: str, use_underscore: bool = False) -> str:
m = __CONVERT_NAME_TO_L_REGEXP.match(name) m = __CONVERT_NAME_TO_L_REGEXP.match(name)
delimiter = "_" if use_underscore else "." delimiter = "_" if use_underscore else "."
if m: if m:
@@ -84,7 +85,7 @@ __CONVERT_L_TO_NAME_REGEXP = re.compile(r"(?P<lr>(?P<separator>[._])[lL])(?P<aft
__CONVERT_R_TO_NAME_REGEXP = re.compile(r"(?P<lr>(?P<separator>[._])[rR])(?P<after>($|(?P=separator)))") __CONVERT_R_TO_NAME_REGEXP = re.compile(r"(?P<lr>(?P<separator>[._])[rR])(?P<after>($|(?P=separator)))")
def convertLRToName(name): def convertLRToName(name: str) -> str:
match = __CONVERT_L_TO_NAME_REGEXP.search(name) match = __CONVERT_L_TO_NAME_REGEXP.search(name)
if match: if match:
return f"{name[0:match.start()]}{match['after']}{name[match.end():]}" return f"{name[0:match.start()]}{match['after']}{name[match.end():]}"
@@ -97,7 +98,7 @@ def convertLRToName(name):
## src_vertex_groupのWeightをdest_vertex_groupにaddする ## src_vertex_groupのWeightをdest_vertex_groupにaddする
def mergeVertexGroup(meshObj, src_vertex_group_name, dest_vertex_group_name): def mergeVertexGroup(meshObj: Object, src_vertex_group_name: str, dest_vertex_group_name: str) -> None:
mesh = meshObj.data mesh = meshObj.data
src_vertex_group = meshObj.vertex_groups[src_vertex_group_name] src_vertex_group = meshObj.vertex_groups[src_vertex_group_name]
dest_vertex_group = meshObj.vertex_groups[dest_vertex_group_name] dest_vertex_group = meshObj.vertex_groups[dest_vertex_group_name]
@@ -111,7 +112,7 @@ def mergeVertexGroup(meshObj, src_vertex_group_name, dest_vertex_group_name):
pass pass
def separateByMaterials(meshObj: bpy.types.Object): def separateByMaterials(meshObj: Object) -> None:
if len(meshObj.data.materials) < 2: if len(meshObj.data.materials) < 2:
selectAObject(meshObj) selectAObject(meshObj)
return return
@@ -134,7 +135,7 @@ def separateByMaterials(meshObj: bpy.types.Object):
bpy.data.objects.remove(dummy_parent) bpy.data.objects.remove(dummy_parent)
def clearUnusedMeshes(): def clearUnusedMeshes() -> None:
meshes_to_delete = [] meshes_to_delete = []
for mesh in bpy.data.meshes: for mesh in bpy.data.meshes:
if mesh.users == 0: if mesh.users == 0:
@@ -146,7 +147,7 @@ def clearUnusedMeshes():
## Boneのカスタムプロパティにname_jが存在する場合、name_jの値を ## Boneのカスタムプロパティにname_jが存在する場合、name_jの値を
# それ以外の場合は通常のbone名をキーとしたpose_boneへの辞書を作成 # それ以外の場合は通常のbone名をキーとしたpose_boneへの辞書を作成
def makePmxBoneMap(armObj): def makePmxBoneMap(armObj: Object) -> Dict[str, PoseBone]:
# Maintain backward compatibility with mmd_tools v0.4.x or older. # Maintain backward compatibility with mmd_tools v0.4.x or older.
return {(i.mmd_bone.name_j or i.get("mmd_bone_name_j", i.get("name_j", i.name))): i for i in armObj.pose.bones} return {(i.mmd_bone.name_j or i.get("mmd_bone_name_j", i.get("name_j", i.name))): i for i in armObj.pose.bones}
@@ -175,7 +176,7 @@ def unique_name(name: str, used_names: Set[str]) -> str:
return new_name return new_name
def int2base(x, base, width=0): def int2base(x: int, base: int, width: int = 0) -> str:
""" """
Method to convert an int to a base Method to convert an int to a base
Source: http://stackoverflow.com/questions/2267362 Source: http://stackoverflow.com/questions/2267362
@@ -198,7 +199,7 @@ def int2base(x, base, width=0):
return digits return digits
def saferelpath(path, start, strategy="inside"): def saferelpath(path: str, start: str, strategy: str = "inside") -> str:
""" """
On Windows relpath will raise a ValueError On Windows relpath will raise a ValueError
when trying to calculate the relative path to a when trying to calculate the relative path to a
@@ -227,13 +228,13 @@ def saferelpath(path, start, strategy="inside"):
class ItemOp: class ItemOp:
@staticmethod @staticmethod
def get_by_index(items, index): def get_by_index(items: bpy.types.bpy_prop_collection, index: int) -> Optional[Any]:
if 0 <= index < len(items): if 0 <= index < len(items):
return items[index] return items[index]
return None return None
@staticmethod @staticmethod
def resize(items: bpy.types.bpy_prop_collection, length: int): def resize(items: bpy.types.bpy_prop_collection, length: int) -> None:
count = length - len(items) count = length - len(items)
if count > 0: if count > 0:
for i in range(count): for i in range(count):
@@ -243,7 +244,7 @@ class ItemOp:
items.remove(length) items.remove(length)
@staticmethod @staticmethod
def add_after(items, index): def add_after(items: bpy.types.bpy_prop_collection, index: int) -> Tuple[Any, int]:
index_end = len(items) index_end = len(items)
index = max(0, min(index_end, index + 1)) index = max(0, min(index_end, index + 1))
items.add() items.add()
@@ -265,7 +266,8 @@ class ItemMoveOp:
) )
@staticmethod @staticmethod
def move(items, index, move_type, index_min=0, index_max=None): def move(items: bpy.types.bpy_prop_collection, index: int, move_type: str,
index_min: int = 0, index_max: Optional[int] = None) -> int:
if index_max is None: if index_max is None:
index_max = len(items) - 1 index_max = len(items) - 1
else: else:
@@ -294,7 +296,7 @@ class ItemMoveOp:
return index_new return index_new
def deprecated(deprecated_in: Optional[str] = None, details: Optional[str] = None): def deprecated(deprecated_in: Optional[str] = None, details: Optional[str] = None) -> Callable:
"""Decorator to mark a function as deprecated. """Decorator to mark a function as deprecated.
Args: Args:
deprecated_in (Optional[str]): Version in which the function was deprecated. deprecated_in (Optional[str]): Version in which the function was deprecated.
@@ -303,8 +305,8 @@ def deprecated(deprecated_in: Optional[str] = None, details: Optional[str] = Non
Callable: The decorated function. Callable: The decorated function.
""" """
def _function_wrapper(function: Callable): def _function_wrapper(function: Callable) -> Callable:
def _inner_wrapper(*args, **kwargs): def _inner_wrapper(*args: Any, **kwargs: Any) -> Any:
warn_deprecation(function.__name__, deprecated_in, details) warn_deprecation(function.__name__, deprecated_in, details)
return function(*args, **kwargs) return function(*args, **kwargs)
@@ -320,7 +322,7 @@ def warn_deprecation(function_name: str, deprecated_in: Optional[str] = None, de
deprecated_in (Optional[str]): Version in which the function was deprecated. deprecated_in (Optional[str]): Version in which the function was deprecated.
details (Optional[str]): Additional details about the deprecation. details (Optional[str]): Additional details about the deprecation.
""" """
logging.warning( logger.warning(
"%s is deprecated%s%s", "%s is deprecated%s%s",
function_name, function_name,
f" since {deprecated_in}" if deprecated_in else "", f" since {deprecated_in}" if deprecated_in else "",
@@ -328,7 +330,3 @@ def warn_deprecation(function_name: str, deprecated_in: Optional[str] = None, de
stack_info=True, stack_info=True,
stacklevel=4, stacklevel=4,
) )
# import warnings # pylint: disable=import-outside-toplevel
# warnings.warn(f"""{function_name}is deprecated{f" since {deprecated_in}" if deprecated_in else ""}{f": {details}" if details else ""}""", category=DeprecationWarning, stacklevel=2)
-240
View File
@@ -1,240 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2012 MMD Tools authors
# This file is part of MMD Tools.
from typing import Iterable, Optional
import bpy
from .core.shader import _NodeGroupUtils
from .core.material import FnMaterial
def __switchToCyclesRenderEngine():
if bpy.context.scene.render.engine != "CYCLES":
bpy.context.scene.render.engine = "CYCLES"
def __exposeNodeTreeInput(in_socket, name, default_value, node_input, shader):
_NodeGroupUtils(shader).new_input_socket(name, in_socket, default_value)
def __exposeNodeTreeOutput(out_socket, name, node_output, shader):
_NodeGroupUtils(shader).new_output_socket(name, out_socket)
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():
__switchToCyclesRenderEngine()
if "MMDAlphaShader" in bpy.data.node_groups:
return bpy.data.node_groups["MMDAlphaShader"]
shader = bpy.data.node_groups.new(name="MMDAlphaShader", type="ShaderNodeTree")
node_input = shader.nodes.new("NodeGroupInput")
node_output = shader.nodes.new("NodeGroupOutput")
node_output.location.x += 250
node_input.location.x -= 500
trans = shader.nodes.new("ShaderNodeBsdfTransparent")
trans.location.x -= 250
trans.location.y += 150
mix = shader.nodes.new("ShaderNodeMixShader")
shader.links.new(mix.inputs[1], trans.outputs["BSDF"])
__exposeNodeTreeInput(mix.inputs[2], "Shader", None, node_input, shader)
__exposeNodeTreeInput(mix.inputs["Fac"], "Alpha", 1.0, node_input, shader)
__exposeNodeTreeOutput(mix.outputs["Shader"], "Shader", node_output, shader)
return shader
def create_MMDBasicShader():
__switchToCyclesRenderEngine()
if "MMDBasicShader" in bpy.data.node_groups:
return bpy.data.node_groups["MMDBasicShader"]
shader: bpy.types.ShaderNodeTree = bpy.data.node_groups.new(name="MMDBasicShader", type="ShaderNodeTree")
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: bpy.types.ShaderNodeBsdfDiffuse = shader.nodes.new("ShaderNodeBsdfDiffuse")
dif.location.x -= 250
dif.location.y += 150
glo: bpy.types.ShaderNodeBsdfAnisotropic = shader.nodes.new("ShaderNodeBsdfAnisotropic")
glo.location.x -= 250
glo.location.y -= 150
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"])
__exposeNodeTreeInput(dif.inputs["Color"], "diffuse", [1.0, 1.0, 1.0, 1.0], node_input, shader)
__exposeNodeTreeInput(glo.inputs["Color"], "glossy", [1.0, 1.0, 1.0, 1.0], node_input, shader)
__exposeNodeTreeInput(glo.inputs["Roughness"], "glossy_rough", 0.0, node_input, shader)
__exposeNodeTreeInput(mix.inputs["Fac"], "reflection", 0.02, node_input, shader)
__exposeNodeTreeOutput(mix.outputs["Shader"], "shader", node_output, shader)
return shader
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):
yield from __enum_linked_nodes(n)
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):
node_names -= set(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=False, clean_nodes=False, subsurface=0.001):
__switchToCyclesRenderEngine()
convertToBlenderShader(obj, use_principled, clean_nodes, subsurface)
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
if not i.material.use_nodes:
i.material.use_nodes = True
__convertToMMDBasicShader(i.material)
if use_principled:
__convertToPrincipledBsdf(i.material, subsurface)
if clean_nodes:
__cleanNodeTree(i.material)
def convertToMMDShader(obj):
"""BSDF -> MMDShaderDev conversion."""
for i in obj.material_slots:
if not i.material:
continue
if not i.material.use_nodes:
i.material.use_nodes = True
FnMaterial.convert_to_mmd_material(i.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: 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]
location = shader.location.copy()
location.x -= 1000
alpha_value = 1.0
if len(material.diffuse_color) > 3:
alpha_value = material.diffuse_color[3]
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]
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: bpy.types.Material, subsurface: float):
node_names = set()
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, subsurface, node_alpha=to_node)
node_names.add(to_node.name)
else:
__switchToPrincipledBsdf(material.node_tree, s, subsurface)
node_names.add(s.name)
elif s.node_tree.name == "MMDShaderDev":
__switchToPrincipledBsdf(material.node_tree, s, subsurface)
node_names.add(s.name)
# remove MMD shader nodes
nodes = material.node_tree.nodes
for name in node_names:
nodes.remove(nodes[name])
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
alpha_socket_name = "Alpha"
if node_basic.node_tree.name == "MMDShaderDev":
node_alpha, alpha_socket_name = node_basic, "Base Alpha"
if "Base Tex" in node_basic.inputs and node_basic.inputs["Base Tex"].is_linked:
node_tree.links.new(node_basic.inputs["Base Tex"].links[0].from_socket, shader.inputs["Base Color"])
elif "Diffuse Color" in node_basic.inputs:
shader.inputs["Base Color"].default_value[:3] = node_basic.inputs["Diffuse Color"].default_value[:3]
elif "diffuse" in node_basic.inputs:
shader.inputs["Base Color"].default_value[:3] = node_basic.inputs["diffuse"].default_value[:3]
if node_basic.inputs["diffuse"].is_linked:
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 Weight"].default_value = subsurface
output_links = node_basic.outputs[0].links
if node_alpha:
output_links = node_alpha.outputs[0].links
shader.parent = node_alpha.parent or shader.parent
shader.location.x = node_alpha.location.x
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
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:
shader.inputs["Transmission"].default_value = 1 - node_alpha.inputs[alpha_socket_name].default_value
if node_alpha.inputs[alpha_socket_name].is_linked:
node_invert = node_tree.nodes.new("ShaderNodeMath")
node_invert.parent = shader.parent
node_invert.location.x = node_alpha.location.x - 250
node_invert.location.y = node_alpha.location.y - 300
node_invert.operation = "SUBTRACT"
node_invert.use_clamp = True
node_invert.inputs[0].default_value = 1
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)