- 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
147 lines
4.9 KiB
Python
147 lines
4.9 KiB
Python
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
|
import difflib
|
|
import math
|
|
from collections.abc import Mapping
|
|
from json import dumps as json_dumps
|
|
|
|
from . import convert
|
|
from .convert import Json
|
|
from .logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def make_json(v: object) -> Json:
|
|
if v is None:
|
|
return None
|
|
if isinstance(v, int):
|
|
return v
|
|
if isinstance(v, float):
|
|
if not math.isfinite(v):
|
|
logger.warning("%s %s is non-finite value", v, type(v))
|
|
return 0.0
|
|
return v
|
|
if isinstance(v, bool):
|
|
return v
|
|
if isinstance(v, str):
|
|
return v
|
|
|
|
mapping = convert.mapping_or_none(v)
|
|
if mapping is not None:
|
|
dict_result: dict[str, Json] = {}
|
|
for key, value in mapping.items():
|
|
if isinstance(key, str):
|
|
dict_result[key] = make_json(value)
|
|
continue
|
|
logger.warning("%s %s is unrecognized type for dict key", key, type(key))
|
|
return dict_result
|
|
|
|
iterator = convert.iterator_or_none(v)
|
|
if iterator is not None:
|
|
return [make_json(x) for x in iterator]
|
|
|
|
logger.warning("%s %s is unrecognized type", v, type(v))
|
|
return None
|
|
|
|
|
|
def make_json_dict(v: Mapping[str, object]) -> dict[str, Json]:
|
|
return {key: make_json(value) for key, value in v.items()}
|
|
|
|
|
|
def diff(
|
|
left: Json,
|
|
right: Json,
|
|
float_tolerance: float = 0,
|
|
path: str = "",
|
|
) -> list[str]:
|
|
if isinstance(left, list):
|
|
if not isinstance(right, list):
|
|
return [f"{path}: left is list but right is {type(right)}"]
|
|
if len(left) != len(right):
|
|
result = [
|
|
f"{path}: left length is {len(left)} but right length is {len(right)}"
|
|
]
|
|
|
|
right_json_str = json_dumps(right, indent=4, sort_keys=True)
|
|
right_json_lines = right_json_str.splitlines(keepends=True)
|
|
left_json_str = json_dumps(left, indent=4, sort_keys=True)
|
|
left_json_lines = left_json_str.splitlines(keepends=True)
|
|
|
|
if len(right_json_lines) > 1000 or len(left_json_lines) > 1000:
|
|
return result
|
|
|
|
unified_diff = [
|
|
line.rstrip()
|
|
for line in difflib.unified_diff(
|
|
right_json_lines,
|
|
left_json_lines,
|
|
f"{path}/right",
|
|
f"{path}/left",
|
|
)
|
|
]
|
|
if len(unified_diff) > 1000:
|
|
return result
|
|
return result + unified_diff
|
|
diffs: list[str] = []
|
|
for i, (left_child, right_child) in enumerate(zip(left, right)):
|
|
diffs.extend(diff(left_child, right_child, float_tolerance, f"{path}[{i}]"))
|
|
return diffs
|
|
|
|
if isinstance(left, dict):
|
|
if not isinstance(right, dict):
|
|
return [f"{path}: left is dict but right is {type(right)}"]
|
|
diffs = []
|
|
for key in sorted(set(list(left.keys()) + list(right.keys()))):
|
|
if key not in left:
|
|
diffs.append(f'{path}: {key} not in left. right["{key}"]={right[key]}')
|
|
continue
|
|
if key not in right:
|
|
diffs.append(f'{path}: {key} not in right, left["{key}"]={left[key]}')
|
|
continue
|
|
diffs.extend(
|
|
diff(left[key], right[key], float_tolerance, f'{path}["{key}"]')
|
|
)
|
|
return diffs
|
|
|
|
if isinstance(left, bool):
|
|
if not isinstance(right, bool):
|
|
return [f"{path}: left is bool but right is {type(right)}"]
|
|
if left != right:
|
|
return [f"{path}: left is {left} but right is {right}"]
|
|
return []
|
|
|
|
if isinstance(left, str):
|
|
if not isinstance(right, str):
|
|
return [f"{path}: left is str but right is {type(right)}"]
|
|
if left != right:
|
|
return [f'{path}: left is "{left}" but right is "{right}"']
|
|
return []
|
|
|
|
if left is None and right is not None:
|
|
return [f"{path}: left is None but right is {type(right)}"]
|
|
|
|
if isinstance(left, int) and isinstance(right, int):
|
|
if left != right:
|
|
return [f"{path}: left is {left} but right is {right}"]
|
|
return []
|
|
|
|
if isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
|
if (isinstance(left, float) and not math.isfinite(left)) or (
|
|
isinstance(right, float) and not math.isfinite(right)
|
|
):
|
|
return [
|
|
f"{path}: left is {left}"
|
|
+ f" but right is {right}, They are not comparable numbers"
|
|
]
|
|
|
|
error = math.fabs(float(left) - float(right))
|
|
if error > float_tolerance:
|
|
return [
|
|
f"{path}: left is {float(left):20.17f}"
|
|
+ f" but right is {float(right):20.17f}, error={error:19.17f}"
|
|
]
|
|
return []
|
|
|
|
message = f"{path}: unexpected type left={type(left)} right={type(right)}"
|
|
raise ValueError(message)
|