99from ...editors .meshes import MeshEditor
1010from ...m17n import _
1111from ...tuners import TunerABC , TunerRegistry
12- from ...utilities import MessageException , import_mmd_tools
12+ from ...utilities import MessageException , MMD_TOOLS_IMPORT_HOOKS , import_mmd_tools
1313from .rigid_body_to_cloth import (
1414 PhysicsMode ,
1515 RigidBodyToClothConverter ,
1616)
1717
18- mmd_tools = import_mmd_tools ()
19- if not hasattr (mmd_tools .core .model .Model , "clothGroupObject" ):
18+
19+ def on_import_mmd_tools_ensure_cloth_methods (mmd_tools ):
20+ """
21+ Monkey-patch helper that augments mmd_tools.core.model.Model with cloth-related
22+ convenience methods if they are not already present.
23+
24+ Behavior:
25+ 1. Idempotency: Returns immediately if Model already has the attribute
26+ 'clothGroupObject', preventing duplicate patching.
27+ 2. Adds Model.clothGroupObject(self):
28+ - Lazily resolves (and caches on the instance as _cloth_grp) a dedicated
29+ container object named "cloths" parented to the model's root object.
30+ - Searches existing children of rootObject() for an object named "cloths".
31+ - If absent, creates a new empty object via mmd_tools.bpyutils.FnContext.new_and_link_object.
32+ - Configures the new object to be:
33+ * Hidden and unselectable (hide, hide_select = True)
34+ * Transform-locked on location, rotation, and scale (all axes True)
35+ - Returns the resolved/created container object.
36+ 3. Adds Model.cloths(self):
37+ - Iterates over all objects returned by Model.allObjects(clothGroupObject()).
38+ - Yields only MESH type objects that have an existing cloth modifier, as
39+ determined by MeshEditor(obj).find_cloth_modifier().
40+
41+ Side Effects:
42+ - Mutates the mmd_tools.core.model.Model class by attaching two methods:
43+ 'clothGroupObject' and 'cloths'.
44+ - May create and link a new helper object named "cloths" into the current
45+ Blender context's scene hierarchy.
46+
47+ Caching Details:
48+ - The resolved group object is stored per-model instance in the private
49+ attribute _cloth_grp to avoid repeated scene searches or object creation.
50+
51+ Prerequisites:
52+ - A functioning import_mmd_tools() utility returning the mmd_tools module.
53+ - Availability of bpy (Blender Python API) and MeshEditor in the execution environment.
54+
55+ Returns:
56+ None
57+
58+ Raises:
59+ None (all operations are performed defensively; absence of expected API
60+ components would surface as AttributeError at runtime).
61+
62+ Usage Pattern:
63+ ensure_cloth_methods()
64+ model = mmd_tools.core.model.Model(...)
65+ grp = model.clothGroupObject()
66+ for cloth_obj in model.cloths():
67+ ...
68+
69+ Rationale:
70+ - Centralizes the patch so add-on code can safely call ensure_cloth_methods()
71+ before relying on the extended API, without risking duplicate definitions.
72+ """
73+
74+ if hasattr (mmd_tools .core .model .Model , "clothGroupObject" ):
75+ return
2076
2177 def mmd_model_cloth_group_object_method (self ):
2278 # pylint: disable=protected-access
@@ -50,17 +106,12 @@ def mmd_model_cloths_method(self):
50106 continue
51107 yield obj
52108
53- def iterate_joint_objects (
54- root_object : bpy .types .Object ,
55- ) -> Iterator [bpy .types .Object ]:
56- return mmd_tools .core .model .FnModel .iterate_filtered_child_objects (
57- mmd_tools .core .model .FnModel .is_joint_object ,
58- mmd_tools .core .model .FnModel .find_joint_group_object (root_object ),
59- )
60-
61109 mmd_tools .core .model .Model .cloths = mmd_model_cloths_method
62110
63111
112+ MMD_TOOLS_IMPORT_HOOKS .append (on_import_mmd_tools_ensure_cloth_methods )
113+
114+
64115class ClothTunerABC (TunerABC , MeshEditor ):
65116 pass
66117
@@ -268,6 +319,7 @@ def poll(cls, context: bpy.types.Context):
268319 def filter_only_in_mmd_model (
269320 key_object : bpy .types .Object ,
270321 ) -> Iterable [bpy .types .Object ]:
322+ mmd_tools = import_mmd_tools ()
271323 mmd_root = mmd_tools .core .model .FnModel .find_root_object (key_object )
272324 if mmd_root is None :
273325 return
@@ -362,7 +414,7 @@ def poll(cls, context: bpy.types.Context):
362414 selected_mesh_mmd_root = None
363415 selected_rigid_body_mmd_root = None
364416
365- mmd_find_root = mmd_tools .core .model .FnModel .find_root_object
417+ mmd_find_root = import_mmd_tools () .core .model .FnModel .find_root_object
366418 for obj in context .selected_objects :
367419 if obj .type != "MESH" :
368420 return False
@@ -382,7 +434,7 @@ def invoke(self, context, event):
382434
383435 def execute (self , context : bpy .types .Context ):
384436 try :
385- mmd_find_root = mmd_tools .core .model .FnModel .find_root_object
437+ mmd_find_root = import_mmd_tools () .core .model .FnModel .find_root_object
386438
387439 target_mmd_root_object = None
388440 rigid_body_objects : List [bpy .types .Object ] = []
0 commit comments