Updated Operations and Properties
- Updated Operations and Properties with tpying and logging. I have not updated translation files, this is because i want to gut MMD Tools system and replace it with our own, however I want to make MMD Tools more simple and ajust it to our needs only. This is going to take a while and my aim for this is Alpha 4, also the MMD Translation system hurt my head.... - Fixes a couple of bugs as well, with quick access and the PMX importer.
This commit is contained in:
+56
-27
@@ -6,14 +6,17 @@
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
import re
|
||||
from typing import List, Dict, Any, Set, Optional, Tuple, Union, Type
|
||||
|
||||
import bpy
|
||||
from bpy.types import Context, Object, Operator, ShapeKey
|
||||
|
||||
from .. import utils
|
||||
from ..bpyutils import FnContext, FnObject
|
||||
from ..core.bone import FnBone
|
||||
from ..core.model import FnModel, Model
|
||||
from ..core.morph import FnMorph
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
class SelectObject(bpy.types.Operator):
|
||||
@@ -29,7 +32,8 @@ class SelectObject(bpy.types.Operator):
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
logger.debug(f"Selecting object: {self.name}")
|
||||
utils.selectAObject(context.scene.objects[self.name])
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -43,41 +47,43 @@ class MoveObject(bpy.types.Operator, utils.ItemMoveOp):
|
||||
__PREFIX_REGEXP = re.compile(r"(?P<prefix>[0-9A-Z]{3}_)(?P<name>.*)")
|
||||
|
||||
@classmethod
|
||||
def set_index(cls, obj, index):
|
||||
def set_index(cls, obj: Object, index: int) -> None:
|
||||
m = cls.__PREFIX_REGEXP.match(obj.name)
|
||||
name = m.group("name") if m else obj.name
|
||||
obj.name = "%s_%s" % (utils.int2base(index, 36, 3), name)
|
||||
|
||||
@classmethod
|
||||
def get_name(cls, obj, prefix=None):
|
||||
def get_name(cls, obj: Object, prefix: Optional[str] = None) -> str:
|
||||
m = cls.__PREFIX_REGEXP.match(obj.name)
|
||||
name = m.group("name") if m else obj.name
|
||||
return name[len(prefix) :] if prefix and name.startswith(prefix) else name
|
||||
|
||||
@classmethod
|
||||
def normalize_indices(cls, objects):
|
||||
def normalize_indices(cls, objects: List[Object]) -> None:
|
||||
for i, x in enumerate(objects):
|
||||
cls.set_index(x, i)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return context.active_object is not None
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
objects = self.__get_objects(obj)
|
||||
if obj not in objects:
|
||||
self.report({"ERROR"}, 'Can not move object "%s"' % obj.name)
|
||||
logger.error(f'Cannot move object "{obj.name}"')
|
||||
self.report({"ERROR"}, f'Can not move object "{obj.name}"')
|
||||
return {"CANCELLED"}
|
||||
|
||||
objects.sort(key=lambda x: x.name)
|
||||
logger.debug(f"Moving object {obj.name} {self.type}")
|
||||
self.move(objects, objects.index(obj), self.type)
|
||||
self.normalize_indices(objects)
|
||||
return {"FINISHED"}
|
||||
|
||||
def __get_objects(self, obj):
|
||||
def __get_objects(self, obj: Object) -> Any:
|
||||
class __MovableList(list):
|
||||
def move(self, index_old, index_new):
|
||||
def move(self, index_old: int, index_new: int) -> None:
|
||||
item = self[index_old]
|
||||
self.remove(item)
|
||||
self.insert(index_new, item)
|
||||
@@ -102,11 +108,11 @@ class CleanShapeKeys(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return any(o.type == "MESH" for o in context.selected_objects)
|
||||
|
||||
@staticmethod
|
||||
def __can_remove(key_block):
|
||||
def __can_remove(key_block: ShapeKey) -> bool:
|
||||
if key_block.relative_key == key_block:
|
||||
return False # Basis
|
||||
for v0, v1 in zip(key_block.relative_key.data, key_block.data):
|
||||
@@ -114,20 +120,24 @@ class CleanShapeKeys(bpy.types.Operator):
|
||||
return False
|
||||
return True
|
||||
|
||||
def __shape_key_clean(self, obj, key_blocks):
|
||||
def __shape_key_clean(self, obj: Object, key_blocks: List[ShapeKey]) -> None:
|
||||
for kb in key_blocks:
|
||||
if self.__can_remove(kb):
|
||||
logger.debug(f"Removing unused shape key: {kb.name} from {obj.name}")
|
||||
FnObject.mesh_remove_shape_key(obj, kb)
|
||||
if len(key_blocks) == 1:
|
||||
logger.debug(f"Removing single shape key: {key_blocks[0].name} from {obj.name}")
|
||||
FnObject.mesh_remove_shape_key(obj, key_blocks[0])
|
||||
|
||||
def execute(self, context):
|
||||
obj: bpy.types.Object
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
logger.info("Cleaning shape keys for selected objects")
|
||||
obj: Object
|
||||
for obj in context.selected_objects:
|
||||
if obj.type != "MESH" or obj.data.shape_keys is None:
|
||||
continue
|
||||
if not obj.data.shape_keys.use_relative:
|
||||
continue # not be considered yet
|
||||
logger.debug(f"Processing shape keys for {obj.name}")
|
||||
self.__shape_key_clean(obj, obj.data.shape_keys.key_blocks)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -144,21 +154,25 @@ class SeparateByMaterials(bpy.types.Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: Context) -> bool:
|
||||
obj = context.active_object
|
||||
return obj and obj.type == "MESH"
|
||||
|
||||
def __separate_by_materials(self, obj):
|
||||
def __separate_by_materials(self, obj: Object) -> None:
|
||||
logger.info(f"Separating {obj.name} by materials")
|
||||
utils.separateByMaterials(obj)
|
||||
if self.clean_shape_keys:
|
||||
logger.debug("Cleaning shape keys after separation")
|
||||
bpy.ops.mmd_tools.clean_shape_keys()
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
if root is None:
|
||||
logger.debug("No root object found, separating single object")
|
||||
self.__separate_by_materials(obj)
|
||||
else:
|
||||
logger.debug(f"Root object found: {root.name}, preparing for separation")
|
||||
bpy.ops.mmd_tools.clear_temp_materials()
|
||||
bpy.ops.mmd_tools.clear_uv_morph_view()
|
||||
|
||||
@@ -171,9 +185,11 @@ class SeparateByMaterials(bpy.types.Operator):
|
||||
if len(mesh.data.materials) > 0:
|
||||
mat = mesh.data.materials[0]
|
||||
idx = mat_names.index(getattr(mat, "name", None))
|
||||
logger.debug(f"Setting index {idx} for mesh {mesh.name}")
|
||||
MoveObject.set_index(mesh, idx)
|
||||
|
||||
for morph in root.mmd_root.material_morphs:
|
||||
logger.debug(f"Updating material morph: {morph.name}")
|
||||
FnMorph(morph, rig).update_mat_related_mesh()
|
||||
utils.clearUnusedMeshes()
|
||||
return {"FINISHED"}
|
||||
@@ -191,13 +207,15 @@ class JoinMeshes(bpy.types.Operator):
|
||||
default=True,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
if root is None:
|
||||
logger.error("No MMD model found")
|
||||
self.report({"ERROR"}, "Select a MMD model")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Joining meshes for model: {root.name}")
|
||||
bpy.ops.mmd_tools.clear_temp_materials()
|
||||
bpy.ops.mmd_tools.clear_uv_morph_view()
|
||||
|
||||
@@ -205,9 +223,11 @@ class JoinMeshes(bpy.types.Operator):
|
||||
rig = Model(root)
|
||||
meshes_list = sorted(rig.meshes(), key=lambda x: x.name)
|
||||
if not meshes_list:
|
||||
logger.error("No meshes found in the model")
|
||||
self.report({"ERROR"}, "The model does not have any meshes")
|
||||
return {"CANCELLED"}
|
||||
active_mesh = meshes_list[0]
|
||||
logger.debug(f"Found {len(meshes_list)} meshes, using {active_mesh.name} as active")
|
||||
|
||||
FnContext.select_objects(context, *meshes_list)
|
||||
FnContext.set_active_object(context, active_mesh)
|
||||
@@ -216,15 +236,19 @@ class JoinMeshes(bpy.types.Operator):
|
||||
for m in meshes_list[1:]:
|
||||
for mat in m.data.materials:
|
||||
if mat not in active_mesh.data.materials[:]:
|
||||
logger.debug(f"Adding material {mat.name} to active mesh")
|
||||
active_mesh.data.materials.append(mat)
|
||||
|
||||
# Join selected meshes
|
||||
logger.debug("Joining meshes")
|
||||
bpy.ops.object.join()
|
||||
|
||||
if self.sort_shape_keys:
|
||||
logger.debug("Sorting shape keys")
|
||||
FnMorph.fixShapeKeyOrder(active_mesh, root.mmd_root.vertex_morphs.keys())
|
||||
active_mesh.active_shape_key_index = 0
|
||||
for morph in root.mmd_root.material_morphs:
|
||||
logger.debug(f"Updating material morph: {morph.name}")
|
||||
FnMorph(morph, rig).update_mat_related_mesh(active_mesh)
|
||||
utils.clearUnusedMeshes()
|
||||
return {"FINISHED"}
|
||||
@@ -238,17 +262,20 @@ class AttachMeshesToMMD(bpy.types.Operator):
|
||||
|
||||
add_armature_modifier: bpy.props.BoolProperty(default=True)
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
root = FnModel.find_root_object(context.active_object)
|
||||
if root is None:
|
||||
logger.error("No MMD model found")
|
||||
self.report({"ERROR"}, "Select a MMD model")
|
||||
return {"CANCELLED"}
|
||||
|
||||
armObj = FnModel.find_armature_object(root)
|
||||
if armObj is None:
|
||||
logger.error("Model armature not found")
|
||||
self.report({"ERROR"}, "Model Armature not found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Attaching meshes to model: {root.name}")
|
||||
FnModel.attach_mesh_objects(root, context.visible_objects, self.add_armature_modifier)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -268,17 +295,18 @@ class ChangeMMDIKLoopFactor(bpy.types.Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return FnModel.find_root_object(context.active_object) is not None
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: Context, event: Any) -> Set[str]:
|
||||
root_object = FnModel.find_root_object(context.active_object)
|
||||
self.mmd_ik_loop_factor = root_object.mmd_root.ik_loop_factor
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
root_object = FnModel.find_root_object(context.active_object)
|
||||
logger.info(f"Changing IK loop factor to {self.mmd_ik_loop_factor} for model: {root_object.name}")
|
||||
FnModel.change_mmd_ik_loop_factor(root_object, self.mmd_ik_loop_factor)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -290,21 +318,22 @@ class RecalculateBoneRoll(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: Context) -> bool:
|
||||
obj = context.active_object
|
||||
return obj and obj.type == "ARMATURE"
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: Context, event: Any) -> Set[str]:
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
def draw(self, context: Context) -> None:
|
||||
layout = self.layout
|
||||
c = layout.column()
|
||||
c.label(text="This operation will break existing f-curve/action.", icon="QUESTION")
|
||||
c.label(text="Click [OK] to run the operation.")
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
arm = context.active_object
|
||||
logger.info(f"Recalculating bone roll for armature: {arm.name}")
|
||||
FnBone.apply_auto_bone_roll(arm)
|
||||
return {"FINISHED"}
|
||||
|
||||
Reference in New Issue
Block a user