- 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
1223 lines
42 KiB
Python
1223 lines
42 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.logger import get_logger
|
|
from ...common.vrm1.human_bone import HumanBoneSpecifications
|
|
from .. import ops, search
|
|
from ..extension import get_armature_extension, get_bone_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_vrm1_armature
|
|
from . import ops as vrm1_ops
|
|
from .menu import VRM_MT_vrm1_expression
|
|
from .ops import draw_bone_prop_search
|
|
from .property_group import (
|
|
Vrm1CustomExpressionPropertyGroup,
|
|
Vrm1ExpressionsPropertyGroup,
|
|
Vrm1FirstPersonPropertyGroup,
|
|
Vrm1HumanBonesPropertyGroup,
|
|
Vrm1HumanoidPropertyGroup,
|
|
Vrm1LookAtPropertyGroup,
|
|
Vrm1MaterialColorBindPropertyGroup,
|
|
Vrm1MetaPropertyGroup,
|
|
Vrm1MorphTargetBindPropertyGroup,
|
|
Vrm1TextureTransformBindPropertyGroup,
|
|
)
|
|
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,
|
|
)
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def draw_vrm1_humanoid_required_bones_layout(
|
|
armature: Object,
|
|
layout: UILayout,
|
|
) -> None:
|
|
split_factor = 0.2
|
|
|
|
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.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.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="")
|
|
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_vrm1_humanoid_optional_bones_layout(
|
|
armature: Object,
|
|
layout: UILayout,
|
|
) -> 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="")
|
|
label_column.label(text=HumanBoneSpecifications.LEFT_EYE.label_no_left_right)
|
|
label_column.label(text=HumanBoneSpecifications.JAW.label)
|
|
label_column.label(text=HumanBoneSpecifications.NECK.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.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)
|
|
draw_bone_prop_search(search_column, HumanBoneSpecifications.NECK, 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)
|
|
draw_bone_prop_search(search_column, HumanBoneSpecifications.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_METACARPAL, armature
|
|
)
|
|
draw_bone_prop_search(
|
|
finger_row, HumanBoneSpecifications.LEFT_THUMB_PROXIMAL, 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
|
|
)
|
|
|
|
finger_row = search_column.row(align=True)
|
|
draw_bone_prop_search(
|
|
finger_row, HumanBoneSpecifications.RIGHT_THUMB_METACARPAL, armature
|
|
)
|
|
draw_bone_prop_search(
|
|
finger_row, HumanBoneSpecifications.RIGHT_THUMB_PROXIMAL, 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_vrm1_humanoid_layout(
|
|
context: Context,
|
|
armature: Object,
|
|
layout: UILayout,
|
|
humanoid: Vrm1HumanoidPropertyGroup,
|
|
) -> None:
|
|
if defer_migrate(armature.name):
|
|
data = armature.data
|
|
if not isinstance(data, Armature):
|
|
return
|
|
Vrm1HumanBonesPropertyGroup.defer_update_all_node_candidates(data.name)
|
|
|
|
data = armature.data
|
|
if not isinstance(data, Armature):
|
|
return
|
|
human_bones = humanoid.human_bones
|
|
|
|
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,
|
|
)
|
|
|
|
bone_operator_column = layout.column()
|
|
layout_operator(
|
|
bone_operator_column,
|
|
vrm1_ops.VRM_OT_assign_vrm1_humanoid_human_bones_automatically,
|
|
icon="ARMATURE_DATA",
|
|
).armature_object_name = armature.name
|
|
|
|
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
|
|
|
|
draw_vrm1_humanoid_required_bones_layout(armature, armature_box.box())
|
|
draw_vrm1_humanoid_optional_bones_layout(armature, armature_box.box())
|
|
|
|
non_humanoid_export_column = layout.column()
|
|
non_humanoid_export_column.prop(human_bones, "allow_non_humanoid_rig")
|
|
if human_bones.allow_non_humanoid_rig:
|
|
non_humanoid_warnings_box = non_humanoid_export_column.box()
|
|
non_humanoid_warnings_column = non_humanoid_warnings_box.column(align=True)
|
|
text = pgettext(
|
|
"VRMs exported as Non-Humanoid\n"
|
|
+ "Rigs can not have animations applied\n"
|
|
+ "for humanoid avatars."
|
|
)
|
|
for index, message in enumerate(pgettext(text).splitlines()):
|
|
non_humanoid_warnings_column.label(
|
|
text=message,
|
|
translate=False,
|
|
icon="ERROR" if index == 0 else "NONE",
|
|
)
|
|
|
|
|
|
class VRM_PT_vrm1_humanoid_armature_object_property(Panel):
|
|
bl_idname = "VRM_PT_vrm1_humanoid_armature_object_property"
|
|
bl_label = "Humanoid"
|
|
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="ARMATURE_DATA")
|
|
|
|
def draw(self, context: Context) -> None:
|
|
active_object = context.active_object
|
|
if not active_object:
|
|
return
|
|
if active_object.type != "ARMATURE":
|
|
return
|
|
armature_data = active_object.data
|
|
if not isinstance(armature_data, Armature):
|
|
return
|
|
draw_vrm1_humanoid_layout(
|
|
context,
|
|
active_object,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.humanoid,
|
|
)
|
|
|
|
|
|
class VRM_PT_vrm1_humanoid_ui(Panel):
|
|
bl_idname = "VRM_PT_vrm1_humanoid_ui"
|
|
bl_label = "Humanoid"
|
|
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="ARMATURE_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_vrm1_humanoid_layout(
|
|
context,
|
|
armature,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.humanoid,
|
|
)
|
|
|
|
|
|
def draw_vrm1_first_person_layout(
|
|
armature: Object,
|
|
_context: Context,
|
|
layout: UILayout,
|
|
first_person: Vrm1FirstPersonPropertyGroup,
|
|
) -> None:
|
|
defer_migrate(armature.name)
|
|
column = layout.column()
|
|
column.label(text="Mesh Annotations", icon="FULLSCREEN_EXIT")
|
|
|
|
(
|
|
mesh_annotation_collection_ops,
|
|
mesh_annotation_collection_item_ops,
|
|
mesh_annotation_index,
|
|
_,
|
|
_,
|
|
) = draw_template_list(
|
|
column,
|
|
VRM_UL_vrm1_first_person_mesh_annotation,
|
|
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,
|
|
)
|
|
|
|
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
|
|
|
|
|
|
class VRM_PT_vrm1_first_person_armature_object_property(Panel):
|
|
bl_idname = "VRM_PT_vrm1_first_person_armature_object_property"
|
|
bl_label = "First Person"
|
|
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="HIDE_OFF")
|
|
|
|
def draw(self, context: Context) -> None:
|
|
active_object = context.active_object
|
|
if not active_object:
|
|
return
|
|
if active_object.type != "ARMATURE":
|
|
return
|
|
armature_data = active_object.data
|
|
if not isinstance(armature_data, Armature):
|
|
return
|
|
draw_vrm1_first_person_layout(
|
|
active_object,
|
|
context,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.first_person,
|
|
)
|
|
|
|
|
|
class VRM_PT_vrm1_first_person_ui(Panel):
|
|
bl_idname = "VRM_PT_vrm1_first_person_ui"
|
|
bl_label = "First Person"
|
|
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="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_vrm1_first_person_layout(
|
|
armature,
|
|
context,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.first_person,
|
|
)
|
|
|
|
|
|
def draw_vrm1_look_at_layout(
|
|
armature: Object,
|
|
_context: Context,
|
|
layout: UILayout,
|
|
look_at: Vrm1LookAtPropertyGroup,
|
|
) -> None:
|
|
defer_migrate(armature.name)
|
|
|
|
layout.prop(look_at, "enable_preview")
|
|
|
|
label_input_split = layout.split(factor=0.4)
|
|
label_column = label_input_split.column()
|
|
label_column.label(text="Preview Target:")
|
|
label_column.label(text="Type:")
|
|
input_column = label_input_split.column()
|
|
input_column.prop(look_at, "preview_target_bpy_object", text="", translate=False)
|
|
input_column.prop(look_at, "type", text="", translate=False)
|
|
|
|
offset_from_head_bone_column = layout.column()
|
|
offset_from_head_bone_column.label(text="Offset from Head Bone:")
|
|
offset_from_head_bone_column.row().prop(
|
|
look_at, "offset_from_head_bone", icon="BONE_DATA", text="", translate=False
|
|
)
|
|
|
|
column = layout.box().column(align=True)
|
|
column.label(text="Range Map Horizontal Inner", icon="FULLSCREEN_EXIT")
|
|
column.prop(look_at.range_map_horizontal_inner, "input_max_value")
|
|
column.prop(look_at.range_map_horizontal_inner, "output_scale")
|
|
column = layout.box().column(align=True)
|
|
column.label(text="Range Map Horizontal Outer", icon="FULLSCREEN_ENTER")
|
|
column.prop(look_at.range_map_horizontal_outer, "input_max_value")
|
|
column.prop(look_at.range_map_horizontal_outer, "output_scale")
|
|
column = layout.box().column(align=True)
|
|
column.label(text="Range Map Vertical Up", icon="TRIA_UP")
|
|
column.prop(look_at.range_map_vertical_up, "input_max_value")
|
|
column.prop(look_at.range_map_vertical_up, "output_scale")
|
|
column = layout.box().column(align=True)
|
|
column.label(text="Range Map Vertical Down", icon="TRIA_DOWN")
|
|
column.prop(look_at.range_map_vertical_down, "input_max_value")
|
|
column.prop(look_at.range_map_vertical_down, "output_scale")
|
|
|
|
|
|
class VRM_PT_vrm1_look_at_armature_object_property(Panel):
|
|
bl_idname = "VRM_PT_vrm1_look_at_armature_object_property"
|
|
bl_label = "Look At"
|
|
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="HIDE_OFF")
|
|
|
|
def draw(self, context: Context) -> None:
|
|
active_object = context.active_object
|
|
if not active_object:
|
|
return
|
|
if active_object.type != "ARMATURE":
|
|
return
|
|
armature_data = active_object.data
|
|
if not isinstance(armature_data, Armature):
|
|
return
|
|
draw_vrm1_look_at_layout(
|
|
active_object,
|
|
context,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.look_at,
|
|
)
|
|
|
|
|
|
class VRM_PT_vrm1_look_at_ui(Panel):
|
|
bl_idname = "VRM_PT_vrm1_look_at_ui"
|
|
bl_label = "Look At"
|
|
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="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_vrm1_look_at_layout(
|
|
armature,
|
|
context,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.look_at,
|
|
)
|
|
|
|
|
|
def draw_vrm1_expressions_morph_target_bind_layout(
|
|
context: Context,
|
|
layout: UILayout,
|
|
bind: Vrm1MorphTargetBindPropertyGroup,
|
|
) -> None:
|
|
blend_data = context.blend_data
|
|
|
|
bind_column = layout.column()
|
|
|
|
bind_column.prop(
|
|
bind.node,
|
|
"bpy_object",
|
|
text="Mesh",
|
|
icon="OUTLINER_OB_MESH",
|
|
)
|
|
mesh_object = blend_data.objects.get(bind.node.mesh_object_name)
|
|
if not mesh_object:
|
|
return
|
|
mesh = mesh_object.data
|
|
if not isinstance(mesh, Mesh):
|
|
return
|
|
shape_keys = mesh.shape_keys
|
|
if not shape_keys:
|
|
return
|
|
key_blocks = shape_keys.key_blocks
|
|
if not key_blocks:
|
|
return
|
|
|
|
bind_column.prop_search(
|
|
bind,
|
|
"index",
|
|
shape_keys,
|
|
"key_blocks",
|
|
text="Shape key",
|
|
)
|
|
bind_column.prop(bind, "weight", slider=True)
|
|
|
|
|
|
def draw_vrm1_expressions_material_color_bind_layout(
|
|
context: Context,
|
|
layout: UILayout,
|
|
bind: Vrm1MaterialColorBindPropertyGroup,
|
|
) -> None:
|
|
blend_data = context.blend_data
|
|
|
|
bind_column = layout.column()
|
|
bind_column.prop_search(bind, "material", blend_data, "materials")
|
|
bind_column.prop(bind, "type")
|
|
target_value_split = bind_column.split(factor=0.5)
|
|
target_value_split.label(text="Target Value:")
|
|
if bind.type == "color":
|
|
target_value_split.prop(bind, "target_value", text="", translate=False)
|
|
else:
|
|
target_value_split.prop(bind, "target_value_as_rgb", text="", translate=False)
|
|
|
|
|
|
def draw_vrm1_expressions_texture_transform_bind_layout(
|
|
context: Context,
|
|
layout: UILayout,
|
|
bind: Vrm1TextureTransformBindPropertyGroup,
|
|
) -> None:
|
|
if not bind.show_experimental_preview_feature:
|
|
blend_data = context.blend_data
|
|
bind_column = layout.column()
|
|
bind_column.prop_search(bind, "material", blend_data, "materials")
|
|
bind_column.prop(bind, "scale")
|
|
bind_column.prop(bind, "offset")
|
|
if bpy.app.version < (4, 2):
|
|
return
|
|
bind_column.separator()
|
|
bind_column.prop(bind, "show_experimental_preview_feature")
|
|
return
|
|
|
|
bind_column = layout.column()
|
|
bind_column.prop(bind, "show_experimental_preview_feature")
|
|
|
|
# Find the armature object
|
|
armature = search.current_armature(context)
|
|
if not armature:
|
|
layout.label(text="No VRM Armature found in the scene", icon="ERROR")
|
|
return
|
|
armature_data = armature.data
|
|
if not isinstance(armature_data, Armature):
|
|
layout.label(text="Invalid armature data", icon="ERROR")
|
|
return
|
|
|
|
try:
|
|
extension = get_armature_extension(armature_data)
|
|
vrm1 = extension.vrm1
|
|
expressions = vrm1.expressions
|
|
|
|
all_expressions = list(expressions.all_name_to_expression_dict().values())
|
|
if (
|
|
0
|
|
<= expressions.active_expression_ui_list_element_index
|
|
< len(all_expressions)
|
|
):
|
|
active_expression = all_expressions[
|
|
expressions.active_expression_ui_list_element_index
|
|
]
|
|
if isinstance(active_expression, Vrm1CustomExpressionPropertyGroup):
|
|
expression_name = active_expression.custom_name
|
|
else:
|
|
expression_name = active_expression.name
|
|
else:
|
|
expression_name = ""
|
|
except TypeError:
|
|
layout.label(text="Invalid VRM extension", icon="ERROR")
|
|
return
|
|
|
|
# Add refresh preview button
|
|
refresh_row = bind_column.row()
|
|
refresh_op = layout_operator(
|
|
refresh_row,
|
|
vrm1_ops.VRM_OT_refresh_vrm1_expression_texture_transform_bind_preview,
|
|
text="Refresh Preview",
|
|
icon="FILE_REFRESH",
|
|
)
|
|
refresh_op.armature_object_name = armature.name
|
|
refresh_op.expression_name = expression_name
|
|
|
|
bind_column.separator()
|
|
|
|
bind_column.prop_search(bind, "material", context.blend_data, "materials")
|
|
# Only show scale, and offset properties if there's a valid bind
|
|
if bind and bind.material:
|
|
bind_column.prop(bind, "scale")
|
|
bind_column.prop(bind, "offset")
|
|
else:
|
|
bind_column.label(text="No texture transform bind for this expression")
|
|
|
|
|
|
def draw_vrm1_expressions_layout(
|
|
armature: Object,
|
|
context: Context,
|
|
layout: UILayout,
|
|
expressions: Vrm1ExpressionsPropertyGroup,
|
|
) -> None:
|
|
defer_migrate(armature.name)
|
|
|
|
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,
|
|
expressions,
|
|
"expression_ui_list_elements",
|
|
"active_expression_ui_list_element_index",
|
|
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,
|
|
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)
|
|
),
|
|
menu=VRM_MT_vrm1_expression,
|
|
)
|
|
|
|
for expression_collection_op in expression_collection_ops:
|
|
expression_collection_op.armature_object_name = armature.name
|
|
expression_collection_op.custom_expression_name = "custom"
|
|
|
|
custom_index = expression_ui_list_element_index - len(preset_expressions)
|
|
|
|
if 0 <= expression_ui_list_element_index < len(preset_expressions):
|
|
expression = preset_expressions[expression_ui_list_element_index]
|
|
preset_icon = expressions.preset.get_icon(expression.name)
|
|
if not preset_icon:
|
|
logger.error("Unknown preset expression: %s", 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")
|
|
column.prop(expression, "override_blink")
|
|
column.prop(expression, "override_look_at")
|
|
column.prop(expression, "override_mouth")
|
|
column.separator(factor=0.5)
|
|
|
|
morph_target_binds_box = column.box()
|
|
morph_target_binds_box.label(text="Morph Target Binds", icon="MESH_DATA")
|
|
|
|
(
|
|
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,
|
|
expression,
|
|
"morph_target_binds",
|
|
"active_morph_target_bind_index",
|
|
vrm1_ops.VRM_OT_add_vrm1_expression_morph_target_bind,
|
|
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,
|
|
)
|
|
|
|
for morph_target_bind_collection_op in morph_target_bind_collection_ops:
|
|
morph_target_bind_collection_op.armature_object_name = armature.name
|
|
morph_target_bind_collection_op.expression_name = expression.name
|
|
|
|
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 isinstance(morph_target_bind, Vrm1MorphTargetBindPropertyGroup):
|
|
draw_vrm1_expressions_morph_target_bind_layout(
|
|
context,
|
|
morph_target_binds_box,
|
|
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_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,
|
|
expression,
|
|
"material_color_binds",
|
|
"active_material_color_bind_index",
|
|
vrm1_ops.VRM_OT_add_vrm1_expression_material_color_bind,
|
|
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,
|
|
)
|
|
|
|
for material_color_bind_collection_op in material_color_bind_collection_ops:
|
|
material_color_bind_collection_op.armature_object_name = armature.name
|
|
material_color_bind_collection_op.expression_name = expression.name
|
|
|
|
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
|
|
|
|
if isinstance(material_color_bind, Vrm1MaterialColorBindPropertyGroup):
|
|
draw_vrm1_expressions_material_color_bind_layout(
|
|
context,
|
|
material_color_binds_box,
|
|
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_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,
|
|
expression,
|
|
"texture_transform_binds",
|
|
"active_texture_transform_bind_index",
|
|
vrm1_ops.VRM_OT_add_vrm1_expression_texture_transform_bind,
|
|
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,
|
|
)
|
|
|
|
for texture_transform_bind_collection_op in texture_transform_bind_collection_ops:
|
|
texture_transform_bind_collection_op.armature_object_name = armature.name
|
|
texture_transform_bind_collection_op.expression_name = expression.name
|
|
|
|
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 isinstance(texture_transform_bind, Vrm1TextureTransformBindPropertyGroup):
|
|
draw_vrm1_expressions_texture_transform_bind_layout(
|
|
context,
|
|
texture_transform_binds_box,
|
|
texture_transform_bind,
|
|
)
|
|
|
|
|
|
class VRM_PT_vrm1_expressions_armature_object_property(Panel):
|
|
bl_idname = "VRM_PT_vrm1_expressions_armature_object_property"
|
|
bl_label = "Expressions"
|
|
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="SHAPEKEY_DATA")
|
|
|
|
def draw(self, context: Context) -> None:
|
|
active_object = context.active_object
|
|
if not active_object:
|
|
return
|
|
if active_object.type != "ARMATURE":
|
|
return
|
|
armature_data = active_object.data
|
|
if not isinstance(armature_data, Armature):
|
|
return
|
|
draw_vrm1_expressions_layout(
|
|
active_object,
|
|
context,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.expressions,
|
|
)
|
|
|
|
|
|
class VRM_PT_vrm1_expressions_ui(Panel):
|
|
bl_idname = "VRM_PT_vrm1_expressions_ui"
|
|
bl_label = "Expressions"
|
|
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="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_vrm1_expressions_layout(
|
|
armature,
|
|
context,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.expressions,
|
|
)
|
|
|
|
|
|
def draw_vrm1_meta_layout(
|
|
armature: Object,
|
|
_context: Context,
|
|
layout: UILayout,
|
|
meta: Vrm1MetaPropertyGroup,
|
|
) -> None:
|
|
defer_migrate(armature.name)
|
|
|
|
thumbnail_column = layout.column()
|
|
thumbnail_column.label(text="Thumbnail:")
|
|
thumbnail_column.template_ID_preview(meta, "thumbnail_image")
|
|
|
|
layout.prop(meta, "vrm_name", icon="FILE_BLEND")
|
|
layout.prop(meta, "version", icon="LINENUMBERS_ON")
|
|
|
|
authors_column = layout.column()
|
|
authors_column.label(text="Authors:")
|
|
|
|
(
|
|
author_collection_ops,
|
|
author_collection_item_ops,
|
|
author_index,
|
|
_,
|
|
_,
|
|
) = draw_template_list(
|
|
authors_column,
|
|
VRM_UL_vrm1_meta_author,
|
|
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,
|
|
)
|
|
|
|
for author_collection_op in author_collection_ops:
|
|
author_collection_op.armature_object_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_column = layout.column()
|
|
references_column.label(text="References:")
|
|
|
|
(
|
|
reference_collection_ops,
|
|
reference_collection_item_ops,
|
|
reference_index,
|
|
_,
|
|
_,
|
|
) = draw_template_list(
|
|
references_column,
|
|
VRM_UL_vrm1_meta_reference,
|
|
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,
|
|
)
|
|
|
|
for reference_collection_op in reference_collection_ops:
|
|
reference_collection_op.armature_object_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")
|
|
layout.prop(meta, "avatar_permission", icon="MATCLOTH")
|
|
layout.prop(meta, "commercial_usage", icon="SOLO_OFF")
|
|
layout.prop(meta, "credit_notation")
|
|
layout.prop(meta, "modification")
|
|
layout.prop(meta, "allow_excessively_violent_usage")
|
|
layout.prop(meta, "allow_excessively_sexual_usage")
|
|
layout.prop(meta, "allow_political_or_religious_usage")
|
|
layout.prop(meta, "allow_antisocial_or_hate_usage")
|
|
layout.prop(meta, "allow_redistribution")
|
|
layout.prop(meta, "other_license_url", icon="URL")
|
|
|
|
|
|
class VRM_PT_vrm1_meta_armature_object_property(Panel):
|
|
bl_idname = "VRM_PT_vrm1_meta_armature_object_property"
|
|
bl_label = "Meta"
|
|
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="FILE_BLEND")
|
|
|
|
def draw(self, context: Context) -> None:
|
|
active_object = context.active_object
|
|
if not active_object:
|
|
return
|
|
if active_object.type != "ARMATURE":
|
|
return
|
|
armature_data = active_object.data
|
|
if not isinstance(armature_data, Armature):
|
|
return
|
|
ext = get_armature_extension(armature_data)
|
|
draw_vrm1_meta_layout(active_object, context, self.layout, ext.vrm1.meta)
|
|
|
|
|
|
class VRM_PT_vrm1_meta_ui(Panel):
|
|
bl_idname = "VRM_PT_vrm1_meta_ui"
|
|
bl_label = "Meta"
|
|
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="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_vrm1_meta_layout(
|
|
armature,
|
|
context,
|
|
self.layout,
|
|
get_armature_extension(armature_data).vrm1.meta,
|
|
)
|
|
|
|
|
|
class VRM_PT_vrm1_bone_property(Panel):
|
|
bl_idname = "VRM_PT_vrm1_bone_property"
|
|
bl_label = "VRM"
|
|
bl_space_type = "PROPERTIES"
|
|
bl_region_type = "WINDOW"
|
|
bl_context = "bone"
|
|
|
|
@classmethod
|
|
def poll(cls, context: Context) -> bool:
|
|
active_object = context.active_object
|
|
if not active_object:
|
|
return False
|
|
if active_object.type != "ARMATURE":
|
|
return False
|
|
armature_data = active_object.data
|
|
if not isinstance(armature_data, Armature):
|
|
return False
|
|
if not armature_data.bones.active:
|
|
return False
|
|
return search.current_armature_is_vrm1(context)
|
|
|
|
def draw(self, context: Context) -> None:
|
|
active_object = context.active_object
|
|
if not active_object:
|
|
return
|
|
if active_object.type != "ARMATURE":
|
|
return
|
|
armature_data = active_object.data
|
|
if not isinstance(armature_data, Armature):
|
|
return
|
|
# context.active_bone is a EditBone
|
|
bone = armature_data.bones.active
|
|
if not bone:
|
|
return
|
|
ext = get_bone_extension(bone)
|
|
layout = self.layout
|
|
layout.prop(ext, "axis_translation")
|