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:
2026-01-01 14:21:56 +08:00
commit 091ad6a49a
243 changed files with 60636 additions and 0 deletions

87
common/progress.py Normal file
View File

@@ -0,0 +1,87 @@
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
import math
from collections.abc import Iterator
from contextlib import contextmanager
from typing import Final, Optional
from uuid import uuid4
import bpy
from bpy.types import Context
from .logger import get_logger
logger = get_logger(__name__)
class PartialProgress:
def __init__(
self, progress: "Progress", partial_start_ratio: float, partial_end_ratio: float
) -> None:
self.progress: Final = progress
self.partial_start_ratio: Final = partial_start_ratio
self.partial_end_ratio: Final = partial_end_ratio
def update(self, ratio: float) -> None:
ratio = min(max(0.0, ratio), 1.0)
self.progress.update(
self.partial_start_ratio
+ ratio * (self.partial_end_ratio - self.partial_start_ratio)
)
class Progress:
active_progress_uuid: Optional[str] = None
def __init__(self, context: Context, *, show_progress: bool) -> None:
self.context: Final = context
self.show_progress: Final = show_progress
self.uuid: Final = uuid4().hex
self.last_ratio = 0.0
def partial_progress(self, partial_end_ratio: float) -> PartialProgress:
partial_end_ratio = min(max(0.0, partial_end_ratio), 1.0)
return PartialProgress(self, self.last_ratio, partial_end_ratio)
def update(self, ratio: float) -> None:
ratio = min(max(0.0, ratio), 1.0)
self.last_ratio = ratio
if self.active_progress_uuid != self.uuid:
logger.error(
"Progress.update() called from different progress active=%s self=%s",
self.active_progress_uuid,
self.uuid,
)
return
if not self.show_progress:
return
if bpy.app.version >= (5, 0):
progress_value = ratio * 9999
else:
# The mouse cursor becomes a four-digit number, and there is an area
# where values from 0 to 9999 can be displayed. However, if the
# progress rate is directly converted to a number from 0 to 9999, the
# last two digits will frequently round-trip, making the progress
# difficult to understand. Therefore, only the last two digits display
# area is used to show the progress rate as a number from 0 to 99.
progress_value = math.floor(ratio * 99)
self.context.window_manager.progress_update(progress_value)
@contextmanager
def create_progress(
context: Context, *, show_progress: bool = True
) -> Iterator[Progress]:
saved_progress_uuid = Progress.active_progress_uuid
try:
if show_progress and Progress.active_progress_uuid is None:
context.window_manager.progress_begin(0, 9999)
progress = Progress(context, show_progress=show_progress)
Progress.active_progress_uuid = progress.uuid
yield progress
finally:
Progress.active_progress_uuid = saved_progress_uuid
if show_progress and Progress.active_progress_uuid is None:
context.window_manager.progress_end()