# 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")