- 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
162 lines
4.9 KiB
Python
162 lines
4.9 KiB
Python
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
|
import platform
|
|
from dataclasses import dataclass
|
|
from sys import float_info
|
|
from typing import Optional
|
|
|
|
import bpy
|
|
from bpy.app.translations import pgettext
|
|
|
|
from .blender_manifest import BlenderManifest
|
|
from .logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class Cache:
|
|
use: bool
|
|
last_blender_restart_required: bool
|
|
last_blender_manifest_modification_time: float
|
|
initial_blender_manifest_content: bytes
|
|
|
|
|
|
cache = Cache(
|
|
use=False,
|
|
last_blender_restart_required=False,
|
|
last_blender_manifest_modification_time=0.0,
|
|
initial_blender_manifest_content=BlenderManifest.default_blender_manifest_path().read_bytes(),
|
|
)
|
|
|
|
|
|
def clear_addon_version_cache() -> Optional[float]:
|
|
cache.use = False
|
|
return None
|
|
|
|
|
|
def trigger_clear_addon_version_cache() -> None:
|
|
if tuple(bpy.app.version) >= (4, 2):
|
|
return
|
|
if bpy.app.timers.is_registered(clear_addon_version_cache):
|
|
return
|
|
bpy.app.timers.register(clear_addon_version_cache, first_interval=0.5)
|
|
|
|
|
|
def min_unsupported_blender_major_minor_version() -> Optional[tuple[int, int]]:
|
|
blender_version_max = BlenderManifest.read().blender_version_max
|
|
if blender_version_max is None:
|
|
return None
|
|
return (blender_version_max[0], blender_version_max[1])
|
|
|
|
|
|
def get_addon_version() -> tuple[int, int, int]:
|
|
return BlenderManifest.read().version
|
|
|
|
|
|
def blender_restart_required() -> bool:
|
|
if tuple(bpy.app.version) >= (4, 2):
|
|
return False
|
|
|
|
if cache.use:
|
|
return cache.last_blender_restart_required
|
|
|
|
cache.use = True
|
|
|
|
if cache.last_blender_restart_required:
|
|
return True
|
|
|
|
blender_manifest_path = BlenderManifest.default_blender_manifest_path()
|
|
blender_manifest_modification_time = blender_manifest_path.stat().st_mtime
|
|
if (
|
|
abs(
|
|
cache.last_blender_manifest_modification_time
|
|
- blender_manifest_modification_time
|
|
)
|
|
< float_info.epsilon
|
|
):
|
|
return False
|
|
|
|
cache.last_blender_manifest_modification_time = blender_manifest_modification_time
|
|
|
|
blender_manifest_content = blender_manifest_path.read_bytes()
|
|
if blender_manifest_content == cache.initial_blender_manifest_content:
|
|
return False
|
|
|
|
cache.last_blender_restart_required = True
|
|
return True
|
|
|
|
|
|
def stable_release() -> bool:
|
|
if bpy.app.version_cycle == "release":
|
|
return True
|
|
|
|
# Microsoft Store distributes RC versions of 3.3.11 and 3.6.3 as
|
|
# release versions.
|
|
return platform.system() == "Windows" and bpy.app.version_cycle == "rc"
|
|
|
|
|
|
def supported() -> bool:
|
|
v = min_unsupported_blender_major_minor_version()
|
|
if v is None:
|
|
return True
|
|
return bpy.app.version[:2] < v
|
|
|
|
|
|
def preferences_warning_message() -> Optional[str]:
|
|
if blender_restart_required():
|
|
return pgettext(
|
|
"The VRM add-on has been updated."
|
|
+ " Please restart Blender to apply the changes."
|
|
)
|
|
if not stable_release():
|
|
return pgettext(
|
|
"VRM add-on is not compatible with Blender {blender_version_cycle}."
|
|
).format(blender_version_cycle=bpy.app.version_cycle.capitalize())
|
|
if not supported():
|
|
return pgettext(
|
|
"The installed VRM add-on is not compatible with Blender {blender_version}."
|
|
+ " Please upgrade the add-on.",
|
|
).format(blender_version=".".join(map(str, bpy.app.version[:2])))
|
|
return None
|
|
|
|
|
|
def panel_warning_message() -> Optional[str]:
|
|
if blender_restart_required():
|
|
return pgettext(
|
|
"The VRM add-on has been\n"
|
|
+ "updated. Please restart Blender\n"
|
|
+ "to apply the changes."
|
|
)
|
|
if not stable_release():
|
|
return pgettext(
|
|
"VRM add-on is\n"
|
|
+ "not compatible with\n"
|
|
+ "Blender {blender_version_cycle}."
|
|
).format(blender_version_cycle=bpy.app.version_cycle.capitalize())
|
|
if not supported():
|
|
return pgettext(
|
|
"The installed VRM add-\n"
|
|
+ "on is not compatible with\n"
|
|
+ "Blender {blender_version}. Please update."
|
|
).format(blender_version=".".join(map(str, bpy.app.version[:2])))
|
|
return None
|
|
|
|
|
|
def validation_warning_message() -> Optional[str]:
|
|
if blender_restart_required():
|
|
return pgettext(
|
|
"The VRM add-on has been updated."
|
|
+ " Please restart Blender to apply the changes."
|
|
)
|
|
if not stable_release():
|
|
return pgettext(
|
|
"VRM add-on is not compatible with Blender {blender_version_cycle}."
|
|
+ " The VRM may not be exported correctly.",
|
|
).format(blender_version_cycle=bpy.app.version_cycle.capitalize())
|
|
if not supported():
|
|
return pgettext(
|
|
"The installed VRM add-on is not compatible with Blender {blender_version}."
|
|
+ " The VRM may not be exported correctly."
|
|
).format(blender_version=".".join(map(str, bpy.app.version)))
|
|
return None
|