Spaces:
Running on Zero
Running on Zero
| """ | |
| io/mapping.py | |
| Load / save bone mapping JSON in the exact same format as KeeMap. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from dataclasses import dataclass, field | |
| from typing import List, Optional | |
| import numpy as np | |
| from ..math3d import quat_identity, vec3 | |
| class BoneMappingItem: | |
| name: str = "" | |
| label: str = "" | |
| description: str = "" | |
| source_bone_name: str = "" | |
| destination_bone_name: str = "" | |
| keyframe_this_bone: bool = True | |
| # Rotation correction (Euler, radians) | |
| correction_factor: np.ndarray = field(default_factory=lambda: vec3()) | |
| # Quaternion correction | |
| quat_correction_factor: np.ndarray = field(default_factory=quat_identity) | |
| has_twist_bone: bool = False | |
| twist_bone_name: str = "" | |
| set_bone_position: bool = False | |
| set_bone_rotation: bool = True | |
| set_bone_scale: bool = False | |
| # Rotation options | |
| bone_rotation_application_axis: str = "XYZ" # X Y Z XY XZ YZ XYZ | |
| bone_transpose_axis: str = "NONE" # NONE ZXY ZYX XZY YZX YXZ | |
| # Position options | |
| postion_type: str = "SINGLE_BONE_OFFSET" # SINGLE_BONE_OFFSET | POLE | |
| position_correction_factor: np.ndarray = field(default_factory=lambda: vec3()) | |
| position_gain: float = 1.0 | |
| position_pole_distance: float = 0.3 | |
| # Scale options | |
| scale_secondary_bone_name: str = "" | |
| bone_scale_application_axis: str = "Y" | |
| scale_gain: float = 1.0 | |
| scale_max: float = 1.0 | |
| scale_min: float = 0.5 | |
| class KeeMapSettings: | |
| source_rig_name: str = "" | |
| destination_rig_name: str = "" | |
| bone_mapping_file: str = "" | |
| bone_rotation_mode: str = "EULER" # EULER | QUATERNION | |
| start_frame_to_apply: int = 0 | |
| number_of_frames_to_apply: int = 100 | |
| keyframe_every_n_frames: int = 1 | |
| keyframe_test: bool = False | |
| # --------------------------------------------------------------------------- | |
| # Load | |
| # --------------------------------------------------------------------------- | |
| def load_mapping(filepath: str): | |
| """ | |
| Returns (KeeMapSettings, List[BoneMappingItem]). | |
| Reads the exact same JSON that KeeMap writes. | |
| """ | |
| with open(filepath, "r") as f: | |
| data = json.load(f) | |
| settings = KeeMapSettings( | |
| source_rig_name=data.get("source_rig_name", ""), | |
| destination_rig_name=data.get("destination_rig_name", ""), | |
| bone_mapping_file=data.get("bone_mapping_file", ""), | |
| bone_rotation_mode=data.get("bone_rotation_mode", "EULER"), | |
| start_frame_to_apply=data.get("start_frame_to_apply", 0), | |
| number_of_frames_to_apply=data.get("number_of_frames_to_apply", 100), | |
| keyframe_every_n_frames=data.get("keyframe_every_n_frames", 1), | |
| ) | |
| bones: List[BoneMappingItem] = [] | |
| for p in data.get("bones", []): | |
| item = BoneMappingItem() | |
| item.name = p.get("name", "") | |
| item.label = p.get("label", "") | |
| item.description = p.get("description", "") | |
| item.source_bone_name = p.get("SourceBoneName", "") | |
| item.destination_bone_name = p.get("DestinationBoneName", "") | |
| item.keyframe_this_bone = p.get("keyframe_this_bone", True) | |
| item.correction_factor = np.array([ | |
| p.get("CorrectionFactorX", 0.0), | |
| p.get("CorrectionFactorY", 0.0), | |
| p.get("CorrectionFactorZ", 0.0), | |
| ]) | |
| item.quat_correction_factor = np.array([ | |
| p.get("QuatCorrectionFactorw", 1.0), | |
| p.get("QuatCorrectionFactorx", 0.0), | |
| p.get("QuatCorrectionFactory", 0.0), | |
| p.get("QuatCorrectionFactorz", 0.0), | |
| ]) | |
| item.has_twist_bone = p.get("has_twist_bone", False) | |
| item.twist_bone_name = p.get("TwistBoneName", "") | |
| item.set_bone_position = p.get("set_bone_position", False) | |
| item.set_bone_rotation = p.get("set_bone_rotation", True) | |
| item.set_bone_scale = p.get("set_bone_scale", False) | |
| item.bone_rotation_application_axis = p.get("bone_rotation_application_axis", "XYZ") | |
| item.bone_transpose_axis = p.get("bone_transpose_axis", "NONE") | |
| item.postion_type = p.get("postion_type", "SINGLE_BONE_OFFSET") | |
| item.position_correction_factor = np.array([ | |
| p.get("position_correction_factorX", 0.0), | |
| p.get("position_correction_factorY", 0.0), | |
| p.get("position_correction_factorZ", 0.0), | |
| ]) | |
| item.position_gain = p.get("position_gain", 1.0) | |
| item.position_pole_distance = p.get("position_pole_distance", 0.3) | |
| item.scale_secondary_bone_name = p.get("scale_secondary_bone_name", "") | |
| item.bone_scale_application_axis = p.get("bone_scale_application_axis", "Y") | |
| item.scale_gain = p.get("scale_gain", 1.0) | |
| item.scale_max = p.get("scale_max", 1.0) | |
| item.scale_min = p.get("scale_min", 0.5) | |
| bones.append(item) | |
| return settings, bones | |
| # --------------------------------------------------------------------------- | |
| # Save | |
| # --------------------------------------------------------------------------- | |
| def save_mapping(filepath: str, settings: KeeMapSettings, bones: List[BoneMappingItem]) -> None: | |
| """Write mapping JSON readable by KeeMap.""" | |
| root = { | |
| "source_rig_name": settings.source_rig_name, | |
| "destination_rig_name": settings.destination_rig_name, | |
| "bone_mapping_file": settings.bone_mapping_file, | |
| "bone_rotation_mode": settings.bone_rotation_mode, | |
| "start_frame_to_apply": settings.start_frame_to_apply, | |
| "number_of_frames_to_apply": settings.number_of_frames_to_apply, | |
| "keyframe_every_n_frames": settings.keyframe_every_n_frames, | |
| "bones": [], | |
| } | |
| for b in bones: | |
| root["bones"].append({ | |
| "name": b.name, | |
| "label": b.label, | |
| "description": b.description, | |
| "SourceBoneName": b.source_bone_name, | |
| "DestinationBoneName": b.destination_bone_name, | |
| "keyframe_this_bone": b.keyframe_this_bone, | |
| "CorrectionFactorX": float(b.correction_factor[0]), | |
| "CorrectionFactorY": float(b.correction_factor[1]), | |
| "CorrectionFactorZ": float(b.correction_factor[2]), | |
| "QuatCorrectionFactorw": float(b.quat_correction_factor[0]), | |
| "QuatCorrectionFactorx": float(b.quat_correction_factor[1]), | |
| "QuatCorrectionFactory": float(b.quat_correction_factor[2]), | |
| "QuatCorrectionFactorz": float(b.quat_correction_factor[3]), | |
| "has_twist_bone": b.has_twist_bone, | |
| "TwistBoneName": b.twist_bone_name, | |
| "set_bone_position": b.set_bone_position, | |
| "set_bone_rotation": b.set_bone_rotation, | |
| "set_bone_scale": b.set_bone_scale, | |
| "bone_rotation_application_axis": b.bone_rotation_application_axis, | |
| "bone_transpose_axis": b.bone_transpose_axis, | |
| "postion_type": b.postion_type, | |
| "position_correction_factorX": float(b.position_correction_factor[0]), | |
| "position_correction_factorY": float(b.position_correction_factor[1]), | |
| "position_correction_factorZ": float(b.position_correction_factor[2]), | |
| "position_gain": b.position_gain, | |
| "position_pole_distance": b.position_pole_distance, | |
| "scale_secondary_bone_name": b.scale_secondary_bone_name, | |
| "bone_scale_application_axis": b.bone_scale_application_axis, | |
| "scale_gain": b.scale_gain, | |
| "scale_max": b.scale_max, | |
| "scale_min": b.scale_min, | |
| }) | |
| with open(filepath, "w") as f: | |
| json.dump(root, f, indent=2) | |