Files
Yusarina cfe760e8df 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.
2025-04-23 00:43:38 +01:00

304 lines
9.1 KiB
Python

# -*- 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