# SPDX-License-Identifier: MIT OR GPL-3.0-or-later from collections.abc import Set as AbstractSet from bpy.app.translations import pgettext from bpy.types import ( Armature, Context, CopyRotationConstraint, DampedTrackConstraint, Object, Panel, UILayout, ) from ...common.preferences import get_preferences from .. import search from ..extension import get_armature_extension from ..panel import VRM_PT_vrm_armature_object_property from ..search import active_object_is_vrm1_armature from .property_group import NodeConstraint1NodeConstraintPropertyGroup def draw_roll_constraint_layout( layout: UILayout, node_constraint: NodeConstraint1NodeConstraintPropertyGroup, object_constraints: dict[str, CopyRotationConstraint], bone_constraints: dict[str, CopyRotationConstraint], ) -> None: constraints_box = layout.box() constraints_row = constraints_box.row() constraints_row.alignment = "LEFT" constraints_row.prop( node_constraint, "show_expanded_roll_constraints", icon="TRIA_DOWN" if node_constraint.show_expanded_roll_constraints else "TRIA_RIGHT", emboss=False, ) if not node_constraint.show_expanded_roll_constraints: return constraints_column = constraints_box.column() if object_constraints or bone_constraints: constraints_expanded_column = constraints_column.column() for object_name, constraint in object_constraints.items(): constraint_row = constraints_expanded_column.row() constraint_row.alignment = "LEFT" constraint_row.label( text=object_name + ": " + constraint.name, icon="CONSTRAINT", translate=False, ) for bone_name, constraint in bone_constraints.items(): constraint_row = constraints_expanded_column.row() constraint_row.alignment = "LEFT" constraint_row.label( text=bone_name + ": " + constraint.name, icon="CONSTRAINT_BONE", translate=False, ) constraints_help_column = constraints_column.box().column(align=True) help_message = pgettext( "Conditions exported as Roll Constraint\n" + " - {copy_rotation}\n" + " - Enabled\n" + " - No Vertex Group\n" + " - {axis} is one of X, Y and Z\n" + " - No Inverted\n" + " - {mix} is {add}\n" + " - {target} is {local_space}\n" + " - {owner} is {local_space}\n" + " - No circular dependencies\n" + " - The one at the top of the list of\n" + " those that meet all the conditions\n", ).format( copy_rotation=pgettext("Copy Rotation"), axis=pgettext("Axis"), mix=pgettext("Mix"), add=pgettext("Add"), target=pgettext("Target"), local_space=pgettext("Local Space"), owner=pgettext("Owner"), ) for index, help_line in enumerate(help_message.splitlines()): constraints_help_column.label( text=help_line, translate=False, icon="HELP" if index == 0 else "NONE" ) def draw_aim_constraint_layout( layout: UILayout, node_constraint: NodeConstraint1NodeConstraintPropertyGroup, object_constraints: dict[str, DampedTrackConstraint], bone_constraints: dict[str, DampedTrackConstraint], ) -> None: constraints_box = layout.box() constraints_row = constraints_box.row() constraints_row.alignment = "LEFT" constraints_row.prop( node_constraint, "show_expanded_aim_constraints", icon="TRIA_DOWN" if node_constraint.show_expanded_aim_constraints else "TRIA_RIGHT", emboss=False, ) if not node_constraint.show_expanded_aim_constraints: return constraints_column = constraints_box.column() if object_constraints or bone_constraints: constraints_expanded_column = constraints_column.column() for object_name, constraint in object_constraints.items(): constraint_row = constraints_expanded_column.row() constraint_row.alignment = "LEFT" constraint_row.label( text=object_name + ": " + constraint.name, icon="CONSTRAINT", translate=False, ) for bone_name, constraint in bone_constraints.items(): constraint_row = constraints_expanded_column.row() constraint_row.alignment = "LEFT" constraint_row.label( text=bone_name + ": " + constraint.name, icon="CONSTRAINT_BONE", translate=False, ) constraints_help_column = constraints_column.box().column(align=True) help_message = pgettext( "Conditions exported as Aim Constraint\n" + " - {damped_track}\n" + " - Enabled\n" + " - Target Bone {head_tail} is 0\n" + " - No Follow Target Bone B-Bone\n" + " - No circular dependencies\n" + " - The one at the top of the list of\n" + " those that meet all the conditions\n" ).format( damped_track=pgettext("Damped Track"), head_tail=pgettext("Head/Tail"), ) for index, help_line in enumerate(help_message.splitlines()): constraints_help_column.label( text=help_line, translate=False, icon="HELP" if index == 0 else "NONE" ) def draw_rotation_constraint_layout( layout: UILayout, node_constraint: NodeConstraint1NodeConstraintPropertyGroup, object_constraints: dict[str, CopyRotationConstraint], bone_constraints: dict[str, CopyRotationConstraint], ) -> None: constraints_box = layout.box() constraints_row = constraints_box.row() constraints_row.alignment = "LEFT" constraints_row.prop( node_constraint, "show_expanded_rotation_constraints", icon="TRIA_DOWN" if node_constraint.show_expanded_rotation_constraints else "TRIA_RIGHT", emboss=False, ) if not node_constraint.show_expanded_rotation_constraints: return constraints_column = constraints_box.column() if object_constraints or bone_constraints: constraints_expanded_column = constraints_column.column() for object_name, constraint in object_constraints.items(): constraint_row = constraints_expanded_column.row() constraint_row.alignment = "LEFT" constraint_row.label( text=object_name + ": " + constraint.name, icon="CONSTRAINT", translate=False, ) for bone_name, constraint in bone_constraints.items(): constraint_row = constraints_expanded_column.row() constraint_row.alignment = "LEFT" constraint_row.label( text=bone_name + ": " + constraint.name, icon="CONSTRAINT_BONE", translate=False, ) constraints_help_column = constraints_column.box().column(align=True) help_message = pgettext( "Conditions exported as Rotation Constraint\n" + " - {copy_rotation}\n" + " - Enabled\n" + " - No Vertex Group\n" + " - {axis} is X, Y and Z\n" + " - No Inverted\n" + " - {mix} is {add}\n" + " - {target} is {local_space}\n" + " - {owner} is {local_space}\n" + " - No circular dependencies\n" + " - The one at the top of the list of\n" + " those that meet all the conditions\n" ).format( copy_rotation=pgettext("Copy Rotation"), axis=pgettext("Axis"), mix=pgettext("Mix"), add=pgettext("Add"), target=pgettext("Target"), local_space=pgettext("Local Space"), owner=pgettext("Owner"), ) for index, help_line in enumerate(help_message.splitlines()): constraints_help_column.label( text=help_line, translate=False, icon="HELP" if index == 0 else "NONE" ) def draw_node_constraint1_layout( context: Context, armature: Object, layout: UILayout, node_constraint: NodeConstraint1NodeConstraintPropertyGroup, ) -> None: preferences = get_preferences(context) objs = search.export_objects( context, armature_object_name=armature.name, export_invisibles=preferences.export_invisibles, export_only_selections=False, export_lights=False, ) object_constraints, bone_constraints, _ = search.export_constraints(objs, armature) draw_roll_constraint_layout( layout, node_constraint, object_constraints.roll_constraints, bone_constraints.roll_constraints, ) draw_aim_constraint_layout( layout, node_constraint, object_constraints.aim_constraints, bone_constraints.aim_constraints, ) draw_rotation_constraint_layout( layout, node_constraint, object_constraints.rotation_constraints, bone_constraints.rotation_constraints, ) class VRM_PT_node_constraint1_armature_object_property(Panel): bl_idname = "VRM_PT_node_constraint1_armature_object_property" bl_label = "Node Constraint" bl_translation_context = "VRM" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "object" bl_options: AbstractSet[str] = {"DEFAULT_CLOSED"} bl_parent_id = VRM_PT_vrm_armature_object_property.bl_idname @classmethod def poll(cls, context: Context) -> bool: return active_object_is_vrm1_armature(context) def draw_header(self, _context: Context) -> None: self.layout.label(icon="CONSTRAINT") def draw(self, context: Context) -> None: active_object = context.active_object if not active_object: return armature_data = active_object.data if not isinstance(armature_data, Armature): return draw_node_constraint1_layout( context, active_object, self.layout, get_armature_extension(armature_data).node_constraint1, ) class VRM_PT_node_constraint1_ui(Panel): bl_idname = "VRM_PT_node_constraint1_ui" bl_label = "Node Constraint" bl_translation_context = "VRM" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "VRM" bl_options: AbstractSet[str] = {"DEFAULT_CLOSED"} @classmethod def poll(cls, context: Context) -> bool: return search.current_armature_is_vrm1(context) def draw_header(self, _context: Context) -> None: self.layout.label(icon="CONSTRAINT") def draw(self, context: Context) -> None: armature = search.current_armature(context) if not armature: return armature_data = armature.data if not isinstance(armature_data, Armature): return draw_node_constraint1_layout( context, armature, self.layout, get_armature_extension(armature_data).node_constraint1, )