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:
147
editor/subscription.py
Normal file
147
editor/subscription.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
from dataclasses import dataclass
|
||||
|
||||
import bpy
|
||||
from bpy.types import Armature, Bone, Object
|
||||
|
||||
from ..common.logger import get_logger
|
||||
from .extension import VrmAddonArmatureExtensionPropertyGroup, get_armature_extension
|
||||
from .migration import migrate_all_objects
|
||||
from .mtoon1 import ops as mtoon1_ops
|
||||
from .vrm0.property_group import Vrm0HumanoidPropertyGroup
|
||||
from .vrm1 import property_group as vrm1_property_group
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Subscription:
|
||||
object_name_subscription_owner = object()
|
||||
object_mode_subscription_owner = object()
|
||||
object_location_subscription_owner = object()
|
||||
bone_name_subscription_owner = object()
|
||||
armature_name_subscription_owner = object()
|
||||
setup_once: bool = False
|
||||
|
||||
|
||||
subscription = Subscription()
|
||||
|
||||
|
||||
def setup_subscription(*, load_post: bool) -> None:
|
||||
if subscription.setup_once:
|
||||
if load_post:
|
||||
# If called by load_post, unsubscribe and setup again.
|
||||
teardown_subscription()
|
||||
else:
|
||||
# If a subscription is already setup, do nothing.
|
||||
return
|
||||
|
||||
subscription.setup_once = True
|
||||
|
||||
object_name_subscribe_to = (Object, "name")
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=object_name_subscribe_to,
|
||||
owner=subscription.object_name_subscription_owner,
|
||||
args=(),
|
||||
notify=on_change_bpy_object_name,
|
||||
)
|
||||
|
||||
object_mode_subscribe_to = (Object, "mode")
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=object_mode_subscribe_to,
|
||||
owner=subscription.object_mode_subscription_owner,
|
||||
args=(),
|
||||
notify=on_change_bpy_object_mode,
|
||||
)
|
||||
|
||||
object_location_subscribe_to = (Object, "location")
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=object_location_subscribe_to,
|
||||
owner=subscription.object_location_subscription_owner,
|
||||
args=(),
|
||||
notify=on_change_bpy_object_location,
|
||||
)
|
||||
|
||||
bone_name_subscribe_to = (Bone, "name")
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=bone_name_subscribe_to,
|
||||
owner=subscription.bone_name_subscription_owner,
|
||||
args=(),
|
||||
notify=on_change_bpy_bone_name,
|
||||
)
|
||||
|
||||
armature_name_subscribe_to = (Armature, "name")
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=armature_name_subscribe_to,
|
||||
owner=subscription.armature_name_subscription_owner,
|
||||
args=(),
|
||||
notify=on_change_bpy_armature_name,
|
||||
)
|
||||
|
||||
bpy.msgbus.publish_rna(key=object_name_subscribe_to)
|
||||
bpy.msgbus.publish_rna(key=object_mode_subscribe_to)
|
||||
bpy.msgbus.publish_rna(key=object_location_subscribe_to)
|
||||
bpy.msgbus.publish_rna(key=bone_name_subscribe_to)
|
||||
bpy.msgbus.publish_rna(key=armature_name_subscribe_to)
|
||||
|
||||
|
||||
def teardown_subscription() -> None:
|
||||
subscription.setup_once = False
|
||||
bpy.msgbus.clear_by_owner(subscription.armature_name_subscription_owner)
|
||||
bpy.msgbus.clear_by_owner(subscription.bone_name_subscription_owner)
|
||||
bpy.msgbus.clear_by_owner(subscription.object_location_subscription_owner)
|
||||
bpy.msgbus.clear_by_owner(subscription.object_mode_subscription_owner)
|
||||
bpy.msgbus.clear_by_owner(subscription.object_name_subscription_owner)
|
||||
|
||||
|
||||
def on_change_bpy_object_name() -> None:
|
||||
context = bpy.context
|
||||
|
||||
for armature in context.blend_data.armatures:
|
||||
ext = get_armature_extension(armature)
|
||||
if (
|
||||
tuple(ext.addon_version)
|
||||
== VrmAddonArmatureExtensionPropertyGroup.INITIAL_ADDON_VERSION
|
||||
):
|
||||
continue
|
||||
|
||||
# TODO: Needs optimization!
|
||||
for collider in ext.spring_bone1.colliders:
|
||||
collider.broadcast_bpy_object_name()
|
||||
|
||||
|
||||
def on_change_bpy_object_mode() -> None:
|
||||
context = bpy.context
|
||||
|
||||
active_object = context.active_object
|
||||
if not active_object:
|
||||
return
|
||||
mtoon1_ops.VRM_OT_refresh_mtoon1_outline.refresh_object(context, active_object)
|
||||
|
||||
|
||||
def on_change_bpy_object_location() -> None:
|
||||
context = bpy.context
|
||||
active_object = context.active_object
|
||||
if not active_object:
|
||||
return
|
||||
vrm1_property_group.Vrm1LookAtPropertyGroup.update_all_previews(context)
|
||||
|
||||
|
||||
def on_change_bpy_bone_name() -> None:
|
||||
context = bpy.context
|
||||
|
||||
for armature in context.blend_data.armatures:
|
||||
ext = get_armature_extension(armature)
|
||||
if (
|
||||
tuple(ext.addon_version)
|
||||
== VrmAddonArmatureExtensionPropertyGroup.INITIAL_ADDON_VERSION
|
||||
):
|
||||
continue
|
||||
|
||||
Vrm0HumanoidPropertyGroup.update_all_node_candidates(context, armature.name)
|
||||
|
||||
|
||||
def on_change_bpy_armature_name() -> None:
|
||||
context = bpy.context
|
||||
|
||||
migrate_all_objects(context, skip_non_migrated_armatures=True)
|
||||
Reference in New Issue
Block a user