diff --git a/src/io_scene_vrm/editor/panel.py b/src/io_scene_vrm/editor/panel.py index 19550c0cc..fa0557b06 100644 --- a/src/io_scene_vrm/editor/panel.py +++ b/src/io_scene_vrm/editor/panel.py @@ -1,7 +1,15 @@ from collections.abc import Set as AbstractSet +from typing import Callable, Optional, Protocol, TypeVar, Union, runtime_checkable from bpy.app.translations import pgettext -from bpy.types import Armature, Context, Operator, Panel +from bpy.types import ( + AnyType, + Armature, + Context, + Operator, + Panel, + UILayout, +) from ..common import version from ..common.preferences import get_preferences @@ -14,6 +22,139 @@ ) from .ops import layout_operator +__AddOperator = TypeVar("__AddOperator", bound=Operator) +__RemoveOperator = TypeVar("__RemoveOperator", bound=Operator) +__MoveUpOperator = TypeVar("__MoveUpOperator", bound=Operator) +__MoveDownOperator = TypeVar("__MoveDownOperator", bound=Operator) + + +@runtime_checkable +class TemplateListCollectionProtocol(Protocol): + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> object: ... + + +def draw_template_list( + layout: UILayout, + template_list_idname: str, + base_object: AnyType, + collection_attribue_name: str, + active_index_attribute_name: str, + add_operator_type: type[__AddOperator], + remove_operator_type: type[__RemoveOperator], + move_up_operator_type: type[__MoveUpOperator], + move_down_operator_type: type[__MoveDownOperator], + *, + can_remove: Callable[[int], bool] = lambda _: True, + can_move: Callable[[int], bool] = lambda _: True, + compact: bool = False, +) -> tuple[ + list[Union[__AddOperator, __RemoveOperator, __MoveUpOperator, __MoveDownOperator]], + list[Union[__RemoveOperator, __MoveUpOperator, __MoveDownOperator]], + int, + object, + tuple[ + __AddOperator, + __RemoveOperator, + Optional[__MoveUpOperator], + Optional[__MoveDownOperator], + ], +]: + collection = getattr(base_object, collection_attribue_name, None) + if not isinstance(collection, TemplateListCollectionProtocol): + message = ( + f"{collection}.{collection_attribue_name}" + + " is not a Template List Collection Protocol." + ) + raise TypeError(message) + + active_index = getattr(base_object, active_index_attribute_name, None) + if not isinstance(active_index, int): + message = f"{base_object}.{active_index_attribute_name} is not an int." + raise TypeError(message) + + if 0 <= active_index < len(collection): + active_object = collection[active_index] + else: + active_object = None + + length = len(collection) + list_row_len = 4 if length > int(compact) else 2 + + list_row = layout.row() + list_row.template_list( + template_list_idname, + "", + base_object, + collection_attribue_name, + base_object, + active_index_attribute_name, + rows=list_row_len, + ) + list_side_column = list_row.column(align=True) + add_operator = layout_operator( + list_side_column, add_operator_type, icon="ADD", text="", translate=False + ) + + if length >= 1 and 0 <= active_index < length and can_remove(active_index): + remove_operator_parent = list_side_column + else: + remove_operator_parent = list_side_column.column(align=True) + remove_operator_parent.enabled = False + + remove_operator = layout_operator( + remove_operator_parent, + remove_operator_type, + icon="REMOVE", + text="", + translate=False, + ) + + if length >= 2 and 0 <= active_index < length and can_move(active_index): + move_operator_parent = list_side_column + else: + move_operator_parent = list_side_column.column(align=True) + move_operator_parent.enabled = False + + collection_ops: list[ + Union[__AddOperator, __RemoveOperator, __MoveUpOperator, __MoveDownOperator] + ] = [add_operator] + collection_item_ops: list[ + Union[__RemoveOperator, __MoveUpOperator, __MoveDownOperator] + ] = [remove_operator] + + if length > int(compact): + move_operator_parent.separator() + move_up_operator = layout_operator( + move_operator_parent, + move_up_operator_type, + icon="TRIA_UP", + text="", + translate=False, + ) + collection_item_ops.append(move_up_operator) + move_down_operator = layout_operator( + move_operator_parent, + move_down_operator_type, + icon="TRIA_DOWN", + text="", + translate=False, + ) + collection_item_ops.append(move_down_operator) + else: + move_up_operator = None + move_down_operator = None + + collection_ops.extend(collection_item_ops) + + return ( + collection_ops, + collection_item_ops, + active_index, + active_object, + (add_operator, remove_operator, move_up_operator, move_down_operator), + ) + class VRM_PT_vrm_armature_object_property(Panel): bl_idname = "VRM_PT_vrm_armature_object_property" diff --git a/src/io_scene_vrm/editor/spring_bone1/ops.py b/src/io_scene_vrm/editor/spring_bone1/ops.py index 4bd98d200..efd4b1537 100644 --- a/src/io_scene_vrm/editor/spring_bone1/ops.py +++ b/src/io_scene_vrm/editor/spring_bone1/ops.py @@ -27,10 +27,12 @@ def execute(self, context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - collider = armature_data.vrm_addon_extension.spring_bone1.colliders.add() + spring_bone = armature_data.vrm_addon_extension.spring_bone1 + collider = spring_bone.colliders.add() collider.uuid = uuid.uuid4().hex collider.shape.sphere.radius = 0.125 collider.reset_bpy_object(context, armature) + spring_bone.active_collider_index = len(spring_bone.colliders) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -61,10 +63,10 @@ def execute(self, _context: Context) -> set[str]: if not isinstance(armature_data, Armature): return {"CANCELLED"} spring_bone = armature_data.vrm_addon_extension.spring_bone1 - colliders = spring_bone.colliders - if len(colliders) <= self.collider_index: + if len(spring_bone.colliders) <= self.collider_index: return {"CANCELLED"} - bpy_object = colliders[self.collider_index].bpy_object + + bpy_object = spring_bone.colliders[self.collider_index].bpy_object if bpy_object: remove_objects = [*bpy_object.children, bpy_object] for collection in bpy.data.collections: @@ -76,8 +78,8 @@ def execute(self, _context: Context) -> set[str]: if remove_object.users <= 1: bpy.data.objects.remove(remove_object, do_unlink=True) - collider_uuid = colliders[self.collider_index].uuid - colliders.remove(self.collider_index) + collider_uuid = spring_bone.colliders[self.collider_index].uuid + spring_bone.colliders.remove(self.collider_index) for collider_group in spring_bone.collider_groups: while True: removed = False @@ -90,6 +92,11 @@ def execute(self, _context: Context) -> set[str]: if not removed: break + spring_bone.active_collider_index = min( + spring_bone.active_collider_index, + max(0, len(spring_bone.colliders) - 1), + ) + return {"FINISHED"} if TYPE_CHECKING: @@ -99,6 +106,78 @@ def execute(self, _context: Context) -> set[str]: collider_index: int # type: ignore[no-redef] +class VRM_OT_move_up_spring_bone1_collider_group(Operator): + bl_idname = "vrm.move_up_spring_bone1_collider_group" + bl_label = "Move Up Collider Group" + bl_description = "Move Up VRM 1.0 Collider Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone = armature_data.vrm_addon_extension.spring_bone1 + if len(spring_bone.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + new_index = (self.collider_group_index - 1) % len(spring_bone.collider_groups) + spring_bone.collider_groups.move(self.collider_group_index, new_index) + spring_bone.active_collider_group_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_spring_bone1_collider_group(Operator): + bl_idname = "vrm.move_down_spring_bone1_collider_group" + bl_label = "Move Down Collider Group" + bl_description = "Move Down VRM 1.0 Collider Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone = armature_data.vrm_addon_extension.spring_bone1 + if len(spring_bone.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + new_index = (self.collider_group_index + 1) % len(spring_bone.collider_groups) + spring_bone.collider_groups.move(self.collider_group_index, new_index) + spring_bone.active_collider_group_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + + class VRM_OT_add_spring_bone1_spring(Operator): bl_idname = "vrm.add_spring_bone1_spring" bl_label = "Add Spring" @@ -116,8 +195,10 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - spring = armature_data.vrm_addon_extension.spring_bone1.springs.add() + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + spring = spring_bone1.springs.add() spring.vrm_name = "Spring" + spring_bone1.active_spring_index = len(spring_bone1.springs) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -147,10 +228,88 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - springs = armature_data.vrm_addon_extension.spring_bone1.springs + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + if len(spring_bone1.springs) <= self.spring_index: + return {"CANCELLED"} + spring_bone1.springs.remove(self.spring_index) + spring_bone1.active_spring_index = min( + spring_bone1.active_spring_index, max(0, len(spring_bone1.springs) - 1) + ) + + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + spring_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_spring_bone1_spring(Operator): + bl_idname = "vrm.move_up_spring_bone1_spring" + bl_label = "Move Up Spring" + bl_description = "Move Up VRM 1.0 Spring" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + spring_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + springs = spring_bone1.springs if len(springs) <= self.spring_index: return {"CANCELLED"} - springs.remove(self.spring_index) + new_spring_index = (self.spring_index - 1) % len(springs) + springs.move(self.spring_index, new_spring_index) + spring_bone1.active_spring_index = new_spring_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + spring_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_spring_bone1_spring(Operator): + bl_idname = "vrm.move_down_spring_bone1_spring" + bl_label = "Move Down Spring" + bl_description = "Move Down VRM 1.0 Spring" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + spring_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + springs = spring_bone1.springs + if len(springs) <= self.spring_index: + return {"CANCELLED"} + new_spring_index = (self.spring_index + 1) % len(springs) + springs.move(self.spring_index, new_spring_index) + spring_bone1.active_spring_index = new_spring_index return {"FINISHED"} if TYPE_CHECKING: @@ -177,11 +336,11 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - collider_group = ( - armature_data.vrm_addon_extension.spring_bone1.collider_groups.add() - ) + spring_bone = armature_data.vrm_addon_extension.spring_bone1 + collider_group = spring_bone.collider_groups.add() collider_group.vrm_name = "Collider Group" collider_group.uuid = uuid.uuid4().hex + spring_bone.active_collider_group_index = len(spring_bone.collider_groups) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -231,6 +390,11 @@ def execute(self, _context: Context) -> set[str]: for collider_group in collider_groups: collider_group.fix_index() + spring_bone.active_collider_group_index = min( + spring_bone.active_collider_group_index, + max(0, len(spring_bone.collider_groups) - 1), + ) + return {"FINISHED"} if TYPE_CHECKING: @@ -240,6 +404,80 @@ def execute(self, _context: Context) -> set[str]: collider_group_index: int # type: ignore[no-redef] +class VRM_OT_move_up_spring_bone1_collider(Operator): + bl_idname = "vrm.move_up_spring_bone1_collider" + bl_label = "Move Up Collider" + bl_description = "Move Up VRM 1.0 Collider" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + collider_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + colliders = spring_bone1.colliders + if len(colliders) <= self.collider_index: + return {"CANCELLED"} + new_collider_index = (self.collider_index - 1) % len(colliders) + colliders.move(self.collider_index, new_collider_index) + spring_bone1.active_collider_index = new_collider_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + collider_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_spring_bone1_collider(Operator): + bl_idname = "vrm.move_down_spring_bone1_collider" + bl_label = "Move Down Collider" + bl_description = "Move Down VRM 1.0 Collider" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + collider_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + colliders = spring_bone1.colliders + if len(colliders) <= self.collider_index: + return {"CANCELLED"} + new_collider_index = (self.collider_index + 1) % len(colliders) + colliders.move(self.collider_index, new_collider_index) + spring_bone1.active_collider_index = new_collider_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + collider_index: int # type: ignore[no-redef] + + class VRM_OT_add_spring_bone1_collider_group_collider(Operator): bl_idname = "vrm.add_spring_bone1_collider_group_collider" bl_label = "Add Collider" @@ -264,7 +502,9 @@ def execute(self, _context: Context) -> set[str]: collider_groups = armature_data.vrm_addon_extension.spring_bone1.collider_groups if len(collider_groups) <= self.collider_group_index: return {"CANCELLED"} - collider_groups[self.collider_group_index].colliders.add() + collider_group = collider_groups[self.collider_group_index] + collider_group.colliders.add() + collider_group.active_collider_index = len(collider_group.colliders) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -302,10 +542,102 @@ def execute(self, _context: Context) -> set[str]: collider_groups = armature_data.vrm_addon_extension.spring_bone1.collider_groups if len(collider_groups) <= self.collider_group_index: return {"CANCELLED"} - colliders = collider_groups[self.collider_group_index].colliders - if len(colliders) <= self.collider_index: + collider_group = collider_groups[self.collider_group_index] + if len(collider_group.colliders) <= self.collider_index: return {"CANCELLED"} - colliders.remove(self.collider_index) + collider_group.colliders.remove(self.collider_index) + collider_group.active_collider_index = min( + collider_group.active_collider_index, + max(0, len(collider_group.colliders) - 1), + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + collider_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_spring_bone1_collider_group_collider(Operator): + bl_idname = "vrm.move_up_spring_bone1_collider_group_collider" + bl_label = "Move Up Collider" + bl_description = "Move Up VRM 1.0 Collider Group Collider" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + collider_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + if len(spring_bone1.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + collider_group = spring_bone1.collider_groups[self.collider_group_index] + if len(collider_group.colliders) <= self.collider_index: + return {"CANCELLED"} + new_collider_index = (self.collider_index - 1) % len(collider_group.colliders) + collider_group.colliders.move(self.collider_index, new_collider_index) + collider_group.active_collider_index = new_collider_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + collider_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_spring_bone1_collider_group_collider(Operator): + bl_idname = "vrm.move_down_spring_bone1_collider_group_collider" + bl_label = "Move Down Collider" + bl_description = "Move Down VRM 1.0 Collider Group Collider" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + collider_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + if len(spring_bone1.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + collider_group = spring_bone1.collider_groups[self.collider_group_index] + if len(collider_group.colliders) <= self.collider_index: + return {"CANCELLED"} + new_collider_index = (self.collider_index + 1) % len(collider_group.colliders) + collider_group.colliders.move(self.collider_index, new_collider_index) + collider_group.active_collider_index = new_collider_index return {"FINISHED"} if TYPE_CHECKING: @@ -340,7 +672,9 @@ def execute(self, _context: Context) -> set[str]: springs = armature_data.vrm_addon_extension.spring_bone1.springs if len(springs) <= self.spring_index: return {"CANCELLED"} - springs[self.spring_index].collider_groups.add() + spring = springs[self.spring_index] + spring.collider_groups.add() + spring.active_collider_group_index = len(spring.collider_groups) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -378,10 +712,107 @@ def execute(self, _context: Context) -> set[str]: springs = armature_data.vrm_addon_extension.spring_bone1.springs if len(springs) <= self.spring_index: return {"CANCELLED"} - collider_groups = springs[self.spring_index].collider_groups - if len(collider_groups) <= self.collider_group_index: + spring = springs[self.spring_index] + if len(spring.collider_groups) <= self.collider_group_index: return {"CANCELLED"} - collider_groups.remove(self.collider_group_index) + spring.collider_groups.remove(self.collider_group_index) + spring.active_collider_group_index = min( + spring.active_collider_group_index, max(0, len(spring.collider_groups) - 1) + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + spring_index: int # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_spring_bone1_spring_collider_group(Operator): + bl_idname = "vrm.move_up_spring_bone1_spring_collider_group" + bl_label = "Move Up Collider Group" + bl_description = "Move Up VRM 1.0 Spring Collider Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + spring_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + springs = spring_bone1.springs + if len(springs) <= self.spring_index: + return {"CANCELLED"} + spring = springs[self.spring_index] + if len(spring.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + new_collider_group_index = (self.collider_group_index - 1) % len( + spring.collider_groups + ) + spring.collider_groups.move(self.collider_group_index, new_collider_group_index) + spring.active_collider_group_index = new_collider_group_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + spring_index: int # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_spring_bone1_spring_collider_group(Operator): + bl_idname = "vrm.move_down_spring_bone1_spring_collider_group" + bl_label = "Move Down Collider Group" + bl_description = "Move Down VRM 1.0 Spring Collider Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + spring_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + springs = spring_bone1.springs + if len(springs) <= self.spring_index: + return {"CANCELLED"} + spring = springs[self.spring_index] + if len(spring.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + new_collider_group_index = (self.collider_group_index + 1) % len( + spring.collider_groups + ) + spring.collider_groups.move(self.collider_group_index, new_collider_group_index) + spring.active_collider_group_index = new_collider_group_index return {"FINISHED"} if TYPE_CHECKING: @@ -392,7 +823,7 @@ def execute(self, _context: Context) -> set[str]: collider_group_index: int # type: ignore[no-redef] -class VRM_OT_add_spring_bone1_spring_joint(Operator): +class VRM_OT_add_spring_bone1_joint(Operator): bl_idname = "vrm.add_spring_bone1_spring_joint" bl_label = "Add Joint" bl_description = "Add VRM 1.0 Spring Bone Spring Joint" @@ -419,14 +850,15 @@ def execute(self, _context: Context) -> set[str]: springs = armature_data.vrm_addon_extension.spring_bone1.springs if len(springs) <= self.spring_index: return {"CANCELLED"} - joints = springs[self.spring_index].joints - joints.add() + spring = springs[self.spring_index] + spring.joints.add() + spring.active_joint_index = len(spring.joints) - 1 if not self.guess_properties: return {"FINISHED"} - if len(joints) < 2: + if len(spring.joints) < 2: return {"FINISHED"} - parent_joint, joint = joints[-2:] + parent_joint, joint = spring.joints[-2:] parent_bone = armature_data.bones.get(parent_joint.node.bone_name) if parent_bone and parent_bone.children: joint.node.set_bone_name(parent_bone.children[0].name) @@ -445,7 +877,7 @@ def execute(self, _context: Context) -> set[str]: guess_properties: bool # type: ignore[no-redef] -class VRM_OT_remove_spring_bone1_spring_joint(Operator): +class VRM_OT_remove_spring_bone1_joint(Operator): bl_idname = "vrm.remove_spring_bone1_spring_joint" bl_label = "Remove Joint" bl_description = "Remove VRM 1.0 Spring Bone Spring Joint" @@ -473,10 +905,103 @@ def execute(self, _context: Context) -> set[str]: springs = armature_data.vrm_addon_extension.spring_bone1.springs if len(springs) <= self.spring_index: return {"CANCELLED"} - joints = springs[self.spring_index].joints - if len(joints) <= self.joint_index: + spring = springs[self.spring_index] + if len(spring.joints) <= self.joint_index: + return {"CANCELLED"} + spring.joints.remove(self.joint_index) + spring.active_joint_index = min( + spring.active_joint_index, max(0, len(spring.joints) - 1) + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + spring_index: int # type: ignore[no-redef] + joint_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_spring_bone1_joint(Operator): + bl_idname = "vrm.move_up_spring_bone1_joint" + bl_label = "Move Up Joint" + bl_description = "Move Up VRM 1.0 Joint" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + spring_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + joint_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + springs = spring_bone1.springs + if len(springs) <= self.spring_index: + return {"CANCELLED"} + spring = springs[self.spring_index] + if len(spring.joints) <= self.joint_index: + return {"CANCELLED"} + new_joint_index = (self.joint_index - 1) % len(spring.joints) + spring.joints.move(self.joint_index, new_joint_index) + spring.active_joint_index = new_joint_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + spring_index: int # type: ignore[no-redef] + joint_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_spring_bone1_joint(Operator): + bl_idname = "vrm.move_down_spring_bone1_joint" + bl_label = "Move Down Joint" + bl_description = "Move Down VRM 1.0 Joint" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + spring_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + joint_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + spring_bone1 = armature_data.vrm_addon_extension.spring_bone1 + springs = spring_bone1.springs + if len(springs) <= self.spring_index: + return {"CANCELLED"} + spring = springs[self.spring_index] + if len(spring.joints) <= self.joint_index: return {"CANCELLED"} - joints.remove(self.joint_index) + new_joint_index = (self.joint_index + 1) % len(spring.joints) + spring.joints.move(self.joint_index, new_joint_index) + spring.active_joint_index = new_joint_index return {"FINISHED"} if TYPE_CHECKING: diff --git a/src/io_scene_vrm/editor/spring_bone1/panel.py b/src/io_scene_vrm/editor/spring_bone1/panel.py index 46318837b..c30417d66 100644 --- a/src/io_scene_vrm/editor/spring_bone1/panel.py +++ b/src/io_scene_vrm/editor/spring_bone1/panel.py @@ -6,13 +6,23 @@ from .. import search from ..migration import migrate -from ..ops import layout_operator -from ..panel import VRM_PT_vrm_armature_object_property +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, ) @@ -61,304 +71,272 @@ def draw_spring_bone1_spring_bone_layout( ) -> None: migrate(armature.name, defer=True) - armature_data = armature.data - if not isinstance(armature_data, Armature): - return - 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_row = colliders_box.row() - colliders_row.alignment = "LEFT" - colliders_row.prop( + 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 spring_bone.show_expanded_colliders: - if spring_bone.colliders: - colliders_expanded_box = colliders_box.box().column() - for collider_index, collider in enumerate(spring_bone.colliders): - if not collider.bpy_object: # TODO: restore - continue - - collider_row = colliders_expanded_box.row() - collider_row.alignment = "LEFT" - collider_row.prop( - collider, - "show_expanded", - icon="TRIA_DOWN" if collider.show_expanded else "TRIA_RIGHT", - emboss=False, - text=collider.bpy_object.name, - translate=False, - ) - - if not collider.show_expanded: - continue - - collider_column = colliders_expanded_box.box().column() - draw_spring_bone1_collider_layout(armature, collider_column, collider) - remove_collider_op = layout_operator( - collider_column, - ops.VRM_OT_remove_spring_bone1_collider, - icon="REMOVE", - text="Remove", - ) - remove_collider_op.armature_name = armature.name - remove_collider_op.collider_index = collider_index - - add_collider_op = layout_operator( - colliders_box, - ops.VRM_OT_add_spring_bone1_collider, - icon="ADD", - ) - add_collider_op.armature_name = armature.name + 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.bl_idname, + 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_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_row = collider_groups_box.row() - collider_groups_row.alignment = "LEFT" - collider_groups_row.prop( + 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 spring_bone.show_expanded_collider_groups: - if spring_bone.collider_groups: - collider_groups_expanded_box = collider_groups_box.box().column() - for collider_group_index, collider_group in enumerate( - spring_bone.collider_groups - ): - collider_group_row = collider_groups_expanded_box.row() - collider_group_row.alignment = "LEFT" - collider_group_row.prop( - collider_group, - "show_expanded", - icon="TRIA_DOWN" if collider_group.show_expanded else "TRIA_RIGHT", - emboss=False, - text=collider_group.vrm_name, - translate=False, - ) - - if not collider_group.show_expanded: - continue - - collider_group_column = collider_groups_expanded_box.box().column() - collider_group_column.prop( - collider_group, - "vrm_name", - ) - - collider_group_colliders_box = collider_group_column.box() - if collider_group.colliders: - collider_group_colliders_row = collider_group_colliders_box.split( - factor=0.8 - ) - collider_group_colliders_names_column = ( - collider_group_colliders_row.column() - ) - for collider in collider_group.colliders: - collider_group_colliders_names_column.prop_search( - collider, - "collider_name", - spring_bone, - "colliders", - text="", - translate=False, - ) - collider_group_colliders_buttons_column = ( - collider_group_colliders_row.column() - ) - for ( - collider_index, - _, - ) in enumerate(collider_group.colliders): - remove_collider_group_collider_op = layout_operator( - collider_group_colliders_buttons_column, - ops.VRM_OT_remove_spring_bone1_collider_group_collider, - icon="REMOVE", - text="", - translate=False, - ) - remove_collider_group_collider_op.armature_name = armature.name - remove_collider_group_collider_op.collider_group_index = ( - collider_group_index - ) - remove_collider_group_collider_op.collider_index = ( - collider_index - ) - - add_collider_group_collider_op = layout_operator( - collider_group_colliders_box, - ops.VRM_OT_add_spring_bone1_collider_group_collider, - icon="ADD", - ) - add_collider_group_collider_op.armature_name = armature.name - add_collider_group_collider_op.collider_group_index = ( - collider_group_index - ) - - remove_collider_group_op = layout_operator( - collider_group_column, - ops.VRM_OT_remove_spring_bone1_collider_group, - icon="REMOVE", - ) - remove_collider_group_op.armature_name = armature.name - remove_collider_group_op.collider_group_index = collider_group_index - - add_collider_group_op = layout_operator( - collider_groups_box, - ops.VRM_OT_add_spring_bone1_collider_group, - icon="ADD", - ) - add_collider_group_op.armature_name = armature.name + + 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.bl_idname, + 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_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.bl_idname, + 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_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_row = springs_box.row() - springs_row.alignment = "LEFT" - springs_row.prop( + 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 spring_bone.show_expanded_springs: - if spring_bone.springs: - springs_expanded_box = springs_box.box().column() - for spring_index, spring in enumerate(spring_bone.springs): - spring_row = springs_expanded_box.row() - spring_row.alignment = "LEFT" - spring_row.prop( - spring, - "show_expanded", - icon="TRIA_DOWN" if spring.show_expanded else "TRIA_RIGHT", - emboss=False, - text=spring.vrm_name, - translate=False, - ) - if not spring.show_expanded: - continue - - spring_column = springs_expanded_box.box().column() - spring_column.prop( - spring, - "vrm_name", - ) - spring_column.prop_search( - spring.center, - "bone_name", - armature_data, - "bones", - text="Center", - ) - - spring_joints_box = spring_column.box().column() - if spring.joints: - for joint_index, joint in enumerate(spring.joints): - spring_joints_row = spring_joints_box.row() - spring_joints_row.alignment = "LEFT" - spring_joints_row.prop( - joint, - "show_expanded", - icon="TRIA_DOWN" if joint.show_expanded else "TRIA_RIGHT", - emboss=False, - text=joint.node.bone_name - if joint.node.bone_name - else "(EMPTY)", - translate=False, - ) - if not joint.show_expanded: - continue - - box = spring_joints_box.box().column() - box.prop_search(joint.node, "bone_name", armature_data, "bones") - box.prop(joint, "stiffness", slider=True) - box.prop(joint, "gravity_power", slider=True) - box.prop(joint, "gravity_dir") - box.prop(joint, "drag_force", slider=True) - box.prop(joint, "hit_radius", slider=True) - - box.separator(factor=0.5) - - remove_spring_joint_op = layout_operator( - box, - ops.VRM_OT_remove_spring_bone1_spring_joint, - icon="REMOVE", - ) - remove_spring_joint_op.armature_name = armature.name - remove_spring_joint_op.spring_index = spring_index - remove_spring_joint_op.joint_index = joint_index - - add_spring_joint_op = layout_operator( - spring_joints_box, - ops.VRM_OT_add_spring_bone1_spring_joint, - icon="ADD", - ) - add_spring_joint_op.armature_name = armature.name - add_spring_joint_op.spring_index = spring_index - add_spring_joint_op.guess_properties = True - - spring_collider_groups_box = spring_column.box() - if spring.collider_groups: - spring_collider_groups_row = spring_collider_groups_box.split( - factor=0.8 - ) - spring_collider_groups_names_column = ( - spring_collider_groups_row.column() - ) - for collider_group in spring.collider_groups: - spring_collider_groups_names_column.prop_search( - collider_group, - "collider_group_name", - spring_bone, - "collider_groups", - text="", - translate=False, - ) - spring_collider_groups_buttons_column = ( - spring_collider_groups_row.column() - ) - for ( - collider_group_index, - _, - ) in enumerate(spring.collider_groups): - remove_spring_collider_group_op = layout_operator( - spring_collider_groups_buttons_column, - ops.VRM_OT_remove_spring_bone1_spring_collider_group, - icon="REMOVE", - text="", - translate=False, - ) - remove_spring_collider_group_op.armature_name = armature.name - remove_spring_collider_group_op.spring_index = spring_index - remove_spring_collider_group_op.collider_group_index = ( - collider_group_index - ) - - add_spring_collider_group_op = layout_operator( - spring_collider_groups_box, - ops.VRM_OT_add_spring_bone1_spring_collider_group, - icon="ADD", - ) - add_spring_collider_group_op.armature_name = armature.name - add_spring_collider_group_op.spring_index = spring_index - - remove_spring_op = layout_operator( - spring_column, - ops.VRM_OT_remove_spring_bone1_spring, - icon="REMOVE", - ) - remove_spring_op.armature_name = armature.name - remove_spring_op.spring_index = spring_index - - add_spring_op = layout_operator( - springs_box, - ops.VRM_OT_add_spring_bone1_spring, - icon="ADD", - ) - add_spring_op.armature_name = armature.name + + 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.bl_idname, + 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_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.bl_idname, + 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_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.bl_idname, + 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_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): diff --git a/src/io_scene_vrm/editor/spring_bone1/property_group.py b/src/io_scene_vrm/editor/spring_bone1/property_group.py index 275a8e318..939dc071e 100644 --- a/src/io_scene_vrm/editor/spring_bone1/property_group.py +++ b/src/io_scene_vrm/editor/spring_bone1/property_group.py @@ -11,6 +11,7 @@ EnumProperty, FloatProperty, FloatVectorProperty, + IntProperty, PointerProperty, StringProperty, ) @@ -515,6 +516,7 @@ def fix_index(self) -> None: # for UI show_expanded: BoolProperty() # type: ignore[valid-type] + active_collider_index: IntProperty(min=0) # type: ignore[valid-type] # for reference # オブジェクトをコピーした場合同じuuidをもつオブジェクトが複数ある可能性がある @@ -530,6 +532,7 @@ def fix_index(self) -> None: SpringBone1ColliderReferencePropertyGroup ] show_expanded: bool # type: ignore[no-redef] + active_collider_index: int # type: ignore[no-redef] uuid: str # type: ignore[no-redef] search_one_time_uuid: str # type: ignore[no-redef] @@ -700,6 +703,9 @@ class SpringBone1SpringPropertyGroup(PropertyGroup): name="Collider Groups" ) + active_joint_index: IntProperty(min=0) # type: ignore[valid-type] + active_collider_group_index: IntProperty(min=0) # type: ignore[valid-type] + animation_state: PointerProperty( # type: ignore[valid-type] type=SpringBone1SpringAnimationStatePropertyGroup ) @@ -718,6 +724,8 @@ class SpringBone1SpringPropertyGroup(PropertyGroup): show_expanded: bool # type: ignore[no-redef] show_expanded_bones: bool # type: ignore[no-redef] show_expanded_collider_groups: bool # type: ignore[no-redef] + active_joint_index: int # type: ignore[no-redef] + active_collider_group_index: int # type: ignore[no-redef] animation_state: ( # type: ignore[no-redef] SpringBone1SpringAnimationStatePropertyGroup ) @@ -755,6 +763,11 @@ def update_enable_animation(self, _context: Context) -> None: show_expanded_springs: BoolProperty( # type: ignore[valid-type] name="Spring Bone Springs" ) + + active_collider_index: IntProperty(min=0) # type: ignore[valid-type] + active_collider_group_index: IntProperty(min=0) # type: ignore[valid-type] + active_spring_index: IntProperty(min=0) # type: ignore[valid-type] + if TYPE_CHECKING: # This code is auto generated. # `poetry run python tools/property_typing.py` @@ -771,3 +784,6 @@ def update_enable_animation(self, _context: Context) -> None: show_expanded_colliders: bool # type: ignore[no-redef] show_expanded_collider_groups: bool # type: ignore[no-redef] show_expanded_springs: bool # type: ignore[no-redef] + active_collider_index: int # type: ignore[no-redef] + active_collider_group_index: int # type: ignore[no-redef] + active_spring_index: int # type: ignore[no-redef] diff --git a/src/io_scene_vrm/editor/spring_bone1/ui_list.py b/src/io_scene_vrm/editor/spring_bone1/ui_list.py new file mode 100644 index 000000000..ae243f380 --- /dev/null +++ b/src/io_scene_vrm/editor/spring_bone1/ui_list.py @@ -0,0 +1,254 @@ +import bpy +from bpy.types import Context, UILayout, UIList + +from ...common.logging import get_logger +from .property_group import ( + SpringBone1ColliderGroupPropertyGroup, + SpringBone1ColliderGroupReferencePropertyGroup, + SpringBone1ColliderPropertyGroup, + SpringBone1ColliderReferencePropertyGroup, + SpringBone1JointPropertyGroup, + SpringBone1SpringPropertyGroup, +) + +logger = get_logger(__name__) + + +class VRM_UL_spring_bone1_collider(UIList): + bl_idname = "VRM_UL_spring_bone1_collider" + + def draw_item( + self, + _context: Context, + layout: UILayout, + _data: object, + collider: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + _index: int, + _flt_flag: int, + ) -> None: + if not isinstance(collider, SpringBone1ColliderPropertyGroup): + return + + icon = "SPHERE" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + name = "" + if collider.bpy_object: + name = collider.bpy_object.name + layout.label(text=name, translate=False, icon=icon) + + +class VRM_UL_spring_bone1_collider_group(UIList): + bl_idname = "VRM_UL_spring_bone1_collider_group" + + def draw_item( + self, + _context: Context, + layout: UILayout, + _data: object, + collider_group: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + _index: int, + _flt_flag: int, + ) -> None: + if not isinstance(collider_group, SpringBone1ColliderGroupPropertyGroup): + return + + icon = "PIVOT_INDIVIDUAL" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + layout.label(text=collider_group.vrm_name, translate=False, icon=icon) + + +class VRM_UL_spring_bone1_collider_group_collider(UIList): + bl_idname = "VRM_UL_spring_bone1_collider_group_collider" + + def draw_item( + self, + _context: Context, + layout: UILayout, + collider_group: object, + collider: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + index: int, + _flt_flag: int, + ) -> None: + if not isinstance(collider_group, SpringBone1ColliderGroupPropertyGroup): + return + if not isinstance(collider, SpringBone1ColliderReferencePropertyGroup): + return + icon = "SPHERE" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + # Search for armature + spring_bone = None + for armature in bpy.data.armatures: + ext = armature.vrm_addon_extension + if any(collider_group == c for c in ext.spring_bone1.collider_groups): + spring_bone = ext.spring_bone1 + break + if spring_bone is None: + logger.error("Failed to find armature") + return + + if index == collider_group.active_collider_index: + layout.prop_search( + collider, + "collider_name", + spring_bone, + "colliders", + text="", + translate=False, + icon=icon, + ) + else: + layout.label(text=collider.collider_name, translate=False, icon=icon) + + +class VRM_UL_spring_bone1_spring(UIList): + bl_idname = "VRM_UL_spring_bone1_spring" + + def draw_item( + self, + _context: Context, + layout: UILayout, + _data: object, + spring: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + _index: int, + _flt_flag: int, + ) -> None: + if not isinstance(spring, SpringBone1SpringPropertyGroup): + return + + icon = "PHYSICS" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + layout.label(text=spring.vrm_name, translate=False, icon=icon) + + +class VRM_UL_spring_bone1_joint(UIList): + bl_idname = "VRM_UL_spring_bone1_joint" + + def draw_item( + self, + _context: Context, + layout: UILayout, + _data: object, + joint: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + _index: int, + _flt_flag: int, + ) -> None: + if not isinstance(joint, SpringBone1JointPropertyGroup): + return + + icon = "BONE_DATA" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + layout.label(text=joint.node.bone_name, translate=False, icon=icon) + + +class VRM_UL_spring_bone1_spring_collider_group(UIList): + bl_idname = "VRM_UL_spring_bone1_spring_collider_group" + + def draw_item( + self, + _context: Context, + layout: UILayout, + spring: object, + collider_group: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + index: int, + _flt_flag: int, + ) -> None: + if not isinstance(spring, SpringBone1SpringPropertyGroup): + return + if not isinstance( + collider_group, SpringBone1ColliderGroupReferencePropertyGroup + ): + return + + icon = "PIVOT_INDIVIDUAL" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + # Search for armature + spring_bone = None + for armature in bpy.data.armatures: + ext = armature.vrm_addon_extension + if any(spring == s for s in ext.spring_bone1.springs): + spring_bone = ext.spring_bone1 + break + if spring_bone is None: + logger.error("Failed to find armature") + return + + if index == spring.active_collider_group_index: + layout.prop_search( + collider_group, + "collider_group_name", + spring_bone, + "collider_groups", + text="", + translate=False, + icon=icon, + ) + else: + layout.label( + text=collider_group.collider_group_name, translate=False, icon=icon + ) diff --git a/src/io_scene_vrm/editor/vrm0/ops.py b/src/io_scene_vrm/editor/vrm0/ops.py index 69a77552e..4f3404eb5 100644 --- a/src/io_scene_vrm/editor/vrm0/ops.py +++ b/src/io_scene_vrm/editor/vrm0/ops.py @@ -27,7 +27,11 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - armature_data.vrm_addon_extension.vrm0.first_person.mesh_annotations.add() + first_person = armature_data.vrm_addon_extension.vrm0.first_person + first_person.mesh_annotations.add() + first_person.active_mesh_annotation_index = ( + len(first_person.mesh_annotations) - 1 + ) return {"FINISHED"} if TYPE_CHECKING: @@ -57,12 +61,92 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - mesh_annotations = ( - armature_data.vrm_addon_extension.vrm0.first_person.mesh_annotations + first_person = armature_data.vrm_addon_extension.vrm0.first_person + if len(first_person.mesh_annotations) <= self.mesh_annotation_index: + return {"CANCELLED"} + first_person.mesh_annotations.remove(self.mesh_annotation_index) + first_person.active_mesh_annotation_index = min( + first_person.active_mesh_annotation_index, + max(0, len(first_person.mesh_annotations) - 1), + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + mesh_annotation_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_vrm0_first_person_mesh_annotation(Operator): + bl_idname = "vrm.move_up_vrm0_first_person_mesh_annotation" + bl_label = "Move Up Mesh Annotation" + bl_description = "Move Up VRM 0.x First Person Mesh Annotation" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + mesh_annotation_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + first_person = armature_data.vrm_addon_extension.vrm0.first_person + if len(first_person.mesh_annotations) <= self.mesh_annotation_index: + return {"CANCELLED"} + if len(first_person.mesh_annotations) <= self.mesh_annotation_index: + return {"CANCELLED"} + new_index = (first_person.active_mesh_annotation_index - 1) % len( + first_person.mesh_annotations ) - if len(mesh_annotations) <= self.mesh_annotation_index: + first_person.mesh_annotations.move(self.mesh_annotation_index, new_index) + first_person.active_mesh_annotation_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + mesh_annotation_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm0_first_person_mesh_annotation(Operator): + bl_idname = "vrm.move_down_vrm0_first_person_mesh_annotation" + bl_label = "Move Down Mesh Annotation" + bl_description = "Move Down VRM 0.x First Person Mesh Annotation" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + mesh_annotation_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): return {"CANCELLED"} - mesh_annotations.remove(self.mesh_annotation_index) + first_person = armature_data.vrm_addon_extension.vrm0.first_person + if len(first_person.mesh_annotations) <= self.mesh_annotation_index: + return {"CANCELLED"} + new_index = (first_person.active_mesh_annotation_index + 1) % len( + first_person.mesh_annotations + ) + first_person.mesh_annotations.move(self.mesh_annotation_index, new_index) + first_person.active_mesh_annotation_index = new_index return {"FINISHED"} if TYPE_CHECKING: @@ -160,6 +244,102 @@ def execute(self, _context: Context) -> set[str]: material_value_index: int # type: ignore[no-redef] +class VRM_OT_move_up_vrm0_material_value_bind(Operator): + bl_idname = "vrm.move_up_vrm0_material_value_bind" + bl_label = "Move Up Material Value Bind" + bl_description = "Move Up VRM 0.x Blend Shape Material Value Bind" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + blend_shape_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + material_value_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + blend_shape_groups = ( + armature_data.vrm_addon_extension.vrm0.blend_shape_master.blend_shape_groups + ) + if len(blend_shape_groups) <= self.blend_shape_group_index: + return {"CANCELLED"} + blend_shape_group = blend_shape_groups[self.blend_shape_group_index] + if len(blend_shape_group.material_values) <= self.material_value_index: + return {"CANCELLED"} + new_index = (self.material_value_index - 1) % len( + blend_shape_group.material_values + ) + blend_shape_group.material_values.move(self.material_value_index, new_index) + blend_shape_group.active_material_value_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + blend_shape_group_index: int # type: ignore[no-redef] + material_value_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm0_material_value_bind(Operator): + bl_idname = "vrm.move_down_vrm0_material_value_bind" + bl_label = "Move Down Material Value Bind" + bl_description = "Move Down VRM 0.x Blend Shape Material Value Bind" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + blend_shape_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + material_value_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + blend_shape_groups = ( + armature_data.vrm_addon_extension.vrm0.blend_shape_master.blend_shape_groups + ) + if len(blend_shape_groups) <= self.blend_shape_group_index: + return {"CANCELLED"} + blend_shape_group = blend_shape_groups[self.blend_shape_group_index] + if len(blend_shape_group.material_values) <= self.material_value_index: + return {"CANCELLED"} + new_index = (self.material_value_index + 1) % len( + blend_shape_group.material_values + ) + blend_shape_group.material_values.move(self.material_value_index, new_index) + blend_shape_group.active_material_value_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + blend_shape_group_index: int # type: ignore[no-redef] + material_value_index: int # type: ignore[no-redef] + + class VRM_OT_add_vrm0_material_value_bind_target_value(Operator): bl_idname = "vrm.add_vrm0_material_value_bind_target_value" bl_label = "Add Value" @@ -346,6 +526,98 @@ def execute(self, _context: Context) -> set[str]: bind_index: int # type: ignore[no-redef] +class VRM_OT_move_up_vrm0_blend_shape_bind(Operator): + bl_idname = "vrm.move_up_vrm0_blend_shape_bind" + bl_label = "Move Up Blend Shape Bind" + bl_description = "Move Up VRM 0.x Blend Shape Bind" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + blend_shape_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + bind_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + blend_shape_groups = ( + armature_data.vrm_addon_extension.vrm0.blend_shape_master.blend_shape_groups + ) + if len(blend_shape_groups) <= self.blend_shape_group_index: + return {"CANCELLED"} + blend_shape_group = blend_shape_groups[self.blend_shape_group_index] + if len(blend_shape_group.binds) <= self.bind_index: + return {"CANCELLED"} + new_index = (self.bind_index - 1) % len(blend_shape_group.binds) + blend_shape_group.binds.move(self.bind_index, new_index) + blend_shape_group.active_bind_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + blend_shape_group_index: int # type: ignore[no-redef] + bind_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm0_blend_shape_bind(Operator): + bl_idname = "vrm.move_down_vrm0_blend_shape_bind" + bl_label = "Move Down Blend Shape Bind" + bl_description = "Move Up VRM 0.x Blend Shape Bind" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + blend_shape_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + bind_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + blend_shape_groups = ( + armature_data.vrm_addon_extension.vrm0.blend_shape_master.blend_shape_groups + ) + if len(blend_shape_groups) <= self.blend_shape_group_index: + return {"CANCELLED"} + blend_shape_group = blend_shape_groups[self.blend_shape_group_index] + if len(blend_shape_group.binds) <= self.bind_index: + return {"CANCELLED"} + new_index = (self.bind_index + 1) % len(blend_shape_group.binds) + blend_shape_group.binds.move(self.bind_index, new_index) + blend_shape_group.active_bind_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + blend_shape_group_index: int # type: ignore[no-redef] + bind_index: int # type: ignore[no-redef] + + class VRM_OT_add_vrm0_secondary_animation_collider_group_collider(Operator): bl_idname = "vrm.add_vrm0_secondary_animation_collider_group_collider" bl_label = "Add Collider" @@ -375,7 +647,8 @@ def execute(self, context: Context) -> set[str]: ) if len(collider_groups) <= self.collider_group_index: return {"CANCELLED"} - collider = collider_groups[self.collider_group_index].colliders.add() + collider_group = collider_groups[self.collider_group_index] + collider = collider_group.colliders.add() obj = bpy.data.objects.new( name=f"{self.armature_name}_{self.bone_name}_collider", object_data=None ) @@ -389,6 +662,7 @@ def execute(self, context: Context) -> set[str]: else: obj.parent_type = "OBJECT" context.scene.collection.objects.link(obj) + collider_group.active_collider_index = len(collider_group.colliders) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -429,14 +703,18 @@ def execute(self, context: Context) -> set[str]: ) if len(collider_groups) <= self.collider_group_index: return {"CANCELLED"} - colliders = collider_groups[self.collider_group_index].colliders - if len(colliders) <= self.collider_index: + collider_group = collider_groups[self.collider_group_index] + if len(collider_group.colliders) <= self.collider_index: return {"CANCELLED"} - bpy_object = colliders[self.collider_index].bpy_object + bpy_object = collider_group.colliders[self.collider_index].bpy_object if bpy_object and bpy_object.name in context.scene.collection.objects: bpy_object.parent_type = "OBJECT" context.scene.collection.objects.unlink(bpy_object) - colliders.remove(self.collider_index) + collider_group.colliders.remove(self.collider_index) + collider_group.active_collider_index = min( + collider_group.active_collider_index, + max(0, len(collider_group.colliders) - 1), + ) return {"FINISHED"} if TYPE_CHECKING: @@ -447,16 +725,20 @@ def execute(self, context: Context) -> set[str]: collider_index: int # type: ignore[no-redef] -class VRM_OT_add_vrm0_secondary_animation_group_bone(Operator): - bl_idname = "vrm.add_vrm0_secondary_animation_group_bone" - bl_label = "Add Bone" - bl_description = "Add VRM 0.x Secondary Animation Group Bone" +class VRM_OT_move_up_vrm0_secondary_animation_collider_group_collider(Operator): + bl_idname = "vrm.move_up_vrm0_secondary_animation_collider_group_coll" + bl_label = "Move Up Collider" + bl_description = "Move Up VRM 0.x Collider" bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} armature_name: StringProperty( # type: ignore[valid-type] options={"HIDDEN"} ) - bone_group_index: IntProperty( # type: ignore[valid-type] + collider_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + collider_index: IntProperty( # type: ignore[valid-type] min=0, options={"HIDDEN"}, ) @@ -468,35 +750,41 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - bone_groups = ( - armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + collider_groups = ( + armature_data.vrm_addon_extension.vrm0.secondary_animation.collider_groups ) - if len(bone_groups) <= self.bone_group_index: + if len(collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + collider_group = collider_groups[self.collider_group_index] + if len(collider_group.colliders) <= self.collider_index: return {"CANCELLED"} - bone_groups[self.bone_group_index].bones.add() + new_index = (self.collider_index - 1) % len(collider_group.colliders) + collider_group.colliders.move(self.collider_index, new_index) + collider_group.active_collider_index = new_index return {"FINISHED"} if TYPE_CHECKING: # This code is auto generated. # `poetry run python tools/property_typing.py` armature_name: str # type: ignore[no-redef] - bone_group_index: int # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + collider_index: int # type: ignore[no-redef] -class VRM_OT_remove_vrm0_secondary_animation_group_bone(Operator): - bl_idname = "vrm.remove_vrm0_secondary_animation_group_bone" - bl_label = "Remove Bone" - bl_description = "Remove VRM 0.x Secondary Animation Group Bone" +class VRM_OT_move_down_vrm0_secondary_animation_collider_group_collider(Operator): + bl_idname = "vrm.move_down_vrm0_secondary_animation_collider_group_coll" + bl_label = "Move Down Collider" + bl_description = "Move Down VRM 0.x Collider" bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} armature_name: StringProperty( # type: ignore[valid-type] options={"HIDDEN"} ) - bone_group_index: IntProperty( # type: ignore[valid-type] + collider_group_index: IntProperty( # type: ignore[valid-type] min=0, options={"HIDDEN"}, ) - bone_index: IntProperty( # type: ignore[valid-type] + collider_index: IntProperty( # type: ignore[valid-type] min=0, options={"HIDDEN"}, ) @@ -508,26 +796,206 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - bone_groups = ( - armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + collider_groups = ( + armature_data.vrm_addon_extension.vrm0.secondary_animation.collider_groups ) - if len(bone_groups) <= self.bone_group_index: + if len(collider_groups) <= self.collider_group_index: return {"CANCELLED"} - bones = bone_groups[self.bone_group_index].bones - if len(bones) <= self.bone_index: + collider_group = collider_groups[self.collider_group_index] + if len(collider_group.colliders) <= self.collider_index: return {"CANCELLED"} - bones.remove(self.bone_index) + new_index = (self.collider_index + 1) % len(collider_group.colliders) + collider_group.colliders.move(self.collider_index, new_index) + collider_group.active_collider_index = new_index return {"FINISHED"} if TYPE_CHECKING: # This code is auto generated. # `poetry run python tools/property_typing.py` armature_name: str # type: ignore[no-redef] - bone_group_index: int # type: ignore[no-redef] - bone_index: int # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + collider_index: int # type: ignore[no-redef] -class VRM_OT_add_vrm0_secondary_animation_group_collider_group(Operator): +class VRM_OT_add_vrm0_secondary_animation_group_bone(Operator): + bl_idname = "vrm.add_vrm0_secondary_animation_group_bone" + bl_label = "Add Bone" + bl_description = "Add VRM 0.x Secondary Animation Group Bone" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + bone_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + bone_groups = ( + armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + ) + if len(bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + bone_group = bone_groups[self.bone_group_index] + bone = bone_group.bones.add() + bone.armature_data_name = armature_data.name + bone_group.active_bone_index = len(bone_group.bones) - 1 + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + bone_group_index: int # type: ignore[no-redef] + + +class VRM_OT_remove_vrm0_secondary_animation_group_bone(Operator): + bl_idname = "vrm.remove_vrm0_secondary_animation_group_bone" + bl_label = "Remove Bone" + bl_description = "Remove VRM 0.x Secondary Animation Group Bone" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + bone_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + bone_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + bone_groups = ( + armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + ) + if len(bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + bone_group = bone_groups[self.bone_group_index] + if len(bone_group.bones) <= self.bone_index: + return {"CANCELLED"} + bone_group.bones.remove(self.bone_index) + bone_group.active_bone_index = min( + bone_group.active_bone_index, max(0, len(bone_group.bones) - 1) + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + bone_group_index: int # type: ignore[no-redef] + bone_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_vrm0_secondary_animation_group_bone(Operator): + bl_idname = "vrm.move_up_vrm0_secondary_animation_group_bone" + bl_label = "Move Up Bone" + bl_description = "Move Up VRM 0.x Secondary Animation Group Bone" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + bone_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + bone_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + bone_groups = ( + armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + ) + if len(bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + bone_group = bone_groups[self.bone_group_index] + if len(bone_group.bones) <= self.bone_index: + return {"CANCELLED"} + new_index = (self.bone_index - 1) % len(bone_group.bones) + bone_group.bones.move(self.bone_index, new_index) + bone_group.active_bone_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + bone_group_index: int # type: ignore[no-redef] + bone_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm0_secondary_animation_group_bone(Operator): + bl_idname = "vrm.move_down_vrm0_secondary_animation_group_bone" + bl_label = "Move Down Bone" + bl_description = "Move Down VRM 0.x Secondary Animation Group Bone" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + bone_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + bone_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + bone_groups = ( + armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + ) + if len(bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + bone_group = bone_groups[self.bone_group_index] + if len(bone_group.bones) <= self.bone_index: + return {"CANCELLED"} + new_index = (self.bone_index + 1) % len(bone_group.bones) + bone_group.bones.move(self.bone_index, new_index) + bone_group.active_bone_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + bone_group_index: int # type: ignore[no-redef] + bone_index: int # type: ignore[no-redef] + + +class VRM_OT_add_vrm0_secondary_animation_group_collider_group(Operator): bl_idname = "vrm.add_vrm0_secondary_animation_group_collider_group" bl_label = "Add Collider Group" bl_description = "Add VRM 0.x Secondary Animation Group Collider Group" @@ -553,7 +1021,10 @@ def execute(self, _context: Context) -> set[str]: ) if len(bone_groups) <= self.bone_group_index: return {"CANCELLED"} - bone_groups[self.bone_group_index].collider_groups.add() + bone_group = bone_groups[self.bone_group_index] + collider_group = bone_group.collider_groups.add() + collider_group.value = "" + bone_group.active_collider_group_index = len(bone_group.collider_groups) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -593,10 +1064,104 @@ def execute(self, _context: Context) -> set[str]: ) if len(bone_groups) <= self.bone_group_index: return {"CANCELLED"} - collider_groups = bone_groups[self.bone_group_index].collider_groups - if len(collider_groups) <= self.collider_group_index: + bone_group = bone_groups[self.bone_group_index] + if len(bone_group.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + bone_group.collider_groups.remove(self.collider_group_index) + bone_group.active_collider_group_index = min( + bone_group.active_collider_group_index, + max(0, len(bone_group.collider_groups) - 1), + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + bone_group_index: int # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_vrm0_secondary_animation_group_collider_group(Operator): + bl_idname = "vrm.move_up_vrm0_secondary_animation_group_collider_group" + bl_label = "Move Up Collider Group" + bl_description = "Move Up VRM 0.x Secondary Animation Group Collider Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + bone_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + bone_groups = ( + armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + ) + if len(bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + bone_group = bone_groups[self.bone_group_index] + if len(bone_group.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + new_index = (self.collider_group_index - 1) % len(bone_group.collider_groups) + bone_group.active_collider_group_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + bone_group_index: int # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm0_secondary_animation_group_collider_group(Operator): + bl_idname = "vrm.move_down_vrm0_secondary_animation_group_collider_group" + bl_label = "Move Down Collider Group" + bl_description = "Move Down VRM 0.x Secondary Animation Group Collider Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + bone_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": return {"CANCELLED"} - collider_groups.remove(self.collider_group_index) + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + bone_groups = ( + armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + ) + if len(bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + bone_group = bone_groups[self.bone_group_index] + if len(bone_group.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + new_index = (self.collider_group_index + 1) % len(bone_group.collider_groups) + bone_group.active_collider_group_index = new_index return {"FINISHED"} if TYPE_CHECKING: @@ -680,6 +1245,86 @@ def execute(self, _context: Context) -> set[str]: blend_shape_group_index: int # type: ignore[no-redef] +class VRM_OT_move_up_vrm0_blend_shape_group(Operator): + bl_idname = "vrm.move_up_vrm0_blend_shape_group" + bl_label = "Move Up Blend Shape Group" + bl_description = "Move Up VRM 0.x Blend Shape Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + blend_shape_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + blend_shape_master = armature_data.vrm_addon_extension.vrm0.blend_shape_master + if len(blend_shape_master.blend_shape_groups) <= self.blend_shape_group_index: + return {"CANCELLED"} + new_index = (self.blend_shape_group_index - 1) % len( + blend_shape_master.blend_shape_groups + ) + blend_shape_master.blend_shape_groups.move( + self.blend_shape_group_index, new_index + ) + blend_shape_master.active_blend_shape_group_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + blend_shape_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm0_blend_shape_group(Operator): + bl_idname = "vrm.move_down_vrm0_blend_shape_group" + bl_label = "Move Down Blend Shape Group" + bl_description = "Move Down VRM 0.x Blend Shape Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + blend_shape_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + blend_shape_master = armature_data.vrm_addon_extension.vrm0.blend_shape_master + if len(blend_shape_master.blend_shape_groups) <= self.blend_shape_group_index: + return {"CANCELLED"} + new_index = (self.blend_shape_group_index + 1) % len( + blend_shape_master.blend_shape_groups + ) + blend_shape_master.blend_shape_groups.move( + self.blend_shape_group_index, new_index + ) + blend_shape_master.active_blend_shape_group_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + blend_shape_group_index: int # type: ignore[no-redef] + + class VRM_OT_add_vrm0_secondary_animation_group(Operator): bl_idname = "vrm.add_vrm0_secondary_animation_group" bl_label = "Add Spring Bone" @@ -689,6 +1334,8 @@ class VRM_OT_add_vrm0_secondary_animation_group(Operator): armature_name: StringProperty( # type: ignore[valid-type] options={"HIDDEN"} ) + + # Unnecessary property. Please do not use. blend_shape_group_index: IntProperty( # type: ignore[valid-type] min=0, options={"HIDDEN"}, @@ -701,7 +1348,11 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups.add() + secondary_animation = armature_data.vrm_addon_extension.vrm0.secondary_animation + secondary_animation.bone_groups.add() + secondary_animation.active_bone_group_index = ( + len(secondary_animation.bone_groups) - 1 + ) return {"FINISHED"} if TYPE_CHECKING: @@ -732,12 +1383,86 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - bone_groups = ( - armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups + secondary_animation = armature_data.vrm_addon_extension.vrm0.secondary_animation + if len(secondary_animation.bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + secondary_animation.bone_groups.remove(self.bone_group_index) + secondary_animation.active_bone_group_index = min( + secondary_animation.active_bone_group_index, + max(0, len(secondary_animation.bone_groups) - 1), ) - if len(bone_groups) <= self.bone_group_index: + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + bone_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_vrm0_secondary_animation_group(Operator): + bl_idname = "vrm.move_up_vrm0_secondary_animation_group" + bl_label = "Move Up Spring Bone" + bl_description = "Move Up VRM 0.x Secondary Animation Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + bone_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + secondary_animation = armature_data.vrm_addon_extension.vrm0.secondary_animation + if len(secondary_animation.bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + new_index = (self.bone_group_index - 1) % len(secondary_animation.bone_groups) + secondary_animation.bone_groups.move(self.bone_group_index, new_index) + secondary_animation.active_bone_group_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + bone_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm0_secondary_animation_group(Operator): + bl_idname = "vrm.move_down_vrm0_secondary_animation_group" + bl_label = "Move Down Spring Bone" + bl_description = "Move Down VRM 0.x Secondary Animation Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + bone_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): return {"CANCELLED"} - bone_groups.remove(self.bone_group_index) + secondary_animation = armature_data.vrm_addon_extension.vrm0.secondary_animation + if len(secondary_animation.bone_groups) <= self.bone_group_index: + return {"CANCELLED"} + new_index = (self.bone_group_index - 1) % len(secondary_animation.bone_groups) + secondary_animation.bone_groups.move(self.bone_group_index, new_index) + secondary_animation.active_bone_group_index = new_index return {"FINISHED"} if TYPE_CHECKING: @@ -765,9 +1490,13 @@ def execute(self, _context: Context) -> set[str]: if not isinstance(armature_data, Armature): return {"CANCELLED"} ext = armature_data.vrm_addon_extension - collider_group = ext.vrm0.secondary_animation.collider_groups.add() + secondary_animation = ext.vrm0.secondary_animation + collider_group = secondary_animation.collider_groups.add() collider_group.uuid = uuid.uuid4().hex collider_group.refresh(armature) + secondary_animation.active_collider_group_index = ( + len(secondary_animation.collider_groups) - 1 + ) return {"FINISHED"} if TYPE_CHECKING: @@ -797,17 +1526,96 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - collider_groups = ( - armature_data.vrm_addon_extension.vrm0.secondary_animation.collider_groups - ) - if len(collider_groups) <= self.collider_group_index: + secondary_animation = armature_data.vrm_addon_extension.vrm0.secondary_animation + if len(secondary_animation.collider_groups) <= self.collider_group_index: return {"CANCELLED"} - collider_groups.remove(self.collider_group_index) + secondary_animation.collider_groups.remove(self.collider_group_index) for ( bone_group ) in armature_data.vrm_addon_extension.vrm0.secondary_animation.bone_groups: bone_group.refresh(armature) + + secondary_animation.active_collider_group_index = min( + secondary_animation.active_collider_group_index, + max(0, len(secondary_animation.collider_groups) - 1), + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_vrm0_secondary_animation_collider_group(Operator): + bl_idname = "vrm.move_up_vrm0_secondary_animation_collider_group" + bl_label = "Move Up Collider Group" + bl_description = "Move Up VRM 0.x Secondary Animation Collider Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + secondary_animation = armature_data.vrm_addon_extension.vrm0.secondary_animation + if len(secondary_animation.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + new_index = (self.collider_group_index - 1) % len( + secondary_animation.collider_groups + ) + secondary_animation.collider_groups.move(self.collider_group_index, new_index) + secondary_animation.active_collider_group_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + collider_group_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm0_secondary_animation_collider_group(Operator): + bl_idname = "vrm.move_down_vrm0_secondary_animation_collider_group" + bl_label = "Move Down Collider Group" + bl_description = "Move Down VRM 0.x Secondary Animation Collider Group" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"} + ) + collider_group_index: IntProperty( # type: ignore[valid-type] + min=0, + options={"HIDDEN"}, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + secondary_animation = armature_data.vrm_addon_extension.vrm0.secondary_animation + if len(secondary_animation.collider_groups) <= self.collider_group_index: + return {"CANCELLED"} + new_index = (self.collider_group_index + 1) % len( + secondary_animation.collider_groups + ) + secondary_animation.collider_groups.move(self.collider_group_index, new_index) + secondary_animation.active_collider_group_index = new_index return {"FINISHED"} if TYPE_CHECKING: diff --git a/src/io_scene_vrm/editor/vrm0/panel.py b/src/io_scene_vrm/editor/vrm0/panel.py index 6124693a6..f524fe291 100644 --- a/src/io_scene_vrm/editor/vrm0/panel.py +++ b/src/io_scene_vrm/editor/vrm0/panel.py @@ -2,27 +2,45 @@ import bpy from bpy.app.translations import pgettext -from bpy.types import Armature, Context, Mesh, Object, Panel, UILayout +from bpy.types import ( + Armature, + Context, + Mesh, + Object, + Panel, + UILayout, +) from ...common.vrm0.human_bone import HumanBoneSpecification, HumanBoneSpecifications from .. import ops, search from ..extension import VrmAddonSceneExtensionPropertyGroup from ..migration import migrate from ..ops import layout_operator -from ..panel import VRM_PT_vrm_armature_object_property +from ..panel import VRM_PT_vrm_armature_object_property, draw_template_list from ..search import active_object_is_vrm0_armature from . import ops as vrm0_ops from .property_group import ( + Vrm0BlendShapeBindPropertyGroup, + Vrm0BlendShapeGroupPropertyGroup, Vrm0BlendShapeMasterPropertyGroup, Vrm0FirstPersonPropertyGroup, Vrm0HumanoidPropertyGroup, + Vrm0MaterialValueBindPropertyGroup, Vrm0MetaPropertyGroup, + Vrm0SecondaryAnimationColliderGroupPropertyGroup, + Vrm0SecondaryAnimationGroupPropertyGroup, Vrm0SecondaryAnimationPropertyGroup, ) from .ui_list import ( VRM_UL_vrm0_blend_shape_bind, VRM_UL_vrm0_blend_shape_group, + VRM_UL_vrm0_first_person_mesh_annotation, VRM_UL_vrm0_material_value_bind, + VRM_UL_vrm0_secondary_animation_collider_group, + VRM_UL_vrm0_secondary_animation_collider_group_collider, + VRM_UL_vrm0_secondary_animation_group, + VRM_UL_vrm0_secondary_animation_group_bone, + VRM_UL_vrm0_secondary_animation_group_collider_group, ) @@ -439,32 +457,32 @@ def draw_vrm0_first_person_layout( ) layout.prop(first_person, "first_person_bone_offset", icon="BONE_DATA") layout.prop(first_person, "look_at_type_name") - box = layout.box() - box.label(text="Mesh Annotations", icon="RESTRICT_RENDER_OFF") - for mesh_annotation_index, mesh_annotation in enumerate( - first_person.mesh_annotations - ): - row = box.row() - row.prop_search( - mesh_annotation.mesh, - "mesh_object_name", - context.scene.vrm_addon_extension, - "mesh_object_names", - icon="OUTLINER_OB_MESH", - ) - row.prop(mesh_annotation, "first_person_flag") - remove_mesh_annotation_op = layout_operator( - row, - vrm0_ops.VRM_OT_remove_vrm0_first_person_mesh_annotation, - text="Remove", - icon="REMOVE", - ) - remove_mesh_annotation_op.armature_name = armature.name - remove_mesh_annotation_op.mesh_annotation_index = mesh_annotation_index - add_mesh_annotation_op = layout_operator( - box, vrm0_ops.VRM_OT_add_vrm0_first_person_mesh_annotation, icon="ADD" + layout.label(text="Mesh Annotations", icon="RESTRICT_RENDER_OFF") + + ( + mesh_annotation_collection_ops, + mesh_annotation_collection_item_ops, + mesh_annotation_index, + _, + _, + ) = draw_template_list( + layout, + VRM_UL_vrm0_first_person_mesh_annotation.bl_idname, + first_person, + "mesh_annotations", + "active_mesh_annotation_index", + vrm0_ops.VRM_OT_add_vrm0_first_person_mesh_annotation, + vrm0_ops.VRM_OT_remove_vrm0_first_person_mesh_annotation, + vrm0_ops.VRM_OT_move_up_vrm0_first_person_mesh_annotation, + vrm0_ops.VRM_OT_move_down_vrm0_first_person_mesh_annotation, ) - add_mesh_annotation_op.armature_name = armature.name + + for mesh_annotation_collection_op in mesh_annotation_collection_ops: + mesh_annotation_collection_op.armature_name = armature.name + + for mesh_annotation_collection_item_op in mesh_annotation_collection_item_ops: + mesh_annotation_collection_item_op.mesh_annotation_index = mesh_annotation_index + layout.separator() box = layout.box() box.label(text="Look At Horizontal Inner", icon="FULLSCREEN_EXIT") @@ -562,39 +580,35 @@ def draw_vrm0_blend_shape_master_layout( ) blend_data = context.blend_data - row = layout.row() - row.template_list( + ( + blend_shape_group_collection_ops, + blend_shape_group_collection_item_ops, + blend_shape_group_index, + blend_shape_group, + (add_blend_shape_group_op, _, _, _), + ) = draw_template_list( + layout, VRM_UL_vrm0_blend_shape_group.bl_idname, - "", blend_shape_master, "blend_shape_groups", - blend_shape_master, "active_blend_shape_group_index", - ) - blend_shape_group_index = blend_shape_master.active_blend_shape_group_index - - list_side_column = row.column(align=True) - add_blend_shape_group_op = layout_operator( - list_side_column, vrm0_ops.VRM_OT_add_vrm0_blend_shape_group, - icon="ADD", - text="", - ) - add_blend_shape_group_op.name = "New" - add_blend_shape_group_op.armature_name = armature.name - remove_blend_shape_group_op = layout_operator( - list_side_column, vrm0_ops.VRM_OT_remove_vrm0_blend_shape_group, - icon="REMOVE", - text="", + vrm0_ops.VRM_OT_move_up_vrm0_blend_shape_group, + vrm0_ops.VRM_OT_move_down_vrm0_blend_shape_group, ) - remove_blend_shape_group_op.armature_name = armature.name - remove_blend_shape_group_op.blend_shape_group_index = blend_shape_group_index - if 0 <= blend_shape_group_index < len(blend_shape_master.blend_shape_groups): - blend_shape_group = blend_shape_master.blend_shape_groups[ + for blend_shape_group_collection_op in blend_shape_group_collection_ops: + blend_shape_group_collection_op.armature_name = armature.name + + for blend_shape_group_collection_item_op in blend_shape_group_collection_item_ops: + blend_shape_group_collection_item_op.blend_shape_group_index = ( blend_shape_group_index - ] + ) + + add_blend_shape_group_op.name = "New" + + if isinstance(blend_shape_group, Vrm0BlendShapeGroupPropertyGroup): column = layout.column() column.prop(blend_shape_group, "name") column.prop(blend_shape_group, "preset_name") @@ -604,38 +618,33 @@ def draw_vrm0_blend_shape_master_layout( binds_box = column.box() binds_box.label(text="Binds", icon="MESH_DATA") - binds_row = binds_box.row() - binds_row.template_list( + + ( + bind_collection_ops, + bind_collection_item_ops, + bind_index, + bind, + _, + ) = draw_template_list( + binds_box, VRM_UL_vrm0_blend_shape_bind.bl_idname, - "", blend_shape_group, "binds", - blend_shape_group, "active_bind_index", - ) - - bind_index = blend_shape_group.active_bind_index - binds_side_column = binds_row.column(align=True) - add_blend_shape_bind_op = layout_operator( - binds_side_column, vrm0_ops.VRM_OT_add_vrm0_blend_shape_bind, - icon="ADD", - text="", - ) - add_blend_shape_bind_op.armature_name = armature.name - add_blend_shape_bind_op.blend_shape_group_index = blend_shape_group_index - remove_blend_shape_bind_op = layout_operator( - binds_side_column, vrm0_ops.VRM_OT_remove_vrm0_blend_shape_bind, - icon="REMOVE", - text="", + vrm0_ops.VRM_OT_move_up_vrm0_blend_shape_bind, + vrm0_ops.VRM_OT_move_down_vrm0_blend_shape_bind, ) - remove_blend_shape_bind_op.armature_name = armature.name - remove_blend_shape_bind_op.blend_shape_group_index = blend_shape_group_index - remove_blend_shape_bind_op.bind_index = bind_index - if 0 <= bind_index < len(blend_shape_group.binds): - bind = blend_shape_group.binds[bind_index] + for bind_collection_op in bind_collection_ops: + bind_collection_op.armature_name = armature.name + bind_collection_op.blend_shape_group_index = blend_shape_group_index + + for bind_collection_item_op in bind_collection_item_ops: + bind_collection_item_op.bind_index = bind_index + + if isinstance(bind, Vrm0BlendShapeBindPropertyGroup): bind_column = binds_box.column() bind_column.prop_search( bind.mesh, @@ -664,37 +673,39 @@ def draw_vrm0_blend_shape_master_layout( column.separator(factor=0.2) material_value_binds_box = column.box() material_value_binds_box.label(text="Material Values", icon="MATERIAL") - material_value_binds_row = material_value_binds_box.row() - material_value_binds_row.template_list( + + ( + material_value_collection_ops, + material_value_collection_item_ops, + material_value_index, + material_value, + _, + ) = draw_template_list( + material_value_binds_box, VRM_UL_vrm0_material_value_bind.bl_idname, - "", blend_shape_group, "material_values", - blend_shape_group, "active_material_value_index", - ) - material_value_index = blend_shape_group.active_material_value_index - material_value_binds_side_column = material_value_binds_row.column(align=True) - add_material_value_op = layout_operator( - material_value_binds_side_column, vrm0_ops.VRM_OT_add_vrm0_material_value_bind, - icon="ADD", - text="", - ) - add_material_value_op.armature_name = armature.name - add_material_value_op.blend_shape_group_index = blend_shape_group_index - remove_material_value_op = layout_operator( - material_value_binds_side_column, vrm0_ops.VRM_OT_remove_vrm0_material_value_bind, - icon="REMOVE", - text="", + vrm0_ops.VRM_OT_move_up_vrm0_material_value_bind, + vrm0_ops.VRM_OT_move_down_vrm0_material_value_bind, ) - remove_material_value_op.armature_name = armature.name - remove_material_value_op.blend_shape_group_index = blend_shape_group_index - remove_material_value_op.material_value_index = material_value_index - if 0 <= material_value_index < len(blend_shape_group.material_values): - material_value = blend_shape_group.material_values[material_value_index] + for material_value_bind_collection_op in material_value_collection_ops: + material_value_bind_collection_op.armature_name = armature.name + material_value_bind_collection_op.blend_shape_group_index = ( + blend_shape_group_index + ) + + for ( + material_value_bind_collection_item_op + ) in material_value_collection_item_ops: + material_value_bind_collection_item_op.material_value_index = ( + material_value_index + ) + + if isinstance(material_value, Vrm0MaterialValueBindPropertyGroup): material_value_column = material_value_binds_box.column() material_value_column.prop_search( material_value, "material", blend_data, "materials" @@ -839,214 +850,226 @@ def draw_vrm0_secondary_animation_layout( secondary_animation: Vrm0SecondaryAnimationPropertyGroup, ) -> None: migrate(armature.name, defer=True) + draw_vrm0_secondary_animation_bone_groups_layout( + armature, layout, secondary_animation + ) + draw_vrm0_secondary_animation_collider_groups_layout( + armature, layout, secondary_animation + ) + + +def draw_vrm0_secondary_animation_bone_groups_layout( + armature: Object, + layout: UILayout, + secondary_animation: Vrm0SecondaryAnimationPropertyGroup, +) -> None: data = armature.data if not isinstance(data, Armature): return - bone_groups_box = layout.box() - bone_groups_box.label(text="Spring Bone Groups", icon="GROUP_BONE") - for bone_group_index, bone_group in enumerate(secondary_animation.bone_groups): - row = bone_groups_box.row() - row.alignment = "LEFT" + header_row = layout.row() + header_row.alignment = "LEFT" + header_row.prop( + secondary_animation, + "show_expanded_bone_groups", + icon="TRIA_DOWN" + if secondary_animation.show_expanded_bone_groups + else "TRIA_RIGHT", + emboss=False, + ) + if not secondary_animation.show_expanded_bone_groups: + return - text = "" - if bone_group.bones: - text = ( - "(" + ", ".join(str(bone.bone_name) for bone in bone_group.bones) + ")" - ) + box = layout.box() - if bone_group.center.bone_name: - if text: - text = " - " + text - text = bone_group.center.bone_name + text - - if bone_group.comment: - if text: - text = " / " + text - text = bone_group.comment + text - - if not text: - text = "(EMPTY)" - - row.prop( - bone_group, - "show_expanded", - icon="TRIA_DOWN" if bone_group.show_expanded else "TRIA_RIGHT", - emboss=False, - text=text, - translate=False, - ) - if not bone_group.show_expanded: - continue - - box = bone_groups_box.box() - row = box.row() - box.prop(bone_group, "comment", icon="BOOKMARKS") - box.prop(bone_group, "stiffiness", icon="RIGID_BODY_CONSTRAINT") - box.prop(bone_group, "drag_force", icon="FORCE_DRAG") - box.separator() - box.prop(bone_group, "gravity_power", icon="OUTLINER_OB_FORCE_FIELD") - box.prop(bone_group, "gravity_dir", icon="OUTLINER_OB_FORCE_FIELD") - box.separator() - box.prop_search( - bone_group.center, - "bone_name", - data, - "bones", - icon="PIVOT_MEDIAN", - text="Center Bone", - ) - box.prop( - bone_group, - "hit_radius", - icon="MOD_PHYSICS", - ) - box.separator() - row = box.row() - row.alignment = "LEFT" - row.prop( - bone_group, - "show_expanded_bones", - icon="TRIA_DOWN" if bone_group.show_expanded_bones else "TRIA_RIGHT", - emboss=False, - ) - if bone_group.show_expanded_bones: - for bone_index, bone in enumerate(bone_group.bones): - bone_row = box.split(align=True, factor=0.7) - bone_row.prop_search(bone, "bone_name", data, "bones", text="") - remove_bone_op = layout_operator( - bone_row, - vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group_bone, - icon="REMOVE", - text="Remove", - ) - remove_bone_op.armature_name = armature.name - remove_bone_op.bone_group_index = bone_group_index - remove_bone_op.bone_index = bone_index - add_bone_op = layout_operator( - box, - vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group_bone, - icon="ADD", - ) - add_bone_op.armature_name = armature.name - add_bone_op.bone_group_index = bone_group_index - - row = box.row() - row.alignment = "LEFT" - row.prop( - bone_group, - "show_expanded_collider_groups", - icon="TRIA_DOWN" - if bone_group.show_expanded_collider_groups - else "TRIA_RIGHT", - emboss=False, - ) - if bone_group.show_expanded_collider_groups: - for collider_group_index, collider_group in enumerate( - bone_group.collider_groups - ): - collider_group_row = box.split(align=True, factor=0.7) - collider_group_row.prop_search( - collider_group, - "value", - secondary_animation, - "collider_groups", - text="", - ) - remove_group_collider_group_op = layout_operator( - collider_group_row, - vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group_collider_group, - icon="REMOVE", - text="Remove", - ) - remove_group_collider_group_op.armature_name = armature.name - remove_group_collider_group_op.bone_group_index = bone_group_index - remove_group_collider_group_op.collider_group_index = ( - collider_group_index - ) - add_group_collider_group_op = layout_operator( - box, - vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group_collider_group, - icon="ADD", - ) - add_group_collider_group_op.armature_name = armature.name - add_group_collider_group_op.bone_group_index = bone_group_index + ( + bone_group_collection_ops, + bone_group_collection_item_ops, + bone_group_index, + bone_group, + _, + ) = draw_template_list( + box, + VRM_UL_vrm0_secondary_animation_group.bl_idname, + secondary_animation, + "bone_groups", + "active_bone_group_index", + vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group, + vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_group, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_group, + ) - remove_bone_group_op = layout_operator( - box, - vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group, - icon="REMOVE", - ) - remove_bone_group_op.armature_name = armature.name - remove_bone_group_op.bone_group_index = bone_group_index - add_bone_group_op = layout_operator( - bone_groups_box, vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group, icon="ADD" + for bone_group_collection_op in bone_group_collection_ops: + bone_group_collection_op.armature_name = armature.name + + for bone_group_collection_item_op in bone_group_collection_item_ops: + bone_group_collection_item_op.bone_group_index = bone_group_index + + if not isinstance(bone_group, Vrm0SecondaryAnimationGroupPropertyGroup): + return + + column = box.column() + column.prop(bone_group, "comment", icon="BOOKMARKS") + column.prop(bone_group, "stiffiness", icon="RIGID_BODY_CONSTRAINT") + column.prop(bone_group, "drag_force", icon="FORCE_DRAG") + column.separator() + column.prop(bone_group, "gravity_power", icon="OUTLINER_OB_FORCE_FIELD") + column.prop(bone_group, "gravity_dir", icon="OUTLINER_OB_FORCE_FIELD") + column.separator() + column.prop_search( + bone_group.center, + "bone_name", + data, + "bones", + icon="PIVOT_MEDIAN", + text="Center Bone", ) - add_bone_group_op.armature_name = armature.name - - collider_groups_box = layout.box() - collider_groups_box.label(text="Collider Groups", icon="SPHERE") - for collider_group_index, collider_group in enumerate( - secondary_animation.collider_groups - ): - row = collider_groups_box.row() - row.alignment = "LEFT" - row.prop( - collider_group, - "show_expanded", - icon="TRIA_DOWN" if collider_group.show_expanded else "TRIA_RIGHT", - emboss=False, - text=collider_group.name, - translate=False, - ) - if not collider_group.show_expanded: - continue - - box = collider_groups_box.box() - row = box.row() - box.label(text=collider_group.name) - box.prop_search(collider_group.node, "bone_name", data, "bones") - - box.label(text="Colliders:") - for collider_index, collider in enumerate(collider_group.colliders): - if not collider.bpy_object: - continue - collider_row = box.split(align=True, factor=0.5) - collider_row.prop( - collider.bpy_object, "name", icon="MESH_UVSPHERE", text="" - ) - collider_row.prop(collider.bpy_object, "empty_display_size", text="") - remove_collider_op = layout_operator( - collider_row, - vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_collider_group_collider, - icon="REMOVE", - text="Remove", - ) - remove_collider_op.armature_name = armature.name - remove_collider_op.collider_group_index = collider_group_index - remove_collider_op.collider_index = collider_index - add_collider_op = layout_operator( - box, - vrm0_ops.VRM_OT_add_vrm0_secondary_animation_collider_group_collider, - icon="ADD", - ) - add_collider_op.armature_name = armature.name - add_collider_op.collider_group_index = collider_group_index - add_collider_op.bone_name = collider_group.node.bone_name - - remove_collider_group_op = layout_operator( - box, - vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_collider_group, - icon="REMOVE", - ) - remove_collider_group_op.armature_name = armature.name - remove_collider_group_op.collider_group_index = collider_group_index - add_collider_group_op = layout_operator( - collider_groups_box, + column.prop( + bone_group, + "hit_radius", + icon="MOD_PHYSICS", + ) + column.separator() + + column.label(text="Bones:") + + ( + bone_collection_ops, + bone_collection_item_ops, + bone_index, + _bone, + _, + ) = draw_template_list( + column, + VRM_UL_vrm0_secondary_animation_group_bone.bl_idname, + bone_group, + "bones", + "active_bone_index", + vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group_bone, + vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group_bone, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_group_bone, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_group_bone, + ) + + for bone_collection_op in bone_collection_ops: + bone_collection_op.armature_name = armature.name + bone_collection_op.bone_group_index = bone_group_index + + for bone_collection_item_op in bone_collection_item_ops: + bone_collection_item_op.bone_index = bone_index + + column.separator() + column.label(text="Collider Groups:") + + ( + collider_group_collection_ops, + collider_group_collection_item_ops, + collider_group_index, + _collider_group, + _, + ) = draw_template_list( + column, + VRM_UL_vrm0_secondary_animation_group_collider_group.bl_idname, + bone_group, + "collider_groups", + "active_collider_group_index", + vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group_collider_group, + vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group_collider_group, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_group_collider_group, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_group_collider_group, + ) + + for collider_group_collection_op in collider_group_collection_ops: + collider_group_collection_op.armature_name = armature.name + collider_group_collection_op.bone_group_index = bone_group_index + + for collider_group_collection_item_op in collider_group_collection_item_ops: + collider_group_collection_item_op.collider_group_index = collider_group_index + + +def draw_vrm0_secondary_animation_collider_groups_layout( + armature: Object, + layout: UILayout, + secondary_animation: Vrm0SecondaryAnimationPropertyGroup, +) -> None: + data = armature.data + if not isinstance(data, Armature): + return + + header_row = layout.row() + header_row.alignment = "LEFT" + header_row.prop( + secondary_animation, + "show_expanded_collider_groups", + icon="TRIA_DOWN" + if secondary_animation.show_expanded_collider_groups + else "TRIA_RIGHT", + emboss=False, + ) + if not secondary_animation.show_expanded_collider_groups: + return + + box = layout.box() + + ( + collider_group_collection_ops, + collider_group_collection_item_ops, + collider_group_index, + collider_group, + _, + ) = draw_template_list( + box, + VRM_UL_vrm0_secondary_animation_collider_group.bl_idname, + secondary_animation, + "collider_groups", + "active_collider_group_index", vrm0_ops.VRM_OT_add_vrm0_secondary_animation_collider_group, - icon="ADD", + vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_collider_group, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_collider_group, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_collider_group, + ) + + for collider_group_collection_op in collider_group_collection_ops: + collider_group_collection_op.armature_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, Vrm0SecondaryAnimationColliderGroupPropertyGroup): + return + + column = box.column() + column.label(text=collider_group.name) + column.prop_search(collider_group.node, "bone_name", data, "bones") + column.label(text="Colliders:") + + ( + collider_collection_ops, + collider_collection_item_ops, + collider_index, + _, + _, + ) = draw_template_list( + column, + VRM_UL_vrm0_secondary_animation_collider_group_collider.bl_idname, + collider_group, + "colliders", + "active_collider_index", + vrm0_ops.VRM_OT_add_vrm0_secondary_animation_collider_group_collider, + vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_collider_group_collider, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_collider_group_collider, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_collider_group_collider, ) - add_collider_group_op.armature_name = armature.name + + for collider_collection_op in collider_collection_ops: + collider_collection_op.armature_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.armature_name = armature.name + collider_collection_item_op.collider_group_index = collider_group_index + collider_collection_item_op.collider_index = collider_index class VRM_PT_vrm0_secondary_animation_armature_object_property(Panel): diff --git a/src/io_scene_vrm/editor/vrm0/property_group.py b/src/io_scene_vrm/editor/vrm0/property_group.py index ba6b7a12b..80f66686a 100644 --- a/src/io_scene_vrm/editor/vrm0/property_group.py +++ b/src/io_scene_vrm/editor/vrm0/property_group.py @@ -469,6 +469,9 @@ class Vrm0FirstPersonPropertyGroup(PropertyGroup): name="lookAt Vertical Up", ) + # for UI + active_mesh_annotation_index: IntProperty(min=0) # type: ignore[valid-type] + if TYPE_CHECKING: # This code is auto generated. # `poetry run python tools/property_typing.py` @@ -482,6 +485,7 @@ class Vrm0FirstPersonPropertyGroup(PropertyGroup): look_at_horizontal_outer: Vrm0DegreeMapPropertyGroup # type: ignore[no-redef] look_at_vertical_down: Vrm0DegreeMapPropertyGroup # type: ignore[no-redef] look_at_vertical_up: Vrm0DegreeMapPropertyGroup # type: ignore[no-redef] + active_mesh_annotation_index: int # type: ignore[no-redef] # https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_BlendShape.cs#L18-L30 @@ -596,14 +600,8 @@ class Preset: ) # for UI - active_bind_index: IntProperty( # type: ignore[valid-type] - name="Active Bind Index", - default=0, - ) - active_material_value_index: IntProperty( # type: ignore[valid-type] - name="Active Material Value Index", - default=0, - ) + active_bind_index: IntProperty(min=0) # type: ignore[valid-type] + active_material_value_index: IntProperty(min=0) # type: ignore[valid-type] # アニメーション再生中はframe_change_pre/frame_change_postでしか # シェイプキーの値の変更ができないので、変更された値をここに保存しておく @@ -745,6 +743,8 @@ def refresh(self, armature: Object) -> None: # for UI show_expanded: BoolProperty() # type: ignore[valid-type] + active_collider_index: IntProperty(min=0) # type: ignore[valid-type] + # for reference from Vrm0SecondaryAnimationGroupPropertyGroup name: StringProperty() # type: ignore[valid-type] uuid: StringProperty() # type: ignore[valid-type] @@ -757,6 +757,7 @@ def refresh(self, armature: Object) -> None: Vrm0SecondaryAnimationColliderPropertyGroup ] show_expanded: bool # type: ignore[no-redef] + active_collider_index: int # type: ignore[no-redef] name: str # type: ignore[no-redef] uuid: str # type: ignore[no-redef] @@ -840,6 +841,9 @@ def update_gravity_dir(self, _context: Context) -> None: name="Collider Groups" ) + active_bone_index: IntProperty(min=0) # type: ignore[valid-type] + active_collider_group_index: IntProperty(min=0) # type: ignore[valid-type] + def refresh(self, armature: Object) -> None: armature_data = armature.data if not isinstance(armature_data, Armature): @@ -881,6 +885,8 @@ def refresh(self, armature: Object) -> None: show_expanded: bool # type: ignore[no-redef] show_expanded_bones: bool # type: ignore[no-redef] show_expanded_collider_groups: bool # type: ignore[no-redef] + active_bone_index: int # type: ignore[no-redef] + active_collider_group_index: int # type: ignore[no-redef] # https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_Meta.cs#L33-L149 @@ -1022,10 +1028,7 @@ class Vrm0BlendShapeMasterPropertyGroup(PropertyGroup): ) # for UI - active_blend_shape_group_index: IntProperty( # type: ignore[valid-type] - name="Active Blend Shape Group Index", - default=0, - ) + active_blend_shape_group_index: IntProperty(min=0) # type: ignore[valid-type] if TYPE_CHECKING: # This code is auto generated. @@ -1048,15 +1051,16 @@ class Vrm0SecondaryAnimationPropertyGroup(PropertyGroup): ) # for UI - active_bone_group_index: IntProperty( # type: ignore[valid-type] - name="Active Bone Group Index", - default=0, + show_expanded_bone_groups: BoolProperty( # type: ignore[valid-type] + name="Spring Bone Groups", ) - active_collider_group_index: IntProperty( # type: ignore[valid-type] - name="Active Collider Group Index", - default=0, + show_expanded_collider_groups: BoolProperty( # type: ignore[valid-type] + name="Collider Groups", ) + active_bone_group_index: IntProperty(min=0) # type: ignore[valid-type] + active_collider_group_index: IntProperty(min=0) # type: ignore[valid-type] + if TYPE_CHECKING: # This code is auto generated. # `poetry run python tools/property_typing.py` @@ -1066,6 +1070,8 @@ class Vrm0SecondaryAnimationPropertyGroup(PropertyGroup): collider_groups: CollectionPropertyProtocol[ # type: ignore[no-redef] Vrm0SecondaryAnimationColliderGroupPropertyGroup ] + show_expanded_bone_groups: bool # type: ignore[no-redef] + show_expanded_collider_groups: bool # type: ignore[no-redef] active_bone_group_index: int # type: ignore[no-redef] active_collider_group_index: int # type: ignore[no-redef] diff --git a/src/io_scene_vrm/editor/vrm0/ui_list.py b/src/io_scene_vrm/editor/vrm0/ui_list.py index 05df45c74..d0241b57d 100644 --- a/src/io_scene_vrm/editor/vrm0/ui_list.py +++ b/src/io_scene_vrm/editor/vrm0/ui_list.py @@ -1,14 +1,69 @@ +import bpy from bpy.types import Context, Mesh, UILayout, UIList +from ..property_group import BonePropertyGroup, StringPropertyGroup from .property_group import ( Vrm0BlendShapeBindPropertyGroup, Vrm0BlendShapeGroupPropertyGroup, + Vrm0FirstPersonPropertyGroup, Vrm0MaterialValueBindPropertyGroup, + Vrm0MeshAnnotationPropertyGroup, Vrm0SecondaryAnimationColliderGroupPropertyGroup, + Vrm0SecondaryAnimationColliderPropertyGroup, Vrm0SecondaryAnimationGroupPropertyGroup, ) +class VRM_UL_vrm0_first_person_mesh_annotation(UIList): + bl_idname = "VRM_UL_vrm0_first_person_mesh_annotation" + + def draw_item( + self, + context: Context, + layout: UILayout, + first_person: object, + mesh_annotation: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + index: int, + _flt_flag: int, + ) -> None: + if not isinstance(first_person, Vrm0FirstPersonPropertyGroup): + return + if not isinstance(mesh_annotation, Vrm0MeshAnnotationPropertyGroup): + return + + icon = "OUTLINER_OB_MESH" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + row = layout.split(factor=0.6, align=True) + if index == first_person.active_mesh_annotation_index: + row.prop_search( + mesh_annotation.mesh, + "mesh_object_name", + context.scene.vrm_addon_extension, + "mesh_object_names", + icon=icon, + text="", + translate=False, + ) + else: + row.label( + text=mesh_annotation.mesh.mesh_object_name, + translate=False, + icon=icon, + ) + row.prop(mesh_annotation, "first_person_flag", text="", translate=False) + + class VRM_UL_vrm0_secondary_animation_group(UIList): bl_idname = "VRM_UL_vrm0_secondary_animation_group" @@ -17,29 +72,146 @@ def draw_item( _context: Context, layout: UILayout, _data: object, - item: object, - icon: int, + bone_group: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + _index: int, + _flt_flag: int, + ) -> None: + if not isinstance(bone_group, Vrm0SecondaryAnimationGroupPropertyGroup): + return + + icon = "BONE_DATA" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + text = "" + if bone_group.bones: + text = ( + "(" + ", ".join(str(bone.bone_name) for bone in bone_group.bones) + ")" + ) + + if bone_group.center.bone_name: + if text: + text = " - " + text + text = bone_group.center.bone_name + text + + if bone_group.comment: + if text: + text = " / " + text + text = bone_group.comment + text + + if not text: + text = "(EMPTY)" + + layout.label(text=text, translate=False, icon=icon) + + +class VRM_UL_vrm0_secondary_animation_group_bone(UIList): + bl_idname = "VRM_UL_vrm0_secondary_animation_group_bone" + + def draw_item( + self, + _context: Context, + layout: UILayout, + bone_group: object, + bone: object, + _icon: int, _active_data: object, _active_prop_name: str, index: int, _flt_flag: int, ) -> None: - bone_group = item if not isinstance(bone_group, Vrm0SecondaryAnimationGroupPropertyGroup): return + if not isinstance(bone, BonePropertyGroup): + return + armature = bpy.data.armatures.get(bone.armature_data_name) + if armature is None: + return + + icon = "BONE_DATA" if self.layout_type == "GRID": layout.alignment = "CENTER" - layout.label(text="", translate=False, icon_value=icon) + layout.label(text="", translate=False, icon=icon) return if self.layout_type not in {"DEFAULT", "COMPACT"}: return - text = ( - f"(Spring Bone {index})" if bone_group.comment == "" else bone_group.comment - ) - layout.label(text=text, translate=False, icon="GROUP_BONE") + if index == bone_group.active_bone_index: + layout.prop_search( + bone, + "bone_name", + armature, + "bones", + text="", + translate=False, + icon=icon, + ) + else: + layout.label(text=bone.bone_name, translate=False, icon=icon) + + +class VRM_UL_vrm0_secondary_animation_group_collider_group(UIList): + bl_idname = "VRM_UL_vrm0_secondary_animation_group_collider_group" + + def draw_item( + self, + _context: Context, + layout: UILayout, + bone_group: object, + collider_group: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + index: int, + _flt_flag: int, + ) -> None: + if not isinstance(bone_group, Vrm0SecondaryAnimationGroupPropertyGroup): + return + if not isinstance(collider_group, StringPropertyGroup): + return + + secondary_animation = None + for armature in bpy.data.armatures: + ext = armature.vrm_addon_extension.vrm0 + if any(bone_group == bg for bg in ext.secondary_animation.bone_groups): + secondary_animation = ext.secondary_animation + break + if secondary_animation is None: + return + + icon = "PIVOT_INDIVIDUAL" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + if index == bone_group.active_collider_group_index: + layout.prop_search( + collider_group, + "value", + secondary_animation, + "collider_groups", + text="", + translate=False, + icon=icon, + ) + else: + layout.label(text=collider_group.value, translate=False, icon=icon) class VRM_UL_vrm0_secondary_animation_collider_group(UIList): @@ -50,28 +222,79 @@ def draw_item( _context: Context, layout: UILayout, _data: object, - item: object, - icon: int, + collider_group: object, + _icon: int, _active_data: object, _active_prop_name: str, _index: int, _flt_flag: int, ) -> None: - collider_group = item if not isinstance( collider_group, Vrm0SecondaryAnimationColliderGroupPropertyGroup ): return + icon = "SPHERE" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + layout.label(text=collider_group.name, translate=False, icon=icon) + + +class VRM_UL_vrm0_secondary_animation_collider_group_collider(UIList): + bl_idname = "VRM_UL_vrm0_secondary_animation_collider_group_collider" + + def draw_item( + self, + _context: Context, + layout: UILayout, + collider_group: object, + collider: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + index: int, + _flt_flag: int, + ) -> None: + if not isinstance( + collider_group, Vrm0SecondaryAnimationColliderGroupPropertyGroup + ): + return + if not isinstance(collider, Vrm0SecondaryAnimationColliderPropertyGroup): + return + + icon = "MESH_UVSPHERE" + if self.layout_type == "GRID": layout.alignment = "CENTER" - layout.label(text="", translate=False, icon_value=icon) + layout.label(text="", translate=False, icon=icon) return if self.layout_type not in {"DEFAULT", "COMPACT"}: return - layout.label(text=collider_group.name, translate=False, icon="SPHERE") + if collider.bpy_object is None: + return + + row = layout.split(align=True, factor=0.7) + if index == collider_group.active_collider_index: + row.prop( + collider.bpy_object, + "name", + icon=icon, + translate=False, + text="", + ) + row.prop(collider.bpy_object, "empty_display_size", text="") + else: + row.label(text=collider.bpy_object.name, icon=icon, translate=False) + row.prop(collider.bpy_object, "empty_display_size", text="", emboss=False) class VRM_UL_vrm0_blend_shape_group(UIList): diff --git a/src/io_scene_vrm/editor/vrm1/ops.py b/src/io_scene_vrm/editor/vrm1/ops.py index 90514227a..299d60ca4 100644 --- a/src/io_scene_vrm/editor/vrm1/ops.py +++ b/src/io_scene_vrm/editor/vrm1/ops.py @@ -32,7 +32,10 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - armature_data.vrm_addon_extension.vrm1.meta.authors.add() + meta = armature_data.vrm_addon_extension.vrm1.meta + author = meta.authors.add() + author.value = "" + meta.active_author_index = len(meta.authors) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -62,10 +65,86 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - authors = armature_data.vrm_addon_extension.vrm1.meta.authors - if len(authors) <= self.author_index: + meta = armature_data.vrm_addon_extension.vrm1.meta + if len(meta.authors) <= self.author_index: return {"CANCELLED"} - authors.remove(self.author_index) + meta.authors.remove(self.author_index) + meta.active_author_index = min( + meta.active_author_index, + max(0, len(meta.authors) - 1), + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + author_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_vrm1_meta_author(Operator): + bl_idname = "vrm.move_up_vrm1_meta_author" + bl_label = "Move Up Author" + bl_description = "Move Up VRM 1.0 Meta Author" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + author_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + meta = armature_data.vrm_addon_extension.vrm1.meta + if len(meta.authors) <= self.author_index: + return {"CANCELLED"} + new_index = (self.author_index - 1) % len(meta.authors) + meta.authors.move(self.author_index, new_index) + meta.active_author_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + author_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm1_meta_author(Operator): + bl_idname = "vrm.move_down_vrm1_meta_author" + bl_label = "Move Down Author" + bl_description = "Move Down VRM 1.0 Meta Author" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + author_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + meta = armature_data.vrm_addon_extension.vrm1.meta + if len(meta.authors) <= self.author_index: + return {"CANCELLED"} + new_index = (self.author_index + 1) % len(meta.authors) + meta.authors.move(self.author_index, new_index) + meta.active_author_index = new_index return {"FINISHED"} if TYPE_CHECKING: @@ -92,7 +171,10 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - armature_data.vrm_addon_extension.vrm1.meta.references.add() + meta = armature_data.vrm_addon_extension.vrm1.meta + reference = meta.references.add() + reference.value = "" + meta.active_reference_index = len(meta.references) - 1 return {"FINISHED"} if TYPE_CHECKING: @@ -122,10 +204,86 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - references = armature_data.vrm_addon_extension.vrm1.meta.references - if len(references) <= self.reference_index: + meta = armature_data.vrm_addon_extension.vrm1.meta + if len(meta.references) <= self.reference_index: + return {"CANCELLED"} + meta.references.remove(self.reference_index) + meta.active_reference_index = min( + meta.active_reference_index, + max(0, len(meta.references) - 1), + ) + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + reference_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_vrm1_meta_reference(Operator): + bl_idname = "vrm.move_up_vrm1_meta_reference" + bl_label = "Move Up Reference" + bl_description = "Move Up VRM 1.0 Meta Reference" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + reference_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + meta = armature_data.vrm_addon_extension.vrm1.meta + if len(meta.references) <= self.reference_index: return {"CANCELLED"} - references.remove(self.reference_index) + new_index = (self.reference_index - 1) % len(meta.references) + meta.references.move(self.reference_index, new_index) + meta.active_reference_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + reference_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm1_meta_reference(Operator): + bl_idname = "vrm.move_down_vrm1_meta_reference" + bl_label = "Move Down Reference" + bl_description = "Move Down VRM 1.0 Meta Reference" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + reference_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + meta = armature_data.vrm_addon_extension.vrm1.meta + if len(meta.references) <= self.reference_index: + return {"CANCELLED"} + new_index = (self.reference_index + 1) % len(meta.references) + meta.references.move(self.reference_index, new_index) + meta.active_reference_index = new_index return {"FINISHED"} if TYPE_CHECKING: @@ -318,7 +476,11 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - armature_data.vrm_addon_extension.vrm1.first_person.mesh_annotations.add() + first_person = armature_data.vrm_addon_extension.vrm1.first_person + first_person.mesh_annotations.add() + first_person.active_mesh_annotation_index = ( + len(first_person.mesh_annotations) - 1 + ) return {"FINISHED"} if TYPE_CHECKING: @@ -348,12 +510,90 @@ def execute(self, _context: Context) -> set[str]: armature_data = armature.data if not isinstance(armature_data, Armature): return {"CANCELLED"} - mesh_annotations = ( - armature_data.vrm_addon_extension.vrm1.first_person.mesh_annotations + first_person = armature_data.vrm_addon_extension.vrm1.first_person + if len(first_person.mesh_annotations) <= self.mesh_annotation_index: + return {"CANCELLED"} + first_person.mesh_annotations.remove(self.mesh_annotation_index) + first_person.active_mesh_annotation_index = min( + first_person.active_mesh_annotation_index, + max(0, len(first_person.mesh_annotations) - 1), ) - if len(mesh_annotations) <= self.mesh_annotation_index: + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + mesh_annotation_index: int # type: ignore[no-redef] + + +class VRM_OT_move_up_vrm1_first_person_mesh_annotation(Operator): + bl_idname = "vrm.move_up_vrm1_first_person_mesh_annotation" + bl_label = "Move Up Mesh Annotation" + bl_description = "Move Up VRM 1.0 First Person Mesh Annotation" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + mesh_annotation_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): return {"CANCELLED"} - mesh_annotations.remove(self.mesh_annotation_index) + first_person = armature_data.vrm_addon_extension.vrm1.first_person + if len(first_person.mesh_annotations) <= self.mesh_annotation_index: + return {"CANCELLED"} + new_index = (self.mesh_annotation_index - 1) % len( + first_person.mesh_annotations + ) + first_person.mesh_annotations.move(self.mesh_annotation_index, new_index) + first_person.active_mesh_annotation_index = new_index + return {"FINISHED"} + + if TYPE_CHECKING: + # This code is auto generated. + # `poetry run python tools/property_typing.py` + armature_name: str # type: ignore[no-redef] + mesh_annotation_index: int # type: ignore[no-redef] + + +class VRM_OT_move_down_vrm1_first_person_mesh_annotation(Operator): + bl_idname = "vrm.move_down_vrm1_first_person_mesh_annotation" + bl_label = "Move Down Mesh Annotation" + bl_description = "Move Down VRM 1.0 First Person Mesh Annotation" + bl_options: AbstractSet[str] = {"REGISTER", "UNDO"} + + armature_name: StringProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + ) + mesh_annotation_index: IntProperty( # type: ignore[valid-type] + options={"HIDDEN"}, + min=0, + ) + + def execute(self, _context: Context) -> set[str]: + armature = bpy.data.objects.get(self.armature_name) + if armature is None or armature.type != "ARMATURE": + return {"CANCELLED"} + armature_data = armature.data + if not isinstance(armature_data, Armature): + return {"CANCELLED"} + first_person = armature_data.vrm_addon_extension.vrm1.first_person + if len(first_person.mesh_annotations) <= self.mesh_annotation_index: + return {"CANCELLED"} + new_index = (self.mesh_annotation_index + 1) % len( + first_person.mesh_annotations + ) + first_person.mesh_annotations.move(self.mesh_annotation_index, new_index) + first_person.active_mesh_annotation_index = new_index return {"FINISHED"} if TYPE_CHECKING: diff --git a/src/io_scene_vrm/editor/vrm1/panel.py b/src/io_scene_vrm/editor/vrm1/panel.py index 8f3b2da07..9f49090da 100644 --- a/src/io_scene_vrm/editor/vrm1/panel.py +++ b/src/io_scene_vrm/editor/vrm1/panel.py @@ -10,7 +10,7 @@ from ..extension import VrmAddonSceneExtensionPropertyGroup from ..migration import migrate from ..ops import layout_operator -from ..panel import VRM_PT_vrm_armature_object_property +from ..panel import VRM_PT_vrm_armature_object_property, draw_template_list from ..search import active_object_is_vrm1_armature from . import ops as vrm1_ops from .property_group import ( @@ -28,7 +28,10 @@ ) from .ui_list import ( VRM_UL_vrm1_expression, + VRM_UL_vrm1_first_person_mesh_annotation, VRM_UL_vrm1_material_color_bind, + VRM_UL_vrm1_meta_author, + VRM_UL_vrm1_meta_reference, VRM_UL_vrm1_morph_target_bind, VRM_UL_vrm1_texture_transform_bind, ) @@ -364,35 +367,32 @@ def draw_vrm1_first_person_layout( VrmAddonSceneExtensionPropertyGroup.check_mesh_object_names_and_update( context.scene.name ) - box = layout.box() - column = box.column() + column = layout.column() column.label(text="Mesh Annotations", icon="FULLSCREEN_EXIT") - for mesh_annotation_index, mesh_annotation in enumerate( - first_person.mesh_annotations - ): - row = column.row(align=True) - row.prop_search( - mesh_annotation.node, - "mesh_object_name", - context.scene.vrm_addon_extension, - "mesh_object_names", - text="", - translate=False, - icon="OUTLINER_OB_MESH", - ) - row.prop(mesh_annotation, "type", text="", translate=False) - remove_mesh_annotation_op = layout_operator( - row, - vrm1_ops.VRM_OT_remove_vrm1_first_person_mesh_annotation, - text="Remove", - icon="REMOVE", - ) - remove_mesh_annotation_op.armature_name = armature.name - remove_mesh_annotation_op.mesh_annotation_index = mesh_annotation_index - add_mesh_annotation_op = layout_operator( - column, vrm1_ops.VRM_OT_add_vrm1_first_person_mesh_annotation + + ( + mesh_annotation_collection_ops, + mesh_annotation_collection_item_ops, + mesh_annotation_index, + _, + _, + ) = draw_template_list( + column, + VRM_UL_vrm1_first_person_mesh_annotation.bl_idname, + first_person, + "mesh_annotations", + "active_mesh_annotation_index", + vrm1_ops.VRM_OT_add_vrm1_first_person_mesh_annotation, + vrm1_ops.VRM_OT_remove_vrm1_first_person_mesh_annotation, + vrm1_ops.VRM_OT_move_up_vrm1_first_person_mesh_annotation, + vrm1_ops.VRM_OT_move_down_vrm1_first_person_mesh_annotation, ) - add_mesh_annotation_op.armature_name = armature.name + + for mesh_annotation_collection_op in mesh_annotation_collection_ops: + mesh_annotation_collection_op.armature_name = armature.name + + for mesh_annotation_collection_item_op in mesh_annotation_collection_item_ops: + mesh_annotation_collection_item_op.mesh_annotation_index = mesh_annotation_index class VRM_PT_vrm1_first_person_armature_object_property(Panel): @@ -652,70 +652,41 @@ def draw_vrm1_expressions_layout( context.scene.name ) - row = layout.row() - row.template_list( + preset_expressions = list(expressions.preset.name_to_expression_dict().values()) + + ( + expression_collection_ops, + expression_collection_item_ops, + expression_ui_list_element_index, + _, + _, + ) = draw_template_list( + layout, VRM_UL_vrm1_expression.bl_idname, - "", expressions, "expression_ui_list_elements", - expressions, "active_expression_ui_list_element_index", - ) - active_index = expressions.active_expression_ui_list_element_index - - list_side_column = row.column(align=True) - - add_custom_expression_op = layout_operator( - list_side_column, vrm1_ops.VRM_OT_add_vrm1_expressions_custom_expression, - icon="ADD", - text="", + vrm1_ops.VRM_OT_remove_vrm1_expressions_custom_expression, + vrm1_ops.VRM_OT_move_up_vrm1_expressions_custom_expression, + vrm1_ops.VRM_OT_move_down_vrm1_expressions_custom_expression, + can_remove=lambda active_collection_index: ( + active_collection_index >= len(preset_expressions) + ), + can_move=lambda active_collection_index: ( + len(expressions.custom) >= 2 + and active_collection_index >= len(preset_expressions) + ), ) - add_custom_expression_op.armature_name = armature.name - add_custom_expression_op.custom_expression_name = "custom" - preset_expressions = list(expressions.preset.name_to_expression_dict().values()) - custom_index = active_index - len(preset_expressions) + for expression_collection_op in expression_collection_ops: + expression_collection_op.armature_name = armature.name + expression_collection_op.custom_expression_name = "custom" - if 0 <= active_index < len(preset_expressions): - expression = preset_expressions[active_index] - custom = False - elif 0 <= custom_index < len(expressions.custom): - custom = True - expression = expressions.custom[custom_index] - remove_custom_expression_op = layout_operator( - list_side_column, - vrm1_ops.VRM_OT_remove_vrm1_expressions_custom_expression, - icon="REMOVE", - text="", - ) - remove_custom_expression_op.armature_name = armature.name - remove_custom_expression_op.custom_expression_name = expression.custom_name - - list_side_column.separator() - move_up_custom_expression_op = layout_operator( - list_side_column, - vrm1_ops.VRM_OT_move_up_vrm1_expressions_custom_expression, - icon="TRIA_UP", - text="", - ) - move_up_custom_expression_op.armature_name = armature.name - move_up_custom_expression_op.custom_expression_name = expression.custom_name - - move_down_custom_expression_op = layout_operator( - list_side_column, - vrm1_ops.VRM_OT_move_down_vrm1_expressions_custom_expression, - icon="TRIA_DOWN", - text="", - ) - move_down_custom_expression_op.armature_name = armature.name - move_down_custom_expression_op.custom_expression_name = expression.custom_name - else: - return + custom_index = expression_ui_list_element_index - len(preset_expressions) - if custom: - layout.prop(expression, "custom_name") - else: + if 0 <= expression_ui_list_element_index < len(preset_expressions): + expression = preset_expressions[expression_ui_list_element_index] preset_icon = Vrm1ExpressionsPresetPropertyGroup.NAME_TO_ICON_DICT.get( expression.name ) @@ -723,6 +694,18 @@ def draw_vrm1_expressions_layout( logger.error(f"Unknown preset expression: {expression.name}") preset_icon = "SHAPEKEY_DATA" layout.label(text=expression.name, translate=False, icon=preset_icon) + elif 0 <= custom_index < len(expressions.custom): + expression = expressions.custom[custom_index] + + for expression_collection_item_op in expression_collection_item_ops: + expression_collection_item_op.custom_expression_name = ( + expression.custom_name + ) + + layout.prop(expression, "custom_name") + else: + return + column = layout.column() column.prop(expression, "preview", icon="PLAY", text="Preview") column.prop(expression, "is_binary", icon="IPO_CONSTANT") @@ -733,213 +716,116 @@ def draw_vrm1_expressions_layout( morph_target_binds_box = column.box() morph_target_binds_box.label(text="Morph Target Binds", icon="MESH_DATA") - morph_target_binds_row = morph_target_binds_box.row() - morph_target_binds_row.template_list( + + ( + morph_target_bind_collection_ops, + morph_target_bind_collection_item_ops, + morph_target_bind_index, + morph_target_bind, + _, + ) = draw_template_list( + morph_target_binds_box, VRM_UL_vrm1_morph_target_bind.bl_idname, - "", expression, "morph_target_binds", - expression, "active_morph_target_bind_index", - ) - - active_morph_target_bind_index = expression.active_morph_target_bind_index - morph_target_binds_side_column = morph_target_binds_row.column(align=True) - - add_morph_target_bind_op = layout_operator( - morph_target_binds_side_column, vrm1_ops.VRM_OT_add_vrm1_expression_morph_target_bind, - icon="ADD", - text="", + vrm1_ops.VRM_OT_remove_vrm1_expression_morph_target_bind, + vrm1_ops.VRM_OT_move_up_vrm1_expression_morph_target_bind, + vrm1_ops.VRM_OT_move_down_vrm1_expression_morph_target_bind, ) - add_morph_target_bind_op.armature_name = armature.name - add_morph_target_bind_op.expression_name = expression.name - - if expression.morph_target_binds: - remove_morph_target_bind_op = layout_operator( - morph_target_binds_side_column, - vrm1_ops.VRM_OT_remove_vrm1_expression_morph_target_bind, - icon="REMOVE", - text="", - ) - remove_morph_target_bind_op.armature_name = armature.name - remove_morph_target_bind_op.expression_name = expression.name - remove_morph_target_bind_op.bind_index = active_morph_target_bind_index - - morph_target_binds_side_column.separator() - move_up_morph_target_bind_op = layout_operator( - morph_target_binds_side_column, - vrm1_ops.VRM_OT_move_up_vrm1_expression_morph_target_bind, - icon="TRIA_UP", - text="", - ) - move_up_morph_target_bind_op.armature_name = armature.name - move_up_morph_target_bind_op.expression_name = expression.name - move_up_morph_target_bind_op.bind_index = ( - expression.active_morph_target_bind_index - ) + for morph_target_bind_collection_op in morph_target_bind_collection_ops: + morph_target_bind_collection_op.armature_name = armature.name + morph_target_bind_collection_op.expression_name = expression.name - move_down_morph_target_bind_op = layout_operator( - morph_target_binds_side_column, - vrm1_ops.VRM_OT_move_down_vrm1_expression_morph_target_bind, - icon="TRIA_DOWN", - text="", - ) - move_down_morph_target_bind_op.armature_name = armature.name - move_down_morph_target_bind_op.expression_name = expression.name - move_down_morph_target_bind_op.bind_index = ( - expression.active_morph_target_bind_index - ) + for morph_target_bind_collection_item_op in morph_target_bind_collection_item_ops: + morph_target_bind_collection_item_op.bind_index = morph_target_bind_index - if 0 <= active_morph_target_bind_index < len(expression.morph_target_binds): + if isinstance(morph_target_bind, Vrm1MorphTargetBindPropertyGroup): draw_vrm1_expressions_morph_target_bind_layout( context, morph_target_binds_box, - expression.morph_target_binds[active_morph_target_bind_index], + morph_target_bind, ) column.separator(factor=0.2) material_color_binds_box = column.box() material_color_binds_box.label(text="Material Color Binds", icon="MATERIAL") - material_color_binds_row = material_color_binds_box.row() - material_color_binds_row.template_list( + + ( + material_color_bind_collection_ops, + material_color_bind_collection_item_ops, + material_color_bind_index, + material_color_bind, + _, + ) = draw_template_list( + material_color_binds_box, VRM_UL_vrm1_material_color_bind.bl_idname, - "", expression, "material_color_binds", - expression, "active_material_color_bind_index", - ) - active_material_color_bind_index = expression.active_material_color_bind_index - material_color_binds_side_column = material_color_binds_row.column(align=True) - - add_material_color_bind_op = layout_operator( - material_color_binds_side_column, vrm1_ops.VRM_OT_add_vrm1_expression_material_color_bind, - icon="ADD", - text="", + vrm1_ops.VRM_OT_remove_vrm1_expression_material_color_bind, + vrm1_ops.VRM_OT_move_up_vrm1_expression_material_color_bind, + vrm1_ops.VRM_OT_move_down_vrm1_expression_material_color_bind, ) - add_material_color_bind_op.armature_name = armature.name - add_material_color_bind_op.expression_name = expression.name - - if expression.material_color_binds: - remove_material_color_bind_op = layout_operator( - material_color_binds_side_column, - vrm1_ops.VRM_OT_remove_vrm1_expression_material_color_bind, - icon="REMOVE", - text="", - ) - remove_material_color_bind_op.armature_name = armature.name - remove_material_color_bind_op.expression_name = expression.name - remove_material_color_bind_op.bind_index = active_material_color_bind_index - material_color_binds_side_column.separator() + for material_color_bind_collection_op in material_color_bind_collection_ops: + material_color_bind_collection_op.armature_name = armature.name + material_color_bind_collection_op.expression_name = expression.name - move_up_material_color_bind_op = layout_operator( - material_color_binds_side_column, - vrm1_ops.VRM_OT_move_up_vrm1_expression_material_color_bind, - icon="TRIA_UP", - text="", - ) - move_up_material_color_bind_op.armature_name = armature.name - move_up_material_color_bind_op.expression_name = expression.name - move_up_material_color_bind_op.bind_index = ( - expression.active_material_color_bind_index - ) + for ( + material_color_bind_collection_item_op + ) in material_color_bind_collection_item_ops: + material_color_bind_collection_item_op.bind_index = material_color_bind_index - move_down_material_color_bind_op = layout_operator( - material_color_binds_side_column, - vrm1_ops.VRM_OT_move_down_vrm1_expression_material_color_bind, - icon="TRIA_DOWN", - text="", - ) - move_down_material_color_bind_op.armature_name = armature.name - move_down_material_color_bind_op.expression_name = expression.name - move_down_material_color_bind_op.bind_index = ( - expression.active_material_color_bind_index - ) - - if 0 <= active_material_color_bind_index < len(expression.material_color_binds): + if isinstance(material_color_bind, Vrm1MaterialColorBindPropertyGroup): draw_vrm1_expressions_material_color_bind_layout( context, material_color_binds_box, - expression.material_color_binds[active_material_color_bind_index], + material_color_bind, ) column.separator(factor=0.2) texture_transform_binds_box = column.box() texture_transform_binds_box.label(text="Texture Transform Binds", icon="MATERIAL") - texture_transform_binds_row = texture_transform_binds_box.row() - texture_transform_binds_row.template_list( + + ( + texture_transform_bind_collection_ops, + texture_transform_bind_collection_item_ops, + texture_transform_bind_index, + texture_transform_bind, + _, + ) = draw_template_list( + texture_transform_binds_box, VRM_UL_vrm1_texture_transform_bind.bl_idname, - "", expression, "texture_transform_binds", - expression, "active_texture_transform_bind_index", - ) - active_texture_transform_bind_index = expression.active_texture_transform_bind_index - texture_transform_binds_side_column = texture_transform_binds_row.column(align=True) - - add_texture_transform_bind_op = layout_operator( - texture_transform_binds_side_column, vrm1_ops.VRM_OT_add_vrm1_expression_texture_transform_bind, - icon="ADD", - text="", + vrm1_ops.VRM_OT_remove_vrm1_expression_texture_transform_bind, + vrm1_ops.VRM_OT_move_up_vrm1_expression_texture_transform_bind, + vrm1_ops.VRM_OT_move_down_vrm1_expression_texture_transform_bind, ) - add_texture_transform_bind_op.armature_name = armature.name - add_texture_transform_bind_op.expression_name = expression.name - - if expression.texture_transform_binds: - remove_texture_transform_bind_op = layout_operator( - texture_transform_binds_side_column, - vrm1_ops.VRM_OT_remove_vrm1_expression_texture_transform_bind, - icon="REMOVE", - text="", - ) - remove_texture_transform_bind_op.armature_name = armature.name - remove_texture_transform_bind_op.expression_name = expression.name - remove_texture_transform_bind_op.bind_index = ( - active_texture_transform_bind_index - ) - - texture_transform_binds_side_column.separator() - move_up_texture_transform_bind_op = layout_operator( - texture_transform_binds_side_column, - vrm1_ops.VRM_OT_move_up_vrm1_expression_texture_transform_bind, - icon="TRIA_UP", - text="", - ) - move_up_texture_transform_bind_op.armature_name = armature.name - move_up_texture_transform_bind_op.expression_name = expression.name - move_up_texture_transform_bind_op.bind_index = ( - expression.active_texture_transform_bind_index - ) + for texture_transform_bind_collection_op in texture_transform_bind_collection_ops: + texture_transform_bind_collection_op.armature_name = armature.name + texture_transform_bind_collection_op.expression_name = expression.name - move_down_texture_transform_bind_op = layout_operator( - texture_transform_binds_side_column, - vrm1_ops.VRM_OT_move_down_vrm1_expression_texture_transform_bind, - icon="TRIA_DOWN", - text="", - ) - move_down_texture_transform_bind_op.armature_name = armature.name - move_down_texture_transform_bind_op.expression_name = expression.name - move_down_texture_transform_bind_op.bind_index = ( - expression.active_texture_transform_bind_index + for ( + texture_transform_bind_collection_item_op + ) in texture_transform_bind_collection_item_ops: + texture_transform_bind_collection_item_op.bind_index = ( + texture_transform_bind_index ) - if ( - 0 - <= active_texture_transform_bind_index - < len(expression.texture_transform_binds) - ): + if isinstance(texture_transform_bind, Vrm1TextureTransformBindPropertyGroup): draw_vrm1_expressions_texture_transform_bind_layout( context, texture_transform_binds_box, - expression.texture_transform_binds[active_texture_transform_bind_index], + texture_transform_bind, ) @@ -1023,51 +909,65 @@ def draw_vrm1_meta_layout( layout.prop(meta, "vrm_name", icon="FILE_BLEND") layout.prop(meta, "version", icon="LINENUMBERS_ON") - authors_box = layout.box() - authors_column = authors_box.column() + authors_column = layout.column() authors_column.label(text="Authors:") - if meta.authors: - for author_index, author in enumerate(meta.authors): - author_row = authors_column.split(align=True, factor=0.7) - author_row.prop(author, "value", text="", translate=False, icon="USER") - remove_author_op = layout_operator( - author_row, - vrm1_ops.VRM_OT_remove_vrm1_meta_author, - text="Remove", - icon="REMOVE", - ) - remove_author_op.armature_name = armature.name - remove_author_op.author_index = author_index - add_author_op = layout_operator( - authors_column, vrm1_ops.VRM_OT_add_vrm1_meta_author + ( + author_collection_ops, + author_collection_item_ops, + author_index, + _, + _, + ) = draw_template_list( + authors_column, + VRM_UL_vrm1_meta_author.bl_idname, + meta, + "authors", + "active_author_index", + vrm1_ops.VRM_OT_add_vrm1_meta_author, + vrm1_ops.VRM_OT_remove_vrm1_meta_author, + vrm1_ops.VRM_OT_move_up_vrm1_meta_author, + vrm1_ops.VRM_OT_move_down_vrm1_meta_author, + can_remove=lambda _: len(meta.authors) >= 2, + compact=True, ) - add_author_op.armature_name = armature.name + + for author_collection_op in author_collection_ops: + author_collection_op.armature_name = armature.name + + for author_collection_item_op in author_collection_item_ops: + author_collection_item_op.author_index = author_index layout.prop(meta, "copyright_information") layout.prop(meta, "contact_information") - references_box = layout.box() - references_column = references_box.column() + references_column = layout.column() references_column.label(text="References:") - if meta.references: - for reference_index, reference in enumerate(meta.references): - reference_row = references_column.split(align=True, factor=0.7) - reference_row.prop( - reference, "value", text="", translate=False, icon="USER" - ) - remove_reference_op = layout_operator( - reference_row, - vrm1_ops.VRM_OT_remove_vrm1_meta_reference, - text="Remove", - icon="REMOVE", - ) - remove_reference_op.armature_name = armature.name - remove_reference_op.reference_index = reference_index - add_reference_op = layout_operator( - references_column, vrm1_ops.VRM_OT_add_vrm1_meta_reference + + ( + reference_collection_ops, + reference_collection_item_ops, + reference_index, + _, + _, + ) = draw_template_list( + references_column, + VRM_UL_vrm1_meta_reference.bl_idname, + meta, + "references", + "active_reference_index", + vrm1_ops.VRM_OT_add_vrm1_meta_reference, + vrm1_ops.VRM_OT_remove_vrm1_meta_reference, + vrm1_ops.VRM_OT_move_up_vrm1_meta_reference, + vrm1_ops.VRM_OT_move_down_vrm1_meta_reference, + compact=True, ) - add_reference_op.armature_name = armature.name + + for reference_collection_op in reference_collection_ops: + reference_collection_op.armature_name = armature.name + + for reference_collection_item_op in reference_collection_item_ops: + reference_collection_item_op.reference_index = reference_index layout.prop(meta, "third_party_licenses") # layout.prop(meta, "license_url", icon="URL") diff --git a/src/io_scene_vrm/editor/vrm1/property_group.py b/src/io_scene_vrm/editor/vrm1/property_group.py index fb1b52918..e1ab02771 100644 --- a/src/io_scene_vrm/editor/vrm1/property_group.py +++ b/src/io_scene_vrm/editor/vrm1/property_group.py @@ -946,12 +946,16 @@ class Vrm1FirstPersonPropertyGroup(PropertyGroup): type=Vrm1MeshAnnotationPropertyGroup, ) + # for UI + active_mesh_annotation_index: IntProperty(min=0) # type: ignore[valid-type] + if TYPE_CHECKING: # This code is auto generated. # `poetry run python tools/property_typing.py` mesh_annotations: CollectionPropertyProtocol[ # type: ignore[no-redef] Vrm1MeshAnnotationPropertyGroup ] + active_mesh_annotation_index: int # type: ignore[no-redef] # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.expressions.expression.morphTargetBind.schema.json @@ -1472,6 +1476,10 @@ class Vrm1MetaPropertyGroup(PropertyGroup): name="Other License URL" ) + # for UI + active_author_index: IntProperty(min=0) # type: ignore[valid-type] + active_reference_index: IntProperty(min=0) # type: ignore[valid-type] + if TYPE_CHECKING: # This code is auto generated. # `poetry run python tools/property_typing.py` @@ -1497,6 +1505,8 @@ class Vrm1MetaPropertyGroup(PropertyGroup): allow_redistribution: bool # type: ignore[no-redef] modification: str # type: ignore[no-redef] other_license_url: str # type: ignore[no-redef] + active_author_index: int # type: ignore[no-redef] + active_reference_index: int # type: ignore[no-redef] # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_vrm-1.0-beta/schema/VRMC_vrm.schema.json diff --git a/src/io_scene_vrm/editor/vrm1/ui_list.py b/src/io_scene_vrm/editor/vrm1/ui_list.py index 069b3b0c5..5dd79f87e 100644 --- a/src/io_scene_vrm/editor/vrm1/ui_list.py +++ b/src/io_scene_vrm/editor/vrm1/ui_list.py @@ -1,10 +1,14 @@ from bpy.types import Context, Mesh, UILayout, UIList from ...common.logging import get_logger +from ..property_group import StringPropertyGroup from .property_group import ( Vrm1ExpressionsPresetPropertyGroup, Vrm1ExpressionsPropertyGroup, + Vrm1FirstPersonPropertyGroup, Vrm1MaterialColorBindPropertyGroup, + Vrm1MeshAnnotationPropertyGroup, + Vrm1MetaPropertyGroup, Vrm1MorphTargetBindPropertyGroup, Vrm1TextureTransformBindPropertyGroup, ) @@ -12,6 +16,128 @@ logger = get_logger(__name__) +class VRM_UL_vrm1_meta_author(UIList): + bl_idname = "VRM_UL_vrm1_meta_author" + + def draw_item( + self, + _context: Context, + layout: UILayout, + meta: object, + author: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + index: int, + _flt_flag: int, + ) -> None: + if not isinstance(meta, Vrm1MetaPropertyGroup): + return + if not isinstance(author, StringPropertyGroup): + return + + icon = "USER" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + if index == meta.active_author_index: + layout.prop(author, "value", icon=icon, text="", translate=False) + else: + layout.label(text=author.value, icon=icon, translate=False) + + +class VRM_UL_vrm1_meta_reference(UIList): + bl_idname = "VRM_UL_vrm1_meta_reference" + + def draw_item( + self, + _context: Context, + layout: UILayout, + meta: object, + reference: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + index: int, + _flt_flag: int, + ) -> None: + if not isinstance(meta, Vrm1MetaPropertyGroup): + return + if not isinstance(reference, StringPropertyGroup): + return + + icon = "URL" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + if index == meta.active_reference_index: + layout.prop(reference, "value", icon=icon, text="", translate=False) + else: + layout.label(text=reference.value, icon=icon, translate=False) + + +class VRM_UL_vrm1_first_person_mesh_annotation(UIList): + bl_idname = "VRM_UL_vrm1_first_person_mesh_annotation" + + def draw_item( + self, + context: Context, + layout: UILayout, + first_person: object, + mesh_annotation: object, + _icon: int, + _active_data: object, + _active_prop_name: str, + index: int, + _flt_flag: int, + ) -> None: + if not isinstance(first_person, Vrm1FirstPersonPropertyGroup): + return + if not isinstance(mesh_annotation, Vrm1MeshAnnotationPropertyGroup): + return + + icon = "OUTLINER_OB_MESH" + + if self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", translate=False, icon=icon) + return + + if self.layout_type not in {"DEFAULT", "COMPACT"}: + return + + row = layout.split(factor=0.6, align=True) + if index == first_person.active_mesh_annotation_index: + row.prop_search( + mesh_annotation.node, + "mesh_object_name", + context.scene.vrm_addon_extension, + "mesh_object_names", + text="", + translate=False, + icon=icon, + ) + else: + row.label( + text=mesh_annotation.node.mesh_object_name, + translate=False, + icon=icon, + ) + row.prop(mesh_annotation, "type", text="", translate=False) + + class VRM_UL_vrm1_expression(UIList): bl_idname = "VRM_UL_vrm1_expression" @@ -19,7 +145,7 @@ def draw_item( self, _context: Context, layout: UILayout, - data: object, + expressions: object, _item: object, _icon: int, _active_data: object, @@ -27,7 +153,6 @@ def draw_item( index: int, _flt_flag: int, ) -> None: - expressions = data if not isinstance(expressions, Vrm1ExpressionsPropertyGroup): return preset_expression_items = list( @@ -69,7 +194,7 @@ def draw_item( context: Context, layout: UILayout, _data: object, - item: object, + morph_target_bind: object, icon: int, _active_data: object, _active_prop_name: str, @@ -77,7 +202,6 @@ def draw_item( _flt_flag: int, ) -> None: blend_data = context.blend_data - morph_target_bind = item if not isinstance(morph_target_bind, Vrm1MorphTargetBindPropertyGroup): return @@ -110,14 +234,13 @@ def draw_item( _context: Context, layout: UILayout, _data: object, - item: object, + material_color_bind: object, icon: int, _active_data: object, _active_prop_name: str, _index: int, _flt_flag: int, ) -> None: - material_color_bind = item if not isinstance(material_color_bind, Vrm1MaterialColorBindPropertyGroup): return @@ -153,14 +276,13 @@ def draw_item( _context: Context, layout: UILayout, _data: object, - item: object, + texture_transform_bind: object, icon: int, _active_data: object, _active_prop_name: str, _index: int, _flt_flag: int, ) -> None: - texture_transform_bind = item if not isinstance( texture_transform_bind, Vrm1TextureTransformBindPropertyGroup ): diff --git a/src/io_scene_vrm/importer/gltf2_addon_vrm_importer.py b/src/io_scene_vrm/importer/gltf2_addon_vrm_importer.py index 32392038c..e0086640a 100644 --- a/src/io_scene_vrm/importer/gltf2_addon_vrm_importer.py +++ b/src/io_scene_vrm/importer/gltf2_addon_vrm_importer.py @@ -2408,6 +2408,8 @@ def load_spring_bone1_colliders( if child.name in self.context.scene.collection.objects: self.context.scene.collection.objects.unlink(child) + spring_bone.active_collider_index = 0 + def load_spring_bone1_collider_groups( self, spring_bone: SpringBone1SpringBonePropertyGroup, @@ -2460,6 +2462,8 @@ def load_spring_bone1_collider_groups( continue collider_reference = collider_group.colliders[-1] collider_reference.collider_name = collider.name + collider_group.active_collider_index = 0 + spring_bone.active_collider_group_index = 0 def load_spring_bone1_springs( self, @@ -2534,6 +2538,7 @@ def load_spring_bone1_springs( drag_force = joint_dict.get("dragForce") if isinstance(drag_force, (int, float)): joint.drag_force = drag_force + spring.active_joint_index = 0 collider_group_indices = spring_dict.get("colliderGroups") if not isinstance(collider_group_indices, list): @@ -2553,6 +2558,8 @@ def load_spring_bone1_springs( continue collider_group_reference = spring.collider_groups[-1] collider_group_reference.collider_group_name = collider_group.name + spring.active_collider_group_index = 0 + spring_bone.active_spring_index = 0 def load_spring_bone1( self, diff --git a/src/io_scene_vrm/registration.py b/src/io_scene_vrm/registration.py index 606a072bf..ea3193803 100644 --- a/src/io_scene_vrm/registration.py +++ b/src/io_scene_vrm/registration.py @@ -51,6 +51,7 @@ from .editor.spring_bone1 import ops as spring_bone1_ops from .editor.spring_bone1 import panel as spring_bone1_panel from .editor.spring_bone1 import property_group as spring_bone1_property_group +from .editor.spring_bone1 import ui_list as spring_bone1_ui_list from .editor.vrm0 import handler as vrm0_handler from .editor.vrm0 import ops as vrm0_ops from .editor.vrm0 import panel as vrm0_panel @@ -244,11 +245,15 @@ def save_pre(_dummy: object) -> None: panel.VRM_PT_controller_unsupported_blender_version_warning, panel.VRM_PT_controller, panel.VRM_PT_vrm_armature_object_property, + vrm0_ui_list.VRM_UL_vrm0_first_person_mesh_annotation, vrm0_ui_list.VRM_UL_vrm0_blend_shape_bind, vrm0_ui_list.VRM_UL_vrm0_blend_shape_group, vrm0_ui_list.VRM_UL_vrm0_material_value_bind, vrm0_ui_list.VRM_UL_vrm0_secondary_animation_collider_group, vrm0_ui_list.VRM_UL_vrm0_secondary_animation_group, + vrm0_ui_list.VRM_UL_vrm0_secondary_animation_group_bone, + vrm0_ui_list.VRM_UL_vrm0_secondary_animation_group_collider_group, + vrm0_ui_list.VRM_UL_vrm0_secondary_animation_collider_group_collider, vrm0_panel.VRM_PT_vrm0_meta_armature_object_property, vrm0_panel.VRM_PT_vrm0_meta_ui, vrm0_panel.VRM_PT_vrm0_humanoid_armature_object_property, @@ -271,38 +276,69 @@ def save_pre(_dummy: object) -> None: vrm1_panel.VRM_PT_vrm1_expressions_ui, node_constraint1_panel.VRM_PT_node_constraint1_armature_object_property, node_constraint1_panel.VRM_PT_node_constraint1_ui, + spring_bone1_ui_list.VRM_UL_spring_bone1_collider, + spring_bone1_ui_list.VRM_UL_spring_bone1_collider_group, + spring_bone1_ui_list.VRM_UL_spring_bone1_collider_group_collider, + spring_bone1_ui_list.VRM_UL_spring_bone1_spring, + spring_bone1_ui_list.VRM_UL_spring_bone1_joint, + spring_bone1_ui_list.VRM_UL_spring_bone1_spring_collider_group, spring_bone1_panel.VRM_PT_spring_bone1_armature_object_property, spring_bone1_panel.VRM_PT_spring_bone1_ui, spring_bone1_panel.VRM_PT_spring_bone1_collider_property, + vrm1_ui_list.VRM_UL_vrm1_meta_author, + vrm1_ui_list.VRM_UL_vrm1_meta_reference, + vrm1_ui_list.VRM_UL_vrm1_first_person_mesh_annotation, vrm1_ui_list.VRM_UL_vrm1_expression, vrm1_ui_list.VRM_UL_vrm1_morph_target_bind, vrm1_ui_list.VRM_UL_vrm1_material_color_bind, vrm1_ui_list.VRM_UL_vrm1_texture_transform_bind, vrm0_ops.VRM_OT_add_vrm0_first_person_mesh_annotation, vrm0_ops.VRM_OT_remove_vrm0_first_person_mesh_annotation, + vrm0_ops.VRM_OT_move_up_vrm0_first_person_mesh_annotation, + vrm0_ops.VRM_OT_move_down_vrm0_first_person_mesh_annotation, vrm0_ops.VRM_OT_add_vrm0_material_value_bind, vrm0_ops.VRM_OT_remove_vrm0_material_value_bind, + vrm0_ops.VRM_OT_move_up_vrm0_material_value_bind, + vrm0_ops.VRM_OT_move_down_vrm0_material_value_bind, vrm0_ops.VRM_OT_add_vrm0_material_value_bind_target_value, vrm0_ops.VRM_OT_remove_vrm0_material_value_bind_target_value, + vrm0_ops.VRM_OT_add_vrm0_blend_shape_group, + vrm0_ops.VRM_OT_remove_vrm0_blend_shape_group, + vrm0_ops.VRM_OT_move_up_vrm0_blend_shape_group, + vrm0_ops.VRM_OT_move_down_vrm0_blend_shape_group, vrm0_ops.VRM_OT_add_vrm0_blend_shape_bind, vrm0_ops.VRM_OT_remove_vrm0_blend_shape_bind, + vrm0_ops.VRM_OT_move_down_vrm0_blend_shape_bind, + vrm0_ops.VRM_OT_move_up_vrm0_blend_shape_bind, vrm0_ops.VRM_OT_add_vrm0_secondary_animation_collider_group_collider, vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_collider_group_collider, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_collider_group_collider, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_collider_group_collider, vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group_bone, vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group_bone, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_group_bone, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_group_bone, vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group_collider_group, vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group_collider_group, - vrm0_ops.VRM_OT_add_vrm0_blend_shape_group, - vrm0_ops.VRM_OT_remove_vrm0_blend_shape_group, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_group_collider_group, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_group_collider_group, vrm0_ops.VRM_OT_add_vrm0_secondary_animation_group, vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_group, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_group, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_group, vrm0_ops.VRM_OT_add_vrm0_secondary_animation_collider_group, vrm0_ops.VRM_OT_remove_vrm0_secondary_animation_collider_group, + vrm0_ops.VRM_OT_move_up_vrm0_secondary_animation_collider_group, + vrm0_ops.VRM_OT_move_down_vrm0_secondary_animation_collider_group, vrm0_ops.VRM_OT_assign_vrm0_humanoid_human_bones_automatically, vrm1_ops.VRM_OT_add_vrm1_meta_author, vrm1_ops.VRM_OT_remove_vrm1_meta_author, + vrm1_ops.VRM_OT_move_up_vrm1_meta_author, + vrm1_ops.VRM_OT_move_down_vrm1_meta_author, vrm1_ops.VRM_OT_add_vrm1_meta_reference, vrm1_ops.VRM_OT_remove_vrm1_meta_reference, + vrm1_ops.VRM_OT_move_up_vrm1_meta_reference, + vrm1_ops.VRM_OT_move_down_vrm1_meta_reference, vrm1_ops.VRM_OT_add_vrm1_expressions_custom_expression, vrm1_ops.VRM_OT_remove_vrm1_expressions_custom_expression, vrm1_ops.VRM_OT_move_up_vrm1_expressions_custom_expression, @@ -321,20 +357,34 @@ def save_pre(_dummy: object) -> None: vrm1_ops.VRM_OT_move_down_vrm1_expression_texture_transform_bind, vrm1_ops.VRM_OT_add_vrm1_first_person_mesh_annotation, vrm1_ops.VRM_OT_remove_vrm1_first_person_mesh_annotation, + vrm1_ops.VRM_OT_move_up_vrm1_first_person_mesh_annotation, + vrm1_ops.VRM_OT_move_down_vrm1_first_person_mesh_annotation, vrm1_ops.VRM_OT_assign_vrm1_humanoid_human_bones_automatically, vrm1_ops.VRM_OT_update_vrm1_expression_ui_list_elements, spring_bone1_ops.VRM_OT_add_spring_bone1_collider, spring_bone1_ops.VRM_OT_remove_spring_bone1_collider, + spring_bone1_ops.VRM_OT_move_up_spring_bone1_collider, + spring_bone1_ops.VRM_OT_move_down_spring_bone1_collider, spring_bone1_ops.VRM_OT_add_spring_bone1_collider_group, spring_bone1_ops.VRM_OT_remove_spring_bone1_collider_group, + spring_bone1_ops.VRM_OT_move_up_spring_bone1_collider_group, + spring_bone1_ops.VRM_OT_move_down_spring_bone1_collider_group, spring_bone1_ops.VRM_OT_add_spring_bone1_collider_group_collider, spring_bone1_ops.VRM_OT_remove_spring_bone1_collider_group_collider, + spring_bone1_ops.VRM_OT_move_up_spring_bone1_collider_group_collider, + spring_bone1_ops.VRM_OT_move_down_spring_bone1_collider_group_collider, spring_bone1_ops.VRM_OT_add_spring_bone1_spring, spring_bone1_ops.VRM_OT_remove_spring_bone1_spring, + spring_bone1_ops.VRM_OT_move_up_spring_bone1_spring, + spring_bone1_ops.VRM_OT_move_down_spring_bone1_spring, spring_bone1_ops.VRM_OT_add_spring_bone1_spring_collider_group, spring_bone1_ops.VRM_OT_remove_spring_bone1_spring_collider_group, - spring_bone1_ops.VRM_OT_add_spring_bone1_spring_joint, - spring_bone1_ops.VRM_OT_remove_spring_bone1_spring_joint, + spring_bone1_ops.VRM_OT_move_up_spring_bone1_spring_collider_group, + spring_bone1_ops.VRM_OT_move_down_spring_bone1_spring_collider_group, + spring_bone1_ops.VRM_OT_add_spring_bone1_joint, + spring_bone1_ops.VRM_OT_remove_spring_bone1_joint, + spring_bone1_ops.VRM_OT_move_up_spring_bone1_joint, + spring_bone1_ops.VRM_OT_move_down_spring_bone1_joint, spring_bone1_ops.VRM_OT_reset_spring_bone1_animation_state, spring_bone1_ops.VRM_OT_update_spring_bone1_animation, mtoon1_ops.VRM_OT_convert_material_to_mtoon1,