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
+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.
import contextlib
from typing import Generator, List, Optional, TypeVar
from typing import Generator, List, Optional, TypeVar, Any, Set, Tuple, Dict, Union
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
@@ -20,7 +24,7 @@ class Props: # For API changes of only name changed properties
class __EditMode:
def __init__(self, obj):
def __init__(self, obj: Object):
if not isinstance(obj, bpy.types.Object):
raise ValueError
self.__prevMode = obj.mode
@@ -30,10 +34,10 @@ class __EditMode:
if obj.mode != "EDIT":
bpy.ops.object.mode_set(mode="EDIT")
def __enter__(self):
def __enter__(self) -> Any:
return self.__obj.data
def __exit__(self, type, value, traceback):
def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
if self.__prevMode == "EDIT":
bpy.ops.object.mode_set(mode="OBJECT") # update edited data
bpy.ops.object.mode_set(mode=self.__prevMode)
@@ -41,17 +45,18 @@ class __EditMode:
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):
raise ValueError
try:
bpy.ops.object.mode_set(mode="OBJECT")
except Exception:
logger.debug("Failed to set object mode")
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)
self.__active_object = active_object
@@ -60,23 +65,23 @@ class __SelectObjects:
self.__hides: List[bool] = []
for i in self.__selected_objects:
self.__hides.append(i.hide_get())
FnContext.select_object(contenxt, i)
FnContext.set_active_object(contenxt, active_object)
FnContext.select_object(context, i)
FnContext.set_active_object(context, active_object)
def __enter__(self) -> bpy.types.Object:
def __enter__(self) -> 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):
i.hide_set(j)
def setParent(obj, parent):
def setParent(obj: Object, parent: Object) -> None:
with select_object(parent, objects=[parent, obj]):
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]):
bpy.ops.object.mode_set(mode="POSE")
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")
def edit_object(obj):
def edit_object(obj: Object) -> __EditMode:
"""Set the object interaction mode to 'EDIT'
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)
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.
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)
def duplicateObject(obj, total_len):
def duplicateObject(obj: Object, total_len: int) -> List[Object]:
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)
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
if target_object is None:
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
bm = bmesh.new()
@@ -138,12 +146,15 @@ def makeSphere(segment=8, ring_count=5, radius=1.0, target_object=None):
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
from mathutils import Matrix
if target_object is None:
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
bm = bmesh.new()
@@ -159,13 +170,16 @@ def makeBox(size=(1, 1, 1), target_object=None):
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 bmesh
if target_object is None:
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)
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:
__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
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)
if c and c.type != "TRANSFORM":
constraints.remove(c)
@@ -245,7 +259,7 @@ class TransformConstraintOp:
return c
@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)
ret = cls.__MIN_MAX_MAP.get(key, None)
if ret is None:
@@ -255,7 +269,7 @@ class TransformConstraintOp:
return ret
@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
if not c or c.type != "TRANSFORM":
return
@@ -279,14 +293,14 @@ class FnObject:
raise NotImplementedError("This class is not expected to be instantiated.")
@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)
key: bpy.types.Key = shape_key.id_data
key: Key = shape_key.id_data
assert key == mesh_object.data.shape_keys
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:
if not fc_curve.data_path.startswith(shape_key.path_from_id()):
continue
@@ -310,35 +324,35 @@ class FnContext:
raise NotImplementedError("This class is not expected to be instantiated.")
@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
@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
@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
return obj
@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))
@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
@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_select = False
obj.hide_set(False)
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:
if __layer_check(lc):
lc.hide_viewport = False
@@ -360,44 +374,44 @@ class FnContext:
return obj
@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)
return obj
@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]
@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:
if i != obj:
i.select_set(False)
return FnContext.select_object(context, obj)
@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)
return obj
@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))
@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.
This function duplicates the given object and returns a list of duplicated objects.
Args:
context (bpy.types.Context): The context in which the duplication is performed.
object_to_duplicate (bpy.types.Object): The object to be duplicated.
context (Context): The context in which the duplication is performed.
object_to_duplicate (Object): The object to be duplicated.
target_count (int): The desired count of duplicated objects.
Returns:
List[bpy.types.Object]: A list of duplicated objects.
List[Object]: A list of duplicated objects.
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.
@@ -421,27 +435,28 @@ class FnContext:
last_selected_objects[i].select_set(True)
last_selected_objects = context.selected_objects
assert len(result_objects) == target_count
logger.debug(f"Duplicated object {object_to_duplicate.name} to create {target_count} objects")
return result_objects
@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.
Args:
context (bpy.types.Context): The Blender context.
target_object (bpy.types.Object): The target object to find the layer collection for.
context (Context): The Blender context.
target_object (Object): The target object to find the layer collection for.
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:
return layer_collection
child_layer_collection: bpy.types.LayerCollection
child_layer_collection: LayerCollection
for child_layer_collection in layer_collection.children:
found = find_layer_collection_by_name(child_layer_collection, name)
if found is not None:
@@ -449,7 +464,7 @@ class FnContext:
return None
user_collection: bpy.types.Collection
user_collection: Collection
for user_collection in target_object.users_collection:
found = find_layer_collection_by_name(scene_layer_collection, user_collection.name)
if found is not None:
@@ -459,7 +474,7 @@ class FnContext:
@staticmethod
@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.
@@ -467,11 +482,11 @@ class FnContext:
It ensures that the original active_layer_collection is restored after the context is exited.
Args:
context (bpy.types.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.
context (Context): The context in which the active_layer_collection will be overridden.
target_object (Object): The target object whose layer collection will be set as the active_layer_collection.
Yields:
bpy.types.Context: The modified context with the active_layer_collection overridden.
Context: The modified context with the active_layer_collection overridden.
Example:
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
@staticmethod
def __get_addon_preferences(context: bpy.types.Context) -> Optional[bpy.types.AddonPreferences]:
addon: bpy.types.Addon = context.preferences.addons.get(__package__, None)
def __get_addon_preferences(context: Context) -> Optional[AddonPreferences]:
addon: Addon = context.preferences.addons.get(__package__, None)
return addon.preferences if addon else None
@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)
@staticmethod
def temp_override_objects(
context: bpy.types.Context,
window: Optional[bpy.types.Window] = None,
area: Optional[bpy.types.Area] = None,
region: Optional[bpy.types.Region] = None,
active_object: Optional[bpy.types.Object] = None,
selected_objects: Optional[List[bpy.types.Object]] = None,
**keywords,
) -> Generator[bpy.types.Context, None, None]:
context: Context,
window: Optional[Window] = None,
area: Optional[Area] = None,
region: Optional[Region] = None,
active_object: Optional[Object] = None,
selected_objects: Optional[List[Object]] = None,
**keywords: Any,
) -> Generator[Context, None, None]:
if active_object is not None:
keywords["active_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.
# 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
from bpy.types import Material, NodeTree, Node, NodeSocket, ShaderNodeGroup, ShaderNodeOutputMaterial, NodeLink
from ..logging_setup import logger
from .core.shader import _NodeGroupUtils
from .core.material import FnMaterial
def __switchToCyclesRenderEngine():
def __switchToCyclesRenderEngine() -> None:
if bpy.context.scene.render.engine != "CYCLES":
logger.debug("Switching render engine to 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)
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)
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.is_active_output = True
return o
def create_MMDAlphaShader():
def create_MMDAlphaShader() -> NodeTree:
__switchToCyclesRenderEngine()
if "MMDAlphaShader" in bpy.data.node_groups:
logger.debug("Using existing MMDAlphaShader node group")
return bpy.data.node_groups["MMDAlphaShader"]
logger.info("Creating new MMDAlphaShader node group")
shader = bpy.data.node_groups.new(name="MMDAlphaShader", type="ShaderNodeTree")
node_input = shader.nodes.new("NodeGroupInput")
@@ -59,26 +64,28 @@ def create_MMDAlphaShader():
return shader
def create_MMDBasicShader():
def create_MMDBasicShader() -> NodeTree:
__switchToCyclesRenderEngine()
if "MMDBasicShader" in bpy.data.node_groups:
logger.debug("Using existing MMDBasicShader node group")
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_output: bpy.types.NodeGroupOutput = shader.nodes.new("NodeGroupOutput")
node_input: Node = shader.nodes.new("NodeGroupInput")
node_output: Node = shader.nodes.new("NodeGroupOutput")
node_output.location.x += 250
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.y += 150
glo: bpy.types.ShaderNodeBsdfAnisotropic = shader.nodes.new("ShaderNodeBsdfAnisotropic")
glo: Node = shader.nodes.new("ShaderNodeBsdfAnisotropic")
glo.location.x -= 250
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[2], glo.outputs["BSDF"])
@@ -91,7 +98,7 @@ def create_MMDBasicShader():
return shader
def __enum_linked_nodes(node: bpy.types.Node) -> Iterable[bpy.types.Node]:
def __enum_linked_nodes(node: Node) -> Iterable[Node]:
yield node
if 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)
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
node_names = set(n.name for n in nodes)
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])
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()
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:
if not i.material:
continue
if not i.material.use_nodes:
logger.debug(f"Enabling nodes for material: {i.material.name}")
i.material.use_nodes = True
__convertToMMDBasicShader(i.material)
if use_principled:
logger.debug(f"Converting material to Principled BSDF: {i.material.name}")
__convertToPrincipledBsdf(i.material, subsurface)
if clean_nodes:
__cleanNodeTree(i.material)
def convertToMMDShader(obj):
def convertToMMDShader(obj: bpy.types.Object) -> None:
"""BSDF -> MMDShaderDev conversion."""
logger.info(f"Converting {obj.name} to MMD shader")
for i in obj.material_slots:
if not i.material:
continue
if not i.material.use_nodes:
logger.debug(f"Enabling nodes for material: {i.material.name}")
i.material.use_nodes = True
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
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)):
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
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.inputs[0].default_value[:3] = material.diffuse_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]
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.y = shader.location.y - 150
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)
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_output.location.x = shader.location.x + 500
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()
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":
l: bpy.types.NodeLink
l: 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":
if isinstance(to_node, 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:
@@ -194,8 +210,9 @@ def __convertToPrincipledBsdf(material: bpy.types.Material, subsurface: float):
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")
def __switchToPrincipledBsdf(node_tree: NodeTree, node_basic: ShaderNodeGroup, subsurface: float, node_alpha: Optional[ShaderNodeGroup] = None) -> None:
logger.debug(f"Switching to Principled BSDF: {node_basic.name}")
shader: Node = node_tree.nodes.new("ShaderNodeBsdfPrincipled")
shader.parent = node_basic.parent
shader.location.x = node_basic.location.x
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.
# 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 re
from typing import Callable, Optional, Set
from typing import Callable, Dict, List, Optional, Set, Tuple, Union, Any
import bpy
from bpy.types import Object, Bone, PoseBone, Mesh, VertexGroup
from ..logging_setup import logger
from .bpyutils import FnContext
## 指定したオブジェクトのみを選択状態かつアクティブにする
def selectAObject(obj):
def selectAObject(obj: Object) -> None:
try:
bpy.ops.object.mode_set(mode="OBJECT")
except Exception:
@@ -27,13 +28,13 @@ def selectAObject(obj):
## 現在のモードを指定したオブジェクトのEdit Modeに変更する
def enterEditMode(obj):
def enterEditMode(obj: Object) -> None:
selectAObject(obj)
if obj.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)
FnContext.set_active_object(FnContext.ensure_context(), parent)
bpy.ops.object.mode_set(mode="POSE")
@@ -42,7 +43,7 @@ def setParentToBone(obj, parent, bone_name):
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:
bpy.ops.object.mode_set(mode="OBJECT")
except:
@@ -55,7 +56,7 @@ def selectSingleBone(context, armature, bone_name, reset_pose=False):
for p_bone in armature.pose.bones:
p_bone.matrix_basis.identity()
armature_bones: bpy.types.ArmatureBones = armature.data.bones
i: bpy.types.Bone
i: Bone
for i in armature_bones:
i.select = i.name == bone_name
i.select_head = i.select_tail = i.select
@@ -69,7 +70,7 @@ __CONVERT_NAME_TO_R_REGEXP = re.compile("^(.*)右(.*)$")
## 日本語で左右を命名されている名前を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)
delimiter = "_" if use_underscore else "."
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)))")
def convertLRToName(name):
def convertLRToName(name: str) -> str:
match = __CONVERT_L_TO_NAME_REGEXP.search(name)
if match:
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する
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
src_vertex_group = meshObj.vertex_groups[src_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
def separateByMaterials(meshObj: bpy.types.Object):
def separateByMaterials(meshObj: Object) -> None:
if len(meshObj.data.materials) < 2:
selectAObject(meshObj)
return
@@ -134,7 +135,7 @@ def separateByMaterials(meshObj: bpy.types.Object):
bpy.data.objects.remove(dummy_parent)
def clearUnusedMeshes():
def clearUnusedMeshes() -> None:
meshes_to_delete = []
for mesh in bpy.data.meshes:
if mesh.users == 0:
@@ -146,7 +147,7 @@ def clearUnusedMeshes():
## Boneのカスタムプロパティにname_jが存在する場合、name_jの値を
# それ以外の場合は通常のbone名をキーとしたpose_boneへの辞書を作成
def makePmxBoneMap(armObj):
def makePmxBoneMap(armObj: Object) -> Dict[str, PoseBone]:
# 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}
@@ -175,7 +176,7 @@ def unique_name(name: str, used_names: Set[str]) -> str:
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
Source: http://stackoverflow.com/questions/2267362
@@ -198,7 +199,7 @@ def int2base(x, base, width=0):
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
when trying to calculate the relative path to a
@@ -227,13 +228,13 @@ def saferelpath(path, start, strategy="inside"):
class ItemOp:
@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):
return items[index]
return None
@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)
if count > 0:
for i in range(count):
@@ -243,7 +244,7 @@ class ItemOp:
items.remove(length)
@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 = max(0, min(index_end, index + 1))
items.add()
@@ -265,7 +266,8 @@ class ItemMoveOp:
)
@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:
index_max = len(items) - 1
else:
@@ -294,7 +296,7 @@ class ItemMoveOp:
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.
Args:
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.
"""
def _function_wrapper(function: Callable):
def _inner_wrapper(*args, **kwargs):
def _function_wrapper(function: Callable) -> Callable:
def _inner_wrapper(*args: Any, **kwargs: Any) -> Any:
warn_deprecation(function.__name__, deprecated_in, details)
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.
details (Optional[str]): Additional details about the deprecation.
"""
logging.warning(
logger.warning(
"%s is deprecated%s%s",
function_name,
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,
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)