feat: Add VRM Blender addon with complete import/export functionality
- Add core VRM addon infrastructure with manifest and registration - Add common utilities module with file system, logging, and conversion helpers - Add human bone mapper with support for multiple rigging standards (Mixamo, MMD, Unreal, Rigify, etc.) - Add VRM 0.x and 1.x format support with property groups and handlers - Add editor UI panels for VRM metadata, spring bones, and MToon materials - Add exporter with glTF2 extension support for VRM format serialization - Add importer with scene reconstruction and armature generation - Add MToon shader support with auto-setup and material migration - Add spring bone physics simulation with constraint handling - Add node constraint editor for advanced rigging control - Add comprehensive validation and error handling with user dialogs - Add scene watcher for real-time property synchronization - Add workspace management and preference system - Include Python cache files and Blender manifest configuration - This is the initial commit establishing the complete VRM addon ecosystem for Blender
This commit is contained in:
1
editor/vrm1/__init__.py
Normal file
1
editor/vrm1/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
BIN
editor/vrm1/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm1/__pycache__/handler.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/handler.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm1/__pycache__/menu.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/menu.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm1/__pycache__/migration.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/migration.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm1/__pycache__/ops.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/ops.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm1/__pycache__/panel.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/panel.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm1/__pycache__/property_group.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/property_group.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm1/__pycache__/scene_watcher.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/scene_watcher.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm1/__pycache__/ui_list.cpython-311.pyc
Normal file
BIN
editor/vrm1/__pycache__/ui_list.cpython-311.pyc
Normal file
Binary file not shown.
45
editor/vrm1/handler.py
Normal file
45
editor/vrm1/handler.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
|
||||
import bpy
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
from ...common.logger import get_logger
|
||||
from ...common.scene_watcher import trigger_scene_watcher
|
||||
from .property_group import Vrm1ExpressionPropertyGroup
|
||||
from .scene_watcher import LookAtPreviewUpdater
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@persistent
|
||||
def frame_change_pre(_unused: object) -> None:
|
||||
Vrm1ExpressionPropertyGroup.frame_change_post_shape_key_updates.clear()
|
||||
|
||||
|
||||
@persistent
|
||||
def frame_change_post(_unused: object) -> None:
|
||||
context = bpy.context
|
||||
|
||||
for (
|
||||
shape_key_name,
|
||||
key_block_name,
|
||||
), value in Vrm1ExpressionPropertyGroup.frame_change_post_shape_key_updates.items():
|
||||
shape_key = context.blend_data.shape_keys.get(shape_key_name)
|
||||
if not shape_key:
|
||||
continue
|
||||
key_blocks = shape_key.key_blocks
|
||||
if not key_blocks:
|
||||
continue
|
||||
key_block = key_blocks.get(key_block_name)
|
||||
if not key_block:
|
||||
continue
|
||||
key_block.value = value
|
||||
Vrm1ExpressionPropertyGroup.frame_change_post_shape_key_updates.clear()
|
||||
|
||||
# Update materials
|
||||
Vrm1ExpressionPropertyGroup.update_materials(context)
|
||||
|
||||
|
||||
@persistent
|
||||
def depsgraph_update_pre(_unused: object) -> None:
|
||||
trigger_scene_watcher(LookAtPreviewUpdater)
|
||||
26
editor/vrm1/menu.py
Normal file
26
editor/vrm1/menu.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
from bpy.types import Context, Menu
|
||||
|
||||
from ...common.logger import get_logger
|
||||
from ..ops import layout_operator
|
||||
from ..search import current_armature
|
||||
from .ops import VRM_OT_restore_vrm1_expression_morph_target_bind_object
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class VRM_MT_vrm1_expression(Menu):
|
||||
bl_label = "Expression Menu"
|
||||
bl_idname = "VRM_MT_vrm1_expression"
|
||||
|
||||
def draw(self, context: Context) -> None:
|
||||
layout = self.layout
|
||||
|
||||
armature = current_armature(context)
|
||||
if not armature:
|
||||
return
|
||||
|
||||
op = layout_operator(
|
||||
layout, VRM_OT_restore_vrm1_expression_morph_target_bind_object
|
||||
)
|
||||
op.armature_object_name = armature.name
|
||||
281
editor/vrm1/migration.py
Normal file
281
editor/vrm1/migration.py
Normal file
@@ -0,0 +1,281 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
from bpy.types import Armature, Context, Material, Object
|
||||
from idprop.types import IDPropertyGroup
|
||||
from mathutils import Vector
|
||||
|
||||
from ...common import convert, ops, shader
|
||||
from ..extension import get_armature_extension
|
||||
from .property_group import (
|
||||
Vrm1ExpressionPropertyGroup,
|
||||
Vrm1ExpressionsPropertyGroup,
|
||||
Vrm1HumanBonesPropertyGroup,
|
||||
Vrm1MaterialColorBindPropertyGroup,
|
||||
Vrm1PropertyGroup,
|
||||
)
|
||||
|
||||
|
||||
def migrate_old_expression_layout(
|
||||
old_expression: object, expression: Vrm1ExpressionPropertyGroup
|
||||
) -> None:
|
||||
if not isinstance(old_expression, IDPropertyGroup):
|
||||
return
|
||||
|
||||
old_morph_target_binds = convert.iterator_or_none(
|
||||
old_expression.get("morph_target_binds")
|
||||
)
|
||||
if old_morph_target_binds is not None:
|
||||
for old_morph_target_bind in old_morph_target_binds:
|
||||
if not isinstance(old_morph_target_bind, IDPropertyGroup):
|
||||
continue
|
||||
|
||||
morph_target_bind = expression.morph_target_binds.add()
|
||||
old_node = old_morph_target_bind.get("node")
|
||||
if isinstance(old_node, IDPropertyGroup):
|
||||
old_bpy_object = old_node.get("bpy_object")
|
||||
if isinstance(old_bpy_object, Object) and old_bpy_object.type == "MESH":
|
||||
morph_target_bind.node.mesh_object_name = old_bpy_object.name
|
||||
old_node.clear()
|
||||
|
||||
old_index = old_morph_target_bind.get("index")
|
||||
if isinstance(old_index, str):
|
||||
morph_target_bind.index = old_index
|
||||
|
||||
old_weight = old_morph_target_bind.get("weight")
|
||||
if isinstance(old_weight, (float, int)):
|
||||
morph_target_bind.weight = old_weight
|
||||
|
||||
old_morph_target_bind.clear()
|
||||
|
||||
old_material_color_binds = convert.iterator_or_none(
|
||||
old_expression.get("material_color_binds")
|
||||
)
|
||||
if old_material_color_binds is not None:
|
||||
for old_material_color_bind in old_material_color_binds:
|
||||
if not isinstance(old_material_color_bind, IDPropertyGroup):
|
||||
continue
|
||||
|
||||
material_color_bind = expression.material_color_binds.add()
|
||||
old_material = old_material_color_bind.get("material")
|
||||
if isinstance(old_material, Material):
|
||||
material_color_bind.material = old_material
|
||||
|
||||
old_type = next(
|
||||
(
|
||||
enum.identifier
|
||||
for enum in Vrm1MaterialColorBindPropertyGroup.type_enum
|
||||
if old_material_color_bind.get("type") == enum.value
|
||||
),
|
||||
None,
|
||||
)
|
||||
if old_type is not None:
|
||||
material_color_bind.type = old_type
|
||||
|
||||
old_target_value = shader.rgba_or_none(
|
||||
old_material_color_bind.get("target_value"), 0.0, 1.0
|
||||
)
|
||||
if old_target_value:
|
||||
material_color_bind.target_value = old_target_value
|
||||
|
||||
old_material_color_bind.clear()
|
||||
|
||||
old_texture_transform_binds = convert.iterator_or_none(
|
||||
old_expression.get("texture_transform_binds")
|
||||
)
|
||||
if old_texture_transform_binds is not None:
|
||||
for old_texture_transform_bind in old_texture_transform_binds:
|
||||
if not isinstance(old_texture_transform_bind, IDPropertyGroup):
|
||||
continue
|
||||
|
||||
texture_transform_bind = expression.texture_transform_binds.add()
|
||||
old_material = old_texture_transform_bind.get("material")
|
||||
if isinstance(old_material, Material):
|
||||
texture_transform_bind.material = old_material
|
||||
|
||||
old_scale = convert.float2_or_none(old_texture_transform_bind.get("scale"))
|
||||
if old_scale:
|
||||
texture_transform_bind.scale = old_scale
|
||||
|
||||
old_offset = convert.float2_or_none(
|
||||
old_texture_transform_bind.get("offset")
|
||||
)
|
||||
if old_offset:
|
||||
texture_transform_bind.offset = old_offset
|
||||
|
||||
old_texture_transform_bind.clear()
|
||||
|
||||
old_is_binary = old_expression.get("is_binary")
|
||||
if isinstance(old_is_binary, int):
|
||||
expression.is_binary = bool(old_is_binary)
|
||||
|
||||
old_override_blink = next(
|
||||
(
|
||||
enum.identifier
|
||||
for enum in Vrm1ExpressionPropertyGroup.expression_override_type_enum
|
||||
if old_expression.get("override_blink") == enum.value
|
||||
),
|
||||
None,
|
||||
)
|
||||
if old_override_blink is not None:
|
||||
expression.override_blink = old_override_blink
|
||||
|
||||
old_override_look_at = next(
|
||||
(
|
||||
enum.identifier
|
||||
for enum in Vrm1ExpressionPropertyGroup.expression_override_type_enum
|
||||
if old_expression.get("override_look_at") == enum.value
|
||||
),
|
||||
None,
|
||||
)
|
||||
if old_override_look_at is not None:
|
||||
expression.override_look_at = old_override_look_at
|
||||
|
||||
old_override_mouth = next(
|
||||
(
|
||||
enum.identifier
|
||||
for enum in Vrm1ExpressionPropertyGroup.expression_override_type_enum
|
||||
if old_expression.get("override_mouth") == enum.value
|
||||
),
|
||||
None,
|
||||
)
|
||||
if old_override_mouth is not None:
|
||||
expression.override_mouth = old_override_mouth
|
||||
|
||||
|
||||
def migrate_old_expressions_layout(expressions: Vrm1ExpressionsPropertyGroup) -> None:
|
||||
for name, expression in expressions.preset.name_to_expression_dict().items():
|
||||
property_name = {
|
||||
"blinkLeft": "blink_left",
|
||||
"blinkRight": "blink_right",
|
||||
"lookUp": "look_up",
|
||||
"lookDown": "look_down",
|
||||
"lookLeft": "look_left",
|
||||
"lookRight": "look_right",
|
||||
}.get(name)
|
||||
if property_name is None:
|
||||
property_name = name
|
||||
old_expression = expressions.get(property_name)
|
||||
migrate_old_expression_layout(old_expression, expression)
|
||||
expression.name = name
|
||||
for expression in expressions.custom:
|
||||
old_expression = expression.get("expression")
|
||||
migrate_old_expression_layout(old_expression, expression)
|
||||
|
||||
|
||||
def migrate_pose(context: Context, armature: Object, armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 20, 34):
|
||||
return
|
||||
|
||||
humanoid = ext.vrm1.humanoid
|
||||
if isinstance(humanoid.get("pose"), int):
|
||||
return
|
||||
|
||||
if tuple(ext.addon_version) == ext.INITIAL_ADDON_VERSION:
|
||||
if ext.has_vrm_model_metadata(armature):
|
||||
humanoid.pose = humanoid.POSE_CURRENT_POSE.identifier
|
||||
return
|
||||
|
||||
action = humanoid.pose_library
|
||||
if action and action.name in context.blend_data.actions:
|
||||
humanoid.pose = humanoid.POSE_CUSTOM_POSE.identifier
|
||||
elif armature_data.pose_position == "REST":
|
||||
humanoid.pose = humanoid.POSE_REST_POSITION_POSE.identifier
|
||||
else:
|
||||
humanoid.pose = humanoid.POSE_CURRENT_POSE.identifier
|
||||
|
||||
|
||||
def migrate_auto_pose(_context: Context, armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) == ext.INITIAL_ADDON_VERSION or tuple(
|
||||
ext.addon_version
|
||||
) >= (2, 20, 81):
|
||||
return
|
||||
|
||||
humanoid = ext.vrm1.humanoid
|
||||
if not isinstance(humanoid.get("pose"), int):
|
||||
humanoid.pose = humanoid.POSE_CURRENT_POSE.identifier
|
||||
|
||||
|
||||
def migrate(context: Context, vrm1: Vrm1PropertyGroup, armature: Object) -> None:
|
||||
armature_data = armature.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
return
|
||||
|
||||
human_bones = vrm1.humanoid.human_bones
|
||||
human_bones.last_bone_names_str = ""
|
||||
Vrm1HumanBonesPropertyGroup.fixup_human_bones(armature)
|
||||
Vrm1HumanBonesPropertyGroup.update_all_node_candidates(context, armature_data.name)
|
||||
|
||||
if human_bones.initial_automatic_bone_assignment:
|
||||
human_bones.initial_automatic_bone_assignment = False
|
||||
human_bone_name_to_human_bone = human_bones.human_bone_name_to_human_bone()
|
||||
if all(not b.node.bone_name for b in human_bone_name_to_human_bone.values()):
|
||||
ops.vrm.assign_vrm1_humanoid_human_bones_automatically(
|
||||
armature_object_name=armature.name
|
||||
)
|
||||
|
||||
if tuple(get_armature_extension(armature_data).addon_version) <= (2, 14, 10):
|
||||
ext = get_armature_extension(armature_data)
|
||||
head_bone_name = ext.vrm1.humanoid.human_bones.head.node.bone_name
|
||||
head_bone = armature_data.bones.get(head_bone_name)
|
||||
if head_bone:
|
||||
look_at = get_armature_extension(armature_data).vrm1.look_at
|
||||
world_translation = (
|
||||
armature.matrix_world @ head_bone.matrix_local
|
||||
).to_quaternion() @ Vector(look_at.offset_from_head_bone)
|
||||
look_at.offset_from_head_bone = list(world_translation)
|
||||
|
||||
if tuple(get_armature_extension(armature_data).addon_version) <= (2, 15, 5):
|
||||
# Apply lower limit value
|
||||
look_at = get_armature_extension(armature_data).vrm1.look_at
|
||||
look_at.range_map_horizontal_inner.input_max_value = (
|
||||
look_at.range_map_horizontal_inner.input_max_value
|
||||
)
|
||||
look_at.range_map_horizontal_outer.input_max_value = (
|
||||
look_at.range_map_horizontal_outer.input_max_value
|
||||
)
|
||||
look_at.range_map_vertical_down.input_max_value = (
|
||||
look_at.range_map_vertical_down.input_max_value
|
||||
)
|
||||
look_at.range_map_vertical_up.input_max_value = (
|
||||
look_at.range_map_vertical_up.input_max_value
|
||||
)
|
||||
|
||||
if tuple(get_armature_extension(armature_data).addon_version) < (2, 18, 0):
|
||||
migrate_old_expressions_layout(
|
||||
get_armature_extension(armature_data).vrm1.expressions
|
||||
)
|
||||
|
||||
if tuple(get_armature_extension(armature_data).addon_version) < (2, 20, 0):
|
||||
look_at = get_armature_extension(armature_data).vrm1.look_at
|
||||
look_at.offset_from_head_bone = (
|
||||
look_at.offset_from_head_bone[0],
|
||||
look_at.offset_from_head_bone[2],
|
||||
-look_at.offset_from_head_bone[1],
|
||||
)
|
||||
|
||||
migrate_pose(context, armature, armature_data)
|
||||
migrate_auto_pose(context, armature_data)
|
||||
|
||||
# Set a name for the expression preset.
|
||||
# It's not necessary for management, but I want to set it because it's
|
||||
# displayed in the animation keyframes.
|
||||
expressions = get_armature_extension(armature_data).vrm1.expressions
|
||||
preset_name_to_expression_dict = expressions.preset.name_to_expression_dict()
|
||||
for preset_name, preset_expression in preset_name_to_expression_dict.items():
|
||||
if preset_expression.name != preset_name:
|
||||
preset_expression.name = preset_name
|
||||
|
||||
if tuple(get_armature_extension(armature_data).addon_version) < (3, 9, 0):
|
||||
for expression in expressions.preset.name_to_expression_dict().values():
|
||||
for morph_target_bind in expression.morph_target_binds:
|
||||
morph_target_bind.node.saved_mesh_object_name_to_restore = (
|
||||
morph_target_bind.node.mesh_object_name
|
||||
)
|
||||
|
||||
Vrm1HumanBonesPropertyGroup.update_all_node_candidates(
|
||||
context,
|
||||
armature_data.name,
|
||||
force=True,
|
||||
)
|
||||
ops.vrm.update_vrm1_expression_ui_list_elements()
|
||||
2730
editor/vrm1/ops.py
Normal file
2730
editor/vrm1/ops.py
Normal file
File diff suppressed because it is too large
Load Diff
1222
editor/vrm1/panel.py
Normal file
1222
editor/vrm1/panel.py
Normal file
File diff suppressed because it is too large
Load Diff
1812
editor/vrm1/property_group.py
Normal file
1812
editor/vrm1/property_group.py
Normal file
File diff suppressed because it is too large
Load Diff
93
editor/vrm1/scene_watcher.py
Normal file
93
editor/vrm1/scene_watcher.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
from dataclasses import dataclass
|
||||
|
||||
from bpy.types import Armature, Context
|
||||
from mathutils import Vector
|
||||
|
||||
from ...common import ops
|
||||
from ...common.logger import get_logger
|
||||
from ...common.scene_watcher import RunState, SceneWatcher
|
||||
from ..extension import get_armature_extension
|
||||
from .property_group import Vrm1LookAtPropertyGroup
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LookAtPreviewUpdater(SceneWatcher):
|
||||
armature_index: int = 0
|
||||
|
||||
def reset_run_progress(self) -> None:
|
||||
self.armature_index = 0
|
||||
|
||||
def run(self, context: Context) -> RunState:
|
||||
"""Detect updates to the target object of Look At and update the state."""
|
||||
blend_data = context.blend_data
|
||||
|
||||
if not blend_data.armatures:
|
||||
return RunState.FINISH
|
||||
|
||||
count = 20
|
||||
|
||||
armatures_len = len(blend_data.armatures)
|
||||
if self.armature_index >= armatures_len:
|
||||
self.armature_index = 0
|
||||
|
||||
start_armature_index = self.armature_index
|
||||
end_armature_index = min(self.armature_index + count, armatures_len)
|
||||
changed = False
|
||||
for armature in blend_data.armatures[start_armature_index:end_armature_index]:
|
||||
ext = get_armature_extension(armature)
|
||||
if not ext.is_vrm1():
|
||||
continue
|
||||
look_at = ext.vrm1.look_at
|
||||
if not look_at.enable_preview:
|
||||
continue
|
||||
preview_target_bpy_object = look_at.preview_target_bpy_object
|
||||
if not preview_target_bpy_object:
|
||||
continue
|
||||
if (
|
||||
Vector(look_at.previous_preview_target_bpy_object_location)
|
||||
- preview_target_bpy_object.location
|
||||
).length_squared > 0:
|
||||
changed = True
|
||||
break
|
||||
|
||||
if changed:
|
||||
Vrm1LookAtPropertyGroup.update_all_previews(context)
|
||||
return RunState.FINISH
|
||||
|
||||
self.armature_index += count
|
||||
|
||||
if end_armature_index < armatures_len:
|
||||
return RunState.PREEMPT
|
||||
|
||||
return RunState.FINISH
|
||||
|
||||
def create_fast_path_performance_test_objects(self, context: Context) -> None:
|
||||
blend_data = context.blend_data
|
||||
|
||||
for i in range(300):
|
||||
if i % 3 != 0:
|
||||
mesh = blend_data.meshes.new(f"Mesh#{i}")
|
||||
obj = blend_data.objects.new(f"Object#{i}", mesh)
|
||||
context.scene.collection.objects.link(obj)
|
||||
continue
|
||||
|
||||
ops.icyp.make_basic_armature()
|
||||
active_object = context.active_object
|
||||
if not active_object:
|
||||
message = f"Not an armature: {active_object}"
|
||||
raise ValueError(message)
|
||||
armature = active_object.data
|
||||
if not isinstance(armature, Armature):
|
||||
raise TypeError
|
||||
|
||||
ext = get_armature_extension(armature)
|
||||
ext.spec_version = ext.SPEC_VERSION_VRM1
|
||||
look_at = ext.vrm1.look_at
|
||||
look_at.type = ext.vrm1.look_at.TYPE_BONE.identifier
|
||||
look_at.preview_target_bpy_object = blend_data.objects[
|
||||
i * 5 % len(blend_data.objects)
|
||||
]
|
||||
ext.vrm1.look_at.enable_preview = True
|
||||
302
editor/vrm1/ui_list.py
Normal file
302
editor/vrm1/ui_list.py
Normal file
@@ -0,0 +1,302 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
from bpy.types import Context, Mesh, UILayout, UIList
|
||||
|
||||
from ...common.logger import get_logger
|
||||
from ..property_group import StringPropertyGroup
|
||||
from .property_group import (
|
||||
Vrm1ExpressionsPropertyGroup,
|
||||
Vrm1FirstPersonPropertyGroup,
|
||||
Vrm1MaterialColorBindPropertyGroup,
|
||||
Vrm1MeshAnnotationPropertyGroup,
|
||||
Vrm1MetaPropertyGroup,
|
||||
Vrm1MorphTargetBindPropertyGroup,
|
||||
Vrm1TextureTransformBindPropertyGroup,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class VRM_UL_vrm1_meta_author(UIList):
|
||||
bl_idname = "VRM_UL_vrm1_meta_author"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
meta: object,
|
||||
author: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(meta, Vrm1MetaPropertyGroup):
|
||||
return
|
||||
if not isinstance(author, StringPropertyGroup):
|
||||
return
|
||||
|
||||
icon = "USER"
|
||||
|
||||
if self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", translate=False, icon=icon)
|
||||
return
|
||||
|
||||
if self.layout_type not in {"DEFAULT", "COMPACT"}:
|
||||
return
|
||||
|
||||
if index == meta.active_author_index:
|
||||
layout.prop(author, "value", icon=icon, text="", translate=False)
|
||||
else:
|
||||
layout.label(text=author.value, icon=icon, translate=False)
|
||||
|
||||
|
||||
class VRM_UL_vrm1_meta_reference(UIList):
|
||||
bl_idname = "VRM_UL_vrm1_meta_reference"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
meta: object,
|
||||
reference: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(meta, Vrm1MetaPropertyGroup):
|
||||
return
|
||||
if not isinstance(reference, StringPropertyGroup):
|
||||
return
|
||||
|
||||
icon = "URL"
|
||||
|
||||
if self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", translate=False, icon=icon)
|
||||
return
|
||||
|
||||
if self.layout_type not in {"DEFAULT", "COMPACT"}:
|
||||
return
|
||||
|
||||
if index == meta.active_reference_index:
|
||||
layout.prop(reference, "value", icon=icon, text="", translate=False)
|
||||
else:
|
||||
layout.label(text=reference.value, icon=icon, translate=False)
|
||||
|
||||
|
||||
class VRM_UL_vrm1_first_person_mesh_annotation(UIList):
|
||||
bl_idname = "VRM_UL_vrm1_first_person_mesh_annotation"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
first_person: object,
|
||||
mesh_annotation: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(first_person, Vrm1FirstPersonPropertyGroup):
|
||||
return
|
||||
if not isinstance(mesh_annotation, Vrm1MeshAnnotationPropertyGroup):
|
||||
return
|
||||
|
||||
icon = "OUTLINER_OB_MESH"
|
||||
|
||||
if self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", translate=False, icon=icon)
|
||||
return
|
||||
|
||||
if self.layout_type not in {"DEFAULT", "COMPACT"}:
|
||||
return
|
||||
|
||||
row = layout.split(factor=0.6, align=True)
|
||||
if index == first_person.active_mesh_annotation_index:
|
||||
row.prop(
|
||||
mesh_annotation.node,
|
||||
"bpy_object",
|
||||
text="",
|
||||
translate=False,
|
||||
icon=icon,
|
||||
)
|
||||
else:
|
||||
row.label(
|
||||
text=mesh_annotation.node.mesh_object_name,
|
||||
translate=False,
|
||||
icon=icon,
|
||||
)
|
||||
row.prop(mesh_annotation, "type", text="", translate=False)
|
||||
|
||||
|
||||
class VRM_UL_vrm1_expression(UIList):
|
||||
bl_idname = "VRM_UL_vrm1_expression"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
expressions: object,
|
||||
_item: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(expressions, Vrm1ExpressionsPropertyGroup):
|
||||
return
|
||||
preset_expression_items = list(
|
||||
expressions.preset.name_to_expression_dict().items()
|
||||
)
|
||||
if index < len(preset_expression_items):
|
||||
name, expression = preset_expression_items[index]
|
||||
icon = expressions.preset.get_icon(name)
|
||||
if not icon:
|
||||
logger.error("Unknown preset expression: %s", name)
|
||||
icon = "SHAPEKEY_DATA"
|
||||
else:
|
||||
custom_expressions = expressions.custom
|
||||
custom_index = index - len(preset_expression_items)
|
||||
if custom_index >= len(custom_expressions):
|
||||
return
|
||||
expression = custom_expressions[custom_index]
|
||||
name = expression.custom_name
|
||||
icon = "SHAPEKEY_DATA"
|
||||
|
||||
if self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", translate=False, icon=icon)
|
||||
return
|
||||
|
||||
if self.layout_type not in {"DEFAULT", "COMPACT"}:
|
||||
return
|
||||
|
||||
split = layout.split(align=True, factor=0.55)
|
||||
split.label(text=name, translate=False, icon=icon)
|
||||
split.prop(expression, "preview", text="Preview")
|
||||
|
||||
|
||||
class VRM_UL_vrm1_morph_target_bind(UIList):
|
||||
bl_idname = "VRM_UL_vrm1_morph_target_bind"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
context: Context,
|
||||
layout: UILayout,
|
||||
_data: object,
|
||||
morph_target_bind: object,
|
||||
icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
_index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
blend_data = context.blend_data
|
||||
if not isinstance(morph_target_bind, Vrm1MorphTargetBindPropertyGroup):
|
||||
return
|
||||
|
||||
if self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", icon_value=icon)
|
||||
return
|
||||
|
||||
if self.layout_type not in {"DEFAULT", "COMPACT"}:
|
||||
return
|
||||
|
||||
name = morph_target_bind.node.mesh_object_name
|
||||
mesh_object = blend_data.objects.get(morph_target_bind.node.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:
|
||||
keys = shape_keys.key_blocks.keys()
|
||||
if morph_target_bind.index in keys:
|
||||
name += " / " + morph_target_bind.index
|
||||
layout.label(text=name, translate=False, icon="MESH_DATA")
|
||||
|
||||
|
||||
class VRM_UL_vrm1_material_color_bind(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_material_color_bind"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
_data: object,
|
||||
material_color_bind: object,
|
||||
icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
_index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(material_color_bind, Vrm1MaterialColorBindPropertyGroup):
|
||||
return
|
||||
|
||||
if self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", translate=False, icon_value=icon)
|
||||
return
|
||||
|
||||
if self.layout_type not in {"DEFAULT", "COMPACT"}:
|
||||
return
|
||||
|
||||
name = ""
|
||||
material = material_color_bind.material
|
||||
if material:
|
||||
name = material.name
|
||||
type_name = next(
|
||||
(
|
||||
enum.name
|
||||
for enum in Vrm1MaterialColorBindPropertyGroup.type_enum
|
||||
if enum.identifier == material_color_bind.type
|
||||
),
|
||||
None,
|
||||
)
|
||||
if type_name:
|
||||
name += " / " + type_name
|
||||
layout.label(text=name, translate=False, icon="MATERIAL")
|
||||
|
||||
|
||||
class VRM_UL_vrm1_texture_transform_bind(UIList):
|
||||
bl_idname = "VRM_UL_vrm1_texture_transform_bind"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
_data: object,
|
||||
texture_transform_bind: object,
|
||||
icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
_index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(
|
||||
texture_transform_bind, Vrm1TextureTransformBindPropertyGroup
|
||||
):
|
||||
return
|
||||
|
||||
if self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", translate=False, icon_value=icon)
|
||||
return
|
||||
|
||||
if self.layout_type not in {"DEFAULT", "COMPACT"}:
|
||||
return
|
||||
|
||||
name = ""
|
||||
material = texture_transform_bind.material
|
||||
if material:
|
||||
name = material.name
|
||||
layout.label(text=name, translate=False, icon="MATERIAL")
|
||||
Reference in New Issue
Block a user