# SPDX-License-Identifier: MIT OR GPL-3.0-or-later # SPDX-FileCopyrightText: 2018 iCyP import re from collections.abc import Mapping from dataclasses import dataclass from enum import Enum from typing import ClassVar, Optional # https://github.com/vrm-c/vrm-specification/tree/b5793b4ca250ed3acbde3dd7a47ee9ee1b3d60e9/specification/0.0#vrm-extension-models-bone-mapping-jsonextensionsvrmhumanoid class HumanBoneName(Enum): # Torso HIPS = "hips" SPINE = "spine" CHEST = "chest" UPPER_CHEST = "upperChest" NECK = "neck" # Head HEAD = "head" LEFT_EYE = "leftEye" RIGHT_EYE = "rightEye" JAW = "jaw" # Leg LEFT_UPPER_LEG = "leftUpperLeg" LEFT_LOWER_LEG = "leftLowerLeg" LEFT_FOOT = "leftFoot" LEFT_TOES = "leftToes" RIGHT_UPPER_LEG = "rightUpperLeg" RIGHT_LOWER_LEG = "rightLowerLeg" RIGHT_FOOT = "rightFoot" RIGHT_TOES = "rightToes" # Arm LEFT_SHOULDER = "leftShoulder" LEFT_UPPER_ARM = "leftUpperArm" LEFT_LOWER_ARM = "leftLowerArm" LEFT_HAND = "leftHand" RIGHT_SHOULDER = "rightShoulder" RIGHT_UPPER_ARM = "rightUpperArm" RIGHT_LOWER_ARM = "rightLowerArm" RIGHT_HAND = "rightHand" # Finger LEFT_THUMB_PROXIMAL = "leftThumbProximal" LEFT_THUMB_INTERMEDIATE = "leftThumbIntermediate" LEFT_THUMB_DISTAL = "leftThumbDistal" LEFT_INDEX_PROXIMAL = "leftIndexProximal" LEFT_INDEX_INTERMEDIATE = "leftIndexIntermediate" LEFT_INDEX_DISTAL = "leftIndexDistal" LEFT_MIDDLE_PROXIMAL = "leftMiddleProximal" LEFT_MIDDLE_INTERMEDIATE = "leftMiddleIntermediate" LEFT_MIDDLE_DISTAL = "leftMiddleDistal" LEFT_RING_PROXIMAL = "leftRingProximal" LEFT_RING_INTERMEDIATE = "leftRingIntermediate" LEFT_RING_DISTAL = "leftRingDistal" LEFT_LITTLE_PROXIMAL = "leftLittleProximal" LEFT_LITTLE_INTERMEDIATE = "leftLittleIntermediate" LEFT_LITTLE_DISTAL = "leftLittleDistal" RIGHT_THUMB_PROXIMAL = "rightThumbProximal" RIGHT_THUMB_INTERMEDIATE = "rightThumbIntermediate" RIGHT_THUMB_DISTAL = "rightThumbDistal" RIGHT_INDEX_PROXIMAL = "rightIndexProximal" RIGHT_INDEX_INTERMEDIATE = "rightIndexIntermediate" RIGHT_INDEX_DISTAL = "rightIndexDistal" RIGHT_MIDDLE_PROXIMAL = "rightMiddleProximal" RIGHT_MIDDLE_INTERMEDIATE = "rightMiddleIntermediate" RIGHT_MIDDLE_DISTAL = "rightMiddleDistal" RIGHT_RING_PROXIMAL = "rightRingProximal" RIGHT_RING_INTERMEDIATE = "rightRingIntermediate" RIGHT_RING_DISTAL = "rightRingDistal" RIGHT_LITTLE_PROXIMAL = "rightLittleProximal" RIGHT_LITTLE_INTERMEDIATE = "rightLittleIntermediate" RIGHT_LITTLE_DISTAL = "rightLittleDistal" @staticmethod def from_str(human_bone_name_str: str) -> Optional["HumanBoneName"]: try: return HumanBoneName(human_bone_name_str) except ValueError: return None # https://github.com/vrm-c/vrm.dev/blob/cd1d367417c53ba0f1d46588180c17b5e2768e22/docs/univrm/humanoid/humanoid_overview.md?plain=1#L99-L106 HumanBoneStructure = dict[HumanBoneName, "HumanBoneStructure"] HUMAN_BONE_STRUCTURE: HumanBoneStructure = { HumanBoneName.HIPS: { HumanBoneName.SPINE: { HumanBoneName.CHEST: { HumanBoneName.UPPER_CHEST: { HumanBoneName.NECK: { HumanBoneName.HEAD: { HumanBoneName.LEFT_EYE: {}, HumanBoneName.RIGHT_EYE: {}, HumanBoneName.JAW: {}, } }, HumanBoneName.LEFT_SHOULDER: { HumanBoneName.LEFT_UPPER_ARM: { HumanBoneName.LEFT_LOWER_ARM: { HumanBoneName.LEFT_HAND: { HumanBoneName.LEFT_THUMB_PROXIMAL: { HumanBoneName.LEFT_THUMB_INTERMEDIATE: { HumanBoneName.LEFT_THUMB_DISTAL: {} } }, HumanBoneName.LEFT_INDEX_PROXIMAL: { HumanBoneName.LEFT_INDEX_INTERMEDIATE: { HumanBoneName.LEFT_INDEX_DISTAL: {} } }, HumanBoneName.LEFT_MIDDLE_PROXIMAL: { HumanBoneName.LEFT_MIDDLE_INTERMEDIATE: { HumanBoneName.LEFT_MIDDLE_DISTAL: {} } }, HumanBoneName.LEFT_RING_PROXIMAL: { HumanBoneName.LEFT_RING_INTERMEDIATE: { HumanBoneName.LEFT_RING_DISTAL: {} } }, HumanBoneName.LEFT_LITTLE_PROXIMAL: { HumanBoneName.LEFT_LITTLE_INTERMEDIATE: { HumanBoneName.LEFT_LITTLE_DISTAL: {} } }, } } } }, HumanBoneName.RIGHT_SHOULDER: { HumanBoneName.RIGHT_UPPER_ARM: { HumanBoneName.RIGHT_LOWER_ARM: { HumanBoneName.RIGHT_HAND: { HumanBoneName.RIGHT_THUMB_PROXIMAL: { HumanBoneName.RIGHT_THUMB_INTERMEDIATE: { HumanBoneName.RIGHT_THUMB_DISTAL: {} } }, HumanBoneName.RIGHT_INDEX_PROXIMAL: { HumanBoneName.RIGHT_INDEX_INTERMEDIATE: { HumanBoneName.RIGHT_INDEX_DISTAL: {} } }, HumanBoneName.RIGHT_MIDDLE_PROXIMAL: { HumanBoneName.RIGHT_MIDDLE_INTERMEDIATE: { HumanBoneName.RIGHT_MIDDLE_DISTAL: {} } }, HumanBoneName.RIGHT_RING_PROXIMAL: { HumanBoneName.RIGHT_RING_INTERMEDIATE: { HumanBoneName.RIGHT_RING_DISTAL: {} } }, HumanBoneName.RIGHT_LITTLE_PROXIMAL: { HumanBoneName.RIGHT_LITTLE_INTERMEDIATE: { HumanBoneName.RIGHT_LITTLE_DISTAL: {} } }, } } } }, } } }, HumanBoneName.LEFT_UPPER_LEG: { HumanBoneName.LEFT_LOWER_LEG: { HumanBoneName.LEFT_FOOT: {HumanBoneName.LEFT_TOES: {}} } }, HumanBoneName.RIGHT_UPPER_LEG: { HumanBoneName.RIGHT_LOWER_LEG: { HumanBoneName.RIGHT_FOOT: {HumanBoneName.RIGHT_TOES: {}} } }, } } @dataclass(frozen=True) class HumanBoneSpecification: name: HumanBoneName icon: str title: str label: str label_no_left_right: str requirement: bool parent_name: Optional[HumanBoneName] children_names: list[HumanBoneName] def parent(self) -> Optional["HumanBoneSpecification"]: if self.parent_name is None: return None return HumanBoneSpecifications.get(self.parent_name) def children(self) -> list["HumanBoneSpecification"]: return list(map(HumanBoneSpecifications.get, self.children_names)) def descendants(self) -> list["HumanBoneSpecification"]: result: list[HumanBoneSpecification] = [] searching_children = self.children() while searching_children: child = searching_children.pop() result.append(child) searching_children.extend(child.children()) return result def connected(self) -> list["HumanBoneSpecification"]: children = self.children() parent = self.parent() if parent is None: return children return [*children, parent] @staticmethod def create( human_bone_name: HumanBoneName, *, requirement: bool, icon: str ) -> "HumanBoneSpecification": # https://stackoverflow.com/a/1176023 words = re.sub(r"(? Optional[HumanBoneName]: for ( next_human_bone_name, next_human_bone_structure, ) in human_bone_structure.items(): if child_human_bone_name == next_human_bone_name: return parent_human_bone_name name = HumanBoneSpecification.find_parent_human_bone_name( child_human_bone_name, next_human_bone_name, next_human_bone_structure ) if name: return name return None @staticmethod def find_children_human_bone_names( human_bone_name: HumanBoneName, human_bone_structure: HumanBoneStructure, ) -> list[HumanBoneName]: for ( next_human_bone_name, next_human_bone_structure, ) in human_bone_structure.items(): if human_bone_name == next_human_bone_name: return list(next_human_bone_structure.keys()) children = HumanBoneSpecification.find_children_human_bone_names( human_bone_name, next_human_bone_structure ) if children: return children return [] def is_ancestor_of( self, human_bone_specification: "HumanBoneSpecification" ) -> bool: parent = human_bone_specification.parent() while parent: if parent == self: return True parent = parent.parent() return False def create_and_append_human_bone_specification( human_bone_specifications: list[HumanBoneSpecification], human_bone_name: HumanBoneName, *, requirement: bool, icon: str, ) -> HumanBoneSpecification: human_bone_specification = HumanBoneSpecification.create( human_bone_name, requirement=requirement, icon=icon ) human_bone_specifications.append(human_bone_specification) return human_bone_specification class HumanBoneSpecifications: all_human_bones: ClassVar[list[HumanBoneSpecification]] = [] # https://github.com/vrm-c/vrm-specification/tree/b5793b4ca250ed3acbde3dd7a47ee9ee1b3d60e9/specification/0.0#vrm-extension-models-bone-mapping-jsonextensionsvrmhumanoid # Torso HIPS = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.HIPS, requirement=True, icon="USER", ) SPINE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.SPINE, requirement=True, icon="USER", ) CHEST = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.CHEST, requirement=True, icon="USER", ) UPPER_CHEST = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.UPPER_CHEST, requirement=False, icon="USER", ) NECK = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.NECK, requirement=True, icon="USER", ) # Head HEAD = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.HEAD, requirement=True, icon="USER", ) LEFT_EYE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_EYE, requirement=False, icon="HIDE_OFF", ) RIGHT_EYE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_EYE, requirement=False, icon="HIDE_OFF", ) JAW = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.JAW, requirement=False, icon="USER", ) # Leg LEFT_UPPER_LEG = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_UPPER_LEG, requirement=True, icon="MOD_DYNAMICPAINT", ) LEFT_LOWER_LEG = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_LOWER_LEG, requirement=True, icon="MOD_DYNAMICPAINT", ) LEFT_FOOT = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_FOOT, requirement=True, icon="MOD_DYNAMICPAINT", ) LEFT_TOES = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_TOES, requirement=False, icon="MOD_DYNAMICPAINT", ) RIGHT_UPPER_LEG = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_UPPER_LEG, requirement=True, icon="MOD_DYNAMICPAINT", ) RIGHT_LOWER_LEG = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_LOWER_LEG, requirement=True, icon="MOD_DYNAMICPAINT", ) RIGHT_FOOT = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_FOOT, requirement=True, icon="MOD_DYNAMICPAINT", ) RIGHT_TOES = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_TOES, requirement=False, icon="MOD_DYNAMICPAINT", ) # Arm LEFT_SHOULDER = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_SHOULDER, requirement=False, icon="VIEW_PAN", ) LEFT_UPPER_ARM = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_UPPER_ARM, requirement=True, icon="VIEW_PAN", ) LEFT_LOWER_ARM = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_LOWER_ARM, requirement=True, icon="VIEW_PAN", ) LEFT_HAND = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_HAND, requirement=True, icon="VIEW_PAN", ) RIGHT_SHOULDER = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_SHOULDER, requirement=False, icon="VIEW_PAN", ) RIGHT_UPPER_ARM = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_UPPER_ARM, requirement=True, icon="VIEW_PAN", ) RIGHT_LOWER_ARM = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_LOWER_ARM, requirement=True, icon="VIEW_PAN", ) RIGHT_HAND = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_HAND, requirement=True, icon="VIEW_PAN", ) # Finger LEFT_THUMB_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_THUMB_PROXIMAL, requirement=False, icon="VIEW_PAN", ) LEFT_THUMB_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_THUMB_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) LEFT_THUMB_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_THUMB_DISTAL, requirement=False, icon="VIEW_PAN", ) LEFT_INDEX_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_INDEX_PROXIMAL, requirement=False, icon="VIEW_PAN", ) LEFT_INDEX_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_INDEX_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) LEFT_INDEX_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_INDEX_DISTAL, requirement=False, icon="VIEW_PAN", ) LEFT_MIDDLE_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_MIDDLE_PROXIMAL, requirement=False, icon="VIEW_PAN", ) LEFT_MIDDLE_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_MIDDLE_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) LEFT_MIDDLE_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_MIDDLE_DISTAL, requirement=False, icon="VIEW_PAN", ) LEFT_RING_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_RING_PROXIMAL, requirement=False, icon="VIEW_PAN", ) LEFT_RING_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_RING_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) LEFT_RING_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_RING_DISTAL, requirement=False, icon="VIEW_PAN", ) LEFT_LITTLE_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_LITTLE_PROXIMAL, requirement=False, icon="VIEW_PAN", ) LEFT_LITTLE_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_LITTLE_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) LEFT_LITTLE_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.LEFT_LITTLE_DISTAL, requirement=False, icon="VIEW_PAN", ) RIGHT_THUMB_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_THUMB_PROXIMAL, requirement=False, icon="VIEW_PAN", ) RIGHT_THUMB_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_THUMB_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) RIGHT_THUMB_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_THUMB_DISTAL, requirement=False, icon="VIEW_PAN", ) RIGHT_INDEX_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_INDEX_PROXIMAL, requirement=False, icon="VIEW_PAN", ) RIGHT_INDEX_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_INDEX_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) RIGHT_INDEX_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_INDEX_DISTAL, requirement=False, icon="VIEW_PAN", ) RIGHT_MIDDLE_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_MIDDLE_PROXIMAL, requirement=False, icon="VIEW_PAN", ) RIGHT_MIDDLE_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_MIDDLE_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) RIGHT_MIDDLE_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_MIDDLE_DISTAL, requirement=False, icon="VIEW_PAN", ) RIGHT_RING_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_RING_PROXIMAL, requirement=False, icon="VIEW_PAN", ) RIGHT_RING_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_RING_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) RIGHT_RING_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_RING_DISTAL, requirement=False, icon="VIEW_PAN", ) RIGHT_LITTLE_PROXIMAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_LITTLE_PROXIMAL, requirement=False, icon="VIEW_PAN", ) RIGHT_LITTLE_INTERMEDIATE = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_LITTLE_INTERMEDIATE, requirement=False, icon="VIEW_PAN", ) RIGHT_LITTLE_DISTAL = create_and_append_human_bone_specification( all_human_bones, HumanBoneName.RIGHT_LITTLE_DISTAL, requirement=False, icon="VIEW_PAN", ) human_bone_name_to_human_bone: Mapping[HumanBoneName, HumanBoneSpecification] = { human_bone.name: human_bone for human_bone in all_human_bones } all_names = tuple(b.name.value for b in all_human_bones) required_names = tuple(b.name.value for b in all_human_bones if b.requirement) optional_names = tuple(b.name.value for b in all_human_bones if not b.requirement) @staticmethod def get(name: HumanBoneName) -> HumanBoneSpecification: return HumanBoneSpecifications.human_bone_name_to_human_bone[name] center_req = tuple(b.name.value for b in [HIPS, SPINE, CHEST, NECK, HEAD]) left_leg_req = tuple( b.name.value for b in [LEFT_UPPER_LEG, LEFT_LOWER_LEG, LEFT_FOOT] ) left_arm_req = tuple( b.name.value for b in [LEFT_UPPER_ARM, LEFT_LOWER_ARM, LEFT_HAND] ) right_leg_req = tuple( b.name.value for b in [RIGHT_UPPER_LEG, RIGHT_LOWER_LEG, RIGHT_FOOT] ) right_arm_req = tuple( b.name.value for b in [RIGHT_UPPER_ARM, RIGHT_LOWER_ARM, RIGHT_HAND] ) requires = ( *center_req[:], *left_leg_req[:], *right_leg_req[:], *left_arm_req[:], *right_arm_req[:], ) left_arm_def = tuple( b.name.value for b in [ LEFT_SHOULDER, LEFT_THUMB_PROXIMAL, LEFT_THUMB_INTERMEDIATE, LEFT_THUMB_DISTAL, LEFT_INDEX_PROXIMAL, LEFT_INDEX_INTERMEDIATE, LEFT_INDEX_DISTAL, LEFT_MIDDLE_PROXIMAL, LEFT_MIDDLE_INTERMEDIATE, LEFT_MIDDLE_DISTAL, LEFT_RING_PROXIMAL, LEFT_RING_INTERMEDIATE, LEFT_RING_DISTAL, LEFT_LITTLE_PROXIMAL, LEFT_LITTLE_INTERMEDIATE, LEFT_LITTLE_DISTAL, ] ) right_arm_def = tuple( b.name.value for b in [ RIGHT_SHOULDER, RIGHT_THUMB_PROXIMAL, RIGHT_THUMB_INTERMEDIATE, RIGHT_THUMB_DISTAL, RIGHT_INDEX_PROXIMAL, RIGHT_INDEX_INTERMEDIATE, RIGHT_INDEX_DISTAL, RIGHT_MIDDLE_PROXIMAL, RIGHT_MIDDLE_INTERMEDIATE, RIGHT_MIDDLE_DISTAL, RIGHT_RING_PROXIMAL, RIGHT_RING_INTERMEDIATE, RIGHT_RING_DISTAL, RIGHT_LITTLE_PROXIMAL, RIGHT_LITTLE_INTERMEDIATE, RIGHT_LITTLE_DISTAL, ] ) center_def = (UPPER_CHEST.name.value, JAW.name.value) left_leg_def = (LEFT_TOES.name.value,) right_leg_def = (RIGHT_TOES.name.value,) defines = ( LEFT_EYE.name.value, RIGHT_EYE.name.value, *center_def[:], *left_leg_def[:], *right_leg_def[:], *left_arm_def[:], *right_arm_def[:], )