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:
@@ -41,9 +41,11 @@ class FnModel:
|
||||
Optional[bpy.types.Object]: The root object of the model. If the object is not a part of a model, None is returned.
|
||||
Generally, the root object is a object with type == "EMPTY" and mmd_type == "ROOT".
|
||||
"""
|
||||
while obj is not None and obj.mmd_type != "ROOT":
|
||||
while obj is not None:
|
||||
if hasattr(obj, 'mmd_type') and obj.mmd_type == "ROOT":
|
||||
return obj
|
||||
obj = obj.parent
|
||||
return obj
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def find_armature_object(root_object: bpy.types.Object) -> Optional[bpy.types.Object]:
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
import bpy
|
||||
from bpy.props import BoolProperty, StringProperty
|
||||
from bpy.types import Operator
|
||||
from bpy.props import BoolProperty, StringProperty, FloatProperty
|
||||
from bpy.types import Operator, Context, Object, Material
|
||||
|
||||
from typing import Set, Dict, Any, List, Tuple, Optional, Union, cast
|
||||
|
||||
from .. import cycles_converter
|
||||
from ..core.exceptions import MaterialNotFoundError
|
||||
from ..core.material import FnMaterial
|
||||
from ..core.shader import _NodeGroupUtils
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
class ConvertMaterialsForCycles(Operator):
|
||||
@@ -21,14 +24,14 @@ class ConvertMaterialsForCycles(Operator):
|
||||
bl_description = "Convert materials of selected objects for Cycles."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
use_principled: bpy.props.BoolProperty(
|
||||
use_principled: BoolProperty(
|
||||
name="Convert to Principled BSDF",
|
||||
description="Convert MMD shader nodes to Principled BSDF as well if enabled",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
clean_nodes: bpy.props.BoolProperty(
|
||||
clean_nodes: BoolProperty(
|
||||
name="Clean Nodes",
|
||||
description="Remove redundant nodes as well if enabled. Disable it to keep node data.",
|
||||
default=False,
|
||||
@@ -36,22 +39,27 @@ class ConvertMaterialsForCycles(Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return next((x for x in context.selected_objects if x.type == "MESH"), None)
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return next((x for x in context.selected_objects if x.type == "MESH"), None) is not None
|
||||
|
||||
def draw(self, context):
|
||||
def draw(self, context: Context) -> None:
|
||||
layout = self.layout
|
||||
layout.prop(self, "use_principled")
|
||||
layout.prop(self, "clean_nodes")
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
try:
|
||||
context.scene.render.engine = "CYCLES"
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to change to Cycles render engine: {str(e)}")
|
||||
self.report({"ERROR"}, " * Failed to change to Cycles render engine.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Converting materials for Cycles with principled={self.use_principled}, clean_nodes={self.clean_nodes}")
|
||||
for obj in (x for x in context.selected_objects if x.type == "MESH"):
|
||||
logger.debug(f"Converting materials for object: {obj.name}")
|
||||
cycles_converter.convertToCyclesShader(obj, use_principled=self.use_principled, clean_nodes=self.clean_nodes)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -61,21 +69,21 @@ class ConvertMaterials(Operator):
|
||||
bl_description = "Convert materials of selected objects."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
use_principled: bpy.props.BoolProperty(
|
||||
use_principled: BoolProperty(
|
||||
name="Convert to Principled BSDF",
|
||||
description="Convert MMD shader nodes to Principled BSDF as well if enabled",
|
||||
default=True,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
clean_nodes: bpy.props.BoolProperty(
|
||||
clean_nodes: BoolProperty(
|
||||
name="Clean Nodes",
|
||||
description="Remove redundant nodes as well if enabled. Disable it to keep node data.",
|
||||
default=True,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
subsurface: bpy.props.FloatProperty(
|
||||
subsurface: FloatProperty(
|
||||
name="Subsurface",
|
||||
default=0.001,
|
||||
soft_min=0.000,
|
||||
@@ -85,13 +93,15 @@ class ConvertMaterials(Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return next((x for x in context.selected_objects if x.type == "MESH"), None)
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return next((x for x in context.selected_objects if x.type == "MESH"), None) is not None
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
logger.info(f"Converting materials with principled={self.use_principled}, clean_nodes={self.clean_nodes}, subsurface={self.subsurface}")
|
||||
for obj in context.selected_objects:
|
||||
if obj.type != "MESH":
|
||||
continue
|
||||
logger.debug(f"Converting materials for object: {obj.name}")
|
||||
cycles_converter.convertToBlenderShader(obj, use_principled=self.use_principled, clean_nodes=self.clean_nodes, subsurface=self.subsurface)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -102,20 +112,22 @@ class ConvertBSDFMaterials(Operator):
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return next((x for x in context.selected_objects if x.type == 'MESH'), None)
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return next((x for x in context.selected_objects if x.type == 'MESH'), None) is not None
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
logger.info("Converting BSDF materials to MMD shader")
|
||||
for obj in context.selected_objects:
|
||||
if obj.type != 'MESH':
|
||||
continue
|
||||
logger.debug(f"Converting BSDF materials for object: {obj.name}")
|
||||
cycles_converter.convertToMMDShader(obj)
|
||||
return {'FINISHED'}
|
||||
|
||||
class _OpenTextureBase:
|
||||
"""Create a texture for mmd model material."""
|
||||
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
bl_options: Set[str] = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
filepath: StringProperty(
|
||||
name="File Path",
|
||||
@@ -129,7 +141,7 @@ class _OpenTextureBase:
|
||||
options={"HIDDEN"},
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: Context, event: Any) -> Set[str]:
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
@@ -139,8 +151,13 @@ class OpenTexture(Operator, _OpenTextureBase):
|
||||
bl_label = "Open Texture"
|
||||
bl_description = "Create main texture of active material"
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
mat = context.active_object.active_material
|
||||
if not mat:
|
||||
logger.error("No active material found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Creating texture for material: {mat.name} from {self.filepath}")
|
||||
fnMat = FnMaterial(mat)
|
||||
fnMat.create_texture(self.filepath)
|
||||
return {"FINISHED"}
|
||||
@@ -154,8 +171,13 @@ class RemoveTexture(Operator):
|
||||
bl_description = "Remove main texture of active material"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
mat = context.active_object.active_material
|
||||
if not mat:
|
||||
logger.error("No active material found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Removing texture from material: {mat.name}")
|
||||
fnMat = FnMaterial(mat)
|
||||
fnMat.remove_texture()
|
||||
return {"FINISHED"}
|
||||
@@ -168,8 +190,13 @@ class OpenSphereTextureSlot(Operator, _OpenTextureBase):
|
||||
bl_label = "Open Sphere Texture"
|
||||
bl_description = "Create sphere texture of active material"
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
mat = context.active_object.active_material
|
||||
if not mat:
|
||||
logger.error("No active material found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Creating sphere texture for material: {mat.name} from {self.filepath}")
|
||||
fnMat = FnMaterial(mat)
|
||||
fnMat.create_sphere_texture(self.filepath, context.active_object)
|
||||
return {"FINISHED"}
|
||||
@@ -183,8 +210,13 @@ class RemoveSphereTexture(Operator):
|
||||
bl_description = "Remove sphere texture of active material"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
mat = context.active_object.active_material
|
||||
if not mat:
|
||||
logger.error("No active material found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Removing sphere texture from material: {mat.name}")
|
||||
fnMat = FnMaterial(mat)
|
||||
fnMat.remove_sphere_texture()
|
||||
return {"FINISHED"}
|
||||
@@ -197,18 +229,21 @@ class MoveMaterialUp(Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: Context) -> bool:
|
||||
obj = context.active_object
|
||||
valid_mesh = obj and obj.type == "MESH" and obj.mmd_type == "NONE"
|
||||
return valid_mesh and obj.active_material_index > 0
|
||||
return bool(valid_mesh and obj.active_material_index > 0)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
current_idx = obj.active_material_index
|
||||
prev_index = current_idx - 1
|
||||
|
||||
logger.debug(f"Moving material {current_idx} up to position {prev_index} for object {obj.name}")
|
||||
try:
|
||||
FnMaterial.swap_materials(obj, current_idx, prev_index, reverse=True, swap_slots=True)
|
||||
except MaterialNotFoundError:
|
||||
logger.error(f"Materials not found for indices {current_idx} and {prev_index}")
|
||||
self.report({"ERROR"}, "Materials not found")
|
||||
return {"CANCELLED"}
|
||||
obj.active_material_index = prev_index
|
||||
@@ -223,18 +258,21 @@ class MoveMaterialDown(Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: Context) -> bool:
|
||||
obj = context.active_object
|
||||
valid_mesh = obj and obj.type == "MESH" and obj.mmd_type == "NONE"
|
||||
return valid_mesh and obj.active_material_index < len(obj.material_slots) - 1
|
||||
return bool(valid_mesh and obj.active_material_index < len(obj.material_slots) - 1)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
current_idx = obj.active_material_index
|
||||
next_index = current_idx + 1
|
||||
|
||||
logger.debug(f"Moving material {current_idx} down to position {next_index} for object {obj.name}")
|
||||
try:
|
||||
FnMaterial.swap_materials(obj, current_idx, next_index, reverse=True, swap_slots=True)
|
||||
except MaterialNotFoundError:
|
||||
logger.error(f"Materials not found for indices {current_idx} and {next_index}")
|
||||
self.report({"ERROR"}, "Materials not found")
|
||||
return {"CANCELLED"}
|
||||
obj.active_material_index = next_index
|
||||
@@ -257,26 +295,31 @@ class EdgePreviewSetup(Operator):
|
||||
default="CREATE",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
from ..core.model import FnModel
|
||||
|
||||
root = FnModel.find_root_object(context.active_object)
|
||||
if root is None:
|
||||
logger.error("No MMD model root found")
|
||||
self.report({"ERROR"}, "Select a MMD model")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if self.action == "CLEAN":
|
||||
logger.info(f"Cleaning toon edge for model: {root.name}")
|
||||
for obj in FnModel.iterate_mesh_objects(root):
|
||||
self.__clean_toon_edge(obj)
|
||||
else:
|
||||
from ..bpyutils import Props
|
||||
|
||||
logger.info(f"Creating toon edge for model: {root.name}")
|
||||
scale = 0.2 * getattr(root, Props.empty_display_size)
|
||||
counts = sum(self.__create_toon_edge(obj, scale) for obj in FnModel.iterate_mesh_objects(root))
|
||||
logger.info(f"Created {counts} toon edge(s)")
|
||||
self.report({"INFO"}, "Created %d toon edge(s)" % counts)
|
||||
return {"FINISHED"}
|
||||
|
||||
def __clean_toon_edge(self, obj):
|
||||
def __clean_toon_edge(self, obj: Object) -> None:
|
||||
logger.debug(f"Cleaning toon edge for object: {obj.name}")
|
||||
if "mmd_edge_preview" in obj.modifiers:
|
||||
obj.modifiers.remove(obj.modifiers["mmd_edge_preview"])
|
||||
|
||||
@@ -285,7 +328,8 @@ class EdgePreviewSetup(Operator):
|
||||
|
||||
FnMaterial.clean_materials(obj, can_remove=lambda m: m and m.name.startswith("mmd_edge."))
|
||||
|
||||
def __create_toon_edge(self, obj, scale=1.0):
|
||||
def __create_toon_edge(self, obj: Object, scale: float = 1.0) -> int:
|
||||
logger.debug(f"Creating toon edge for object: {obj.name} with scale {scale}")
|
||||
self.__clean_toon_edge(obj)
|
||||
materials = obj.data.materials
|
||||
material_offset = len(materials)
|
||||
@@ -310,10 +354,10 @@ class EdgePreviewSetup(Operator):
|
||||
mod.vertex_group = "mmd_edge_preview"
|
||||
return len(materials) - material_offset
|
||||
|
||||
def __create_edge_preview_group(self, obj):
|
||||
def __create_edge_preview_group(self, obj: Object) -> None:
|
||||
vertices, materials = obj.data.vertices, obj.data.materials
|
||||
weight_map = {i: m.mmd_material.edge_weight for i, m in enumerate(materials) if m}
|
||||
scale_map = {}
|
||||
scale_map: Dict[int, float] = {}
|
||||
vg_scale_index = obj.vertex_groups.find("mmd_edge_scale")
|
||||
if vg_scale_index >= 0:
|
||||
scale_map = {v.index: g.weight for v in vertices for g in v.groups if g.group == vg_scale_index}
|
||||
@@ -322,7 +366,7 @@ class EdgePreviewSetup(Operator):
|
||||
weight = scale_map.get(i, 1.0) * weight_map.get(mi, 1.0) * 0.02
|
||||
vg_edge_preview.add(index=[i], weight=weight, type="REPLACE")
|
||||
|
||||
def __get_edge_material(self, mat_name, edge_color, materials):
|
||||
def __get_edge_material(self, mat_name: str, edge_color: Tuple[float, float, float, float], materials: List[Material]) -> Material:
|
||||
if mat_name in materials:
|
||||
return materials[mat_name]
|
||||
mat = bpy.data.materials.get(mat_name, None)
|
||||
@@ -340,7 +384,7 @@ class EdgePreviewSetup(Operator):
|
||||
self.__make_shader(mat)
|
||||
return mat
|
||||
|
||||
def __make_shader(self, m):
|
||||
def __make_shader(self, m: Material) -> None:
|
||||
m.use_nodes = True
|
||||
nodes, links = m.node_tree.nodes, m.node_tree.links
|
||||
|
||||
@@ -361,7 +405,7 @@ class EdgePreviewSetup(Operator):
|
||||
node_shader.inputs["Color"].default_value = m.mmd_material.edge_color
|
||||
node_shader.inputs["Alpha"].default_value = m.mmd_material.edge_color[3]
|
||||
|
||||
def __get_edge_preview_shader(self):
|
||||
def __get_edge_preview_shader(self) -> bpy.types.NodeTree:
|
||||
group_name = "MMDEdgePreview"
|
||||
shader = bpy.data.node_groups.get(group_name, None) or bpy.data.node_groups.new(name=group_name, type="ShaderNodeTree")
|
||||
if len(shader.nodes):
|
||||
|
||||
+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"}
|
||||
|
||||
+61
-24
@@ -6,10 +6,12 @@
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
import bpy
|
||||
from typing import Optional, Set, Dict, Any, List, Tuple, Union
|
||||
|
||||
from ..bpyutils import FnContext
|
||||
from ..core.bone import FnBone, MigrationFnBone
|
||||
from ..core.model import FnModel, Model
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
class MorphSliderSetup(bpy.types.Operator):
|
||||
@@ -29,18 +31,22 @@ class MorphSliderSetup(bpy.types.Operator):
|
||||
default="CREATE",
|
||||
)
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object = context.active_object
|
||||
root_object = FnModel.find_root_object(active_object)
|
||||
assert root_object is not None
|
||||
|
||||
logger.debug(f"Executing MorphSliderSetup with type: {self.type}")
|
||||
with FnContext.temp_override_active_layer_collection(context, root_object):
|
||||
rig = Model(root_object)
|
||||
if self.type == "BIND":
|
||||
logger.info(f"Binding morph sliders for {root_object.name}")
|
||||
rig.morph_slider.bind()
|
||||
elif self.type == "UNBIND":
|
||||
logger.info(f"Unbinding morph sliders for {root_object.name}")
|
||||
rig.morph_slider.unbind()
|
||||
else:
|
||||
logger.info(f"Creating morph sliders for {root_object.name}")
|
||||
rig.morph_slider.create()
|
||||
FnContext.set_active_object(context, active_object)
|
||||
|
||||
@@ -53,10 +59,11 @@ class CleanRiggingObjects(bpy.types.Operator):
|
||||
bl_description = "Delete temporary physics objects of selected object and revert physics to default MMD state"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
root_object = FnModel.find_root_object(context.active_object)
|
||||
assert root_object is not None
|
||||
|
||||
logger.info(f"Cleaning rig for {root_object.name}")
|
||||
rig = Model(root_object)
|
||||
rig.clean()
|
||||
FnContext.set_active_object(context, root_object)
|
||||
@@ -86,9 +93,10 @@ class BuildRig(bpy.types.Operator):
|
||||
default=1e-06,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
root_object = FnModel.find_root_object(context.active_object)
|
||||
|
||||
logger.info(f"Building rig for {root_object.name} with non_collision_distance_scale={self.non_collision_distance_scale}, collision_margin={self.collision_margin}")
|
||||
with FnContext.temp_override_active_layer_collection(context, root_object):
|
||||
rig = Model(root_object)
|
||||
rig.build(self.non_collision_distance_scale, self.collision_margin)
|
||||
@@ -103,11 +111,14 @@ class CleanAdditionalTransformConstraints(bpy.types.Operator):
|
||||
bl_description = "Delete shadow bones of selected object and revert bones to default MMD state"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object = context.active_object
|
||||
root_object = FnModel.find_root_object(active_object)
|
||||
assert root_object is not None
|
||||
FnBone.clean_additional_transformation(FnModel.find_armature_object(root_object))
|
||||
|
||||
logger.info(f"Cleaning additional transform constraints for {root_object.name}")
|
||||
armature_object = FnModel.find_armature_object(root_object)
|
||||
FnBone.clean_additional_transformation(armature_object)
|
||||
FnContext.set_active_object(context, active_object)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -118,11 +129,12 @@ class ApplyAdditionalTransformConstraints(bpy.types.Operator):
|
||||
bl_description = "Translate appended bones of selected object for Blender"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object = context.active_object
|
||||
root_object = FnModel.find_root_object(active_object)
|
||||
assert root_object is not None
|
||||
|
||||
logger.info(f"Applying additional transform constraints for {root_object.name}")
|
||||
armature_object = FnModel.find_armature_object(root_object)
|
||||
assert armature_object is not None
|
||||
|
||||
@@ -149,12 +161,14 @@ class SetupBoneFixedAxes(bpy.types.Operator):
|
||||
default="LOAD",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
armature_object = context.active_object
|
||||
if not armature_object or armature_object.type != "ARMATURE":
|
||||
self.report({"ERROR"}, "Active object is not an armature object")
|
||||
logger.error("Setup Bone Fixed Axis failed: Active object is not an armature object")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Setting up bone fixed axes with type: {self.type}")
|
||||
if self.type == "APPLY":
|
||||
FnBone.apply_bone_fixed_axis(armature_object)
|
||||
FnBone.apply_additional_transformation(armature_object)
|
||||
@@ -180,12 +194,14 @@ class SetupBoneLocalAxes(bpy.types.Operator):
|
||||
default="LOAD",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
armature_object = context.active_object
|
||||
if not armature_object or armature_object.type != "ARMATURE":
|
||||
self.report({"ERROR"}, "Active object is not an armature object")
|
||||
logger.error("Setup Bone Local Axes failed: Active object is not an armature object")
|
||||
return {"CANCELLED"}
|
||||
|
||||
logger.info(f"Setting up bone local axes with type: {self.type}")
|
||||
if self.type == "APPLY":
|
||||
FnBone.apply_bone_local_axes(armature_object)
|
||||
FnBone.apply_additional_transformation(armature_object)
|
||||
@@ -207,16 +223,18 @@ class AddMissingVertexGroupsFromBones(bpy.types.Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
return FnModel.find_root_object(context.active_object) is not None
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object: bpy.types.Object = context.active_object
|
||||
root_object = FnModel.find_root_object(active_object)
|
||||
assert root_object is not None
|
||||
|
||||
logger.info(f"Adding missing vertex groups from bones for {root_object.name}, search_in_all_meshes={self.search_in_all_meshes}")
|
||||
bone_order_mesh_object = FnModel.find_bone_order_mesh_object(root_object)
|
||||
if bone_order_mesh_object is None:
|
||||
logger.error("Failed to find bone order mesh object")
|
||||
return {"CANCELLED"}
|
||||
|
||||
FnModel.add_missing_vertex_groups_from_bones(root_object, bone_order_mesh_object, self.search_in_all_meshes)
|
||||
@@ -246,12 +264,13 @@ class CreateMMDModelRoot(bpy.types.Operator):
|
||||
default=0.08,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
logger.info(f"Creating MMD model root object with name_j={self.name_j}, name_e={self.name_e}, scale={self.scale}")
|
||||
rig = Model.create(self.name_j, self.name_e, self.scale, add_root_bone=True)
|
||||
rig.initialDisplayFrames()
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: bpy.types.Context, event: Any) -> Set[str]:
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
@@ -305,15 +324,16 @@ class ConvertToMMDModel(bpy.types.Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
obj = context.active_object
|
||||
return obj and obj.type == "ARMATURE" and obj.mode != "EDIT"
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: bpy.types.Context, event: Any) -> Set[str]:
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
logger.info(f"Converting to MMD model with scale={self.scale}, convert_material_nodes={self.convert_material_nodes}")
|
||||
# TODO convert some basic MMD properties
|
||||
armature_object = context.active_object
|
||||
scale = self.scale
|
||||
@@ -321,29 +341,31 @@ class ConvertToMMDModel(bpy.types.Operator):
|
||||
|
||||
root_object = FnModel.find_root_object(armature_object)
|
||||
if root_object is None or root_object != armature_object.parent:
|
||||
logger.debug("Creating new MMD model")
|
||||
Model.create(model_name, model_name, scale, armature_object=armature_object)
|
||||
|
||||
self.__attach_meshes_to(armature_object, FnContext.get_scene_objects(context))
|
||||
self.__configure_rig(context, Model(armature_object.parent))
|
||||
return {"FINISHED"}
|
||||
|
||||
def __attach_meshes_to(self, armature_object: bpy.types.Object, objects: bpy.types.SceneObjects):
|
||||
def __is_child_of_armature(mesh):
|
||||
def __attach_meshes_to(self, armature_object: bpy.types.Object, objects: bpy.types.SceneObjects) -> None:
|
||||
def __is_child_of_armature(mesh: bpy.types.Object) -> bool:
|
||||
if mesh.parent is None:
|
||||
return False
|
||||
return mesh.parent == armature_object or __is_child_of_armature(mesh.parent)
|
||||
|
||||
def __is_using_armature(mesh):
|
||||
def __is_using_armature(mesh: bpy.types.Object) -> bool:
|
||||
for m in mesh.modifiers:
|
||||
if m.type == "ARMATURE" and m.object == armature_object:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __get_root(mesh):
|
||||
def __get_root(mesh: bpy.types.Object) -> bpy.types.Object:
|
||||
if mesh.parent is None:
|
||||
return mesh
|
||||
return __get_root(mesh.parent)
|
||||
|
||||
attached_count = 0
|
||||
for x in objects:
|
||||
if __is_using_armature(x) and not __is_child_of_armature(x):
|
||||
x_root = __get_root(x)
|
||||
@@ -351,27 +373,35 @@ class ConvertToMMDModel(bpy.types.Operator):
|
||||
x_root.parent_type = "OBJECT"
|
||||
x_root.parent = armature_object
|
||||
x_root.matrix_world = m
|
||||
attached_count += 1
|
||||
|
||||
logger.debug(f"Attached {attached_count} meshes to armature")
|
||||
|
||||
def __configure_rig(self, context: bpy.types.Context, mmd_model: Model):
|
||||
def __configure_rig(self, context: bpy.types.Context, mmd_model: Model) -> None:
|
||||
root_object = mmd_model.rootObject()
|
||||
armature_object = mmd_model.armature()
|
||||
mesh_objects = tuple(mmd_model.meshes())
|
||||
|
||||
logger.info(f"Configuring rig for {root_object.name} with {len(mesh_objects)} meshes")
|
||||
mmd_model.loadMorphs()
|
||||
|
||||
if self.middle_joint_bones_lock:
|
||||
vertex_groups = {g.name for mesh in mesh_objects for g in mesh.vertex_groups}
|
||||
locked_bones = 0
|
||||
for pose_bone in armature_object.pose.bones:
|
||||
if not pose_bone.parent:
|
||||
continue
|
||||
if not pose_bone.bone.use_connect and pose_bone.name not in vertex_groups:
|
||||
continue
|
||||
pose_bone.lock_location = (True, True, True)
|
||||
locked_bones += 1
|
||||
logger.debug(f"Locked {locked_bones} middle joint bones")
|
||||
|
||||
from ..core.material import FnMaterial
|
||||
|
||||
FnMaterial.set_nodes_are_readonly(not self.convert_material_nodes)
|
||||
try:
|
||||
converted_materials = 0
|
||||
for m in (x for mesh in mesh_objects for x in mesh.data.materials if x):
|
||||
FnMaterial.convert_to_mmd_material(m, context)
|
||||
mmd_material = m.mmd_material
|
||||
@@ -384,6 +414,8 @@ class ConvertToMMDModel(bpy.types.Operator):
|
||||
line_color = list(m.line_color)
|
||||
mmd_material.enabled_toon_edge = line_color[3] >= self.edge_threshold
|
||||
mmd_material.edge_color = line_color[:3] + [max(line_color[3], self.edge_alpha_min)]
|
||||
converted_materials += 1
|
||||
logger.debug(f"Converted {converted_materials} materials")
|
||||
finally:
|
||||
FnMaterial.set_nodes_are_readonly(False)
|
||||
from .display_item import DisplayItemQuickSetup
|
||||
@@ -400,16 +432,17 @@ class ResetObjectVisibility(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
active_object: bpy.types.Object = context.active_object
|
||||
return FnModel.find_root_object(active_object) is not None
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object: bpy.types.Object = context.active_object
|
||||
mmd_root_object = FnModel.find_root_object(active_object)
|
||||
assert mmd_root_object is not None
|
||||
mmd_root = mmd_root_object.mmd_root
|
||||
|
||||
logger.info(f"Resetting object visibility for {mmd_root_object.name}")
|
||||
mmd_root_object.hide_set(False)
|
||||
|
||||
rigid_group_object = FnModel.find_rigid_group_object(mmd_root_object)
|
||||
@@ -440,11 +473,12 @@ class AssembleAll(bpy.types.Operator):
|
||||
bl_label = "Assemble All"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object = context.active_object
|
||||
root_object = FnModel.find_root_object(active_object)
|
||||
assert root_object is not None
|
||||
|
||||
logger.info(f"Assembling all components for {root_object.name}")
|
||||
with FnContext.temp_override_active_layer_collection(context, root_object) as context:
|
||||
rig = Model(root_object)
|
||||
MigrationFnBone.fix_mmd_ik_limit_override(rig.armature())
|
||||
@@ -452,6 +486,7 @@ class AssembleAll(bpy.types.Operator):
|
||||
rig.build()
|
||||
rig.morph_slider.bind()
|
||||
|
||||
logger.debug("Binding SDEF weights")
|
||||
with context.temp_override(selected_objects=[active_object]):
|
||||
bpy.ops.mmd_tools.sdef_bind()
|
||||
root_object.mmd_root.use_property_driver = True
|
||||
@@ -466,13 +501,15 @@ class DisassembleAll(bpy.types.Operator):
|
||||
bl_label = "Disassemble All"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object = context.active_object
|
||||
root_object = FnModel.find_root_object(active_object)
|
||||
assert root_object is not None
|
||||
|
||||
logger.info(f"Disassembling all components for {root_object.name}")
|
||||
with FnContext.temp_override_active_layer_collection(context, root_object) as context:
|
||||
root_object.mmd_root.use_property_driver = False
|
||||
logger.debug("Unbinding SDEF weights")
|
||||
with context.temp_override(selected_objects=[active_object]):
|
||||
bpy.ops.mmd_tools.sdef_unbind()
|
||||
|
||||
|
||||
@@ -7,13 +7,17 @@
|
||||
|
||||
import itertools
|
||||
from operator import itemgetter
|
||||
from typing import Dict, List, Optional, Set
|
||||
from typing import Dict, List, Optional, Set, Tuple, Any
|
||||
|
||||
import bmesh
|
||||
import bpy
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from bpy.types import Context, Object, Operator, EditBone, Mesh, Armature
|
||||
|
||||
from ..bpyutils import FnContext
|
||||
from ..core.model import FnModel, Model
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
class MessageException(Exception):
|
||||
@@ -35,8 +39,8 @@ class ModelJoinByBonesOperator(bpy.types.Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context):
|
||||
active_object: Optional[bpy.types.Object] = context.active_object
|
||||
def poll(cls, context: Context) -> bool:
|
||||
active_object: Optional[Object] = context.active_object
|
||||
|
||||
if context.mode != "POSE":
|
||||
return False
|
||||
@@ -52,19 +56,22 @@ class ModelJoinByBonesOperator(bpy.types.Operator):
|
||||
|
||||
return len(context.selected_pose_bones) > 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: Context, event: Any) -> Set[str]:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
try:
|
||||
logger.info("Starting model join by bones operation")
|
||||
self.join(context)
|
||||
logger.info("Model join by bones completed successfully")
|
||||
except MessageException as ex:
|
||||
logger.error(f"Model join by bones failed: {str(ex)}")
|
||||
self.report(type={"ERROR"}, message=str(ex))
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def join(self, context: bpy.types.Context):
|
||||
def join(self, context: Context) -> None:
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
parent_root_object = FnModel.find_root_object(context.active_object)
|
||||
@@ -74,6 +81,7 @@ class ModelJoinByBonesOperator(bpy.types.Operator):
|
||||
if parent_root_object is None or len(child_root_objects) == 0:
|
||||
raise MessageException("No MMD Models selected")
|
||||
|
||||
logger.debug(f"Joining {len(child_root_objects)} models into parent model: {parent_root_object.name}")
|
||||
with FnContext.temp_override_active_layer_collection(context, parent_root_object):
|
||||
FnModel.join_models(parent_root_object, child_root_objects)
|
||||
|
||||
@@ -82,11 +90,12 @@ class ModelJoinByBonesOperator(bpy.types.Operator):
|
||||
|
||||
# Connect child bones
|
||||
if self.join_type == "CONNECTED":
|
||||
parent_edit_bone: bpy.types.EditBone = context.active_bone
|
||||
child_edit_bones: Set[bpy.types.EditBone] = set(context.selected_bones)
|
||||
parent_edit_bone: EditBone = context.active_bone
|
||||
child_edit_bones: Set[EditBone] = set(context.selected_bones)
|
||||
child_edit_bones.remove(parent_edit_bone)
|
||||
|
||||
child_edit_bone: bpy.types.EditBone
|
||||
logger.debug(f"Connecting {len(child_edit_bones)} child bones to parent bone: {parent_edit_bone.name}")
|
||||
child_edit_bone: EditBone
|
||||
for child_edit_bone in child_edit_bones:
|
||||
child_edit_bone.use_connect = True
|
||||
|
||||
@@ -111,8 +120,8 @@ class ModelSeparateByBonesOperator(bpy.types.Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context):
|
||||
active_object: Optional[bpy.types.Object] = context.active_object
|
||||
def poll(cls, context: Context) -> bool:
|
||||
active_object: Optional[Object] = context.active_object
|
||||
|
||||
if context.mode != "POSE":
|
||||
return False
|
||||
@@ -128,56 +137,70 @@ class ModelSeparateByBonesOperator(bpy.types.Operator):
|
||||
|
||||
return len(context.selected_pose_bones) > 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: Context, event: Any) -> Set[str]:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
try:
|
||||
logger.info("Starting model separate by bones operation")
|
||||
self.separate(context)
|
||||
logger.info("Model separate by bones completed successfully")
|
||||
except MessageException as ex:
|
||||
logger.error(f"Model separate by bones failed: {str(ex)}")
|
||||
self.report(type={"ERROR"}, message=str(ex))
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def separate(self, context: bpy.types.Context):
|
||||
def separate(self, context: Context) -> None:
|
||||
weight_threshold: float = self.weight_threshold
|
||||
mmd_scale = 0.08
|
||||
|
||||
target_armature_object: bpy.types.Object = context.active_object
|
||||
target_armature_object: Object = context.active_object
|
||||
logger.debug(f"Target armature: {target_armature_object.name}")
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
root_bones: Set[bpy.types.EditBone] = set(context.selected_bones)
|
||||
root_bones: Set[EditBone] = set(context.selected_bones)
|
||||
logger.debug(f"Selected root bones: {len(root_bones)}")
|
||||
|
||||
if self.include_descendant_bones:
|
||||
logger.debug("Including descendant bones")
|
||||
for edit_bone in root_bones:
|
||||
with context.temp_override(active_bone=edit_bone):
|
||||
bpy.ops.armature.select_similar(type="CHILDREN", threshold=0.1)
|
||||
|
||||
separate_bones: Dict[str, bpy.types.EditBone] = {b.name: b for b in context.selected_bones}
|
||||
deform_bones: Dict[str, bpy.types.EditBone] = {b.name: b for b in target_armature_object.data.edit_bones if b.use_deform}
|
||||
separate_bones: Dict[str, EditBone] = {b.name: b for b in context.selected_bones}
|
||||
deform_bones: Dict[str, EditBone] = {b.name: b for b in target_armature_object.data.edit_bones if b.use_deform}
|
||||
logger.debug(f"Total bones to separate: {len(separate_bones)}")
|
||||
|
||||
mmd_root_object: bpy.types.Object = FnModel.find_root_object(context.active_object)
|
||||
mmd_root_object: Object = FnModel.find_root_object(context.active_object)
|
||||
mmd_model = Model(mmd_root_object)
|
||||
mmd_model_mesh_objects: List[bpy.types.Object] = list(mmd_model.meshes())
|
||||
mmd_model_mesh_objects: List[Object] = list(mmd_model.meshes())
|
||||
logger.debug(f"Found {len(mmd_model_mesh_objects)} mesh objects in model")
|
||||
|
||||
mmd_model_mesh_objects = list(self.select_weighted_vertices(mmd_model_mesh_objects, separate_bones, deform_bones, weight_threshold).keys())
|
||||
mesh_selection_result = self.select_weighted_vertices(mmd_model_mesh_objects, separate_bones, deform_bones, weight_threshold)
|
||||
mmd_model_mesh_objects = list(mesh_selection_result.keys())
|
||||
logger.debug(f"Selected {len(mmd_model_mesh_objects)} mesh objects with weighted vertices")
|
||||
|
||||
# separate armature bones
|
||||
separate_armature_object: Optional[bpy.types.Object]
|
||||
separate_armature_object: Optional[Object]
|
||||
if self.separate_armature:
|
||||
logger.debug("Separating armature")
|
||||
target_armature_object.select_set(True)
|
||||
bpy.ops.armature.separate()
|
||||
separate_armature_object = next(iter([a for a in context.selected_objects if a != target_armature_object]), None)
|
||||
if separate_armature_object:
|
||||
logger.debug(f"Created separate armature: {separate_armature_object.name}")
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
# collect separate rigid bodies
|
||||
separate_rigid_bodies: Set[bpy.types.Object] = {rigid_body_object for rigid_body_object in mmd_model.rigidBodies() if rigid_body_object.mmd_rigid.bone in separate_bones}
|
||||
separate_rigid_bodies: Set[Object] = {rigid_body_object for rigid_body_object in mmd_model.rigidBodies() if rigid_body_object.mmd_rigid.bone in separate_bones}
|
||||
logger.debug(f"Found {len(separate_rigid_bodies)} rigid bodies to separate")
|
||||
|
||||
boundary_joint_owner_condition = any if self.boundary_joint_owner == "DESTINATION" else all
|
||||
|
||||
# collect separate joints
|
||||
separate_joints: Set[bpy.types.Object] = {
|
||||
separate_joints: Set[Object] = {
|
||||
joint_object
|
||||
for joint_object in mmd_model.joints()
|
||||
if boundary_joint_owner_condition(
|
||||
@@ -187,35 +210,43 @@ class ModelSeparateByBonesOperator(bpy.types.Operator):
|
||||
]
|
||||
)
|
||||
}
|
||||
logger.debug(f"Found {len(separate_joints)} joints to separate")
|
||||
|
||||
separate_mesh_objects: Set[bpy.types.Object]
|
||||
model2separate_mesh_objects: Dict[bpy.types.Object, bpy.types.Object]
|
||||
separate_mesh_objects: Set[Object]
|
||||
model2separate_mesh_objects: Dict[Object, Object]
|
||||
if len(mmd_model_mesh_objects) == 0:
|
||||
logger.debug("No mesh objects to separate")
|
||||
separate_mesh_objects = set()
|
||||
model2separate_mesh_objects = dict()
|
||||
else:
|
||||
# select meshes
|
||||
obj: bpy.types.Object
|
||||
logger.debug("Selecting meshes for separation")
|
||||
obj: Object
|
||||
for obj in context.view_layer.objects:
|
||||
obj.select_set(obj in mmd_model_mesh_objects)
|
||||
context.view_layer.objects.active = mmd_model_mesh_objects[0]
|
||||
|
||||
# separate mesh by selected vertices
|
||||
logger.debug("Separating meshes by selected vertices")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.mesh.separate(type="SELECTED")
|
||||
separate_mesh_objects: List[bpy.types.Object] = [m for m in context.selected_objects if m.type == "MESH" and m not in mmd_model_mesh_objects]
|
||||
separate_mesh_objects: List[Object] = [m for m in context.selected_objects if m.type == "MESH" and m not in mmd_model_mesh_objects]
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
logger.debug(f"Created {len(separate_mesh_objects)} separate mesh objects")
|
||||
|
||||
model2separate_mesh_objects = dict(zip(mmd_model_mesh_objects, separate_mesh_objects))
|
||||
|
||||
logger.debug(f"Creating new model with scale {mmd_scale}")
|
||||
separate_model: Model = Model.create(mmd_root_object.mmd_root.name, mmd_root_object.mmd_root.name_e, mmd_scale, add_root_bone=False)
|
||||
|
||||
separate_model.initialDisplayFrames()
|
||||
separate_root_object = separate_model.rootObject()
|
||||
separate_root_object.matrix_world = mmd_root_object.matrix_world
|
||||
separate_model_armature_object = separate_model.armature()
|
||||
logger.debug(f"Created separate model with root: {separate_root_object.name}")
|
||||
|
||||
if self.separate_armature:
|
||||
logger.debug("Joining separate armature to new model")
|
||||
with context.temp_override(
|
||||
active_object=separate_model_armature_object,
|
||||
selected_editable_objects=[separate_model_armature_object, separate_armature_object],
|
||||
@@ -223,6 +254,7 @@ class ModelSeparateByBonesOperator(bpy.types.Operator):
|
||||
bpy.ops.object.join()
|
||||
|
||||
# add mesh
|
||||
logger.debug("Parenting separate mesh objects to new model")
|
||||
with context.temp_override(
|
||||
object=separate_model_armature_object,
|
||||
selected_editable_objects=[separate_model_armature_object, *separate_mesh_objects],
|
||||
@@ -230,19 +262,23 @@ class ModelSeparateByBonesOperator(bpy.types.Operator):
|
||||
bpy.ops.object.parent_set(type="OBJECT", keep_transform=True)
|
||||
|
||||
# replace mesh armature modifier.object
|
||||
logger.debug("Updating armature modifiers on separate meshes")
|
||||
for separate_mesh in separate_mesh_objects:
|
||||
armature_modifier: Optional[bpy.types.ArmatureModifier] = next(iter([m for m in separate_mesh.modifiers if m.type == "ARMATURE"]), None)
|
||||
if armature_modifier is None:
|
||||
logger.debug(f"Creating new armature modifier for {separate_mesh.name}")
|
||||
armature_modifier: bpy.types.ArmatureModifier = separate_mesh.modifiers.new("mmd_bone_order_override", "ARMATURE")
|
||||
|
||||
armature_modifier.object = separate_model_armature_object
|
||||
|
||||
logger.debug("Parenting rigid bodies to new model")
|
||||
with context.temp_override(
|
||||
object=separate_model.rigidGroupObject(),
|
||||
selected_editable_objects=[separate_model.rigidGroupObject(), *separate_rigid_bodies],
|
||||
):
|
||||
bpy.ops.object.parent_set(type="OBJECT", keep_transform=True)
|
||||
|
||||
logger.debug("Parenting joints to new model")
|
||||
with context.temp_override(
|
||||
object=separate_model.jointGroupObject(),
|
||||
selected_editable_objects=[separate_model.jointGroupObject(), *separate_joints],
|
||||
@@ -257,10 +293,12 @@ class ModelSeparateByBonesOperator(bpy.types.Operator):
|
||||
assert separate_layer_collection is not None
|
||||
|
||||
if mmd_layer_collection.name != separate_layer_collection.name:
|
||||
logger.debug(f"Moving objects from collection {mmd_layer_collection.name} to {separate_layer_collection.name}")
|
||||
for separate_object in itertools.chain(separate_mesh_objects, separate_rigid_bodies, separate_joints):
|
||||
separate_layer_collection.collection.objects.link(separate_object)
|
||||
mmd_layer_collection.collection.objects.unlink(separate_object)
|
||||
|
||||
logger.debug("Copying MMD root properties")
|
||||
FnModel.copy_mmd_root(
|
||||
separate_root_object,
|
||||
mmd_root_object,
|
||||
@@ -271,13 +309,15 @@ class ModelSeparateByBonesOperator(bpy.types.Operator):
|
||||
},
|
||||
)
|
||||
|
||||
def select_weighted_vertices(self, mmd_model_mesh_objects: List[bpy.types.Object], separate_bones: Dict[str, bpy.types.EditBone], deform_bones: Dict[str, bpy.types.EditBone], weight_threshold: float) -> Dict[bpy.types.Object, int]:
|
||||
mesh2selected_vertex_count: Dict[bpy.types.Object, int] = dict()
|
||||
def select_weighted_vertices(self, mmd_model_mesh_objects: List[Object], separate_bones: Dict[str, EditBone], deform_bones: Dict[str, EditBone], weight_threshold: float) -> Dict[Object, int]:
|
||||
"""Select vertices weighted to the bones to be separated"""
|
||||
logger.debug(f"Selecting vertices weighted to {len(separate_bones)} bones with threshold {weight_threshold}")
|
||||
mesh2selected_vertex_count: Dict[Object, int] = dict()
|
||||
target_bmesh: bmesh.types.BMesh = bmesh.new()
|
||||
for mesh_object in mmd_model_mesh_objects:
|
||||
vertex_groups: bpy.types.VertexGroups = mesh_object.vertex_groups
|
||||
|
||||
mesh: bpy.types.Mesh = mesh_object.data
|
||||
mesh: Mesh = mesh_object.data
|
||||
target_bmesh.from_mesh(mesh, face_normals=False)
|
||||
target_bmesh.select_mode |= {"VERT"}
|
||||
deform_layer = target_bmesh.verts.layers.deform.verify()
|
||||
@@ -304,6 +344,7 @@ class ModelSeparateByBonesOperator(bpy.types.Operator):
|
||||
vert.select_set(True)
|
||||
|
||||
if selected_vertex_count > 0:
|
||||
logger.debug(f"Selected {selected_vertex_count} vertices in mesh {mesh_object.name}")
|
||||
mesh2selected_vertex_count[mesh_object] = selected_vertex_count
|
||||
target_bmesh.select_flush_mode()
|
||||
target_bmesh.to_mesh(mesh)
|
||||
|
||||
+59
-32
@@ -5,7 +5,7 @@
|
||||
# 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 Optional, cast
|
||||
from typing import Optional, cast, List, Dict, Any, Set, Tuple, Union
|
||||
|
||||
import bpy
|
||||
from mathutils import Quaternion, Vector
|
||||
@@ -16,10 +16,11 @@ from ..core.exceptions import MaterialNotFoundError
|
||||
from ..core.material import FnMaterial
|
||||
from ..core.morph import FnMorph
|
||||
from ..utils import ItemMoveOp, ItemOp
|
||||
from ....logging_setup import logger
|
||||
|
||||
|
||||
# Util functions
|
||||
def divide_vector_components(vec1, vec2):
|
||||
def divide_vector_components(vec1: List[float], vec2: List[float]) -> List[float]:
|
||||
if len(vec1) != len(vec2):
|
||||
raise ValueError("Vectors should have the same number of components")
|
||||
result = []
|
||||
@@ -33,7 +34,7 @@ def divide_vector_components(vec1, vec2):
|
||||
return result
|
||||
|
||||
|
||||
def multiply_vector_components(vec1, vec2):
|
||||
def multiply_vector_components(vec1: List[float], vec2: List[float]) -> List[float]:
|
||||
if len(vec1) != len(vec2):
|
||||
raise ValueError("Vectors should have the same number of components")
|
||||
result = []
|
||||
@@ -42,7 +43,7 @@ def multiply_vector_components(vec1, vec2):
|
||||
return result
|
||||
|
||||
|
||||
def special_division(n1, n2):
|
||||
def special_division(n1: float, n2: float) -> float:
|
||||
"""This function returns 0 in case of 0/0. If non-zero divided by zero case is found, an Exception is raised"""
|
||||
if n2 == 0:
|
||||
if n1 == 0:
|
||||
@@ -58,7 +59,7 @@ class AddMorph(bpy.types.Operator):
|
||||
bl_description = "Add a morph item to active morph list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
@@ -68,6 +69,7 @@ class AddMorph(bpy.types.Operator):
|
||||
morph.name = "New Morph"
|
||||
if morph_type.startswith("uv"):
|
||||
morph.data_type = "VERTEX_GROUP"
|
||||
logger.debug(f"Added new morph of type {morph_type}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -84,7 +86,7 @@ class RemoveMorph(bpy.types.Operator):
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
@@ -99,9 +101,11 @@ class RemoveMorph(bpy.types.Operator):
|
||||
if self.all:
|
||||
morphs.clear()
|
||||
mmd_root.active_morph = 0
|
||||
logger.debug(f"Removed all morphs of type {morph_type}")
|
||||
else:
|
||||
morphs.remove(mmd_root.active_morph)
|
||||
mmd_root.active_morph = max(0, mmd_root.active_morph - 1)
|
||||
logger.debug(f"Removed morph at index {mmd_root.active_morph} of type {morph_type}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -111,7 +115,7 @@ class MoveMorph(bpy.types.Operator, ItemMoveOp):
|
||||
bl_description = "Move active morph item up/down in the list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
@@ -120,6 +124,7 @@ class MoveMorph(bpy.types.Operator, ItemMoveOp):
|
||||
mmd_root.active_morph,
|
||||
self.type,
|
||||
)
|
||||
logger.debug(f"Moved morph to index {mmd_root.active_morph}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -129,7 +134,7 @@ class CopyMorph(bpy.types.Operator):
|
||||
bl_description = "Make a copy of active morph in the list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -156,6 +161,7 @@ class CopyMorph(bpy.types.Operator):
|
||||
for k, v in morph.items():
|
||||
morph_new[k] = v if k != "name" else name_tmp
|
||||
morph_new.name = name_orig + "_copy" # trigger name check
|
||||
logger.debug(f"Copied morph {name_orig} to {morph_new.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -165,17 +171,17 @@ class OverwriteBoneMorphsFromActionPose(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
root = FnModel.find_root_object(context.active_object)
|
||||
if root is None:
|
||||
return False
|
||||
|
||||
return root.mmd_root.active_morph_type == "bone_morphs"
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
root = FnModel.find_root_object(context.active_object)
|
||||
FnMorph.overwrite_bone_morphs_from_action_pose(FnModel.find_armature_object(root))
|
||||
|
||||
logger.info("Overwrote bone morphs from active action pose")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -185,7 +191,7 @@ class AddMorphOffset(bpy.types.Operator):
|
||||
bl_description = "Add a morph offset item to the list"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
@@ -210,6 +216,7 @@ class AddMorphOffset(bpy.types.Operator):
|
||||
item.location = pose_bone.location
|
||||
item.rotation = pose_bone.rotation_quaternion
|
||||
|
||||
logger.debug(f"Added morph offset to {morph_type}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -226,7 +233,7 @@ class RemoveMorphOffset(bpy.types.Operator):
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -243,17 +250,21 @@ class RemoveMorphOffset(bpy.types.Operator):
|
||||
if morph_type.startswith("vertex"):
|
||||
for obj in FnModel.iterate_mesh_objects(root):
|
||||
FnMorph.remove_shape_key(obj, morph.name)
|
||||
logger.debug(f"Removed all vertex morph offsets for {morph.name}")
|
||||
return {"FINISHED"}
|
||||
elif morph_type.startswith("uv"):
|
||||
if morph.data_type == "VERTEX_GROUP":
|
||||
for obj in FnModel.iterate_mesh_objects(root):
|
||||
FnMorph.store_uv_morph_data(obj, morph)
|
||||
logger.debug(f"Removed all UV morph offsets for {morph.name}")
|
||||
return {"FINISHED"}
|
||||
morph.data.clear()
|
||||
morph.active_data = 0
|
||||
logger.debug(f"Cleared all morph offsets for {morph.name}")
|
||||
else:
|
||||
morph.data.remove(morph.active_data)
|
||||
morph.active_data = max(0, morph.active_data - 1)
|
||||
logger.debug(f"Removed morph offset at index {morph.active_data}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -269,7 +280,7 @@ class InitMaterialOffset(bpy.types.Operator):
|
||||
default=0,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
@@ -281,6 +292,7 @@ class InitMaterialOffset(bpy.types.Operator):
|
||||
mat_data.specular_color = mat_data.ambient_color = (val,) * 3
|
||||
mat_data.shininess = mat_data.edge_weight = val
|
||||
mat_data.texture_factor = mat_data.toon_texture_factor = mat_data.sphere_texture_factor = (val,) * 4
|
||||
logger.debug(f"Initialized material offset with value {val}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -290,7 +302,7 @@ class ApplyMaterialOffset(bpy.types.Operator):
|
||||
bl_description = "Calculates the offsets and apply them, then the temporary material is removed"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
@@ -328,6 +340,7 @@ class ApplyMaterialOffset(bpy.types.Operator):
|
||||
|
||||
except ZeroDivisionError:
|
||||
mat_data.offset_type = "ADD" # If there is any 0 division we automatically switch it to type ADD
|
||||
logger.warning("Zero division detected, switching to ADD offset type")
|
||||
except ValueError:
|
||||
self.report({"ERROR"}, "An unexpected error happened")
|
||||
# We should stop on our tracks and re-raise the exception
|
||||
@@ -345,6 +358,7 @@ class ApplyMaterialOffset(bpy.types.Operator):
|
||||
mat_data.edge_weight = work_mmd_mat.edge_weight - base_mmd_mat.edge_weight
|
||||
|
||||
FnMaterial.clean_materials(meshObj, can_remove=lambda m: m == work_mat)
|
||||
logger.info(f"Applied material offset for {mat_data.material}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -354,7 +368,7 @@ class CreateWorkMaterial(bpy.types.Operator):
|
||||
bl_description = "Creates a temporary material to edit this offset"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
@@ -413,6 +427,7 @@ class CreateWorkMaterial(bpy.types.Operator):
|
||||
work_mmd_mat.edge_color = list(edge_offset)
|
||||
work_mmd_mat.edge_weight += mat_data.edge_weight
|
||||
|
||||
logger.info(f"Created work material {work_mat_name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -422,13 +437,13 @@ class ClearTempMaterials(bpy.types.Operator):
|
||||
bl_description = "Clears all the temporary materials"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
for meshObj in FnModel.iterate_mesh_objects(root):
|
||||
|
||||
def __pre_remove(m):
|
||||
def __pre_remove(m: Optional[bpy.types.Material]) -> bool:
|
||||
if m and "_temp" in m.name:
|
||||
base_mat_name = m.name.split("_temp")[0]
|
||||
try:
|
||||
@@ -439,6 +454,7 @@ class ClearTempMaterials(bpy.types.Operator):
|
||||
return False
|
||||
|
||||
FnMaterial.clean_materials(meshObj, can_remove=__pre_remove)
|
||||
logger.info("Cleared all temporary materials")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -448,7 +464,7 @@ class ViewBoneMorph(bpy.types.Operator):
|
||||
bl_description = "View the result of active bone morph"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -463,6 +479,7 @@ class ViewBoneMorph(bpy.types.Operator):
|
||||
mtx = (p_bone.matrix_basis.to_3x3() @ Quaternion(*morph_data.rotation.to_axis_angle()).to_matrix()).to_4x4()
|
||||
mtx.translation = p_bone.location + morph_data.location
|
||||
p_bone.matrix_basis = mtx
|
||||
logger.info(f"Viewing bone morph: {morph.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -472,13 +489,14 @@ class ClearBoneMorphView(bpy.types.Operator):
|
||||
bl_description = "Reset transforms of all bones to their default values"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
armature = FnModel.find_armature_object(root)
|
||||
for p_bone in armature.pose.bones:
|
||||
p_bone.matrix_basis.identity()
|
||||
logger.info("Cleared bone morph view")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -488,7 +506,7 @@ class ApplyBoneMorph(bpy.types.Operator):
|
||||
bl_description = "Apply current pose to active bone morph"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -506,6 +524,7 @@ class ApplyBoneMorph(bpy.types.Operator):
|
||||
p_bone.bone.select = True
|
||||
else:
|
||||
p_bone.bone.select = False
|
||||
logger.info(f"Applied current pose to bone morph: {morph.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -515,7 +534,7 @@ class SelectRelatedBone(bpy.types.Operator):
|
||||
bl_description = "Select the bone assigned to this offset in the armature"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -524,6 +543,7 @@ class SelectRelatedBone(bpy.types.Operator):
|
||||
morph = mmd_root.bone_morphs[mmd_root.active_morph]
|
||||
morph_data = morph.data[morph.active_data]
|
||||
utils.selectSingleBone(context, armature, morph_data.bone)
|
||||
logger.debug(f"Selected bone: {morph_data.bone}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -533,7 +553,7 @@ class EditBoneOffset(bpy.types.Operator):
|
||||
bl_description = "Applies the location and rotation of this offset to the bone"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -546,6 +566,7 @@ class EditBoneOffset(bpy.types.Operator):
|
||||
mtx.translation = morph_data.location
|
||||
p_bone.matrix_basis = mtx
|
||||
utils.selectSingleBone(context, armature, p_bone.name)
|
||||
logger.debug(f"Edited bone offset for {p_bone.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -555,7 +576,7 @@ class ApplyBoneOffset(bpy.types.Operator):
|
||||
bl_description = "Stores the current bone location and rotation into this offset"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -567,6 +588,7 @@ class ApplyBoneOffset(bpy.types.Operator):
|
||||
p_bone = armature.pose.bones[morph_data.bone]
|
||||
morph_data.location = p_bone.location
|
||||
morph_data.rotation = p_bone.rotation_quaternion if p_bone.rotation_mode == "QUATERNION" else p_bone.matrix_basis.to_quaternion()
|
||||
logger.debug(f"Applied bone offset for {p_bone.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -576,7 +598,7 @@ class ViewUVMorph(bpy.types.Operator):
|
||||
bl_description = "View the result of active UV morph on current mesh object"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -627,6 +649,7 @@ class ViewUVMorph(bpy.types.Operator):
|
||||
uv_tex.active_render = True
|
||||
meshObj.hide_set(False)
|
||||
meshObj.select_set(selected)
|
||||
logger.info(f"Viewing UV morph: {morph.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -636,7 +659,7 @@ class ClearUVMorphView(bpy.types.Operator):
|
||||
bl_description = "Clear all temporary data of UV morphs"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
assert root is not None
|
||||
@@ -664,6 +687,7 @@ class ClearUVMorphView(bpy.types.Operator):
|
||||
for act in bpy.data.actions:
|
||||
if act.name.startswith("__uv.") and act.users < 1:
|
||||
bpy.data.actions.remove(act)
|
||||
logger.info("Cleared UV morph view")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -674,14 +698,14 @@ class EditUVMorph(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
obj = context.active_object
|
||||
if obj.type != "MESH":
|
||||
return False
|
||||
active_uv_layer = obj.data.uv_layers.active
|
||||
return active_uv_layer and active_uv_layer.name.startswith("__uv.")
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
meshObj = obj
|
||||
|
||||
@@ -704,6 +728,7 @@ class EditUVMorph(bpy.types.Operator):
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
meshObj.select_set(selected)
|
||||
logger.info("Editing UV morph")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -714,14 +739,14 @@ class ApplyUVMorph(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
obj = context.active_object
|
||||
if obj.type != "MESH":
|
||||
return False
|
||||
active_uv_layer = obj.data.uv_layers.active
|
||||
return active_uv_layer and active_uv_layer.name.startswith("__uv.")
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
mmd_root = root.mmd_root
|
||||
@@ -756,6 +781,7 @@ class ApplyUVMorph(bpy.types.Operator):
|
||||
morph.data_type = "VERTEX_GROUP"
|
||||
|
||||
meshObj.select_set(selected)
|
||||
logger.info(f"Applied UV morph: {morph.name}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -766,11 +792,12 @@ class CleanDuplicatedMaterialMorphs(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
return FnModel.find_root_object(context.active_object) is not None
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
mmd_root_object = FnModel.find_root_object(context.active_object)
|
||||
FnMorph.clean_duplicated_material_morphs(mmd_root_object)
|
||||
logger.info("Cleaned duplicated material morphs")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
import math
|
||||
from typing import Dict, Optional, Tuple, cast
|
||||
from typing import Dict, Optional, Tuple, cast, Set, List, Any, Union, Generator
|
||||
|
||||
import bpy
|
||||
from mathutils import Euler, Vector
|
||||
@@ -16,6 +16,7 @@ from ..bpyutils import FnContext, Props
|
||||
from ..core import rigid_body
|
||||
from ..core.model import FnModel, Model
|
||||
from ..core.rigid_body import FnRigidBody
|
||||
from ...logging_setup import logger
|
||||
|
||||
|
||||
class SelectRigidBody(bpy.types.Operator):
|
||||
@@ -43,15 +44,15 @@ class SelectRigidBody(bpy.types.Operator):
|
||||
default=False,
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: bpy.types.Context, event: Any) -> Set[str]:
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
return FnModel.is_rigid_body_object(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
if root is None:
|
||||
@@ -173,7 +174,7 @@ class AddRigidBody(bpy.types.Operator):
|
||||
default=0.1,
|
||||
)
|
||||
|
||||
def __add_rigid_body(self, context: bpy.types.Context, root_object: bpy.types.Object, pose_bone: Optional[bpy.types.PoseBone] = None):
|
||||
def __add_rigid_body(self, context: bpy.types.Context, root_object: bpy.types.Object, pose_bone: Optional[bpy.types.PoseBone] = None) -> bpy.types.Object:
|
||||
name_j: str = self.name_j
|
||||
name_e: str = self.name_e
|
||||
size = self.size.copy()
|
||||
@@ -226,7 +227,7 @@ class AddRigidBody(bpy.types.Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
root_object = FnModel.find_root_object(context.active_object)
|
||||
if root_object is None:
|
||||
return False
|
||||
@@ -237,7 +238,7 @@ class AddRigidBody(bpy.types.Operator):
|
||||
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object = context.active_object
|
||||
|
||||
root_object = cast(bpy.types.Object, FnModel.find_root_object(active_object))
|
||||
@@ -254,15 +255,17 @@ class AddRigidBody(bpy.types.Operator):
|
||||
|
||||
armature_object.select_set(False)
|
||||
if len(selected_pose_bones) > 0:
|
||||
logger.info(f"Adding rigid bodies to {len(selected_pose_bones)} selected bones")
|
||||
for pose_bone in selected_pose_bones:
|
||||
rigid = self.__add_rigid_body(context, root_object, pose_bone)
|
||||
rigid.select_set(True)
|
||||
else:
|
||||
logger.info("Adding a single rigid body without bone attachment")
|
||||
rigid = self.__add_rigid_body(context, root_object)
|
||||
rigid.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: bpy.types.Context, event: Any) -> Set[str]:
|
||||
no_bone = True
|
||||
if context.selected_bones and len(context.selected_bones) > 0:
|
||||
no_bone = False
|
||||
@@ -288,12 +291,13 @@ class RemoveRigidBody(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
return FnModel.is_rigid_body_object(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
logger.info(f"Removing rigid body: {obj.name}")
|
||||
utils.selectAObject(obj) # ensure this is the only one object select
|
||||
bpy.ops.object.delete(use_global=True)
|
||||
if root:
|
||||
@@ -306,7 +310,8 @@ class RigidBodyBake(bpy.types.Operator):
|
||||
bl_label = "Bake"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
logger.info("Baking rigid body simulation")
|
||||
with context.temp_override(scene=context.scene, point_cache=context.scene.rigidbody_world.point_cache):
|
||||
bpy.ops.ptcache.bake("INVOKE_DEFAULT", bake=True)
|
||||
|
||||
@@ -318,7 +323,8 @@ class RigidBodyDeleteBake(bpy.types.Operator):
|
||||
bl_label = "Delete Bake"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
logger.info("Deleting rigid body simulation bake")
|
||||
with context.temp_override(scene=context.scene, point_cache=context.scene.rigidbody_world.point_cache):
|
||||
bpy.ops.ptcache.free_bake("INVOKE_DEFAULT")
|
||||
|
||||
@@ -381,7 +387,7 @@ class AddJoint(bpy.types.Operator):
|
||||
min=0,
|
||||
)
|
||||
|
||||
def __enumerate_rigid_pair(self, bone_map: Dict[bpy.types.Object, Optional[bpy.types.Bone]]):
|
||||
def __enumerate_rigid_pair(self, bone_map: Dict[bpy.types.Object, Optional[bpy.types.Bone]]) -> Generator[Tuple[bpy.types.Object, bpy.types.Object], None, None]:
|
||||
obj_seq = tuple(bone_map.keys())
|
||||
for rigid_a, bone_a in bone_map.items():
|
||||
for rigid_b, bone_b in bone_map.items():
|
||||
@@ -394,7 +400,7 @@ class AddJoint(bpy.types.Operator):
|
||||
else:
|
||||
yield obj_seq
|
||||
|
||||
def __add_joint(self, context: bpy.types.Context, root_object: bpy.types.Object, rigid_pair: Tuple[bpy.types.Object, bpy.types.Object], bone_map):
|
||||
def __add_joint(self, context: bpy.types.Context, root_object: bpy.types.Object, rigid_pair: Tuple[bpy.types.Object, bpy.types.Object], bone_map: Dict[bpy.types.Object, Optional[bpy.types.Bone]]) -> bpy.types.Object:
|
||||
loc: Optional[Vector] = None
|
||||
rot = Euler((0.0, 0.0, 0.0))
|
||||
rigid_a, rigid_b = rigid_pair
|
||||
@@ -432,7 +438,7 @@ class AddJoint(bpy.types.Operator):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
root_object = FnModel.find_root_object(context.active_object)
|
||||
if root_object is None:
|
||||
return False
|
||||
@@ -443,7 +449,7 @@ class AddJoint(bpy.types.Operator):
|
||||
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
active_object = context.active_object
|
||||
root_object = cast(bpy.types.Object, FnModel.find_root_object(active_object))
|
||||
armature_object = cast(bpy.types.Object, FnModel.find_armature_object(root_object))
|
||||
@@ -456,15 +462,19 @@ class AddJoint(bpy.types.Operator):
|
||||
|
||||
FnContext.select_single_object(context, root_object).select_set(False)
|
||||
if context.scene.rigidbody_world is None:
|
||||
logger.info("Creating rigid body world")
|
||||
bpy.ops.rigidbody.world_add()
|
||||
|
||||
joint_count = 0
|
||||
for pair in self.__enumerate_rigid_pair(bone_map):
|
||||
joint = self.__add_joint(context, root_object, pair, bone_map)
|
||||
joint.select_set(True)
|
||||
|
||||
joint_count += 1
|
||||
|
||||
logger.info(f"Added {joint_count} joints between rigid bodies")
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: bpy.types.Context, event: Any) -> Set[str]:
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
@@ -476,12 +486,13 @@ class RemoveJoint(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
return FnModel.is_joint_object(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
logger.info(f"Removing joint: {obj.name}")
|
||||
utils.selectAObject(obj) # ensure this is the only one object select
|
||||
bpy.ops.object.delete(use_global=True)
|
||||
if root:
|
||||
@@ -496,7 +507,7 @@ class UpdateRigidBodyWorld(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@staticmethod
|
||||
def __get_rigid_body_world_objects():
|
||||
def __get_rigid_body_world_objects() -> Tuple[bpy.types.Collection, bpy.types.Collection]:
|
||||
rigid_body.setRigidBodyWorldEnabled(True)
|
||||
rbw = bpy.context.scene.rigidbody_world
|
||||
if not rbw.collection:
|
||||
@@ -511,12 +522,12 @@ class UpdateRigidBodyWorld(bpy.types.Operator):
|
||||
|
||||
return rbw.collection.objects, rbw.constraints.objects
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.types.Context) -> Set[str]:
|
||||
scene = context.scene
|
||||
scene_objs = set(scene.objects)
|
||||
scene_objs.union(o for x in scene.objects if x.instance_type == "COLLECTION" and x.instance_collection for o in x.instance_collection.objects)
|
||||
|
||||
def _update_group(obj, group):
|
||||
def _update_group(obj: bpy.types.Object, group: bpy.types.Collection) -> bool:
|
||||
if obj in scene_objs:
|
||||
if obj not in group.values():
|
||||
group.link(obj)
|
||||
@@ -525,7 +536,7 @@ class UpdateRigidBodyWorld(bpy.types.Operator):
|
||||
group.unlink(obj)
|
||||
return False
|
||||
|
||||
def _references(obj):
|
||||
def _references(obj: bpy.types.Object) -> Generator[bpy.types.Object, None, None]:
|
||||
yield obj
|
||||
if getattr(obj, "proxy", None):
|
||||
yield from _references(obj.proxy)
|
||||
@@ -542,6 +553,7 @@ class UpdateRigidBodyWorld(bpy.types.Operator):
|
||||
# Object.rigid_body are removed,
|
||||
# but Object.rigid_body_constraint are retained.
|
||||
# Therefore, it must be checked with Object.mmd_type.
|
||||
logger.info("Updating rigid body world objects")
|
||||
for i in (x for x in objects if x.mmd_type == "RIGID_BODY"):
|
||||
if not _update_group(i, rb_objs):
|
||||
continue
|
||||
@@ -556,6 +568,7 @@ class UpdateRigidBodyWorld(bpy.types.Operator):
|
||||
# TODO Modify mmd_rigid to allow recovery of the remaining rigidbody parameters.
|
||||
# mass, friction, restitution, linear_dumping, angular_dumping
|
||||
|
||||
logger.info("Updating rigid body constraints")
|
||||
for i in (x for x in objects if x.rigid_body_constraint):
|
||||
if not _update_group(i, rbc_objs):
|
||||
continue
|
||||
@@ -566,6 +579,7 @@ class UpdateRigidBodyWorld(bpy.types.Operator):
|
||||
rbc.object2 = rb_map.get(rbc.object2, rbc.object2)
|
||||
|
||||
if need_rebuild_physics:
|
||||
logger.info("Rebuilding physics for models")
|
||||
for root_object in scene.objects:
|
||||
if root_object.mmd_type != "ROOT":
|
||||
continue
|
||||
|
||||
+17
-11
@@ -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.
|
||||
|
||||
from typing import Set
|
||||
from typing import Set, Tuple
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
from bpy.types import Operator, Context, Object
|
||||
|
||||
from ..core.model import FnModel
|
||||
from ..core.sdef import FnSDEF
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
def _get_target_objects(context):
|
||||
root_objects: Set[bpy.types.Object] = set()
|
||||
selected_objects: Set[bpy.types.Object] = set()
|
||||
def _get_target_objects(context: Context) -> Tuple[Set[Object], Set[Object]]:
|
||||
root_objects: Set[Object] = set()
|
||||
selected_objects: Set[Object] = set()
|
||||
for i in context.selected_objects:
|
||||
if i.type == "MESH":
|
||||
selected_objects.add(i)
|
||||
@@ -40,11 +41,13 @@ class ResetSDEFCache(Operator):
|
||||
bl_description = "Reset MMD SDEF cache of selected objects and clean unused cache"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
target_meshes, _ = _get_target_objects(context)
|
||||
logger.info(f"Resetting SDEF cache for {len(target_meshes)} objects")
|
||||
for i in target_meshes:
|
||||
FnSDEF.clear_cache(i)
|
||||
FnSDEF.clear_cache(unused_only=True)
|
||||
logger.debug("SDEF cache reset completed")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -75,19 +78,20 @@ class BindSDEF(Operator):
|
||||
default=False,
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
def invoke(self, context: Context, event: bpy.types.Event) -> Set[str]:
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
# TODO: Utility Functionalize
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
target_meshes, root_objects = _get_target_objects(context)
|
||||
logger.info(f"Binding SDEF for {len(target_meshes)} objects with mode={self.mode}, skip={self.use_skip}, scale={self.use_scale}")
|
||||
|
||||
for r in root_objects:
|
||||
r.mmd_root.use_sdef = True
|
||||
|
||||
param = ((None, False, True)[int(self.mode)], self.use_skip, self.use_scale)
|
||||
count = sum(FnSDEF.bind(i, *param) for i in target_meshes)
|
||||
logger.info(f"Successfully bound SDEF for {count} of {len(target_meshes)} meshes")
|
||||
self.report({"INFO"}, f"Binded {count} of {len(target_meshes)} selected mesh(es)")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -98,13 +102,15 @@ class UnbindSDEF(Operator):
|
||||
bl_description = "Unbind MMD SDEF data of selected objects"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
# TODO: Utility Functionalize
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
target_meshes, root_objects = _get_target_objects(context)
|
||||
logger.info(f"Unbinding SDEF for {len(target_meshes)} objects")
|
||||
|
||||
for i in target_meshes:
|
||||
FnSDEF.unbind(i)
|
||||
|
||||
for r in root_objects:
|
||||
r.mmd_root.use_sdef = False
|
||||
|
||||
logger.debug("SDEF unbinding completed")
|
||||
return {"FINISHED"}
|
||||
|
||||
+42
-34
@@ -6,29 +6,32 @@
|
||||
# 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 Optional, Tuple, List, Set, Dict, Any, Generator, Callable, Union, Type, Iterator
|
||||
|
||||
from bpy.types import Operator
|
||||
from mathutils import Matrix
|
||||
from bpy.types import Operator, Context
|
||||
from mathutils import Matrix, Vector, Quaternion
|
||||
|
||||
from ...logging_setup import logger
|
||||
|
||||
|
||||
class _SetShadingBase:
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_options: Set[str] = {"REGISTER", "UNDO"}
|
||||
|
||||
@staticmethod
|
||||
def _get_view3d_spaces(context):
|
||||
def _get_view3d_spaces(context: Context) -> Iterator[Any]:
|
||||
if getattr(context.area, "type", None) == "VIEW_3D":
|
||||
return (context.area.spaces[0],)
|
||||
return (area.spaces[0] for area in getattr(context.screen, "areas", ()) if area.type == "VIEW_3D")
|
||||
|
||||
@staticmethod
|
||||
def _reset_color_management(context, use_display_device=True):
|
||||
def _reset_color_management(context: Context, use_display_device: bool = True) -> None:
|
||||
try:
|
||||
context.scene.display_settings.display_device = ("None", "sRGB")[use_display_device]
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _reset_material_shading(context, use_shadeless=False):
|
||||
def _reset_material_shading(context: Context, use_shadeless: bool = False) -> None:
|
||||
for i in (x for x in context.scene.objects if x.type == "MESH" and x.mmd_type == "NONE"):
|
||||
for s in i.material_slots:
|
||||
if s.material is None:
|
||||
@@ -36,10 +39,11 @@ class _SetShadingBase:
|
||||
s.material.use_nodes = False
|
||||
s.material.use_shadeless = use_shadeless
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Dict[str, str]:
|
||||
context.scene.render.engine = "BLENDER_EEVEE_NEXT"
|
||||
logger.debug(f"Setting render engine to BLENDER_EEVEE_NEXT")
|
||||
|
||||
shading_mode = getattr(self, "_shading_mode", None)
|
||||
shading_mode: Optional[str] = getattr(self, "_shading_mode", None)
|
||||
for space in self._get_view3d_spaces(context):
|
||||
shading = space.shading
|
||||
shading.type = "SOLID"
|
||||
@@ -47,39 +51,40 @@ class _SetShadingBase:
|
||||
shading.color_type = "TEXTURE" if shading_mode else "MATERIAL"
|
||||
shading.show_object_outline = False
|
||||
shading.show_backface_culling = False
|
||||
logger.debug(f"Applied shading mode: {shading_mode or 'DEFAULT'}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetGLSLShading(Operator, _SetShadingBase):
|
||||
bl_idname = "mmd_tools.set_glsl_shading"
|
||||
bl_label = "GLSL View"
|
||||
bl_description = "Use GLSL shading with additional lighting"
|
||||
bl_idname: str = "mmd_tools.set_glsl_shading"
|
||||
bl_label: str = "GLSL View"
|
||||
bl_description: str = "Use GLSL shading with additional lighting"
|
||||
|
||||
_shading_mode = "GLSL"
|
||||
_shading_mode: str = "GLSL"
|
||||
|
||||
|
||||
class SetShadelessGLSLShading(Operator, _SetShadingBase):
|
||||
bl_idname = "mmd_tools.set_shadeless_glsl_shading"
|
||||
bl_label = "Shadeless GLSL View"
|
||||
bl_description = "Use only toon shading"
|
||||
bl_idname: str = "mmd_tools.set_shadeless_glsl_shading"
|
||||
bl_label: str = "Shadeless GLSL View"
|
||||
bl_description: str = "Use only toon shading"
|
||||
|
||||
_shading_mode = "SHADELESS"
|
||||
_shading_mode: str = "SHADELESS"
|
||||
|
||||
|
||||
class ResetShading(Operator, _SetShadingBase):
|
||||
bl_idname = "mmd_tools.reset_shading"
|
||||
bl_label = "Reset View"
|
||||
bl_description = "Reset to default Blender shading"
|
||||
bl_idname: str = "mmd_tools.reset_shading"
|
||||
bl_label: str = "Reset View"
|
||||
bl_description: str = "Reset to default Blender shading"
|
||||
|
||||
|
||||
class FlipPose(Operator):
|
||||
bl_idname = "mmd_tools.flip_pose"
|
||||
bl_label = "Flip Pose"
|
||||
bl_description = "Apply the current pose of selected bones to matching bone on opposite side of X-Axis."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_idname: str = "mmd_tools.flip_pose"
|
||||
bl_label: str = "Flip Pose"
|
||||
bl_description: str = "Apply the current pose of selected bones to matching bone on opposite side of X-Axis."
|
||||
bl_options: Set[str] = {"REGISTER", "UNDO"}
|
||||
|
||||
# https://docs.blender.org/manual/en/dev/rigging/armatures/bones/editing/naming.html
|
||||
__LR_REGEX = [
|
||||
__LR_REGEX: List[Dict[str, Any]] = [
|
||||
{"re": re.compile(r"^(.+)(RIGHT|LEFT)(\.\d+)?$", re.IGNORECASE), "lr": 1},
|
||||
{"re": re.compile(r"^(.+)([\.\- _])(L|R)(\.\d+)?$", re.IGNORECASE), "lr": 2},
|
||||
{"re": re.compile(r"^(LEFT|RIGHT)(.+)$", re.IGNORECASE), "lr": 0},
|
||||
@@ -87,7 +92,7 @@ class FlipPose(Operator):
|
||||
{"re": re.compile(r"^(.+)(左|右)(\.\d+)?$"), "lr": 1},
|
||||
{"re": re.compile(r"^(左|右)(.+)$"), "lr": 0},
|
||||
]
|
||||
__LR_MAP = {
|
||||
__LR_MAP: Dict[str, str] = {
|
||||
"RIGHT": "LEFT",
|
||||
"Right": "Left",
|
||||
"right": "left",
|
||||
@@ -103,7 +108,7 @@ class FlipPose(Operator):
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def flip_name(cls, name):
|
||||
def flip_name(cls, name: str) -> str:
|
||||
for regex in cls.__LR_REGEX:
|
||||
match = regex["re"].match(name)
|
||||
if match:
|
||||
@@ -121,17 +126,15 @@ class FlipPose(Operator):
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def __cmul(vec1, vec2):
|
||||
def __cmul(vec1: Union[Vector, Quaternion], vec2: Tuple[float, float, float, float]) -> Union[Vector, Quaternion]:
|
||||
return type(vec1)([x * y for x, y in zip(vec1, vec2)])
|
||||
|
||||
@staticmethod
|
||||
def __matrix_compose(loc, rot, scale):
|
||||
def __matrix_compose(loc: Vector, rot: Quaternion, scale: Vector) -> Matrix:
|
||||
return (Matrix.Translation(loc) @ rot.to_matrix().to_4x4()) @ Matrix([(scale[0], 0, 0, 0), (0, scale[1], 0, 0), (0, 0, scale[2], 0), (0, 0, 0, 1)])
|
||||
|
||||
@classmethod
|
||||
def __flip_pose(cls, matrix_basis, bone_src, bone_dest):
|
||||
from mathutils import Quaternion
|
||||
|
||||
def __flip_pose(cls, matrix_basis: Matrix, bone_src: Any, bone_dest: Any) -> None:
|
||||
m = bone_dest.bone.matrix_local.to_3x3().transposed()
|
||||
mi = bone_src.bone.matrix_local.to_3x3().transposed().inverted() if bone_src != bone_dest else m.inverted()
|
||||
loc, rot, scale = matrix_basis.decompose()
|
||||
@@ -140,11 +143,16 @@ class FlipPose(Operator):
|
||||
bone_dest.matrix_basis = cls.__matrix_compose(m @ loc, Quaternion(m @ rot.axis, rot.angle).normalized(), scale)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return context.active_object and context.active_object.type == "ARMATURE" and context.active_object.mode == "POSE"
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: Context) -> Dict[str, str]:
|
||||
logger.info("Executing flip pose operation")
|
||||
pose_bones = context.active_object.pose.bones
|
||||
for b, mat in [(x, x.matrix_basis.copy()) for x in context.selected_pose_bones]:
|
||||
self.__flip_pose(mat, b, pose_bones.get(self.flip_name(b.name), b))
|
||||
flip_name = self.flip_name(b.name)
|
||||
target_bone = pose_bones.get(flip_name, b)
|
||||
logger.debug(f"Flipping pose from {b.name} to {target_bone.name}")
|
||||
self.__flip_pose(mat, b, target_bone)
|
||||
logger.info("Flip pose operation completed")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -6,81 +6,85 @@
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
import bpy
|
||||
from typing import Optional, Set, Dict, Any, List, Tuple, Union, Type
|
||||
|
||||
from .. import utils
|
||||
from ..core import material
|
||||
from ..core.material import FnMaterial
|
||||
from ..core.model import FnModel
|
||||
from . import patch_library_overridable
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
def _mmd_material_update_ambient_color(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_ambient_color(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_ambient_color()
|
||||
|
||||
|
||||
def _mmd_material_update_diffuse_color(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_diffuse_color(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_diffuse_color()
|
||||
|
||||
|
||||
def _mmd_material_update_alpha(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_alpha(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_alpha()
|
||||
|
||||
|
||||
def _mmd_material_update_specular_color(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_specular_color(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_specular_color()
|
||||
|
||||
|
||||
def _mmd_material_update_shininess(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_shininess(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_shininess()
|
||||
|
||||
|
||||
def _mmd_material_update_is_double_sided(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_is_double_sided(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_is_double_sided()
|
||||
|
||||
|
||||
def _mmd_material_update_sphere_texture_type(prop: "MMDMaterial", context):
|
||||
def _mmd_material_update_sphere_texture_type(prop: "MMDMaterial", context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_sphere_texture_type(context.active_object)
|
||||
|
||||
|
||||
def _mmd_material_update_toon_texture(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_toon_texture(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_toon_texture()
|
||||
|
||||
|
||||
def _mmd_material_update_enabled_drop_shadow(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_enabled_drop_shadow(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_drop_shadow()
|
||||
|
||||
|
||||
def _mmd_material_update_enabled_self_shadow_map(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_enabled_self_shadow_map(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_self_shadow_map()
|
||||
|
||||
|
||||
def _mmd_material_update_enabled_self_shadow(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_enabled_self_shadow(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_self_shadow()
|
||||
|
||||
|
||||
def _mmd_material_update_enabled_toon_edge(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_enabled_toon_edge(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_enabled_toon_edge()
|
||||
|
||||
|
||||
def _mmd_material_update_edge_color(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_edge_color(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_edge_color()
|
||||
|
||||
|
||||
def _mmd_material_update_edge_weight(prop: "MMDMaterial", _context):
|
||||
def _mmd_material_update_edge_weight(prop: "MMDMaterial", _context: bpy.types.Context) -> None:
|
||||
FnMaterial(prop.id_data).update_edge_weight()
|
||||
|
||||
|
||||
def _mmd_material_get_name_j(prop: "MMDMaterial"):
|
||||
def _mmd_material_get_name_j(prop: "MMDMaterial") -> str:
|
||||
return prop.get("name_j", "")
|
||||
|
||||
|
||||
def _mmd_material_set_name_j(prop: "MMDMaterial", value: str):
|
||||
def _mmd_material_set_name_j(prop: "MMDMaterial", value: str) -> None:
|
||||
prop_value = value
|
||||
if prop_value and prop_value != prop.get("name_j"):
|
||||
root = FnModel.find_root_object(bpy.context.active_object)
|
||||
if root is None:
|
||||
logger.debug(f"No root object found, using unique name for material: {value}")
|
||||
prop_value = utils.unique_name(value, {mat.mmd_material.name_j for mat in bpy.data.materials})
|
||||
else:
|
||||
logger.debug(f"Root object found, using unique name for material within model: {value}")
|
||||
prop_value = utils.unique_name(value, {mat.mmd_material.name_j for mat in FnModel.iterate_materials(root)})
|
||||
|
||||
prop["name_j"] = prop_value
|
||||
@@ -275,13 +279,15 @@ class MMDMaterial(bpy.types.PropertyGroup):
|
||||
description="Comment",
|
||||
)
|
||||
|
||||
def is_id_unique(self):
|
||||
def is_id_unique(self) -> bool:
|
||||
return self.material_id < 0 or not next((m for m in bpy.data.materials if m.mmd_material != self and m.mmd_material.material_id == self.material_id), None)
|
||||
|
||||
@staticmethod
|
||||
def register():
|
||||
def register() -> None:
|
||||
logger.debug("Registering MMD material properties")
|
||||
bpy.types.Material.mmd_material = patch_library_overridable(bpy.props.PointerProperty(type=MMDMaterial))
|
||||
|
||||
@staticmethod
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
logger.debug("Unregistering MMD material properties")
|
||||
del bpy.types.Material.mmd_material
|
||||
|
||||
@@ -6,33 +6,33 @@
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
import bpy
|
||||
from typing import Optional, List, Dict, Any, Set, Tuple, Union, TypeVar, Type
|
||||
from bpy.types import PropertyGroup, Object, ShapeKey
|
||||
|
||||
from .. import utils
|
||||
from ..core.bone import FnBone
|
||||
from ..core.material import FnMaterial
|
||||
from ..core.model import FnModel, Model
|
||||
from ..core.morph import FnMorph
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
def _morph_base_get_name(prop: "_MorphBase") -> str:
|
||||
return prop.get("name", "")
|
||||
|
||||
|
||||
def _morph_base_set_name(prop: "_MorphBase", value: str):
|
||||
def _morph_base_set_name(prop: "_MorphBase", value: str) -> None:
|
||||
mmd_root = prop.id_data.mmd_root
|
||||
# morph_type = mmd_root.active_morph_type
|
||||
morph_type = "%s_morphs" % prop.bl_rna.identifier[:-5].lower()
|
||||
# assert(prop.bl_rna.identifier.endswith('Morph'))
|
||||
# logging.debug('_set_name: %s %s %s', prop, value, morph_type)
|
||||
prop_name = prop.get("name", None)
|
||||
if prop_name == value:
|
||||
return
|
||||
|
||||
used_names = {x.name for x in getattr(mmd_root, morph_type) if x != prop}
|
||||
used_names: Set[str] = {x.name for x in getattr(mmd_root, morph_type) if x != prop}
|
||||
value = utils.unique_name(value, used_names)
|
||||
if prop_name is not None:
|
||||
if morph_type == "vertex_morphs":
|
||||
kb_list = {}
|
||||
kb_list: Dict[str, List[ShapeKey]] = {}
|
||||
for mesh in FnModel.iterate_mesh_objects(prop.id_data):
|
||||
for kb in getattr(mesh.data.shape_keys, "key_blocks", ()):
|
||||
kb_list.setdefault(kb.name, []).append(kb)
|
||||
@@ -43,7 +43,7 @@ def _morph_base_set_name(prop: "_MorphBase", value: str):
|
||||
kb.name = value
|
||||
|
||||
elif morph_type == "uv_morphs":
|
||||
vg_list = {}
|
||||
vg_list: Dict[str, List[Any]] = {}
|
||||
for mesh in FnModel.iterate_mesh_objects(prop.id_data):
|
||||
for vg, n, x in FnMorph.get_uv_morph_vertex_groups(mesh):
|
||||
vg_list.setdefault(n, []).append(vg)
|
||||
@@ -72,6 +72,7 @@ def _morph_base_set_name(prop: "_MorphBase", value: str):
|
||||
kb.name = value
|
||||
|
||||
prop["name"] = value
|
||||
logger.debug(f"Renamed morph from '{prop_name}' to '{value}'")
|
||||
|
||||
|
||||
class _MorphBase:
|
||||
@@ -101,11 +102,11 @@ class _MorphBase:
|
||||
|
||||
|
||||
def _bone_morph_data_get_bone(prop: "BoneMorphData") -> str:
|
||||
bone_id = prop.get("bone_id", -1)
|
||||
bone_id: int = prop.get("bone_id", -1)
|
||||
if bone_id < 0:
|
||||
return ""
|
||||
root_object = prop.id_data
|
||||
armature_object = FnModel.find_armature_object(root_object)
|
||||
root_object: Object = prop.id_data
|
||||
armature_object: Optional[Object] = FnModel.find_armature_object(root_object)
|
||||
if armature_object is None:
|
||||
return ""
|
||||
pose_bone = FnBone.find_pose_bone_by_bone_id(armature_object, bone_id)
|
||||
@@ -114,9 +115,9 @@ def _bone_morph_data_get_bone(prop: "BoneMorphData") -> str:
|
||||
return pose_bone.name
|
||||
|
||||
|
||||
def _bone_morph_data_set_bone(prop: "BoneMorphData", value: str):
|
||||
root = prop.id_data
|
||||
arm = FnModel.find_armature_object(root)
|
||||
def _bone_morph_data_set_bone(prop: "BoneMorphData", value: str) -> None:
|
||||
root: Object = prop.id_data
|
||||
arm: Optional[Object] = FnModel.find_armature_object(root)
|
||||
|
||||
# Load the library_override file. This function is triggered when loading, but the arm obj cannot be found.
|
||||
# The arm obj is exist, but the relative relationship has not yet been established.
|
||||
@@ -128,9 +129,10 @@ def _bone_morph_data_set_bone(prop: "BoneMorphData", value: str):
|
||||
return
|
||||
pose_bone = arm.pose.bones[value]
|
||||
prop["bone_id"] = FnBone.get_or_assign_bone_id(pose_bone)
|
||||
logger.debug(f"Set bone morph data bone to '{value}' with ID {prop['bone_id']}")
|
||||
|
||||
|
||||
def _bone_morph_data_update_location_or_rotation(prop: "BoneMorphData", _context):
|
||||
def _bone_morph_data_update_location_or_rotation(prop: "BoneMorphData", _context: bpy.types.Context) -> None:
|
||||
if not prop.name.startswith("mmd_bind"):
|
||||
return
|
||||
arm = FnModel(prop.id_data).morph_slider.dummy_armature
|
||||
@@ -139,6 +141,7 @@ def _bone_morph_data_update_location_or_rotation(prop: "BoneMorphData", _context
|
||||
if bone:
|
||||
bone.location = prop.location
|
||||
bone.rotation_quaternion = prop.rotation.__class__(*prop.rotation.to_axis_angle()) # Fix for consistency
|
||||
logger.debug(f"Updated bone morph data location/rotation for '{prop.name}'")
|
||||
|
||||
|
||||
class BoneMorphData(bpy.types.PropertyGroup):
|
||||
@@ -188,40 +191,44 @@ class BoneMorph(_MorphBase, bpy.types.PropertyGroup):
|
||||
)
|
||||
|
||||
|
||||
def _material_morph_data_get_material(prop: "MaterialMorphData"):
|
||||
def _material_morph_data_get_material(prop: "MaterialMorphData") -> str:
|
||||
mat_p = prop.get("material_data", None)
|
||||
if mat_p is not None:
|
||||
return mat_p.name
|
||||
return ""
|
||||
|
||||
|
||||
def _material_morph_data_set_material(prop: "MaterialMorphData", value: str):
|
||||
def _material_morph_data_set_material(prop: "MaterialMorphData", value: str) -> None:
|
||||
if value not in bpy.data.materials:
|
||||
prop["material_data"] = None
|
||||
prop["material_id"] = -1
|
||||
logger.debug(f"Material '{value}' not found, setting material_data to None")
|
||||
else:
|
||||
mat = bpy.data.materials[value]
|
||||
fnMat = FnMaterial(mat)
|
||||
prop["material_data"] = mat
|
||||
prop["material_id"] = fnMat.material_id
|
||||
logger.debug(f"Set material morph data material to '{value}' with ID {fnMat.material_id}")
|
||||
|
||||
|
||||
def _material_morph_data_set_related_mesh(prop: "MaterialMorphData", value: str):
|
||||
def _material_morph_data_set_related_mesh(prop: "MaterialMorphData", value: str) -> None:
|
||||
mesh = FnModel.find_mesh_object_by_name(prop.id_data, value)
|
||||
if mesh is not None:
|
||||
prop["related_mesh_data"] = mesh.data
|
||||
logger.debug(f"Set material morph data related mesh to '{value}'")
|
||||
else:
|
||||
prop["related_mesh_data"] = None
|
||||
logger.debug(f"Mesh '{value}' not found, setting related_mesh_data to None")
|
||||
|
||||
|
||||
def _material_morph_data_get_related_mesh(prop):
|
||||
def _material_morph_data_get_related_mesh(prop: "MaterialMorphData") -> str:
|
||||
mesh_p = prop.get("related_mesh_data", None)
|
||||
if mesh_p is not None:
|
||||
return mesh_p.name
|
||||
return ""
|
||||
|
||||
|
||||
def _material_morph_data_update_modifiable_values(prop: "MaterialMorphData", _context):
|
||||
def _material_morph_data_update_modifiable_values(prop: "MaterialMorphData", _context: bpy.types.Context) -> None:
|
||||
if not prop.name.startswith("mmd_bind"):
|
||||
return
|
||||
from ..core.shader import _MaterialMorph
|
||||
@@ -229,9 +236,11 @@ def _material_morph_data_update_modifiable_values(prop: "MaterialMorphData", _co
|
||||
mat = prop["material_data"]
|
||||
if mat is not None:
|
||||
_MaterialMorph.update_morph_inputs(mat, prop)
|
||||
logger.debug(f"Updated material morph modifiable values for '{prop.name}'")
|
||||
else:
|
||||
for mat in FnModel(prop.id_data).materials():
|
||||
_MaterialMorph.update_morph_inputs(mat, prop)
|
||||
logger.debug(f"Updated material morph modifiable values for all materials")
|
||||
|
||||
|
||||
class MaterialMorphData(bpy.types.PropertyGroup):
|
||||
@@ -407,9 +416,6 @@ class UVMorphOffset(bpy.types.PropertyGroup):
|
||||
name="UV Offset",
|
||||
description="UV offset",
|
||||
size=4,
|
||||
# min=-1,
|
||||
# max=1,
|
||||
# precision=3,
|
||||
step=0.1,
|
||||
default=[0, 0, 0, 0],
|
||||
)
|
||||
|
||||
@@ -5,29 +5,33 @@
|
||||
# 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 cast
|
||||
from typing import cast, Optional, Any, Union
|
||||
import bpy
|
||||
from bpy.types import Context, PropertyGroup, PoseBone, Object, Armature
|
||||
|
||||
from ..core.bone import FnBone
|
||||
from . import patch_library_overridable
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
def _mmd_bone_update_additional_transform(prop: "MMDBone", context: bpy.types.Context):
|
||||
def _mmd_bone_update_additional_transform(prop: "MMDBone", context: Context) -> None:
|
||||
prop["is_additional_transform_dirty"] = True
|
||||
p_bone = context.active_pose_bone
|
||||
if p_bone and p_bone.mmd_bone.as_pointer() == prop.as_pointer():
|
||||
logger.debug(f"Applying additional transformation for {p_bone.name}")
|
||||
FnBone.apply_additional_transformation(prop.id_data)
|
||||
|
||||
|
||||
def _mmd_bone_update_additional_transform_influence(prop: "MMDBone", context: bpy.types.Context):
|
||||
def _mmd_bone_update_additional_transform_influence(prop: "MMDBone", context: Context) -> None:
|
||||
pose_bone = context.active_pose_bone
|
||||
if pose_bone and pose_bone.mmd_bone.as_pointer() == prop.as_pointer():
|
||||
logger.debug(f"Updating additional transform influence for {pose_bone.name}")
|
||||
FnBone.update_additional_transform_influence(pose_bone)
|
||||
else:
|
||||
prop["is_additional_transform_dirty"] = True
|
||||
|
||||
|
||||
def _mmd_bone_get_additional_transform_bone(prop: "MMDBone"):
|
||||
def _mmd_bone_get_additional_transform_bone(prop: "MMDBone") -> str:
|
||||
arm = prop.id_data
|
||||
bone_id = prop.get("additional_transform_bone_id", -1)
|
||||
if bone_id < 0:
|
||||
@@ -38,7 +42,7 @@ def _mmd_bone_get_additional_transform_bone(prop: "MMDBone"):
|
||||
return pose_bone.name
|
||||
|
||||
|
||||
def _mmd_bone_set_additional_transform_bone(prop: "MMDBone", value: str):
|
||||
def _mmd_bone_set_additional_transform_bone(prop: "MMDBone", value: str) -> None:
|
||||
arm = prop.id_data
|
||||
prop["is_additional_transform_dirty"] = True
|
||||
if value not in arm.pose.bones.keys():
|
||||
@@ -48,7 +52,7 @@ def _mmd_bone_set_additional_transform_bone(prop: "MMDBone", value: str):
|
||||
prop["additional_transform_bone_id"] = FnBone.get_or_assign_bone_id(pose_bone)
|
||||
|
||||
|
||||
class MMDBone(bpy.types.PropertyGroup):
|
||||
class MMDBone(PropertyGroup):
|
||||
name_j: bpy.props.StringProperty(
|
||||
name="Name",
|
||||
description="Japanese Name",
|
||||
@@ -184,11 +188,12 @@ class MMDBone(bpy.types.PropertyGroup):
|
||||
|
||||
is_additional_transform_dirty: bpy.props.BoolProperty(name="", default=True)
|
||||
|
||||
def is_id_unique(self):
|
||||
def is_id_unique(self) -> bool:
|
||||
return self.bone_id < 0 or not next((b for b in self.id_data.pose.bones if b.mmd_bone != self and b.mmd_bone.bone_id == self.bone_id), None)
|
||||
|
||||
@staticmethod
|
||||
def register():
|
||||
def register() -> None:
|
||||
logger.debug("Registering MMDBone properties")
|
||||
bpy.types.PoseBone.mmd_bone = patch_library_overridable(bpy.props.PointerProperty(type=MMDBone))
|
||||
bpy.types.PoseBone.is_mmd_shadow_bone = patch_library_overridable(bpy.props.BoolProperty(name="is_mmd_shadow_bone", default=False))
|
||||
bpy.types.PoseBone.mmd_shadow_bone_type = patch_library_overridable(bpy.props.StringProperty(name="mmd_shadow_bone_type"))
|
||||
@@ -202,20 +207,21 @@ class MMDBone(bpy.types.PropertyGroup):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
logger.debug("Unregistering MMDBone properties")
|
||||
del bpy.types.PoseBone.mmd_ik_toggle
|
||||
del bpy.types.PoseBone.mmd_shadow_bone_type
|
||||
del bpy.types.PoseBone.is_mmd_shadow_bone
|
||||
del bpy.types.PoseBone.mmd_bone
|
||||
|
||||
|
||||
def _pose_bone_update_mmd_ik_toggle(prop: bpy.types.PoseBone, _context):
|
||||
def _pose_bone_update_mmd_ik_toggle(prop: PoseBone, _context: Any) -> None:
|
||||
v = prop.mmd_ik_toggle
|
||||
armature_object = cast(bpy.types.Object, prop.id_data)
|
||||
armature_object = cast(Object, prop.id_data)
|
||||
for b in armature_object.pose.bones:
|
||||
for c in b.constraints:
|
||||
if c.type == "IK" and c.subtarget == prop.name:
|
||||
# logging.debug(' %s %s', b.name, c.name)
|
||||
logger.debug(f"Updating IK toggle for {b.name} {c.name}")
|
||||
c.influence = v
|
||||
b = b if c.use_tail else b.parent
|
||||
for b in ([b] + b.parent_recursive)[: c.chain_count]:
|
||||
|
||||
@@ -8,32 +8,35 @@
|
||||
"""Properties for rigid bodies and joints"""
|
||||
|
||||
import bpy
|
||||
from typing import Optional, Any, Set, List, Dict, Tuple, Union
|
||||
from bpy.types import Context, Object, PropertyGroup, Material
|
||||
|
||||
from .. import bpyutils
|
||||
from ..core import rigid_body
|
||||
from ..core.rigid_body import RigidBodyMaterial, FnRigidBody
|
||||
from ..core.model import FnModel
|
||||
from . import patch_library_overridable
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
def _updateCollisionGroup(prop, _context):
|
||||
obj = prop.id_data
|
||||
materials = obj.data.materials
|
||||
def _updateCollisionGroup(prop: PropertyGroup, _context: Context) -> None:
|
||||
obj: Object = prop.id_data
|
||||
materials: List[Material] = obj.data.materials
|
||||
if len(materials) == 0:
|
||||
materials.append(RigidBodyMaterial.getMaterial(prop.collision_group_number))
|
||||
else:
|
||||
obj.material_slots[0].material = RigidBodyMaterial.getMaterial(prop.collision_group_number)
|
||||
|
||||
|
||||
def _updateType(prop, _context):
|
||||
obj = prop.id_data
|
||||
def _updateType(prop: PropertyGroup, _context: Context) -> None:
|
||||
obj: Object = prop.id_data
|
||||
rb = obj.rigid_body
|
||||
if rb:
|
||||
rb.kinematic = int(prop.type) == rigid_body.MODE_STATIC
|
||||
|
||||
|
||||
def _updateShape(prop, _context):
|
||||
obj = prop.id_data
|
||||
def _updateShape(prop: PropertyGroup, _context: Context) -> None:
|
||||
obj: Object = prop.id_data
|
||||
|
||||
if len(obj.data.vertices) > 0:
|
||||
size = prop.size
|
||||
@@ -44,8 +47,8 @@ def _updateShape(prop, _context):
|
||||
rb.collision_shape = prop.shape
|
||||
|
||||
|
||||
def _get_bone(prop):
|
||||
obj = prop.id_data
|
||||
def _get_bone(prop: PropertyGroup) -> str:
|
||||
obj: Object = prop.id_data
|
||||
relation = obj.constraints.get("mmd_tools_rigid_parent", None)
|
||||
if relation:
|
||||
arm = relation.target
|
||||
@@ -55,9 +58,9 @@ def _get_bone(prop):
|
||||
return prop.get("bone", "")
|
||||
|
||||
|
||||
def _set_bone(prop, value):
|
||||
bone_name = value
|
||||
obj = prop.id_data
|
||||
def _set_bone(prop: PropertyGroup, value: str) -> None:
|
||||
bone_name: str = value
|
||||
obj: Object = prop.id_data
|
||||
relation = obj.constraints.get("mmd_tools_rigid_parent", None)
|
||||
if relation is None:
|
||||
relation = obj.constraints.new("CHILD_OF")
|
||||
@@ -78,16 +81,16 @@ def _set_bone(prop, value):
|
||||
prop["bone"] = bone_name
|
||||
|
||||
|
||||
def _get_size(prop):
|
||||
def _get_size(prop: PropertyGroup) -> Tuple[float, float, float]:
|
||||
if prop.id_data.mmd_type != "RIGID_BODY":
|
||||
return (0, 0, 0)
|
||||
return FnRigidBody.get_rigid_body_size(prop.id_data)
|
||||
|
||||
|
||||
def _set_size(prop, value):
|
||||
obj = prop.id_data
|
||||
def _set_size(prop: PropertyGroup, value: Tuple[float, float, float]) -> None:
|
||||
obj: Object = prop.id_data
|
||||
assert obj.mode == "OBJECT" # not support other mode yet
|
||||
shape = prop.shape
|
||||
shape: str = prop.shape
|
||||
|
||||
mesh = obj.data
|
||||
rb = obj.rigid_body
|
||||
@@ -146,15 +149,15 @@ def _set_size(prop, value):
|
||||
mesh.update()
|
||||
|
||||
|
||||
def _get_rigid_name(prop):
|
||||
def _get_rigid_name(prop: PropertyGroup) -> str:
|
||||
return prop.get("name", "")
|
||||
|
||||
|
||||
def _set_rigid_name(prop, value):
|
||||
def _set_rigid_name(prop: PropertyGroup, value: str) -> None:
|
||||
prop["name"] = value
|
||||
|
||||
|
||||
class MMDRigidBody(bpy.types.PropertyGroup):
|
||||
class MMDRigidBody(PropertyGroup):
|
||||
name_j: bpy.props.StringProperty(
|
||||
name="Name",
|
||||
description="Japanese Name",
|
||||
@@ -227,16 +230,18 @@ class MMDRigidBody(bpy.types.PropertyGroup):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def register():
|
||||
def register() -> None:
|
||||
logger.debug("Registering MMDRigidBody property")
|
||||
bpy.types.Object.mmd_rigid = patch_library_overridable(bpy.props.PointerProperty(type=MMDRigidBody))
|
||||
|
||||
@staticmethod
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
logger.debug("Unregistering MMDRigidBody property")
|
||||
del bpy.types.Object.mmd_rigid
|
||||
|
||||
|
||||
def _updateSpringLinear(prop, context):
|
||||
obj = prop.id_data
|
||||
def _updateSpringLinear(prop: PropertyGroup, context: Context) -> None:
|
||||
obj: Object = prop.id_data
|
||||
rbc = obj.rigid_body_constraint
|
||||
if rbc:
|
||||
rbc.spring_stiffness_x = prop.spring_linear[0]
|
||||
@@ -244,8 +249,8 @@ def _updateSpringLinear(prop, context):
|
||||
rbc.spring_stiffness_z = prop.spring_linear[2]
|
||||
|
||||
|
||||
def _updateSpringAngular(prop, context):
|
||||
obj = prop.id_data
|
||||
def _updateSpringAngular(prop: PropertyGroup, context: Context) -> None:
|
||||
obj: Object = prop.id_data
|
||||
rbc = obj.rigid_body_constraint
|
||||
if rbc and hasattr(rbc, "use_spring_ang_x"):
|
||||
rbc.spring_stiffness_ang_x = prop.spring_angular[0]
|
||||
@@ -253,7 +258,7 @@ def _updateSpringAngular(prop, context):
|
||||
rbc.spring_stiffness_ang_z = prop.spring_angular[2]
|
||||
|
||||
|
||||
class MMDJoint(bpy.types.PropertyGroup):
|
||||
class MMDJoint(PropertyGroup):
|
||||
name_j: bpy.props.StringProperty(
|
||||
name="Name",
|
||||
description="Japanese Name",
|
||||
@@ -287,9 +292,12 @@ class MMDJoint(bpy.types.PropertyGroup):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def register():
|
||||
def register() -> None:
|
||||
logger.debug("Registering MMDJoint property")
|
||||
bpy.types.Object.mmd_joint = patch_library_overridable(bpy.props.PointerProperty(type=MMDJoint))
|
||||
|
||||
@staticmethod
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
logger.debug("Unregistering MMDJoint property")
|
||||
del bpy.types.Object.mmd_joint
|
||||
|
||||
|
||||
+41
-25
@@ -8,6 +8,7 @@
|
||||
"""Properties for MMD model root object"""
|
||||
|
||||
import bpy
|
||||
from typing import Optional, List, Dict, Any, Set, Tuple, Union, Type, TypeVar, cast
|
||||
|
||||
from .. import utils
|
||||
from ..bpyutils import FnContext
|
||||
@@ -17,9 +18,10 @@ from ..core.sdef import FnSDEF
|
||||
from . import patch_library_overridable
|
||||
from .morph import BoneMorph, GroupMorph, MaterialMorph, UVMorph, VertexMorph
|
||||
from .translations import MMDTranslation
|
||||
from ....core.logging_setup import logger
|
||||
|
||||
|
||||
def __driver_variables(constraint: bpy.types.Constraint, path: str, index=-1):
|
||||
def __driver_variables(constraint: bpy.types.Constraint, path: str, index: int = -1) -> Tuple[bpy.types.Driver, Any]:
|
||||
d = constraint.driver_add(path, index)
|
||||
variables = d.driver.variables
|
||||
for x in variables:
|
||||
@@ -27,7 +29,7 @@ def __driver_variables(constraint: bpy.types.Constraint, path: str, index=-1):
|
||||
return d.driver, variables
|
||||
|
||||
|
||||
def __add_single_prop(variables, id_obj, data_path, prefix):
|
||||
def __add_single_prop(variables: Any, id_obj: bpy.types.Object, data_path: str, prefix: str) -> Any:
|
||||
var = variables.new()
|
||||
var.name = prefix + str(len(variables))
|
||||
var.type = "SINGLE_PROP"
|
||||
@@ -38,17 +40,18 @@ def __add_single_prop(variables, id_obj, data_path, prefix):
|
||||
return var
|
||||
|
||||
|
||||
def _toggleUsePropertyDriver(self: "MMDRoot", _context):
|
||||
def _toggleUsePropertyDriver(self: "MMDRoot", _context: bpy.types.Context) -> None:
|
||||
root_object: bpy.types.Object = self.id_data
|
||||
armature_object = FnModel.find_armature_object(root_object)
|
||||
|
||||
if armature_object is None:
|
||||
ik_map = {}
|
||||
ik_map: Dict[Any, Tuple[Any, Any]] = {}
|
||||
else:
|
||||
bones = armature_object.pose.bones
|
||||
ik_map = {bones[c.subtarget]: (b, c) for b in bones for c in b.constraints if c.type == "IK" and c.is_valid and c.subtarget in bones}
|
||||
|
||||
if self.use_property_driver:
|
||||
logger.debug("Enabling property drivers for %s", root_object.name)
|
||||
for ik, (b, c) in ik_map.items():
|
||||
driver, variables = __driver_variables(c, "influence")
|
||||
driver.expression = "%s" % __add_single_prop(variables, ik.id_data, ik.path_from_id("mmd_ik_toggle"), "use_ik").name
|
||||
@@ -63,6 +66,7 @@ def _toggleUsePropertyDriver(self: "MMDRoot", _context):
|
||||
driver, variables = __driver_variables(i, prop_hide)
|
||||
driver.expression = "not %s" % __add_single_prop(variables, root_object, "mmd_root.show_meshes", "show").name
|
||||
else:
|
||||
logger.debug("Disabling property drivers for %s", root_object.name)
|
||||
for ik, (b, c) in ik_map.items():
|
||||
c.driver_remove("influence")
|
||||
b = b if c.use_tail else b.parent
|
||||
@@ -80,31 +84,35 @@ def _toggleUsePropertyDriver(self: "MMDRoot", _context):
|
||||
# ===========================================
|
||||
|
||||
|
||||
def _toggleUseToonTexture(self: "MMDRoot", _context):
|
||||
def _toggleUseToonTexture(self: "MMDRoot", _context: bpy.types.Context) -> None:
|
||||
use_toon = self.use_toon_texture
|
||||
logger.debug("Toggling toon texture to %s for %s", use_toon, self.id_data.name)
|
||||
for i in FnModel.iterate_mesh_objects(self.id_data):
|
||||
for m in i.data.materials:
|
||||
if m:
|
||||
FnMaterial(m).use_toon_texture(use_toon)
|
||||
|
||||
|
||||
def _toggleUseSphereTexture(self: "MMDRoot", _context):
|
||||
def _toggleUseSphereTexture(self: "MMDRoot", _context: bpy.types.Context) -> None:
|
||||
use_sphere = self.use_sphere_texture
|
||||
logger.debug("Toggling sphere texture to %s for %s", use_sphere, self.id_data.name)
|
||||
for i in FnModel.iterate_mesh_objects(self.id_data):
|
||||
for m in i.data.materials:
|
||||
if m:
|
||||
FnMaterial(m).use_sphere_texture(use_sphere, i)
|
||||
|
||||
|
||||
def _toggleUseSDEF(self: "MMDRoot", _context):
|
||||
def _toggleUseSDEF(self: "MMDRoot", _context: bpy.types.Context) -> None:
|
||||
mute_sdef = not self.use_sdef
|
||||
logger.debug("Toggling SDEF to %s for %s", not mute_sdef, self.id_data.name)
|
||||
for i in FnModel.iterate_mesh_objects(self.id_data):
|
||||
FnSDEF.mute_sdef_set(i, mute_sdef)
|
||||
|
||||
|
||||
def _toggleVisibilityOfMeshes(self: "MMDRoot", context: bpy.types.Context):
|
||||
def _toggleVisibilityOfMeshes(self: "MMDRoot", context: bpy.types.Context) -> None:
|
||||
root = self.id_data
|
||||
hide = not self.show_meshes
|
||||
logger.debug("Toggling mesh visibility to %s for %s", not hide, root.name)
|
||||
for i in FnModel.iterate_mesh_objects(self.id_data):
|
||||
i.hide_set(hide)
|
||||
i.hide_render = hide
|
||||
@@ -112,27 +120,30 @@ def _toggleVisibilityOfMeshes(self: "MMDRoot", context: bpy.types.Context):
|
||||
FnContext.set_active_object(context, root)
|
||||
|
||||
|
||||
def _toggleVisibilityOfRigidBodies(self: "MMDRoot", context: bpy.types.Context):
|
||||
def _toggleVisibilityOfRigidBodies(self: "MMDRoot", context: bpy.types.Context) -> None:
|
||||
root = self.id_data
|
||||
hide = not self.show_rigid_bodies
|
||||
logger.debug("Toggling rigid body visibility to %s for %s", not hide, root.name)
|
||||
for i in FnModel.iterate_rigid_body_objects(root):
|
||||
i.hide_set(hide)
|
||||
if hide and context.active_object is None:
|
||||
FnContext.set_active_object(context, root)
|
||||
|
||||
|
||||
def _toggleVisibilityOfJoints(self: "MMDRoot", context):
|
||||
def _toggleVisibilityOfJoints(self: "MMDRoot", context: bpy.types.Context) -> None:
|
||||
root_object = self.id_data
|
||||
hide = not self.show_joints
|
||||
logger.debug("Toggling joint visibility to %s for %s", not hide, root_object.name)
|
||||
for i in FnModel.iterate_joint_objects(root_object):
|
||||
i.hide_set(hide)
|
||||
if hide and context.active_object is None:
|
||||
FnContext.set_active_object(context, root_object)
|
||||
|
||||
|
||||
def _toggleVisibilityOfTemporaryObjects(self: "MMDRoot", context: bpy.types.Context):
|
||||
def _toggleVisibilityOfTemporaryObjects(self: "MMDRoot", context: bpy.types.Context) -> None:
|
||||
root_object: bpy.types.Object = self.id_data
|
||||
hide = not self.show_temporary_objects
|
||||
logger.debug("Toggling temporary object visibility to %s for %s", not hide, root_object.name)
|
||||
with FnContext.temp_override_active_layer_collection(context, root_object):
|
||||
for i in FnModel.iterate_temporary_objects(root_object):
|
||||
i.hide_set(hide)
|
||||
@@ -140,45 +151,48 @@ def _toggleVisibilityOfTemporaryObjects(self: "MMDRoot", context: bpy.types.Cont
|
||||
FnContext.set_active_object(context, root_object)
|
||||
|
||||
|
||||
def _toggleShowNamesOfRigidBodies(self: "MMDRoot", _context):
|
||||
def _toggleShowNamesOfRigidBodies(self: "MMDRoot", _context: bpy.types.Context) -> None:
|
||||
root = self.id_data
|
||||
show_names = root.mmd_root.show_names_of_rigid_bodies
|
||||
logger.debug("Toggling rigid body names to %s for %s", show_names, root.name)
|
||||
for i in FnModel.iterate_rigid_body_objects(root):
|
||||
i.show_name = show_names
|
||||
|
||||
|
||||
def _toggleShowNamesOfJoints(self: "MMDRoot", _context):
|
||||
def _toggleShowNamesOfJoints(self: "MMDRoot", _context: bpy.types.Context) -> None:
|
||||
root = self.id_data
|
||||
show_names = root.mmd_root.show_names_of_joints
|
||||
logger.debug("Toggling joint names to %s for %s", show_names, root.name)
|
||||
for i in FnModel.iterate_joint_objects(root):
|
||||
i.show_name = show_names
|
||||
|
||||
|
||||
def _setVisibilityOfMMDRigArmature(prop: "MMDRoot", v: bool):
|
||||
def _setVisibilityOfMMDRigArmature(prop: "MMDRoot", v: bool) -> None:
|
||||
root = prop.id_data
|
||||
arm = FnModel.find_armature_object(root)
|
||||
if arm is None:
|
||||
return
|
||||
if not v and bpy.context.active_object == arm:
|
||||
FnContext.set_active_object(bpy.context, root)
|
||||
logger.debug("Setting armature visibility to %s for %s", v, root.name)
|
||||
arm.hide_set(not v)
|
||||
|
||||
|
||||
def _getVisibilityOfMMDRigArmature(prop: "MMDRoot"):
|
||||
def _getVisibilityOfMMDRigArmature(prop: "MMDRoot") -> bool:
|
||||
if prop.id_data.mmd_type != "ROOT":
|
||||
return False
|
||||
arm = FnModel.find_armature_object(prop.id_data)
|
||||
return arm and not arm.hide_get()
|
||||
|
||||
|
||||
def _setActiveRigidbodyObject(prop: "MMDRoot", v: int):
|
||||
def _setActiveRigidbodyObject(prop: "MMDRoot", v: int) -> None:
|
||||
obj = FnContext.get_scene_objects(bpy.context)[v]
|
||||
if FnModel.is_rigid_body_object(obj):
|
||||
FnContext.set_active_and_select_single_object(bpy.context, obj)
|
||||
prop["active_rigidbody_object_index"] = v
|
||||
|
||||
|
||||
def _getActiveRigidbodyObject(prop: "MMDRoot"):
|
||||
def _getActiveRigidbodyObject(prop: "MMDRoot") -> int:
|
||||
context = bpy.context
|
||||
active_obj = FnContext.get_active_object(context)
|
||||
if FnModel.is_rigid_body_object(active_obj):
|
||||
@@ -186,14 +200,14 @@ def _getActiveRigidbodyObject(prop: "MMDRoot"):
|
||||
return prop.get("active_rigidbody_object_index", 0)
|
||||
|
||||
|
||||
def _setActiveJointObject(prop: "MMDRoot", v: int):
|
||||
def _setActiveJointObject(prop: "MMDRoot", v: int) -> None:
|
||||
obj = FnContext.get_scene_objects(bpy.context)[v]
|
||||
if FnModel.is_joint_object(obj):
|
||||
FnContext.set_active_and_select_single_object(bpy.context, obj)
|
||||
prop["active_joint_object_index"] = v
|
||||
|
||||
|
||||
def _getActiveJointObject(prop: "MMDRoot"):
|
||||
def _getActiveJointObject(prop: "MMDRoot") -> int:
|
||||
context = bpy.context
|
||||
active_obj = FnContext.get_active_object(context)
|
||||
if FnModel.is_joint_object(active_obj):
|
||||
@@ -201,26 +215,26 @@ def _getActiveJointObject(prop: "MMDRoot"):
|
||||
return prop.get("active_joint_object_index", 0)
|
||||
|
||||
|
||||
def _setActiveMorph(prop: "MMDRoot", v: bool):
|
||||
def _setActiveMorph(prop: "MMDRoot", v: bool) -> None:
|
||||
if "active_morph_indices" not in prop:
|
||||
prop["active_morph_indices"] = [0] * 5
|
||||
prop["active_morph_indices"][prop.get("active_morph_type", 3)] = v
|
||||
|
||||
|
||||
def _getActiveMorph(prop: "MMDRoot"):
|
||||
def _getActiveMorph(prop: "MMDRoot") -> int:
|
||||
if "active_morph_indices" in prop:
|
||||
return prop["active_morph_indices"][prop.get("active_morph_type", 3)]
|
||||
return 0
|
||||
|
||||
|
||||
def _setActiveMeshObject(prop: "MMDRoot", v: int):
|
||||
def _setActiveMeshObject(prop: "MMDRoot", v: int) -> None:
|
||||
obj = FnContext.get_scene_objects(bpy.context)[v]
|
||||
if FnModel.is_mesh_object(obj):
|
||||
FnContext.set_active_and_select_single_object(bpy.context, obj)
|
||||
prop["active_mesh_index"] = v
|
||||
|
||||
|
||||
def _getActiveMeshObject(prop: "MMDRoot"):
|
||||
def _getActiveMeshObject(prop: "MMDRoot") -> int:
|
||||
context = bpy.context
|
||||
active_obj = FnContext.get_active_object(context)
|
||||
if FnModel.is_mesh_object(active_obj):
|
||||
@@ -520,7 +534,8 @@ class MMDRoot(bpy.types.PropertyGroup):
|
||||
prop.hide_viewport = value
|
||||
|
||||
@staticmethod
|
||||
def register():
|
||||
def register() -> None:
|
||||
logger.debug("Registering MMDRoot property group")
|
||||
bpy.types.Object.mmd_type = patch_library_overridable(
|
||||
bpy.props.EnumProperty(
|
||||
name="Type",
|
||||
@@ -570,7 +585,8 @@ class MMDRoot(bpy.types.PropertyGroup):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
logger.debug("Unregistering MMDRoot property group")
|
||||
del bpy.types.Object.hide
|
||||
del bpy.types.Object.select
|
||||
del bpy.types.Object.mmd_root
|
||||
|
||||
Reference in New Issue
Block a user