# SPDX-License-Identifier: MIT OR GPL-3.0-or-later from collections.abc import Set as AbstractSet from typing import Optional from bpy.types import Armature, Context, Object, Panel, UILayout from .. import search from ..extension import get_armature_extension from ..migration import defer_migrate from ..panel import VRM_PT_vrm_armature_object_property, draw_template_list from ..search import active_object_is_vrm1_armature from . import ops from .property_group import ( SpringBone1ColliderGroupPropertyGroup, SpringBone1ColliderPropertyGroup, SpringBone1JointPropertyGroup, SpringBone1SpringBonePropertyGroup, SpringBone1SpringPropertyGroup, ) from .ui_list import ( VRM_UL_spring_bone1_collider, VRM_UL_spring_bone1_collider_group, VRM_UL_spring_bone1_collider_group_collider, VRM_UL_spring_bone1_joint, VRM_UL_spring_bone1_spring, VRM_UL_spring_bone1_spring_collider_group, ) def draw_spring_bone1_collider_sphere_layout( layout: UILayout, armature_data: Armature, collider: SpringBone1ColliderPropertyGroup, ) -> None: layout.prop_search(collider.node, "bone_name", armature_data, "bones") layout.prop(collider, "ui_collider_type") bpy_object = collider.bpy_object if bpy_object: layout.prop(bpy_object, "name", icon="MESH_UVSPHERE", text="Name") layout.prop(collider.shape.sphere, "offset", text="Offset") layout.separator(factor=0.5) layout.prop(collider.shape.sphere, "radius", slider=True) layout.prop(collider.extensions.vrmc_spring_bone_extended_collider, "enabled") def draw_spring_bone1_collider_capsule_layout( layout: UILayout, armature_data: Armature, collider: SpringBone1ColliderPropertyGroup, ) -> None: layout.prop_search(collider.node, "bone_name", armature_data, "bones") layout.prop(collider, "ui_collider_type") bpy_object = collider.bpy_object if bpy_object: layout.prop(bpy_object, "name", icon="MESH_UVSPHERE", text="Head") layout.prop(collider.shape.capsule, "offset", text="") layout.separator(factor=0.5) layout.prop(collider.shape.capsule, "radius", slider=True) layout.separator(factor=0.5) if bpy_object and bpy_object.children: layout.prop( bpy_object.children[0], "name", icon="MESH_UVSPHERE", text="Tail", ) layout.prop(collider.shape.capsule, "tail", text="") layout.prop(collider.extensions.vrmc_spring_bone_extended_collider, "enabled") def draw_spring_bone1_collider_extended_fallback_layout( layout: UILayout, _armature_data: Armature, collider: SpringBone1ColliderPropertyGroup, ) -> None: extended = collider.extensions.vrmc_spring_bone_extended_collider layout.prop(extended, "automatic_fallback_generation") if extended.automatic_fallback_generation: return layout.prop(collider, "shape_type", text="Fallback") if collider.shape_type == collider.SHAPE_TYPE_SPHERE.identifier: layout.prop(collider.shape.sphere, "fallback_offset") layout.separator(factor=0.5) layout.prop(collider.shape.sphere, "fallback_radius", slider=True) elif collider.shape_type == collider.SHAPE_TYPE_CAPSULE.identifier: layout.prop(collider.shape.capsule, "fallback_offset") layout.separator(factor=0.5) layout.prop(collider.shape.capsule, "fallback_radius", slider=True) layout.separator(factor=0.5) layout.prop(collider.shape.capsule, "fallback_tail") def draw_spring_bone1_collider_extended_sphere_layout( layout: UILayout, armature_data: Armature, collider: SpringBone1ColliderPropertyGroup, ) -> None: extended = collider.extensions.vrmc_spring_bone_extended_collider layout.prop_search(collider.node, "bone_name", armature_data, "bones") layout.prop(collider, "ui_collider_type") bpy_object = collider.bpy_object if bpy_object: layout.prop(bpy_object, "name", icon="MESH_UVSPHERE", text="Offset") layout.prop(extended.shape.sphere, "offset", text="") layout.separator(factor=0.5) layout.prop(extended.shape.sphere, "radius", slider=True) if not (extended.enabled and extended.shape.sphere.inside): layout.prop(extended, "enabled") draw_spring_bone1_collider_extended_fallback_layout( layout, armature_data, collider, ) def draw_spring_bone1_collider_extended_capsule_layout( layout: UILayout, armature_data: Armature, collider: SpringBone1ColliderPropertyGroup, ) -> None: extended = collider.extensions.vrmc_spring_bone_extended_collider layout.prop_search(collider.node, "bone_name", armature_data, "bones") layout.prop(collider, "ui_collider_type") bpy_object = collider.bpy_object if bpy_object: layout.prop(bpy_object, "name", icon="MESH_UVSPHERE", text="Head") layout.prop(extended.shape.capsule, "offset", text="") layout.separator(factor=0.5) layout.prop(extended.shape.capsule, "radius", slider=True) layout.separator(factor=0.5) if bpy_object and bpy_object.children: layout.prop( bpy_object.children[0], "name", icon="MESH_UVSPHERE", text="Tail", ) layout.prop(extended.shape.capsule, "tail", text="") if not (extended.enabled and extended.shape.capsule.inside): layout.prop(extended, "enabled") draw_spring_bone1_collider_extended_fallback_layout( layout, armature_data, collider, ) def draw_spring_bone1_collider_extended_plane_layout( layout: UILayout, armature_data: Armature, collider: SpringBone1ColliderPropertyGroup, ) -> None: extended = collider.extensions.vrmc_spring_bone_extended_collider layout.prop_search(collider.node, "bone_name", armature_data, "bones") layout.prop(collider, "ui_collider_type") bpy_object = collider.bpy_object if bpy_object: layout.prop(bpy_object, "name", icon="MESH_UVSPHERE", text="Offset") layout.prop(extended.shape.plane, "offset", text="") layout.separator(factor=0.5) layout.prop(extended.shape.plane, "normal") draw_spring_bone1_collider_extended_fallback_layout( layout, armature_data, collider, ) def draw_spring_bone1_collider_layout( armature: Object, layout: UILayout, collider: SpringBone1ColliderPropertyGroup, ) -> None: armature_data = armature.data if not isinstance(armature_data, Armature): return extended = collider.extensions.vrmc_spring_bone_extended_collider if extended.enabled: if extended.shape_type == extended.SHAPE_TYPE_EXTENDED_PLANE.identifier: draw_spring_bone1_collider_extended_plane_layout( layout, armature_data, collider ) elif extended.shape_type == extended.SHAPE_TYPE_EXTENDED_SPHERE.identifier: draw_spring_bone1_collider_extended_sphere_layout( layout, armature_data, collider ) elif extended.shape_type == extended.SHAPE_TYPE_EXTENDED_CAPSULE.identifier: draw_spring_bone1_collider_extended_capsule_layout( layout, armature_data, collider ) elif collider.shape_type == collider.SHAPE_TYPE_SPHERE.identifier: draw_spring_bone1_collider_sphere_layout(layout, armature_data, collider) elif collider.shape_type == collider.SHAPE_TYPE_CAPSULE.identifier: draw_spring_bone1_collider_capsule_layout(layout, armature_data, collider) layout.separator(factor=0.5) def draw_spring_bone1_spring_bone_layout( armature: Object, layout: UILayout, spring_bone: SpringBone1SpringBonePropertyGroup, ) -> None: defer_migrate(armature.name) layout.prop(spring_bone, "enable_animation") # layout.operator(ops.VRM_OT_reset_spring_bone1_animation_state.bl_idname) draw_spring_bone1_colliders_layout(armature, layout, spring_bone) draw_spring_bone1_collider_groups_layout(armature, layout, spring_bone) draw_spring_bone1_springs_layout(armature, layout, spring_bone) def draw_spring_bone1_colliders_layout( armature: Object, layout: UILayout, spring_bone: SpringBone1SpringBonePropertyGroup, ) -> None: colliders_box = layout.box() colliders_header_row = colliders_box.row() colliders_header_row.alignment = "LEFT" colliders_header_row.prop( spring_bone, "show_expanded_colliders", icon="TRIA_DOWN" if spring_bone.show_expanded_colliders else "TRIA_RIGHT", emboss=False, ) if not spring_bone.show_expanded_colliders: return ( collider_collection_ops, collider_collection_item_ops, collider_index, collider, _, ) = draw_template_list( colliders_box, VRM_UL_spring_bone1_collider, spring_bone, "colliders", "active_collider_index", ops.VRM_OT_add_spring_bone1_collider, ops.VRM_OT_remove_spring_bone1_collider, ops.VRM_OT_move_up_spring_bone1_collider, ops.VRM_OT_move_down_spring_bone1_collider, ) for collider_collection_op in collider_collection_ops: collider_collection_op.armature_object_name = armature.name for collider_collection_item_op in collider_collection_item_ops: collider_collection_item_op.collider_index = collider_index if not isinstance(collider, SpringBone1ColliderPropertyGroup): return draw_spring_bone1_collider_layout(armature, colliders_box.column(), collider) def draw_spring_bone1_collider_groups_layout( armature: Object, layout: UILayout, spring_bone: SpringBone1SpringBonePropertyGroup, ) -> None: collider_groups_box = layout.box() collider_groups_header_row = collider_groups_box.row() collider_groups_header_row.alignment = "LEFT" collider_groups_header_row.prop( spring_bone, "show_expanded_collider_groups", icon="TRIA_DOWN" if spring_bone.show_expanded_collider_groups else "TRIA_RIGHT", emboss=False, ) if not spring_bone.show_expanded_collider_groups: return ( collider_group_collection_ops, collider_group_collection_item_ops, collider_group_index, collider_group, _, ) = draw_template_list( collider_groups_box, VRM_UL_spring_bone1_collider_group, spring_bone, "collider_groups", "active_collider_group_index", ops.VRM_OT_add_spring_bone1_collider_group, ops.VRM_OT_remove_spring_bone1_collider_group, ops.VRM_OT_move_up_spring_bone1_collider_group, ops.VRM_OT_move_down_spring_bone1_collider_group, ) for collider_group_collection_op in collider_group_collection_ops: collider_group_collection_op.armature_object_name = armature.name for collider_group_collection_item_op in collider_group_collection_item_ops: collider_group_collection_item_op.collider_group_index = collider_group_index if not isinstance(collider_group, SpringBone1ColliderGroupPropertyGroup): return collider_group_column = collider_groups_box.column() collider_group_column.prop( collider_group, "vrm_name", ) colliders_box = collider_group_column.box() colliders_column = colliders_box.column() colliders_column.label(text="Colliders:") ( collider_collection_ops, collider_collection_item_ops, collider_index, _collider, _, ) = draw_template_list( colliders_column, VRM_UL_spring_bone1_collider_group_collider, collider_group, "colliders", "active_collider_index", ops.VRM_OT_add_spring_bone1_collider_group_collider, ops.VRM_OT_remove_spring_bone1_collider_group_collider, ops.VRM_OT_move_up_spring_bone1_collider_group_collider, ops.VRM_OT_move_down_spring_bone1_collider_group_collider, ) for collider_collection_op in collider_collection_ops: collider_collection_op.armature_object_name = armature.name collider_collection_op.collider_group_index = collider_group_index for collider_collection_item_op in collider_collection_item_ops: collider_collection_item_op.collider_index = collider_index def draw_spring_bone1_springs_layout( armature: Object, layout: UILayout, spring_bone: SpringBone1SpringBonePropertyGroup, ) -> None: armature_data = armature.data if not isinstance(armature_data, Armature): return springs_box = layout.box() springs_header_row = springs_box.row() springs_header_row.alignment = "LEFT" springs_header_row.prop( spring_bone, "show_expanded_springs", icon="TRIA_DOWN" if spring_bone.show_expanded_springs else "TRIA_RIGHT", emboss=False, ) if not spring_bone.show_expanded_springs: return ( spring_collection_ops, spring_collection_item_ops, spring_index, spring, _, ) = draw_template_list( springs_box, VRM_UL_spring_bone1_spring, spring_bone, "springs", "active_spring_index", ops.VRM_OT_add_spring_bone1_spring, ops.VRM_OT_remove_spring_bone1_spring, ops.VRM_OT_move_up_spring_bone1_spring, ops.VRM_OT_move_down_spring_bone1_spring, ) for spring_collection_op in spring_collection_ops: spring_collection_op.armature_object_name = armature.name for spring_collection_item_op in spring_collection_item_ops: spring_collection_item_op.spring_index = spring_index if not isinstance(spring, SpringBone1SpringPropertyGroup): return spring_column = springs_box.column() spring_column.prop( spring, "vrm_name", ) spring_column.prop_search( spring.center, "bone_name", armature_data, "bones", text="Center", ) joints_box = spring_column.box() joints_column = joints_box.column() joints_column.label(text="Joints:") ( joint_collection_ops, joint_collection_item_ops, joint_index, joint, (add_joint_op, _, _, _), ) = draw_template_list( joints_column, VRM_UL_spring_bone1_joint, spring, "joints", "active_joint_index", ops.VRM_OT_add_spring_bone1_joint, ops.VRM_OT_remove_spring_bone1_joint, ops.VRM_OT_move_up_spring_bone1_joint, ops.VRM_OT_move_down_spring_bone1_joint, ) for joint_collection_op in joint_collection_ops: joint_collection_op.armature_object_name = armature.name joint_collection_op.spring_index = spring_index for joint_collection_item_op in joint_collection_item_ops: joint_collection_item_op.joint_index = joint_index add_joint_op.guess_properties = True if isinstance(joint, SpringBone1JointPropertyGroup): joints_column.prop_search(joint.node, "bone_name", armature_data, "bones") joints_column.prop(joint, "stiffness", slider=True) joints_column.prop(joint, "gravity_power", slider=True) joints_column.prop(joint, "gravity_dir") joints_column.prop(joint, "drag_force", slider=True) joints_column.prop(joint, "hit_radius", slider=True) collider_groups_box = spring_column.box() collider_groups_column = collider_groups_box.column() collider_groups_column.label(text="Collider Groups:") ( collider_group_collection_ops, collider_group_collection_item_ops, collider_group_index, _collider_group, _, ) = draw_template_list( collider_groups_column, VRM_UL_spring_bone1_spring_collider_group, spring, "collider_groups", "active_collider_group_index", ops.VRM_OT_add_spring_bone1_spring_collider_group, ops.VRM_OT_remove_spring_bone1_spring_collider_group, ops.VRM_OT_move_up_spring_bone1_spring_collider_group, ops.VRM_OT_move_down_spring_bone1_spring_collider_group, ) for collider_group_collection_op in collider_group_collection_ops: collider_group_collection_op.armature_object_name = armature.name collider_group_collection_op.spring_index = spring_index for collider_group_collection_item_op in collider_group_collection_item_ops: collider_group_collection_item_op.collider_group_index = collider_group_index class VRM_PT_spring_bone1_armature_object_property(Panel): bl_idname = "VRM_PT_vrm1_spring_bone_armature_object_property" bl_label = "Spring Bone" bl_translation_context = "VRM" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" bl_options: AbstractSet[str] = {"DEFAULT_CLOSED"} bl_parent_id = VRM_PT_vrm_armature_object_property.bl_idname @classmethod def poll(cls, context: Context) -> bool: return active_object_is_vrm1_armature(context) def draw_header(self, _context: Context) -> None: self.layout.label(icon="PHYSICS") def draw(self, context: Context) -> None: active_object = context.active_object if not active_object: return armature_data = active_object.data if not isinstance(armature_data, Armature): return draw_spring_bone1_spring_bone_layout( active_object, self.layout, get_armature_extension(armature_data).spring_bone1, ) class VRM_PT_spring_bone1_ui(Panel): bl_idname = "VRM_PT_vrm1_spring_bone_ui" bl_label = "Spring Bone" bl_translation_context = "VRM" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "VRM" bl_options: AbstractSet[str] = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context: Context) -> bool: return search.current_armature_is_vrm1(context) def draw_header(self, _context: Context) -> None: self.layout.label(icon="PHYSICS") def draw(self, context: Context) -> None: armature = search.current_armature(context) if not armature: return armature_data = armature.data if not isinstance(armature_data, Armature): return draw_spring_bone1_spring_bone_layout( armature, self.layout, get_armature_extension(armature_data).spring_bone1, ) class VRM_PT_spring_bone1_collider_property(Panel): bl_idname = "VRM_PT_spring_bone1_collider_property" bl_label = "VRM Spring Bone Collider" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "data" @classmethod def active_armature_and_collider( cls, context: Context ) -> Optional[tuple[Object, SpringBone1ColliderPropertyGroup]]: active_object = context.active_object if not active_object: return None if active_object.type != "EMPTY": return None parent = active_object.parent if not parent: return None if active_object.parent_type == "BONE": collider_object = context.active_object elif active_object.parent_type == "OBJECT": if parent.type == "ARMATURE": collider_object = active_object elif parent.parent_type == "BONE" or ( parent.parent_type == "OBJECT" and parent.parent and parent.parent.type == "ARMATURE" ): collider_object = parent else: return None else: return None for obj in context.blend_data.objects: if obj.type != "ARMATURE": continue armature_data = obj.data if not isinstance(armature_data, Armature): continue for collider in get_armature_extension( armature_data ).spring_bone1.colliders: if collider.bpy_object == collider_object: return (obj, collider) return None @classmethod def poll(cls, context: Context) -> bool: return cls.active_armature_and_collider(context) is not None def draw(self, context: Context) -> None: armature_and_collider = self.active_armature_and_collider(context) if armature_and_collider is None: return armature, collider = armature_and_collider armature_data = armature.data if not isinstance(armature_data, Armature): return if get_armature_extension(armature_data).is_vrm1(): draw_spring_bone1_collider_layout(armature, self.layout.column(), collider) return self.layout.label(text="This is a VRM 1.0 Spring Bone Collider", icon="INFO")