Files
blender-vrm/editor/vrm0/panel.py
小煜 091ad6a49a 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
2026-01-01 14:21:56 +08:00

1250 lines
44 KiB
Python

# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
from collections.abc import Set as AbstractSet
import bpy
from bpy.app.translations import pgettext
from bpy.types import (
Armature,
Context,
Mesh,
Object,
Panel,
UILayout,
)
from ...common.vrm0.human_bone import HumanBoneSpecifications
from .. import ops, search
from ..extension import (
get_armature_extension,
get_material_extension,
get_scene_extension,
)
from ..migration import defer_migrate
from ..ops import layout_operator
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 .menu import VRM_MT_vrm0_blend_shape_master
from .ops import draw_bone_prop_search
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,
)
def draw_vrm0_humanoid_operators_layout(
armature: Object,
layout: UILayout,
) -> None:
bone_operator_column = layout.column()
layout_operator(
bone_operator_column,
vrm0_ops.VRM_OT_assign_vrm0_humanoid_human_bones_automatically,
icon="ARMATURE_DATA",
).armature_object_name = armature.name
save_load_row = bone_operator_column.split(factor=0.5, align=True)
save_load_row.operator(
ops.VRM_OT_save_human_bone_mappings.bl_idname,
icon="EXPORT",
text="Save",
)
save_load_row.operator(
ops.VRM_OT_load_human_bone_mappings.bl_idname, icon="IMPORT", text="Load"
)
def draw_vrm0_humanoid_required_bones_layout(
armature: Object,
layout: UILayout,
split_factor: float = 0.2,
) -> None:
armature_data = armature.data
if not isinstance(armature_data, Armature):
return
layout.label(text="Required VRM Human Bones", icon="ARMATURE_DATA")
row = layout.row(align=True).split(factor=split_factor, align=True)
label_column = row.column(align=True)
label_column.label(text=HumanBoneSpecifications.HEAD.label)
label_column.label(text=HumanBoneSpecifications.NECK.label)
label_column.label(text=HumanBoneSpecifications.CHEST.label)
label_column.label(text=HumanBoneSpecifications.SPINE.label)
label_column.label(text=HumanBoneSpecifications.HIPS.label)
search_column = row.column(align=True)
draw_bone_prop_search(search_column, HumanBoneSpecifications.HEAD, armature)
draw_bone_prop_search(search_column, HumanBoneSpecifications.NECK, armature)
draw_bone_prop_search(search_column, HumanBoneSpecifications.CHEST, armature)
draw_bone_prop_search(search_column, HumanBoneSpecifications.SPINE, armature)
draw_bone_prop_search(search_column, HumanBoneSpecifications.HIPS, armature)
row = layout.row(align=True).split(factor=split_factor, align=True)
label_column = row.column(align=True)
label_column.label(text="", translate=False)
label_column.label(text=HumanBoneSpecifications.LEFT_UPPER_ARM.label_no_left_right)
label_column.label(text=HumanBoneSpecifications.LEFT_LOWER_ARM.label_no_left_right)
label_column.label(text=HumanBoneSpecifications.LEFT_HAND.label_no_left_right)
label_column.separator()
label_column.label(text=HumanBoneSpecifications.LEFT_UPPER_LEG.label_no_left_right)
label_column.label(text=HumanBoneSpecifications.LEFT_LOWER_LEG.label_no_left_right)
label_column.label(text=HumanBoneSpecifications.LEFT_FOOT.label_no_left_right)
search_column = row.column(align=True)
right_left_row = search_column.row(align=True)
right_left_row.label(text="Right")
right_left_row.label(text="Left")
right_left_row = search_column.row(align=True)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.RIGHT_UPPER_ARM, armature
)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.LEFT_UPPER_ARM, armature
)
right_left_row = search_column.row(align=True)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.RIGHT_LOWER_ARM, armature
)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.LEFT_LOWER_ARM, armature
)
right_left_row = search_column.row(align=True)
draw_bone_prop_search(right_left_row, HumanBoneSpecifications.RIGHT_HAND, armature)
draw_bone_prop_search(right_left_row, HumanBoneSpecifications.LEFT_HAND, armature)
search_column.separator()
right_left_row = search_column.row(align=True)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.RIGHT_UPPER_LEG, armature
)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.LEFT_UPPER_LEG, armature
)
right_left_row = search_column.row(align=True)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.RIGHT_LOWER_LEG, armature
)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.LEFT_LOWER_LEG, armature
)
right_left_row = search_column.row(align=True)
draw_bone_prop_search(right_left_row, HumanBoneSpecifications.RIGHT_FOOT, armature)
draw_bone_prop_search(right_left_row, HumanBoneSpecifications.LEFT_FOOT, armature)
def draw_vrm0_humanoid_optional_bones_layout(
armature: Object,
layout: UILayout,
split_factor: float = 0.2,
) -> None:
split_factor = 0.2
layout.label(text="Optional VRM Human Bones", icon="BONE_DATA")
row = layout.row(align=True).split(factor=split_factor, align=True)
label_column = row.column(align=True)
label_column.label(text="", translate=False)
label_column.label(text=HumanBoneSpecifications.LEFT_EYE.label_no_left_right)
label_column.label(text=HumanBoneSpecifications.JAW.label)
label_column.label(text=HumanBoneSpecifications.RIGHT_SHOULDER.label_no_left_right)
label_column.label(text=HumanBoneSpecifications.UPPER_CHEST.label)
label_column.label(text=HumanBoneSpecifications.RIGHT_TOES.label_no_left_right)
search_column = row.column(align=True)
right_left_row = search_column.row(align=True)
right_left_row.label(text="Right")
right_left_row.label(text="Left")
right_left_row = search_column.row(align=True)
draw_bone_prop_search(right_left_row, HumanBoneSpecifications.RIGHT_EYE, armature)
draw_bone_prop_search(right_left_row, HumanBoneSpecifications.LEFT_EYE, armature)
draw_bone_prop_search(search_column, HumanBoneSpecifications.JAW, armature)
right_left_row = search_column.row(align=True)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.RIGHT_SHOULDER, armature
)
draw_bone_prop_search(
right_left_row, HumanBoneSpecifications.LEFT_SHOULDER, armature
)
draw_bone_prop_search(search_column, HumanBoneSpecifications.UPPER_CHEST, armature)
right_left_row = search_column.row(align=True)
draw_bone_prop_search(right_left_row, HumanBoneSpecifications.RIGHT_TOES, armature)
draw_bone_prop_search(right_left_row, HumanBoneSpecifications.LEFT_TOES, armature)
row = layout.row(align=True).split(factor=split_factor, align=True)
label_column = row.column(align=True)
label_column.label(text="", translate=False)
label_column.label(text="Left Thumb:")
label_column.label(text="Left Index:")
label_column.label(text="Left Middle:")
label_column.label(text="Left Ring:")
label_column.label(text="Left Little:")
label_column.separator()
label_column.label(text="Right Thumb:")
label_column.label(text="Right Index:")
label_column.label(text="Right Middle:")
label_column.label(text="Right Ring:")
label_column.label(text="Right Little:")
search_column = row.column(align=True)
finger_row = search_column.row(align=True)
finger_row.label(text="Root")
finger_row.label(text="", translate=False)
finger_row.label(text="Tip")
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_THUMB_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_THUMB_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_THUMB_DISTAL, armature
)
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_INDEX_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_INDEX_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_INDEX_DISTAL, armature
)
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_MIDDLE_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_MIDDLE_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_MIDDLE_DISTAL, armature
)
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_RING_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_RING_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_RING_DISTAL, armature
)
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_LITTLE_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_LITTLE_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.LEFT_LITTLE_DISTAL, armature
)
search_column.separator()
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_THUMB_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_THUMB_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_THUMB_DISTAL, armature
)
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_INDEX_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_INDEX_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_INDEX_DISTAL, armature
)
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_MIDDLE_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_MIDDLE_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_MIDDLE_DISTAL, armature
)
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_RING_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_RING_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_RING_DISTAL, armature
)
finger_row = search_column.row(align=True)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_LITTLE_PROXIMAL, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_LITTLE_INTERMEDIATE, armature
)
draw_bone_prop_search(
finger_row, HumanBoneSpecifications.RIGHT_LITTLE_DISTAL, armature
)
def draw_vrm0_humanoid_layout(
context: Context,
armature: Object,
layout: UILayout,
humanoid: Vrm0HumanoidPropertyGroup,
) -> None:
if defer_migrate(armature.name):
armature_data = armature.data
if not isinstance(armature_data, Armature):
return
Vrm0HumanoidPropertyGroup.defer_update_all_node_candidates(armature_data.name)
data = armature.data
if not isinstance(data, Armature):
return
armature_box = layout
t_pose_box = armature_box.box()
column = t_pose_box.row().column()
column.label(text="VRM T-Pose", icon="OUTLINER_OB_ARMATURE")
column.prop(humanoid, "pose")
if humanoid.pose == humanoid.POSE_CUSTOM_POSE.identifier:
label = "Pose Library" if bpy.app.version < (3, 0) else "Pose Asset"
column.prop_search(
humanoid,
"pose_library",
context.blend_data,
"actions",
text=label,
translate=False,
)
pose_library = humanoid.pose_library
if pose_library and pose_library.pose_markers:
column.prop_search(
humanoid,
"pose_marker_name",
pose_library,
"pose_markers",
text="Pose",
translate=False,
)
draw_vrm0_humanoid_operators_layout(armature, armature_box)
if ops.VRM_OT_simplify_vroid_bones.vroid_bones_exist(data):
simplify_vroid_bones_op = layout_operator(
armature_box,
ops.VRM_OT_simplify_vroid_bones,
text=pgettext(ops.VRM_OT_simplify_vroid_bones.bl_label),
icon="GREASEPENCIL",
)
simplify_vroid_bones_op.armature_object_name = armature.name
split_factor = 0.2
draw_vrm0_humanoid_required_bones_layout(armature, armature_box.box(), split_factor)
draw_vrm0_humanoid_optional_bones_layout(armature, armature_box.box(), split_factor)
layout.label(text="Arm", icon="VIEW_PAN", translate=False) # TODO: Translation
layout.prop(
humanoid,
"arm_stretch",
)
layout.prop(humanoid, "upper_arm_twist")
layout.prop(humanoid, "lower_arm_twist")
layout.separator()
layout.label(text="Leg", icon="MOD_DYNAMICPAINT")
layout.prop(humanoid, "leg_stretch")
layout.prop(humanoid, "upper_leg_twist")
layout.prop(humanoid, "lower_leg_twist")
layout.prop(humanoid, "feet_spacing")
layout.separator()
layout.prop(humanoid, "has_translation_dof")
class VRM_PT_vrm0_humanoid_armature_object_property(Panel):
bl_idname = "VRM_PT_vrm0_humanoid_armature_object_property"
bl_label = "VRM 0.x Humanoid"
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_vrm0_armature(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="ARMATURE_DATA")
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_vrm0_humanoid_layout(
context,
active_object,
self.layout,
get_armature_extension(armature_data).vrm0.humanoid,
)
class VRM_PT_vrm0_humanoid_ui(Panel):
bl_idname = "VRM_PT_vrm0_humanoid_ui"
bl_label = "VRM 0.x Humanoid"
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_vrm0(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="USER")
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_vrm0_humanoid_layout(
context,
armature,
self.layout,
get_armature_extension(armature_data).vrm0.humanoid,
)
def draw_vrm0_first_person_layout(
armature: Object,
_context: Context,
layout: UILayout,
first_person: Vrm0FirstPersonPropertyGroup,
) -> None:
defer_migrate(armature.name)
armature_data = armature.data
if not isinstance(armature_data, Armature):
return
layout.prop_search(
first_person.first_person_bone, "bone_name", armature_data, "bones"
)
layout.prop(first_person, "first_person_bone_offset", icon="BONE_DATA")
layout.prop(first_person, "look_at_type_name")
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,
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,
)
for mesh_annotation_collection_op in mesh_annotation_collection_ops:
mesh_annotation_collection_op.armature_object_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")
box.prop(first_person.look_at_horizontal_inner, "curve")
box.prop(first_person.look_at_horizontal_inner, "x_range")
box.prop(first_person.look_at_horizontal_inner, "y_range")
box = layout.box()
box.label(text="Look At Horizontal Outer", icon="FULLSCREEN_ENTER")
box.prop(first_person.look_at_horizontal_outer, "curve")
box.prop(first_person.look_at_horizontal_outer, "x_range")
box.prop(first_person.look_at_horizontal_outer, "y_range")
box = layout.box()
box.label(text="Look At Vertical Up", icon="TRIA_UP")
box.prop(first_person.look_at_vertical_up, "curve")
box.prop(first_person.look_at_vertical_up, "x_range")
box.prop(first_person.look_at_vertical_up, "y_range")
box = layout.box()
box.label(text="Look At Vertical Down", icon="TRIA_DOWN")
box.prop(first_person.look_at_vertical_down, "curve")
box.prop(first_person.look_at_vertical_down, "x_range")
box.prop(first_person.look_at_vertical_down, "y_range")
class VRM_PT_vrm0_first_person_armature_object_property(Panel):
bl_idname = "VRM_PT_vrm0_first_person_armature_object_property"
bl_label = "VRM 0.x First Person"
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_vrm0_armature(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="HIDE_OFF")
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
ext = get_armature_extension(armature_data)
draw_vrm0_first_person_layout(
active_object,
context,
self.layout,
ext.vrm0.first_person,
)
class VRM_PT_vrm0_first_person_ui(Panel):
bl_idname = "VRM_PT_vrm0_first_person_ui"
bl_label = "VRM 0.x First Person"
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_vrm0(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="HIDE_OFF")
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_vrm0_first_person_layout(
armature,
context,
self.layout,
get_armature_extension(armature_data).vrm0.first_person,
)
def draw_vrm0_blend_shape_master_layout(
armature: Object,
context: Context,
layout: UILayout,
blend_shape_master: Vrm0BlendShapeMasterPropertyGroup,
) -> None:
defer_migrate(armature.name)
blend_data = context.blend_data
(
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,
blend_shape_master,
"blend_shape_groups",
"active_blend_shape_group_index",
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,
menu=VRM_MT_vrm0_blend_shape_master,
)
for blend_shape_group_collection_op in blend_shape_group_collection_ops:
blend_shape_group_collection_op.armature_object_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")
column.prop(blend_shape_group, "is_binary", icon="IPO_CONSTANT")
column.prop(blend_shape_group, "preview", icon="PLAY", text="Preview")
column.separator(factor=0.5)
binds_box = column.box()
binds_box.label(text="Binds", icon="MESH_DATA")
(
bind_collection_ops,
bind_collection_item_ops,
bind_index,
bind,
_,
) = draw_template_list(
binds_box,
VRM_UL_vrm0_blend_shape_bind,
blend_shape_group,
"binds",
"active_bind_index",
vrm0_ops.VRM_OT_add_vrm0_blend_shape_bind,
vrm0_ops.VRM_OT_remove_vrm0_blend_shape_bind,
vrm0_ops.VRM_OT_move_up_vrm0_blend_shape_bind,
vrm0_ops.VRM_OT_move_down_vrm0_blend_shape_bind,
)
for bind_collection_op in bind_collection_ops:
bind_collection_op.armature_object_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(
bind.mesh,
"bpy_object",
text="Mesh",
icon="OUTLINER_OB_MESH",
)
mesh_object = blend_data.objects.get(bind.mesh.mesh_object_name)
if mesh_object:
mesh_data = mesh_object.data
if isinstance(mesh_data, Mesh):
shape_keys = mesh_data.shape_keys
if shape_keys:
bind_column.prop_search(
bind,
"index",
shape_keys,
"key_blocks",
text="Shape key",
)
bind_column.prop(bind, "weight", slider=True)
column.separator(factor=0.2)
material_value_binds_box = column.box()
material_value_binds_box.label(text="Material Values", icon="MATERIAL")
(
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,
blend_shape_group,
"material_values",
"active_material_value_index",
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,
)
for material_value_bind_collection_op in material_value_collection_ops:
material_value_bind_collection_op.armature_object_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"
)
material = material_value.material
if not material:
material_value_column.prop(
material_value, "property_name", icon="PROPERTIES"
)
else:
scene_extension = get_scene_extension(context.scene)
scene_extension.defer_update_vrm0_material_property_names()
ext = get_material_extension(material)
node, legacy_shader_name = search.legacy_shader_node(material)
if ext.mtoon1.enabled or (
node and legacy_shader_name == "MToon_unversioned"
):
if bpy.app.version >= (3, 2):
material_value_column.prop_search(
material_value,
"property_name",
get_scene_extension(context.scene),
"vrm0_material_mtoon0_property_names",
icon="PROPERTIES",
results_are_suggestions=True,
)
else:
material_value_column.prop_search(
material_value,
"property_name",
get_scene_extension(context.scene),
"vrm0_material_mtoon0_property_names",
icon="PROPERTIES",
)
elif bpy.app.version >= (3, 2):
material_value_column.prop_search(
material_value,
"property_name",
get_scene_extension(context.scene),
"vrm0_material_gltf_property_names",
icon="PROPERTIES",
results_are_suggestions=True,
)
else:
material_value_column.prop_search(
material_value,
"property_name",
get_scene_extension(context.scene),
"vrm0_material_gltf_property_names",
icon="PROPERTIES",
)
for (
target_value_index,
target_value,
) in enumerate(material_value.target_value):
target_value_row = material_value_column.row()
target_value_row.prop(
target_value, "value", text=f"Value {target_value_index}"
)
remove_target_value_op = layout_operator(
target_value_row,
vrm0_ops.VRM_OT_remove_vrm0_material_value_bind_target_value,
text="",
translate=False,
icon="REMOVE",
)
remove_target_value_op.armature_object_name = armature.name
remove_target_value_op.blend_shape_group_index = blend_shape_group_index
remove_target_value_op.material_value_index = material_value_index
remove_target_value_op.target_value_index = target_value_index
add_target_value_op = layout_operator(
material_value_column,
vrm0_ops.VRM_OT_add_vrm0_material_value_bind_target_value,
icon="ADD",
)
add_target_value_op.armature_object_name = armature.name
add_target_value_op.blend_shape_group_index = blend_shape_group_index
add_target_value_op.material_value_index = material_value_index
class VRM_PT_vrm0_blend_shape_master_armature_object_property(Panel):
bl_idname = "VRM_PT_vrm0_blend_shape_master_armature_object_property"
bl_label = "VRM 0.x Blend Shape Proxy"
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_vrm0_armature(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="SHAPEKEY_DATA")
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
ext = get_armature_extension(armature_data)
draw_vrm0_blend_shape_master_layout(
active_object, context, self.layout, ext.vrm0.blend_shape_master
)
class VRM_PT_vrm0_blend_shape_master_ui(Panel):
bl_idname = "VRM_PT_vrm0_blend_shape_master_ui"
bl_label = "VRM 0.x Blend Shape Proxy"
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_vrm0(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="SHAPEKEY_DATA")
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_vrm0_blend_shape_master_layout(
armature,
context,
self.layout,
get_armature_extension(armature_data).vrm0.blend_shape_master,
)
def draw_vrm0_secondary_animation_layout(
armature: Object,
layout: UILayout,
secondary_animation: Vrm0SecondaryAnimationPropertyGroup,
) -> None:
defer_migrate(armature.name)
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
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
box = layout.box()
(
bone_group_collection_ops,
bone_group_collection_item_ops,
bone_group_index,
bone_group,
_,
) = draw_template_list(
box,
VRM_UL_vrm0_secondary_animation_group,
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,
)
for bone_group_collection_op in bone_group_collection_ops:
bone_group_collection_op.armature_object_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",
)
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,
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_object_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,
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_object_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,
secondary_animation,
"collider_groups",
"active_collider_group_index",
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,
)
for collider_group_collection_op in collider_group_collection_ops:
collider_group_collection_op.armature_object_name = armature.name
for collider_group_collection_item_op in collider_group_collection_item_ops:
collider_group_collection_item_op.collider_group_index = collider_group_index
if not isinstance(collider_group, 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,
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,
)
for collider_collection_op in collider_collection_ops:
collider_collection_op.armature_object_name = armature.name
collider_collection_op.collider_group_index = collider_group_index
for collider_collection_item_op in collider_collection_item_ops:
collider_collection_item_op.armature_object_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):
bl_idname = "VRM_PT_vrm0_secondary_animation_armature_object_property"
bl_label = "VRM 0.x Spring Bone"
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_vrm0_armature(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="PHYSICS")
def draw(self, context: Context) -> None:
active_object = context.active_object
if not active_object:
return
armature_data = active_object.data
if not isinstance(armature_data, Armature):
return
ext = get_armature_extension(armature_data)
draw_vrm0_secondary_animation_layout(
active_object, self.layout, ext.vrm0.secondary_animation
)
class VRM_PT_vrm0_secondary_animation_ui(Panel):
bl_idname = "VRM_PT_vrm0_secondary_animation_ui"
bl_label = "VRM 0.x Spring Bone"
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_vrm0(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="PHYSICS")
def draw(self, context: Context) -> None:
armature = search.current_armature(context)
if not armature:
return
armature_data = armature.data
if not isinstance(armature_data, Armature):
return
draw_vrm0_secondary_animation_layout(
armature,
self.layout,
get_armature_extension(armature_data).vrm0.secondary_animation,
)
def draw_vrm0_meta_layout(
armature: Object,
_context: Context,
layout: UILayout,
meta: Vrm0MetaPropertyGroup,
) -> None:
defer_migrate(armature.name)
thumbnail_column = layout.column()
thumbnail_column.label(text="Thumbnail:")
thumbnail_column.template_ID_preview(meta, "texture")
layout.prop(meta, "title", icon="FILE_BLEND")
layout.prop(meta, "version", icon="LINENUMBERS_ON")
layout.prop(meta, "author", icon="USER")
layout.prop(meta, "contact_information", icon="URL")
layout.prop(meta, "reference", icon="URL")
layout.prop(meta, "allowed_user_name", icon="MATCLOTH")
layout.prop(
meta,
"violent_ussage_name",
icon="ORPHAN_DATA",
)
layout.prop(meta, "sexual_ussage_name", icon="HEART")
layout.prop(
meta,
"commercial_ussage_name",
icon="SOLO_OFF",
)
layout.prop(meta, "other_permission_url", icon="URL")
layout.prop(meta, "license_name", icon="COMMUNITY")
if meta.license_name == Vrm0MetaPropertyGroup.LICENSE_NAME_OTHER.identifier:
layout.prop(meta, "other_license_url", icon="URL")
class VRM_PT_vrm0_meta_armature_object_property(Panel):
bl_idname = "VRM_PT_vrm0_meta_armature_object_property"
bl_label = "VRM 0.x Meta"
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_vrm0_armature(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="FILE_BLEND")
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
ext = get_armature_extension(armature_data)
draw_vrm0_meta_layout(active_object, context, self.layout, ext.vrm0.meta)
class VRM_PT_vrm0_meta_ui(Panel):
bl_idname = "VRM_PT_vrm0_meta_ui"
bl_label = "VRM 0.x Meta"
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_vrm0(context)
def draw_header(self, _context: Context) -> None:
self.layout.label(icon="FILE_BLEND")
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_vrm0_meta_layout(
armature,
context,
self.layout,
get_armature_extension(armature_data).vrm0.meta,
)