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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user