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:
146
common/deep.py
Normal file
146
common/deep.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user