Update Logging
You can choose between errors, warning, info or full debug, errors will always log to ensure we don't have silent failures with debug on or off.
This commit is contained in:
@@ -0,0 +1,579 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2014 MMD Tools authors
|
||||
# This file was originally part of the MMD Tools add-on for Blender
|
||||
# You can find MMD Tools here: https://github.com/MMD-Blender/blender_mmd_tools
|
||||
# Neoneko has modified this file to work with Avatar Toolkit and may of made changes or improvements.
|
||||
# MMD Tools is licensed under the terms of the GNU General Public License version 3 (GPLv3) same as Avatar Toolkit.
|
||||
|
||||
import math
|
||||
from typing import Dict, Optional, Tuple, cast
|
||||
|
||||
import bpy
|
||||
from mathutils import Euler, Vector
|
||||
|
||||
from .. import utils
|
||||
from ..bpyutils import FnContext, Props
|
||||
from ..core import rigid_body
|
||||
from ..core.model import FnModel, Model
|
||||
from ..core.rigid_body import FnRigidBody
|
||||
|
||||
|
||||
class SelectRigidBody(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.rigid_body_select"
|
||||
bl_label = "Select Rigid Body"
|
||||
bl_description = "Select similar rigidbody objects which have the same property values with active rigidbody object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
properties: bpy.props.EnumProperty(
|
||||
name="Properties",
|
||||
description="Select the properties to be compared",
|
||||
options={"ENUM_FLAG"},
|
||||
items=[
|
||||
("collision_group_number", "Collision Group", "Collision group", 1),
|
||||
("collision_group_mask", "Collision Group Mask", "Collision group mask", 2),
|
||||
("type", "Rigid Type", "Rigid type", 4),
|
||||
("shape", "Shape", "Collision shape", 8),
|
||||
("bone", "Bone", "Target bone", 16),
|
||||
],
|
||||
default=set(),
|
||||
)
|
||||
hide_others: bpy.props.BoolProperty(
|
||||
name="Hide Others",
|
||||
description="Hide the rigidbody object which does not have the same property values with active rigidbody object",
|
||||
default=False,
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return FnModel.is_rigid_body_object(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
if root is None:
|
||||
self.report({"ERROR"}, "The model root can't be found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
selection = set(FnModel.iterate_rigid_body_objects(root))
|
||||
|
||||
for prop_name in self.properties:
|
||||
prop_value = getattr(obj.mmd_rigid, prop_name)
|
||||
if prop_name == "collision_group_mask":
|
||||
prop_value = tuple(prop_value)
|
||||
for i in selection.copy():
|
||||
if tuple(i.mmd_rigid.collision_group_mask) != prop_value:
|
||||
selection.remove(i)
|
||||
if self.hide_others:
|
||||
i.select_set(False)
|
||||
i.hide_set(True)
|
||||
else:
|
||||
for i in selection.copy():
|
||||
if getattr(i.mmd_rigid, prop_name) != prop_value:
|
||||
selection.remove(i)
|
||||
if self.hide_others:
|
||||
i.select_set(False)
|
||||
i.hide_set(True)
|
||||
|
||||
for i in selection:
|
||||
i.hide_set(False)
|
||||
i.select_set(True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddRigidBody(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.rigid_body_add"
|
||||
bl_label = "Add Rigid Body"
|
||||
bl_description = "Add Rigid Bodies to selected bones"
|
||||
bl_options = {"REGISTER", "UNDO", "PRESET", "INTERNAL"}
|
||||
|
||||
name_j: bpy.props.StringProperty(
|
||||
name="Name",
|
||||
description="The name of rigid body ($name_j means use the japanese name of target bone)",
|
||||
default="$name_j",
|
||||
)
|
||||
name_e: bpy.props.StringProperty(
|
||||
name="Name(Eng)",
|
||||
description="The english name of rigid body ($name_e means use the english name of target bone)",
|
||||
default="$name_e",
|
||||
)
|
||||
collision_group_number: bpy.props.IntProperty(
|
||||
name="Collision Group",
|
||||
description="The collision group of the object",
|
||||
min=0,
|
||||
max=15,
|
||||
)
|
||||
collision_group_mask: bpy.props.BoolVectorProperty(
|
||||
name="Collision Group Mask",
|
||||
description="The groups the object can not collide with",
|
||||
size=16,
|
||||
subtype="LAYER",
|
||||
)
|
||||
rigid_type: bpy.props.EnumProperty(
|
||||
name="Rigid Type",
|
||||
description="Select rigid type",
|
||||
items=[
|
||||
(str(rigid_body.MODE_STATIC), "Bone", "Rigid body's orientation completely determined by attached bone", 1),
|
||||
(str(rigid_body.MODE_DYNAMIC), "Physics", "Attached bone's orientation completely determined by rigid body", 2),
|
||||
(str(rigid_body.MODE_DYNAMIC_BONE), "Physics + Bone", "Bone determined by combination of parent and attached rigid body", 3),
|
||||
],
|
||||
)
|
||||
rigid_shape: bpy.props.EnumProperty(
|
||||
name="Shape",
|
||||
description="Select the collision shape",
|
||||
items=[
|
||||
("SPHERE", "Sphere", "", 1),
|
||||
("BOX", "Box", "", 2),
|
||||
("CAPSULE", "Capsule", "", 3),
|
||||
],
|
||||
)
|
||||
size: bpy.props.FloatVectorProperty(
|
||||
name="Size",
|
||||
description="Size of the object, the values will multiply the length of target bone",
|
||||
subtype="XYZ",
|
||||
size=3,
|
||||
min=0,
|
||||
default=[0.6, 0.6, 0.6],
|
||||
)
|
||||
mass: bpy.props.FloatProperty(
|
||||
name="Mass",
|
||||
description="How much the object 'weights' irrespective of gravity",
|
||||
min=0.001,
|
||||
default=1,
|
||||
)
|
||||
friction: bpy.props.FloatProperty(
|
||||
name="Friction",
|
||||
description="Resistance of object to movement",
|
||||
min=0,
|
||||
soft_max=1,
|
||||
default=0.5,
|
||||
)
|
||||
bounce: bpy.props.FloatProperty(
|
||||
name="Restitution",
|
||||
description="Tendency of object to bounce after colliding with another (0 = stays still, 1 = perfectly elastic)",
|
||||
min=0,
|
||||
soft_max=1,
|
||||
)
|
||||
linear_damping: bpy.props.FloatProperty(
|
||||
name="Linear Damping",
|
||||
description="Amount of linear velocity that is lost over time",
|
||||
min=0,
|
||||
max=1,
|
||||
default=0.04,
|
||||
)
|
||||
angular_damping: bpy.props.FloatProperty(
|
||||
name="Angular Damping",
|
||||
description="Amount of angular velocity that is lost over time",
|
||||
min=0,
|
||||
max=1,
|
||||
default=0.1,
|
||||
)
|
||||
|
||||
def __add_rigid_body(self, context: bpy.types.Context, root_object: bpy.types.Object, pose_bone: Optional[bpy.types.PoseBone] = None):
|
||||
name_j: str = self.name_j
|
||||
name_e: str = self.name_e
|
||||
size = self.size.copy()
|
||||
loc = Vector((0.0, 0.0, 0.0))
|
||||
rot = Euler((0.0, 0.0, 0.0))
|
||||
bone_name: Optional[str] = None
|
||||
|
||||
if pose_bone is None:
|
||||
size *= getattr(root_object, Props.empty_display_size)
|
||||
else:
|
||||
bone_name = pose_bone.name
|
||||
mmd_bone = pose_bone.mmd_bone
|
||||
name_j = name_j.replace("$name_j", mmd_bone.name_j or bone_name)
|
||||
name_e = name_e.replace("$name_e", mmd_bone.name_e or bone_name)
|
||||
|
||||
target_bone = pose_bone.bone
|
||||
loc = (target_bone.head_local + target_bone.tail_local) / 2
|
||||
rot = target_bone.matrix_local.to_euler("YXZ")
|
||||
rot.rotate_axis("X", math.pi / 2)
|
||||
|
||||
size *= target_bone.length
|
||||
if 1:
|
||||
pass # bypass resizing
|
||||
elif self.rigid_shape == "SPHERE":
|
||||
size.x *= 0.8
|
||||
elif self.rigid_shape == "BOX":
|
||||
size.x /= 3
|
||||
size.y /= 3
|
||||
size.z *= 0.8
|
||||
elif self.rigid_shape == "CAPSULE":
|
||||
size.x /= 3
|
||||
|
||||
return FnRigidBody.setup_rigid_body_object(
|
||||
obj=FnRigidBody.new_rigid_body_object(context, FnModel.ensure_rigid_group_object(context, root_object)),
|
||||
shape_type=rigid_body.shapeType(self.rigid_shape),
|
||||
location=loc,
|
||||
rotation=rot,
|
||||
size=size,
|
||||
dynamics_type=int(self.rigid_type),
|
||||
name=name_j,
|
||||
name_e=name_e,
|
||||
collision_group_number=self.collision_group_number,
|
||||
collision_group_mask=self.collision_group_mask,
|
||||
mass=self.mass,
|
||||
friction=self.friction,
|
||||
bounce=self.bounce,
|
||||
linear_damping=self.linear_damping,
|
||||
angular_damping=self.angular_damping,
|
||||
bone=bone_name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
root_object = FnModel.find_root_object(context.active_object)
|
||||
if root_object is None:
|
||||
return False
|
||||
|
||||
armature_object = FnModel.find_armature_object(root_object)
|
||||
if armature_object is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
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))
|
||||
|
||||
if active_object != armature_object:
|
||||
FnContext.select_single_object(context, root_object).select_set(False)
|
||||
elif armature_object.mode != "POSE":
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
|
||||
selected_pose_bones = []
|
||||
if context.selected_pose_bones:
|
||||
selected_pose_bones = context.selected_pose_bones
|
||||
|
||||
armature_object.select_set(False)
|
||||
if len(selected_pose_bones) > 0:
|
||||
for pose_bone in selected_pose_bones:
|
||||
rigid = self.__add_rigid_body(context, root_object, pose_bone)
|
||||
rigid.select_set(True)
|
||||
else:
|
||||
rigid = self.__add_rigid_body(context, root_object)
|
||||
rigid.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
no_bone = True
|
||||
if context.selected_bones and len(context.selected_bones) > 0:
|
||||
no_bone = False
|
||||
elif context.selected_pose_bones and len(context.selected_pose_bones) > 0:
|
||||
no_bone = False
|
||||
|
||||
if no_bone:
|
||||
self.name_j = "Rigid"
|
||||
self.name_e = "Rigid_e"
|
||||
else:
|
||||
if self.name_j == "Rigid":
|
||||
self.name_j = "$name_j"
|
||||
if self.name_e == "Rigid_e":
|
||||
self.name_e = "$name_e"
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class RemoveRigidBody(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.rigid_body_remove"
|
||||
bl_label = "Remove Rigid Body"
|
||||
bl_description = "Deletes the currently selected Rigid Body"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return FnModel.is_rigid_body_object(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
utils.selectAObject(obj) # ensure this is the only one object select
|
||||
bpy.ops.object.delete(use_global=True)
|
||||
if root:
|
||||
utils.selectAObject(root)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RigidBodyBake(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.ptcache_rigid_body_bake"
|
||||
bl_label = "Bake"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
with context.temp_override(scene=context.scene, point_cache=context.scene.rigidbody_world.point_cache):
|
||||
bpy.ops.ptcache.bake("INVOKE_DEFAULT", bake=True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RigidBodyDeleteBake(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.ptcache_rigid_body_delete_bake"
|
||||
bl_label = "Delete Bake"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
with context.temp_override(scene=context.scene, point_cache=context.scene.rigidbody_world.point_cache):
|
||||
bpy.ops.ptcache.free_bake("INVOKE_DEFAULT")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddJoint(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.joint_add"
|
||||
bl_label = "Add Joint"
|
||||
bl_description = "Add Joint(s) to selected rigidbody objects"
|
||||
bl_options = {"REGISTER", "UNDO", "PRESET", "INTERNAL"}
|
||||
|
||||
use_bone_rotation: bpy.props.BoolProperty(
|
||||
name="Use Bone Rotation",
|
||||
description="Match joint orientation to bone orientation if enabled",
|
||||
default=True,
|
||||
)
|
||||
limit_linear_lower: bpy.props.FloatVectorProperty(
|
||||
name="Limit Linear Lower",
|
||||
description="Lower limit of translation",
|
||||
subtype="XYZ",
|
||||
size=3,
|
||||
)
|
||||
limit_linear_upper: bpy.props.FloatVectorProperty(
|
||||
name="Limit Linear Upper",
|
||||
description="Upper limit of translation",
|
||||
subtype="XYZ",
|
||||
size=3,
|
||||
)
|
||||
limit_angular_lower: bpy.props.FloatVectorProperty(
|
||||
name="Limit Angular Lower",
|
||||
description="Lower limit of rotation",
|
||||
subtype="EULER",
|
||||
size=3,
|
||||
min=-math.pi * 2,
|
||||
max=math.pi * 2,
|
||||
default=[-math.pi / 4] * 3,
|
||||
)
|
||||
limit_angular_upper: bpy.props.FloatVectorProperty(
|
||||
name="Limit Angular Upper",
|
||||
description="Upper limit of rotation",
|
||||
subtype="EULER",
|
||||
size=3,
|
||||
min=-math.pi * 2,
|
||||
max=math.pi * 2,
|
||||
default=[math.pi / 4] * 3,
|
||||
)
|
||||
spring_linear: bpy.props.FloatVectorProperty(
|
||||
name="Spring(Linear)",
|
||||
description="Spring constant of movement",
|
||||
subtype="XYZ",
|
||||
size=3,
|
||||
min=0,
|
||||
)
|
||||
spring_angular: bpy.props.FloatVectorProperty(
|
||||
name="Spring(Angular)",
|
||||
description="Spring constant of rotation",
|
||||
subtype="XYZ",
|
||||
size=3,
|
||||
min=0,
|
||||
)
|
||||
|
||||
def __enumerate_rigid_pair(self, bone_map: Dict[bpy.types.Object, Optional[bpy.types.Bone]]):
|
||||
obj_seq = tuple(bone_map.keys())
|
||||
for rigid_a, bone_a in bone_map.items():
|
||||
for rigid_b, bone_b in bone_map.items():
|
||||
if bone_a and bone_b and bone_b.parent == bone_a:
|
||||
obj_seq = ()
|
||||
yield (rigid_a, rigid_b)
|
||||
if len(obj_seq) == 2:
|
||||
if obj_seq[1].mmd_rigid.type == str(rigid_body.MODE_STATIC):
|
||||
yield (obj_seq[1], obj_seq[0])
|
||||
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):
|
||||
loc: Optional[Vector] = None
|
||||
rot = Euler((0.0, 0.0, 0.0))
|
||||
rigid_a, rigid_b = rigid_pair
|
||||
bone_a = bone_map[rigid_a]
|
||||
bone_b = bone_map[rigid_b]
|
||||
if bone_a and bone_b:
|
||||
if bone_a.parent == bone_b:
|
||||
rigid_b, rigid_a = rigid_a, rigid_b
|
||||
bone_b, bone_a = bone_a, bone_b
|
||||
if bone_b.parent == bone_a:
|
||||
loc = bone_b.head_local
|
||||
if self.use_bone_rotation:
|
||||
rot = bone_b.matrix_local.to_euler("YXZ")
|
||||
rot.rotate_axis("X", math.pi / 2)
|
||||
if loc is None:
|
||||
loc = (rigid_a.location + rigid_b.location) / 2
|
||||
|
||||
name_j = rigid_b.mmd_rigid.name_j or rigid_b.name
|
||||
name_e = rigid_b.mmd_rigid.name_e or rigid_b.name
|
||||
|
||||
return FnRigidBody.setup_joint_object(
|
||||
obj=FnRigidBody.new_joint_object(context, FnModel.ensure_joint_group_object(context, root_object), FnModel.get_empty_display_size(root_object)),
|
||||
name=name_j,
|
||||
name_e=name_e,
|
||||
location=loc,
|
||||
rotation=rot,
|
||||
rigid_a=rigid_a,
|
||||
rigid_b=rigid_b,
|
||||
maximum_location=self.limit_linear_upper,
|
||||
minimum_location=self.limit_linear_lower,
|
||||
maximum_rotation=self.limit_angular_upper,
|
||||
minimum_rotation=self.limit_angular_lower,
|
||||
spring_linear=self.spring_linear,
|
||||
spring_angular=self.spring_angular,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
root_object = FnModel.find_root_object(context.active_object)
|
||||
if root_object is None:
|
||||
return False
|
||||
|
||||
armature_object = FnModel.find_armature_object(root_object)
|
||||
if armature_object is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
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))
|
||||
bones = cast(bpy.types.Armature, armature_object.data).bones
|
||||
bone_map: Dict[bpy.types.Object, Optional[bpy.types.Bone]] = {r: bones.get(r.mmd_rigid.bone, None) for r in FnModel.iterate_rigid_body_objects(root_object) if r.select_get()}
|
||||
|
||||
if len(bone_map) < 2:
|
||||
self.report({"ERROR"}, "Please select two or more mmd rigid objects")
|
||||
return {"CANCELLED"}
|
||||
|
||||
FnContext.select_single_object(context, root_object).select_set(False)
|
||||
if context.scene.rigidbody_world is None:
|
||||
bpy.ops.rigidbody.world_add()
|
||||
|
||||
for pair in self.__enumerate_rigid_pair(bone_map):
|
||||
joint = self.__add_joint(context, root_object, pair, bone_map)
|
||||
joint.select_set(True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
vm = context.window_manager
|
||||
return vm.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class RemoveJoint(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.joint_remove"
|
||||
bl_label = "Remove Joint"
|
||||
bl_description = "Deletes the currently selected Joint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return FnModel.is_joint_object(context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
root = FnModel.find_root_object(obj)
|
||||
utils.selectAObject(obj) # ensure this is the only one object select
|
||||
bpy.ops.object.delete(use_global=True)
|
||||
if root:
|
||||
utils.selectAObject(root)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UpdateRigidBodyWorld(bpy.types.Operator):
|
||||
bl_idname = "mmd_tools.rigid_body_world_update"
|
||||
bl_label = "Update Rigid Body World"
|
||||
bl_description = "Update rigid body world and references of rigid body constraint according to current scene objects (experimental)"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@staticmethod
|
||||
def __get_rigid_body_world_objects():
|
||||
rigid_body.setRigidBodyWorldEnabled(True)
|
||||
rbw = bpy.context.scene.rigidbody_world
|
||||
if not rbw.collection:
|
||||
rbw.collection = bpy.data.collections.new("RigidBodyWorld")
|
||||
rbw.collection.use_fake_user = True
|
||||
if not rbw.constraints:
|
||||
rbw.constraints = bpy.data.collections.new("RigidBodyConstraints")
|
||||
rbw.constraints.use_fake_user = True
|
||||
|
||||
bpy.context.scene.rigidbody_world.substeps_per_frame = 6
|
||||
bpy.context.scene.rigidbody_world.solver_iterations = 10
|
||||
|
||||
return rbw.collection.objects, rbw.constraints.objects
|
||||
|
||||
def execute(self, context):
|
||||
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):
|
||||
if obj in scene_objs:
|
||||
if obj not in group.values():
|
||||
group.link(obj)
|
||||
return True
|
||||
elif obj in group.values():
|
||||
group.unlink(obj)
|
||||
return False
|
||||
|
||||
def _references(obj):
|
||||
yield obj
|
||||
if getattr(obj, "proxy", None):
|
||||
yield from _references(obj.proxy)
|
||||
if getattr(obj, "override_library", None):
|
||||
yield from _references(obj.override_library.reference)
|
||||
|
||||
need_rebuild_physics = scene.rigidbody_world is None or scene.rigidbody_world.collection is None or scene.rigidbody_world.constraints is None
|
||||
rb_objs, rbc_objs = self.__get_rigid_body_world_objects()
|
||||
objects = bpy.data.objects
|
||||
table = {}
|
||||
|
||||
# Perhaps due to a bug in Blender,
|
||||
# when bpy.ops.rigidbody.world_remove(),
|
||||
# Object.rigid_body are removed,
|
||||
# but Object.rigid_body_constraint are retained.
|
||||
# Therefore, it must be checked with Object.mmd_type.
|
||||
for i in (x for x in objects if x.mmd_type == "RIGID_BODY"):
|
||||
if not _update_group(i, rb_objs):
|
||||
continue
|
||||
|
||||
rb_map = table.setdefault(FnModel.find_root_object(i), {})
|
||||
if i in rb_map: # means rb_map[i] will replace i
|
||||
rb_objs.unlink(i)
|
||||
continue
|
||||
for r in _references(i):
|
||||
rb_map[r] = i
|
||||
|
||||
# TODO Modify mmd_rigid to allow recovery of the remaining rigidbody parameters.
|
||||
# mass, friction, restitution, linear_dumping, angular_dumping
|
||||
|
||||
for i in (x for x in objects if x.rigid_body_constraint):
|
||||
if not _update_group(i, rbc_objs):
|
||||
continue
|
||||
|
||||
rbc, root_object = i.rigid_body_constraint, FnModel.find_root_object(i)
|
||||
rb_map = table.get(root_object, {})
|
||||
rbc.object1 = rb_map.get(rbc.object1, rbc.object1)
|
||||
rbc.object2 = rb_map.get(rbc.object2, rbc.object2)
|
||||
|
||||
if need_rebuild_physics:
|
||||
for root_object in scene.objects:
|
||||
if root_object.mmd_type != "ROOT":
|
||||
continue
|
||||
if not root_object.mmd_root.is_built:
|
||||
continue
|
||||
with FnContext.temp_override_active_layer_collection(context, root_object):
|
||||
Model(root_object).build()
|
||||
# After rebuild. First play. Will be crash!
|
||||
# But saved it before. Reload after crash. The play can be work.
|
||||
|
||||
return {"FINISHED"}
|
||||
Reference in New Issue
Block a user