Files
blender-vrm/editor/vrm0/property_group.py
小煜 091ad6a49a feat: Add VRM Blender addon with complete import/export functionality
- Add core VRM addon infrastructure with manifest and registration
- Add common utilities module with file system, logging, and conversion helpers
- Add human bone mapper with support for multiple rigging standards (Mixamo, MMD, Unreal, Rigify, etc.)
- Add VRM 0.x and 1.x format support with property groups and handlers
- Add editor UI panels for VRM metadata, spring bones, and MToon materials
- Add exporter with glTF2 extension support for VRM format serialization
- Add importer with scene reconstruction and armature generation
- Add MToon shader support with auto-setup and material migration
- Add spring bone physics simulation with constraint handling
- Add node constraint editor for advanced rigging control
- Add comprehensive validation and error handling with user dialogs
- Add scene watcher for real-time property synchronization
- Add workspace management and preference system
- Include Python cache files and Blender manifest configuration
- This is the initial commit establishing the complete VRM addon ecosystem for Blender
2026-01-01 14:21:56 +08:00

1118 lines
41 KiB
Python

# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
import functools
from collections.abc import Sequence
from sys import float_info
from typing import TYPE_CHECKING, ClassVar, Optional
import bpy
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
PointerProperty,
StringProperty,
)
from bpy.types import (
Action,
Armature,
Context,
Image,
Material,
Mesh,
Object,
PropertyGroup,
)
from mathutils import Vector
from ...common import convert
from ...common.logger import get_logger
from ...common.vrm0.human_bone import (
HumanBoneName,
HumanBoneSpecification,
HumanBoneSpecifications,
)
from ..property_group import (
BonePropertyGroup,
FloatPropertyGroup,
MeshObjectPropertyGroup,
StringPropertyGroup,
property_group_enum,
)
from ..vrm1.property_group import Vrm1HumanoidPropertyGroup
if TYPE_CHECKING:
from ..property_group import CollectionPropertyProtocol
logger = get_logger(__name__)
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_Humanoid.cs#L70-L164
class Vrm0HumanoidBonePropertyGroup(PropertyGroup):
bone: StringProperty( # type: ignore[valid-type]
name="VRM Humanoid Bone Name"
)
node: PointerProperty( # type: ignore[valid-type]
name="Bone Name",
type=BonePropertyGroup,
)
use_default_values: BoolProperty( # type: ignore[valid-type]
name="Unity's HumanLimit.useDefaultValues",
default=True,
)
min: FloatVectorProperty( # type: ignore[valid-type]
size=3,
name="Unity's HumanLimit.min",
)
max: FloatVectorProperty( # type: ignore[valid-type]
size=3,
name="Unity's HumanLimit.max",
)
center: FloatVectorProperty( # type: ignore[valid-type]
size=3,
name="Unity's HumanLimit.center",
)
axis_length: FloatProperty( # type: ignore[valid-type]
name="Unity's HumanLimit.axisLength"
)
# for UI
node_candidates: CollectionProperty( # type: ignore[valid-type]
type=StringPropertyGroup
)
def update_node_candidates(
self,
armature_data: Armature,
bpy_bone_name_to_human_bone_specification: dict[str, HumanBoneSpecification],
error_bpy_bone_names: Sequence[str],
) -> bool:
human_bone_name = HumanBoneName.from_str(self.bone)
if human_bone_name is None:
logger.warning("Bone name '%s' is invalid", self.bone)
return False
target = HumanBoneSpecifications.get(human_bone_name)
new_candidates = BonePropertyGroup.find_bone_candidates(
armature_data,
target,
bpy_bone_name_to_human_bone_specification,
error_bpy_bone_names,
)
if {n.value for n in self.node_candidates} == new_candidates:
return False
self.node_candidates.clear()
# Preserving list order
for bone_name in armature_data.bones.keys():
if bone_name not in new_candidates:
continue
candidate = self.node_candidates.add()
candidate.value = bone_name
return True
def specification(self) -> HumanBoneSpecification:
name = HumanBoneName.from_str(self.bone)
if name is None:
message = f'HumanBone "{self.bone}" is invalid'
raise ValueError(message)
return HumanBoneSpecifications.get(name)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
bone: str # type: ignore[no-redef]
node: BonePropertyGroup # type: ignore[no-redef]
use_default_values: bool # type: ignore[no-redef]
min: Sequence[float] # type: ignore[no-redef]
max: Sequence[float] # type: ignore[no-redef]
center: Sequence[float] # type: ignore[no-redef]
axis_length: float # type: ignore[no-redef]
node_candidates: CollectionPropertyProtocol[ # type: ignore[no-redef]
StringPropertyGroup
]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_Humanoid.cs#L166-L195
class Vrm0HumanoidPropertyGroup(PropertyGroup):
human_bones: CollectionProperty( # type: ignore[valid-type]
name="Human Bones",
type=Vrm0HumanoidBonePropertyGroup,
)
arm_stretch: FloatProperty( # type: ignore[valid-type]
name="Arm Stretch",
default=0.05,
)
leg_stretch: FloatProperty( # type: ignore[valid-type]
name="Leg Stretch",
default=0.05,
)
upper_arm_twist: FloatProperty( # type: ignore[valid-type]
name="Upper Arm Twist",
default=0.5,
)
lower_arm_twist: FloatProperty( # type: ignore[valid-type]
name="Lower Arm Twist",
default=0.5,
)
upper_leg_twist: FloatProperty( # type: ignore[valid-type]
name="Upper Leg Twist",
default=0.5,
)
lower_leg_twist: FloatProperty( # type: ignore[valid-type]
name="Lower Leg Twist",
default=0.5,
)
feet_spacing: FloatProperty( # type: ignore[valid-type]
name="Feet Spacing",
default=0,
)
has_translation_dof: BoolProperty( # type: ignore[valid-type]
name="Has Translation DoF",
default=False,
)
POSE_AUTO_POSE = Vrm1HumanoidPropertyGroup.POSE_AUTO_POSE
POSE_REST_POSITION_POSE = Vrm1HumanoidPropertyGroup.POSE_REST_POSITION_POSE
POSE_CURRENT_POSE = Vrm1HumanoidPropertyGroup.POSE_CURRENT_POSE
POSE_CUSTOM_POSE = Vrm1HumanoidPropertyGroup.POSE_CUSTOM_POSE
pose: bpy.props.EnumProperty( # type: ignore[valid-type]
items=Vrm1HumanoidPropertyGroup.pose_enum.items(),
name="T-Pose",
description="T-Pose",
default=POSE_AUTO_POSE.identifier,
)
# for T-Pose
def update_pose_library(self, _context: Context) -> None:
self.pose_marker_name = ""
pose_library: PointerProperty( # type: ignore[valid-type]
type=Action,
name="Pose Library",
description="Pose library for T Pose",
update=update_pose_library,
)
pose_marker_name: StringProperty() # type: ignore[valid-type]
# for UI
last_bone_names_str: StringProperty() # type: ignore[valid-type]
initial_automatic_bone_assignment: BoolProperty( # type: ignore[valid-type]
default=True
)
def all_required_bones_are_assigned(self) -> bool:
for name in HumanBoneSpecifications.required_names:
for human_bone in self.human_bones:
if human_bone.bone != name:
continue
if human_bone.node.bone_name not in human_bone.node_candidates:
return False
return True
@staticmethod
def defer_update_all_node_candidates(
armature_data_name: str,
*,
force: bool = False,
) -> None:
bpy.app.timers.register(
functools.partial(
Vrm0HumanoidPropertyGroup.update_all_node_candidates_timer_callback,
armature_data_name,
force=force,
)
)
@staticmethod
def update_all_node_candidates_timer_callback(
armature_object_name: str, *, force: bool = False
) -> None:
"""Wrap update_all_node_candidates() to match bpy.app.timers.register."""
context = bpy.context # Context cannot cross frames so we need to get a new one
Vrm0HumanoidPropertyGroup.update_all_node_candidates(
context, armature_object_name, force=force
)
@staticmethod
def update_all_node_candidates(
context: Optional[Context],
armature_data_name: str,
*,
force: bool = False,
) -> None:
from ..extension import get_armature_extension
if context is None:
context = bpy.context
armature_data = context.blend_data.armatures.get(armature_data_name)
if not armature_data:
return
humanoid = get_armature_extension(armature_data).vrm0.humanoid
bone_names_str = "\n".join(
bone.name + "\n" + (parent.name if (parent := bone.parent) else "")
for bone in sorted(armature_data.bones.values(), key=lambda b: b.name)
)
if not force and humanoid.last_bone_names_str == bone_names_str:
return
humanoid.last_bone_names_str = bone_names_str
BonePropertyGroup.update_all_vrm0_node_candidates(armature_data)
@staticmethod
def fixup_human_bones(obj: Object) -> None:
from ..extension import get_armature_extension
armature_data = obj.data
if not isinstance(armature_data, Armature):
return
humanoid = get_armature_extension(armature_data).vrm0.humanoid
# Add bone maps that don't exist
refresh = False
for human_bone_name in HumanBoneSpecifications.all_names:
if any(
human_bone.bone == human_bone_name
for human_bone in humanoid.human_bones
):
continue
human_bone = humanoid.human_bones.add()
human_bone.bone = human_bone_name
refresh = True
# Remove duplicate bone maps
fixup = True
while fixup:
fixup = False
found_bones: list[str] = []
for i, human_bone in enumerate(list(humanoid.human_bones)):
if (
human_bone.bone in HumanBoneSpecifications.all_names
and human_bone.bone not in found_bones
):
found_bones.append(human_bone.bone)
continue
humanoid.human_bones.remove(i)
refresh = True
fixup = True
break
# If multiple bone maps have the same Blender bone assigned, remove one of them
fixup = True
while fixup:
fixup = False
found_node_bone_names: list[str] = []
for human_bone in humanoid.human_bones:
if not human_bone.node.bone_name:
continue
if human_bone.node.bone_name not in found_node_bone_names:
found_node_bone_names.append(human_bone.node.bone_name)
continue
human_bone.node.bone_name = ""
refresh = True
fixup = True
break
if not refresh:
return
secondary_animation = get_armature_extension(
armature_data
).vrm0.secondary_animation
for collider_group in secondary_animation.collider_groups:
collider_group.refresh(obj)
for bone_group in secondary_animation.bone_groups:
bone_group.refresh(obj)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
human_bones: CollectionPropertyProtocol[ # type: ignore[no-redef]
Vrm0HumanoidBonePropertyGroup
]
arm_stretch: float # type: ignore[no-redef]
leg_stretch: float # type: ignore[no-redef]
upper_arm_twist: float # type: ignore[no-redef]
lower_arm_twist: float # type: ignore[no-redef]
upper_leg_twist: float # type: ignore[no-redef]
lower_leg_twist: float # type: ignore[no-redef]
feet_spacing: float # type: ignore[no-redef]
has_translation_dof: bool # type: ignore[no-redef]
pose: str # type: ignore[no-redef]
pose_library: Optional[Action] # type: ignore[no-redef]
pose_marker_name: str # type: ignore[no-redef]
last_bone_names_str: str # type: ignore[no-redef]
initial_automatic_bone_assignment: bool # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_FirstPerson.cs#L10-L22
class Vrm0DegreeMapPropertyGroup(PropertyGroup):
curve: FloatVectorProperty( # type: ignore[valid-type]
size=8,
name="Curve",
default=(0, 0, 0, 1, 1, 1, 1, 0),
)
x_range: FloatProperty( # type: ignore[valid-type]
name="X Range",
default=90,
)
y_range: FloatProperty( # type: ignore[valid-type]
name="Y Range",
default=10,
)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
curve: Sequence[float] # type: ignore[no-redef]
x_range: float # type: ignore[no-redef]
y_range: float # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_FirstPerson.cs#L32-L41
class Vrm0MeshAnnotationPropertyGroup(PropertyGroup):
mesh: PointerProperty( # type: ignore[valid-type]
name="Mesh",
type=MeshObjectPropertyGroup,
description="Mesh on restrict render in the first person camera",
)
first_person_flag_enum, *__first_person_flags = property_group_enum(
("Auto", "Auto", "Auto restrict render", "NONE", 0),
(
"FirstPersonOnly",
"First-Person Only",
"(Maybe needless) Restrict render in the third person camera",
"NONE",
1,
),
(
"ThirdPersonOnly",
"Third-Person Only",
"Restrict render in the first person camera for face, hairs or hat",
"NONE",
2,
),
("Both", "Both", "No restrict render for body, arms or legs", "NONE", 3),
)
first_person_flag: EnumProperty( # type: ignore[valid-type]
items=first_person_flag_enum.items(),
name="First Person Flag",
description="Restrict render in the first person camera",
)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
mesh: MeshObjectPropertyGroup # type: ignore[no-redef]
first_person_flag: str # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_FirstPerson.cs#L50-L91
class Vrm0FirstPersonPropertyGroup(PropertyGroup):
first_person_bone: PointerProperty( # type: ignore[valid-type]
name="First Person Bone",
type=BonePropertyGroup,
description="Bone to follow the first person camera",
)
first_person_bone_offset: FloatVectorProperty( # type: ignore[valid-type]
size=3,
name="First Person Bone Offset",
description=(
"Offset from the first person bone to follow the first person camera"
),
subtype="TRANSLATION",
unit="LENGTH",
default=(0, 0, 0),
)
mesh_annotations: CollectionProperty( # type: ignore[valid-type]
name="Mesh Annotations",
type=Vrm0MeshAnnotationPropertyGroup,
)
look_at_type_name_enum, *__look_at_types = property_group_enum(
("Bone", "Bone", "Use bones to eye movement", "BONE_DATA", 0),
(
"BlendShape",
"Blend Shape",
"Use blend Shapes of VRM Blend Shape Proxy to eye movement.",
"SHAPEKEY_DATA",
1,
),
)
look_at_type_name: EnumProperty( # type: ignore[valid-type]
items=look_at_type_name_enum.items(),
name="Look At Type Name",
description="How to eye movement",
)
look_at_horizontal_inner: PointerProperty( # type: ignore[valid-type]
type=Vrm0DegreeMapPropertyGroup,
name="Look At Horizontal Inner",
)
look_at_horizontal_outer: PointerProperty( # type: ignore[valid-type]
type=Vrm0DegreeMapPropertyGroup,
name="Look At Horizontal Outer",
)
look_at_vertical_down: PointerProperty( # type: ignore[valid-type]
type=Vrm0DegreeMapPropertyGroup,
name="Look At Vertical Down",
)
look_at_vertical_up: PointerProperty( # type: ignore[valid-type]
type=Vrm0DegreeMapPropertyGroup,
name="lookAt Vertical Up",
)
# for UI
active_mesh_annotation_index: IntProperty(min=0) # type: ignore[valid-type]
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
first_person_bone: BonePropertyGroup # type: ignore[no-redef]
first_person_bone_offset: Sequence[float] # type: ignore[no-redef]
mesh_annotations: CollectionPropertyProtocol[ # type: ignore[no-redef]
Vrm0MeshAnnotationPropertyGroup
]
look_at_type_name: str # type: ignore[no-redef]
look_at_horizontal_inner: Vrm0DegreeMapPropertyGroup # type: ignore[no-redef]
look_at_horizontal_outer: Vrm0DegreeMapPropertyGroup # type: ignore[no-redef]
look_at_vertical_down: Vrm0DegreeMapPropertyGroup # type: ignore[no-redef]
look_at_vertical_up: Vrm0DegreeMapPropertyGroup # type: ignore[no-redef]
active_mesh_annotation_index: int # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_BlendShape.cs#L18-L30
class Vrm0BlendShapeBindPropertyGroup(PropertyGroup):
mesh: PointerProperty( # type: ignore[valid-type]
name="Mesh",
type=MeshObjectPropertyGroup,
)
index: StringProperty( # type: ignore[valid-type]
name="Index"
)
weight: FloatProperty( # type: ignore[valid-type]
name="Weight",
min=0,
default=1,
max=1,
subtype="FACTOR",
)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
mesh: MeshObjectPropertyGroup # type: ignore[no-redef]
index: str # type: ignore[no-redef]
weight: float # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_BlendShape.cs#L9-L16
class Vrm0MaterialValueBindPropertyGroup(PropertyGroup):
material: PointerProperty( # type: ignore[valid-type]
name="Material",
type=Material,
)
# Use StringProperty instead of EnumProperty for property_name.
# This is necessary to allow arbitrary values to be entered.
property_name: StringProperty( # type: ignore[valid-type]
name="Property Name"
)
target_value: CollectionProperty( # type: ignore[valid-type]
name="Target Value",
type=FloatPropertyGroup,
)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
material: Optional[Material] # type: ignore[no-redef]
property_name: str # type: ignore[no-redef]
target_value: CollectionPropertyProtocol[ # type: ignore[no-redef]
FloatPropertyGroup
]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_BlendShape.cs#L62-L99
class Vrm0BlendShapeGroupPropertyGroup(PropertyGroup):
name: StringProperty( # type: ignore[valid-type]
name="Name",
description="Name of the blendshape group",
)
preset_name_enum, (PRESET_NAME_UNKNOWN, *__preset_names) = property_group_enum(
("unknown", "Unknown", "", "SHAPEKEY_DATA", 0),
("neutral", "Neutral", "", "VIEW_ORTHO", 1),
("a", "A", "", "EVENT_A", 2),
("i", "I", "", "EVENT_I", 3),
("u", "U", "", "EVENT_U", 4),
("e", "E", "", "EVENT_E", 5),
("o", "O", "", "EVENT_O", 6),
("blink", "Blink", "", "HIDE_ON", 7),
("joy", "Joy", "", "HEART", 8),
("angry", "Angry", "", "ORPHAN_DATA", 9),
("sorrow", "Sorrow", "", "MOD_FLUIDSIM", 10),
("fun", "Fun", "", "LIGHT_SUN", 11),
("lookup", "Look Up", "", "ANCHOR_TOP", 12),
("lookdown", "Look Down", "", "ANCHOR_BOTTOM", 13),
("lookleft", "Look Left", "", "ANCHOR_RIGHT", 14),
("lookright", "Look Right", "", "ANCHOR_LEFT", 15),
("blink_l", "Blink_L", "", "HIDE_ON", 16),
("blink_r", "Blink_R", "", "HIDE_ON", 17),
)
preset_name: EnumProperty( # type: ignore[valid-type]
items=preset_name_enum.items(),
name="Preset",
description="Preset name in VRM avatar",
)
binds: CollectionProperty( # type: ignore[valid-type]
type=Vrm0BlendShapeBindPropertyGroup,
name="Binds",
)
material_values: CollectionProperty( # type: ignore[valid-type]
type=Vrm0MaterialValueBindPropertyGroup,
name="Material Values",
)
is_binary: BoolProperty( # type: ignore[valid-type]
name="Is Binary",
description="Use binary change in the blendshape group",
)
# for UI
active_bind_index: IntProperty(min=0) # type: ignore[valid-type]
active_material_value_index: IntProperty(min=0) # type: ignore[valid-type]
# During animation playback, shape key values can only be changed
# in frame_change_pre/frame_change_post, so we store the changed values here
frame_change_post_shape_key_updates: ClassVar[dict[tuple[str, str], float]] = {}
def get_preview(self) -> float:
value = self.get("preview")
if isinstance(value, (float, int)):
return float(value)
return 0.0
def set_preview(self, value_obj: object) -> None:
context = bpy.context
value = convert.float_or_none(value_obj)
if value is None:
return
current_value = convert.float_or_none(self.get("preview"))
if (
current_value is not None
and abs(current_value - value) < float_info.epsilon
):
return
self["preview"] = value
for bind in self.binds:
mesh_object = context.blend_data.objects.get(bind.mesh.mesh_object_name)
if not mesh_object or mesh_object.type != "MESH":
continue
mesh = mesh_object.data
if not isinstance(mesh, Mesh):
continue
mesh_shape_keys = mesh.shape_keys
if not mesh_shape_keys:
continue
shape_key = context.blend_data.shape_keys.get(mesh_shape_keys.name)
if not shape_key:
continue
key_blocks = shape_key.key_blocks
if not key_blocks:
continue
if bind.index not in key_blocks:
continue
if self.is_binary:
# https://github.com/vrm-c/UniVRM/blob/38ccb92300c9ab41c72eb3d5b8dc8ce664a659d5/Assets/VRM/Runtime/BlendShape/BlendShapeMerger.cs#L89-L92
preview = 0.0 if self.preview < 0.5 else 1.0
else:
preview = self.preview
key_block_value = bind.weight * preview # Lerp 0.0 * (1 - a) + weight * a
key_blocks[bind.index].value = key_block_value
Vrm0BlendShapeGroupPropertyGroup.frame_change_post_shape_key_updates[
(shape_key.name, bind.index)
] = key_block_value
preview: FloatProperty( # type: ignore[valid-type]
name="Blend Shape Proxy",
min=0,
max=1,
subtype="FACTOR",
get=get_preview,
set=set_preview,
)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
name: str # type: ignore[no-redef]
preset_name: str # type: ignore[no-redef]
binds: CollectionPropertyProtocol[ # type: ignore[no-redef]
Vrm0BlendShapeBindPropertyGroup
]
material_values: CollectionPropertyProtocol[ # type: ignore[no-redef]
Vrm0MaterialValueBindPropertyGroup
]
is_binary: bool # type: ignore[no-redef]
active_bind_index: int # type: ignore[no-redef]
active_material_value_index: int # type: ignore[no-redef]
preview: float # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_SecondaryAnimation.cs#L10-L18
class Vrm0SecondaryAnimationColliderPropertyGroup(PropertyGroup):
bpy_object: PointerProperty( # type: ignore[valid-type]
type=Object
)
def refresh(self, armature: Object, bone_name: str) -> None:
if not self.bpy_object or not self.bpy_object.name:
return
if self.bpy_object.parent != armature:
self.bpy_object.parent = armature
if self.bpy_object.empty_display_type != "SPHERE":
self.bpy_object.empty_display_type = "SPHERE"
if bone_name:
if self.bpy_object.parent_type != "BONE":
self.bpy_object.parent_type = "BONE"
if self.bpy_object.parent_bone != bone_name:
self.bpy_object.parent_bone = bone_name
elif self.bpy_object.parent_type != "OBJECT":
self.bpy_object.parent_type = "OBJECT"
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
bpy_object: Optional[Object] # type: ignore[no-redef]
# https://github.com/vrm-c/vrm-specification/blob/f2d8f158297fc883aef9c3071ca68fbe46b03f45/specification/0.0/schema/vrm.secondaryanimation.collidergroup.schema.json
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_SecondaryAnimation.cs#L21-L29
class Vrm0SecondaryAnimationColliderGroupPropertyGroup(PropertyGroup):
node: PointerProperty( # type: ignore[valid-type]
name="Node",
type=BonePropertyGroup,
)
# Use the collider's own data for offset and radius
colliders: CollectionProperty( # type: ignore[valid-type]
name="Colliders",
type=Vrm0SecondaryAnimationColliderPropertyGroup,
)
def refresh(self, armature: Object) -> None:
from ..extension import get_armature_extension
name = (
str(self.node.bone_name) if self.node and self.node.bone_name else ""
) + f"#{self.uuid}"
if self.name != name:
self.name = name
for index, collider in reversed(
tuple((index, collider) for index, collider in enumerate(self.colliders))
):
if not collider.bpy_object or not collider.bpy_object.name:
self.colliders.remove(index)
else:
collider.refresh(armature, self.node.bone_name)
armature_data = armature.data
if not isinstance(armature_data, Armature):
return
for bone_group in get_armature_extension(
armature_data
).vrm0.secondary_animation.bone_groups:
bone_group.refresh(armature)
# for UI
show_expanded: BoolProperty() # type: ignore[valid-type]
active_collider_index: IntProperty(min=0) # type: ignore[valid-type]
# for reference from Vrm0SecondaryAnimationGroupPropertyGroup
name: StringProperty() # type: ignore[valid-type]
uuid: StringProperty() # type: ignore[valid-type]
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
node: BonePropertyGroup # type: ignore[no-redef]
colliders: CollectionPropertyProtocol[ # type: ignore[no-redef]
Vrm0SecondaryAnimationColliderPropertyGroup
]
show_expanded: bool # type: ignore[no-redef]
active_collider_index: int # type: ignore[no-redef]
name: str # type: ignore[no-redef]
uuid: str # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_SecondaryAnimation.cs#L32-L67
class Vrm0SecondaryAnimationGroupPropertyGroup(PropertyGroup):
comment: StringProperty( # type: ignore[valid-type]
name="Comment",
description="Comment about the purpose of springs",
)
# typo in VRM 0.0 specification
# https://github.com/vrm-c/vrm-specification/blob/1723a45abfb4f12ac5d3635a3f66dc45e2f93c83/specification/0.0/schema/vrm.secondaryanimation.spring.schema.json#L9-L12
stiffiness: FloatProperty( # type: ignore[valid-type]
name="Stiffness",
min=0.0,
soft_max=4.0,
subtype="FACTOR",
description="Stiffness of springs",
)
gravity_power: FloatProperty( # type: ignore[valid-type]
name="Gravity Power",
min=0.0,
soft_max=2.0,
subtype="FACTOR",
description="Gravity power of springs",
)
def update_gravity_dir(self, _context: Context) -> None:
gravity_dir = Vector(self.gravity_dir)
normalized_gravity_dir = gravity_dir.normalized()
if (gravity_dir - normalized_gravity_dir).length > 0.0001:
self.gravity_dir = normalized_gravity_dir
gravity_dir: FloatVectorProperty( # type: ignore[valid-type]
size=3,
min=-1,
max=1,
subtype="XYZ",
name="Gravity Direction",
description="Gravity direction of springs",
update=update_gravity_dir,
)
drag_force: FloatProperty( # type: ignore[valid-type]
name="Drag Force",
min=0.0,
max=1.0,
subtype="FACTOR",
description="Drag Force of springs",
)
center: PointerProperty( # type: ignore[valid-type]
name="Center",
type=BonePropertyGroup,
description="Origin of Physics simulation to stop springs on moving",
)
hit_radius: FloatProperty( # type: ignore[valid-type]
name="Hit Radius",
min=0.0,
soft_max=0.5,
subtype="DISTANCE",
description="Hit Radius of springs",
)
bones: CollectionProperty( # type: ignore[valid-type]
name="Bones",
type=BonePropertyGroup,
description="Bones of the spring roots",
)
collider_groups: CollectionProperty( # type: ignore[valid-type]
name="Collider Group",
type=StringPropertyGroup,
description="Enabled collider Groups of springs",
)
# for UI
show_expanded: BoolProperty() # type: ignore[valid-type]
show_expanded_bones: BoolProperty( # type: ignore[valid-type]
name="Bones"
)
show_expanded_collider_groups: BoolProperty( # type: ignore[valid-type]
name="Collider Groups"
)
active_bone_index: IntProperty(min=0) # type: ignore[valid-type]
active_collider_group_index: IntProperty(min=0) # type: ignore[valid-type]
def refresh(self, armature: Object) -> None:
from ..extension import get_armature_extension
armature_data = armature.data
if not isinstance(armature_data, Armature):
return
ext = get_armature_extension(armature_data)
collider_group_uuid_to_name = {
collider_group.uuid: collider_group.name
for collider_group in ext.vrm0.secondary_animation.collider_groups
}
for index, collider_group in reversed(list(enumerate(self.collider_groups))):
uuid_str = collider_group.value.split("#")[-1]
if not uuid_str:
self.collider_groups.remove(index)
continue
name = collider_group_uuid_to_name.get(uuid_str)
if not name:
self.collider_groups.remove(index)
continue
if collider_group.value != name:
collider_group.value = name
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
comment: str # type: ignore[no-redef]
stiffiness: float # type: ignore[no-redef]
gravity_power: float # type: ignore[no-redef]
gravity_dir: Sequence[float] # type: ignore[no-redef]
drag_force: float # type: ignore[no-redef]
center: BonePropertyGroup # type: ignore[no-redef]
hit_radius: float # type: ignore[no-redef]
bones: CollectionPropertyProtocol[ # type: ignore[no-redef]
BonePropertyGroup
]
collider_groups: CollectionPropertyProtocol[ # type: ignore[no-redef]
StringPropertyGroup
]
show_expanded: bool # type: ignore[no-redef]
show_expanded_bones: bool # type: ignore[no-redef]
show_expanded_collider_groups: bool # type: ignore[no-redef]
active_bone_index: int # type: ignore[no-redef]
active_collider_group_index: int # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_Meta.cs#L33-L149
class Vrm0MetaPropertyGroup(PropertyGroup):
allowed_user_name_enum, __allowed_user_names = property_group_enum(
("OnlyAuthor", "Only Author", "", "NONE", 0),
("ExplicitlyLicensedPerson", "Explicitly Licensed Person", "", "NONE", 1),
("Everyone", "Everyone", "", "NONE", 2),
)
violent_ussage_name_enum, __violent_ussage_names = property_group_enum(
("Disallow", "Disallow", "", "NONE", 0),
("Allow", "Allow", "", "NONE", 1),
)
sexual_ussage_name_enum, __sexual_ussage_names = property_group_enum(
("Disallow", "Disallow", "", "NONE", 0),
("Allow", "Allow", "", "NONE", 1),
)
commercial_ussage_name_enum, __commercial_ussage_names = property_group_enum(
("Disallow", "Disallow", "", "NONE", 0),
("Allow", "Allow", "", "NONE", 1),
)
(
license_name_enum,
(
LICENSE_NAME_REDISTRIBUTION_PROHIBITED,
LICENSE_NAME_CC0,
LICENSE_NAME_CC_BY,
LICENSE_NAME_CC_BY_NC,
LICENSE_NAME_CC_BY_SA,
LICENSE_NAME_CC_BY_NC_SA,
LICENSE_NAME_CC_BY_ND,
LICENSE_NAME_CC_BY_NC_ND,
LICENSE_NAME_OTHER,
),
) = property_group_enum(
("Redistribution_Prohibited", "Redistribution Prohibited", "", "NONE", 0),
("CC0", "CC0", "", "NONE", 1),
("CC_BY", "CC BY", "", "NONE", 2),
("CC_BY_NC", "CC BY NC", "", "NONE", 3),
("CC_BY_SA", "CC BY SA", "", "NONE", 4),
("CC_BY_NC_SA", "CC BY NC SA", "", "NONE", 5),
("CC_BY_ND", "CC BY ND", "", "NONE", 6), # codespell:ignore nd
("CC_BY_NC_ND", "CC BY NC ND", "", "NONE", 7), # codespell:ignore nd
("Other", "Other", "", "NONE", 8),
)
title: StringProperty( # type: ignore[valid-type]
name="Title",
description="Title of the avatar",
)
version: StringProperty( # type: ignore[valid-type]
name="Version",
description="Version of the avatar",
)
author: StringProperty( # type: ignore[valid-type]
name="Author",
description="Author of the avatar",
)
contact_information: StringProperty( # type: ignore[valid-type]
name="Contact Information",
description="Contact Information about the avatar",
)
reference: StringProperty( # type: ignore[valid-type]
name="Reference",
description="Referenced works about the avatar",
)
allowed_user_name: EnumProperty( # type: ignore[valid-type]
items=allowed_user_name_enum.items(),
name="Allowed User",
description="Allowed user of the avatar",
)
violent_ussage_name: EnumProperty( # type: ignore[valid-type]
items=violent_ussage_name_enum.items(),
name="Violent Usage",
description="Violent usage of the avatar",
)
sexual_ussage_name: EnumProperty( # type: ignore[valid-type]
items=sexual_ussage_name_enum.items(),
name="Sexual Usage",
description="Sexual Usage of the avatar",
)
commercial_ussage_name: EnumProperty( # type: ignore[valid-type]
items=commercial_ussage_name_enum.items(),
name="Commercial Usage",
description="Commercial Usage of the avatar",
)
other_permission_url: StringProperty( # type: ignore[valid-type]
name="Other Permission URL",
description="URL about other permissions of the avatar",
)
license_name: EnumProperty( # type: ignore[valid-type]
items=license_name_enum.items(),
name="License",
description="License of the avatar",
)
other_license_url: StringProperty( # type: ignore[valid-type]
name="Other License URL",
description="URL about other License of the avatar",
)
texture: PointerProperty( # type: ignore[valid-type]
name="Thumbnail",
type=Image,
description="Thumbnail of the avatar",
)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
title: str # type: ignore[no-redef]
version: str # type: ignore[no-redef]
author: str # type: ignore[no-redef]
contact_information: str # type: ignore[no-redef]
reference: str # type: ignore[no-redef]
allowed_user_name: str # type: ignore[no-redef]
violent_ussage_name: str # type: ignore[no-redef]
sexual_ussage_name: str # type: ignore[no-redef]
commercial_ussage_name: str # type: ignore[no-redef]
other_permission_url: str # type: ignore[no-redef]
license_name: str # type: ignore[no-redef]
other_license_url: str # type: ignore[no-redef]
texture: Optional[Image] # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_BlendShape.cs#L101-L106
class Vrm0BlendShapeMasterPropertyGroup(PropertyGroup):
blend_shape_groups: CollectionProperty( # type: ignore[valid-type]
name="Blend Shape Group",
type=Vrm0BlendShapeGroupPropertyGroup,
)
def restore_blend_shape_group_bind_object_assignments(
self, context: Context
) -> None:
for blend_shape_group in self.blend_shape_groups:
for bind in blend_shape_group.binds:
bind.mesh.restore_object_assignment(context)
# for UI
active_blend_shape_group_index: IntProperty(min=0) # type: ignore[valid-type]
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
blend_shape_groups: CollectionPropertyProtocol[ # type: ignore[no-redef]
Vrm0BlendShapeGroupPropertyGroup
]
active_blend_shape_group_index: int # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_SecondaryAnimation.cs#L69-L78
class Vrm0SecondaryAnimationPropertyGroup(PropertyGroup):
bone_groups: CollectionProperty( # type: ignore[valid-type]
name="Secondary Animation Groups",
type=Vrm0SecondaryAnimationGroupPropertyGroup,
)
collider_groups: CollectionProperty( # type: ignore[valid-type]
name="Collider Groups",
type=Vrm0SecondaryAnimationColliderGroupPropertyGroup,
)
# for UI
show_expanded_bone_groups: BoolProperty( # type: ignore[valid-type]
name="Spring Bone Groups",
)
show_expanded_collider_groups: BoolProperty( # type: ignore[valid-type]
name="Collider Groups",
)
active_bone_group_index: IntProperty(min=0) # type: ignore[valid-type]
active_collider_group_index: IntProperty(min=0) # type: ignore[valid-type]
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
bone_groups: CollectionPropertyProtocol[ # type: ignore[no-redef]
Vrm0SecondaryAnimationGroupPropertyGroup
]
collider_groups: CollectionPropertyProtocol[ # type: ignore[no-redef]
Vrm0SecondaryAnimationColliderGroupPropertyGroup
]
show_expanded_bone_groups: bool # type: ignore[no-redef]
show_expanded_collider_groups: bool # type: ignore[no-redef]
active_bone_group_index: int # type: ignore[no-redef]
active_collider_group_index: int # type: ignore[no-redef]
# https://github.com/vrm-c/UniVRM/blob/v0.91.1/Assets/VRM/Runtime/Format/glTF_VRM_extensions.cs#L8-L48
class Vrm0PropertyGroup(PropertyGroup):
meta: PointerProperty( # type: ignore[valid-type]
name="VRM Meta",
type=Vrm0MetaPropertyGroup,
)
humanoid: PointerProperty( # type: ignore[valid-type]
name="VRM Humanoid",
type=Vrm0HumanoidPropertyGroup,
)
first_person: PointerProperty( # type: ignore[valid-type]
name="VRM First Person",
type=Vrm0FirstPersonPropertyGroup,
)
blend_shape_master: PointerProperty( # type: ignore[valid-type]
name="VRM Blend Shape Master",
type=Vrm0BlendShapeMasterPropertyGroup,
)
secondary_animation: PointerProperty( # type: ignore[valid-type]
name="VRM Secondary Animation",
type=Vrm0SecondaryAnimationPropertyGroup,
)
if TYPE_CHECKING:
# This code is auto generated.
# To regenerate, run the `uv run tools/property_typing.py` command.
meta: Vrm0MetaPropertyGroup # type: ignore[no-redef]
humanoid: Vrm0HumanoidPropertyGroup # type: ignore[no-redef]
first_person: Vrm0FirstPersonPropertyGroup # type: ignore[no-redef]
blend_shape_master: Vrm0BlendShapeMasterPropertyGroup # type: ignore[no-redef]
secondary_animation: ( # type: ignore[no-redef]
Vrm0SecondaryAnimationPropertyGroup
)