feat: Add VRM Blender addon with complete import/export functionality
- Add core VRM addon infrastructure with manifest and registration - Add common utilities module with file system, logging, and conversion helpers - Add human bone mapper with support for multiple rigging standards (Mixamo, MMD, Unreal, Rigify, etc.) - Add VRM 0.x and 1.x format support with property groups and handlers - Add editor UI panels for VRM metadata, spring bones, and MToon materials - Add exporter with glTF2 extension support for VRM format serialization - Add importer with scene reconstruction and armature generation - Add MToon shader support with auto-setup and material migration - Add spring bone physics simulation with constraint handling - Add node constraint editor for advanced rigging control - Add comprehensive validation and error handling with user dialogs - Add scene watcher for real-time property synchronization - Add workspace management and preference system - Include Python cache files and Blender manifest configuration - This is the initial commit establishing the complete VRM addon ecosystem for Blender
This commit is contained in:
316
editor/node_constraint1/panel.py
Normal file
316
editor/node_constraint1/panel.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# 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,
|
||||
)
|
||||
Reference in New Issue
Block a user