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