# -*- 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. """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: 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: 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: PropertyGroup, _context: Context) -> None: obj: Object = prop.id_data if len(obj.data.vertices) > 0: size = prop.size prop.size = size # update mesh rb = obj.rigid_body if rb: rb.collision_shape = prop.shape 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 bone_name = relation.subtarget if arm is not None and bone_name in arm.data.bones: return bone_name return prop.get("bone", "") 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") relation.name = "mmd_tools_rigid_parent" relation.mute = True arm = relation.target if arm is None: root = FnModel.find_root_object(obj) if root: arm = relation.target = FnModel.find_armature_object(root) if arm is not None and bone_name in arm.data.bones: relation.subtarget = bone_name else: relation.subtarget = bone_name = "" prop["bone"] = bone_name 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: PropertyGroup, value: Tuple[float, float, float]) -> None: obj: Object = prop.id_data assert obj.mode == "OBJECT" # not support other mode yet shape: str = prop.shape mesh = obj.data rb = obj.rigid_body if len(mesh.vertices) == 0 or rb is None or rb.collision_shape != shape: if shape == "SPHERE": bpyutils.makeSphere( radius=value[0], target_object=obj, ) elif shape == "BOX": bpyutils.makeBox( size=value, target_object=obj, ) elif shape == "CAPSULE": bpyutils.makeCapsule( radius=value[0], height=value[1], target_object=obj, ) mesh.update() if rb: rb.collision_shape = shape else: if shape == "SPHERE": radius = max(value[0], 1e-3) for v in mesh.vertices: vec = v.co.normalized() v.co = vec * radius elif shape == "BOX": x = max(value[0], 1e-3) y = max(value[1], 1e-3) z = max(value[2], 1e-3) for v in mesh.vertices: x0, y0, z0 = v.co x0 = -x if x0 < 0 else x y0 = -y if y0 < 0 else y z0 = -z if z0 < 0 else z v.co = [x0, y0, z0] elif shape == "CAPSULE": r0, h0, xx = FnRigidBody.get_rigid_body_size(prop.id_data) h0 *= 0.5 radius = max(value[0], 1e-3) height = max(value[1], 1e-3) * 0.5 scale = radius / max(r0, 1e-3) for v in mesh.vertices: x0, y0, z0 = v.co x0 *= scale y0 *= scale if z0 < 0: z0 = (z0 + h0) * scale - height else: z0 = (z0 - h0) * scale + height v.co = [x0, y0, z0] mesh.update() def _get_rigid_name(prop: PropertyGroup) -> str: return prop.get("name", "") def _set_rigid_name(prop: PropertyGroup, value: str) -> None: prop["name"] = value class MMDRigidBody(PropertyGroup): name_j: bpy.props.StringProperty( name="Name", description="Japanese Name", default="", get=_get_rigid_name, set=_set_rigid_name, ) name_e: bpy.props.StringProperty( name="Name(Eng)", description="English Name", default="", ) collision_group_number: bpy.props.IntProperty( name="Collision Group", description="The collision group of the object", min=0, max=15, default=1, update=_updateCollisionGroup, ) collision_group_mask: bpy.props.BoolVectorProperty( name="Collision Group Mask", description="The groups the object can not collide with", size=16, subtype="LAYER", ) 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), ], update=_updateType, ) shape: bpy.props.EnumProperty( name="Shape", description="Select the collision shape", items=[ ("SPHERE", "Sphere", "", 1), ("BOX", "Box", "", 2), ("CAPSULE", "Capsule", "", 3), ], update=_updateShape, ) bone: bpy.props.StringProperty( name="Bone", description="Target bone", default="", get=_get_bone, set=_set_bone, ) size: bpy.props.FloatVectorProperty( name="Size", description="Size of the object", subtype="XYZ", size=3, min=0, step=0.1, get=_get_size, set=_set_size, ) @staticmethod def register() -> None: logger.debug("Registering MMDRigidBody property") bpy.types.Object.mmd_rigid = patch_library_overridable(bpy.props.PointerProperty(type=MMDRigidBody)) @staticmethod def unregister() -> None: logger.debug("Unregistering MMDRigidBody property") del bpy.types.Object.mmd_rigid 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] rbc.spring_stiffness_y = prop.spring_linear[1] rbc.spring_stiffness_z = prop.spring_linear[2] 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] rbc.spring_stiffness_ang_y = prop.spring_angular[1] rbc.spring_stiffness_ang_z = prop.spring_angular[2] class MMDJoint(PropertyGroup): name_j: bpy.props.StringProperty( name="Name", description="Japanese Name", default="", ) name_e: bpy.props.StringProperty( name="Name(Eng)", description="English Name", default="", ) spring_linear: bpy.props.FloatVectorProperty( name="Spring(Linear)", description="Spring constant of movement", subtype="XYZ", size=3, min=0, step=0.1, update=_updateSpringLinear, ) spring_angular: bpy.props.FloatVectorProperty( name="Spring(Angular)", description="Spring constant of rotation", subtype="XYZ", size=3, min=0, step=0.1, update=_updateSpringAngular, ) @staticmethod def register() -> None: logger.debug("Registering MMDJoint property") bpy.types.Object.mmd_joint = patch_library_overridable(bpy.props.PointerProperty(type=MMDJoint)) @staticmethod def unregister() -> None: logger.debug("Unregistering MMDJoint property") del bpy.types.Object.mmd_joint