@volter/blender-engine 0.1.147 → 0.1.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +16 -0
- package/README.md +4 -11
- package/browser/addons/vrm/LICENSE_(OPTION1)_MIT.txt +22 -0
- package/browser/addons/vrm/LICENSE_(OPTION2)_GPL-3_0.txt +674 -0
- package/browser/addons/vrm/LICENSE_MAIN.txt +1 -0
- package/browser/addons/vrm/README.txt +3 -0
- package/browser/addons/vrm/__init__.py +41 -0
- package/browser/addons/vrm/blender_manifest.toml +16 -0
- package/browser/addons/vrm/common/__init__.py +1 -0
- package/browser/addons/vrm/common/animation.py +58 -0
- package/browser/addons/vrm/common/blender_manifest.py +82 -0
- package/browser/addons/vrm/common/char.py +16 -0
- package/browser/addons/vrm/common/convert.py +287 -0
- package/browser/addons/vrm/common/convert_any.py +48 -0
- package/browser/addons/vrm/common/debug.py +40 -0
- package/browser/addons/vrm/common/deep.py +162 -0
- package/browser/addons/vrm/common/error_dialog.py +274 -0
- package/browser/addons/vrm/common/fs.py +71 -0
- package/browser/addons/vrm/common/gl.py +20 -0
- package/browser/addons/vrm/common/gltf.py +1149 -0
- package/browser/addons/vrm/common/human_bone_mapper/__init__.py +1 -0
- package/browser/addons/vrm/common/human_bone_mapper/biped_mapping.py +84 -0
- package/browser/addons/vrm/common/human_bone_mapper/cats_blender_plugin_fix_model_mapping.py +63 -0
- package/browser/addons/vrm/common/human_bone_mapper/human_bone_mapper.py +203 -0
- package/browser/addons/vrm/common/human_bone_mapper/microsoft_rocketbox_mapping.py +69 -0
- package/browser/addons/vrm/common/human_bone_mapper/mixamo_mapping.py +62 -0
- package/browser/addons/vrm/common/human_bone_mapper/mmd_mapping.py +115 -0
- package/browser/addons/vrm/common/human_bone_mapper/mpfb_mapping.py +120 -0
- package/browser/addons/vrm/common/human_bone_mapper/ready_player_me_mapping.py +65 -0
- package/browser/addons/vrm/common/human_bone_mapper/reallusion_mapping.py +94 -0
- package/browser/addons/vrm/common/human_bone_mapper/rigify_meta_rig_mapping.py +65 -0
- package/browser/addons/vrm/common/human_bone_mapper/structure_based_mapping.py +891 -0
- package/browser/addons/vrm/common/human_bone_mapper/unreal_mapping.py +62 -0
- package/browser/addons/vrm/common/human_bone_mapper/vrm_addon_mapping.py +112 -0
- package/browser/addons/vrm/common/human_bone_mapper/vroid_mapping.py +89 -0
- package/browser/addons/vrm/common/legacy_gltf.py +16 -0
- package/browser/addons/vrm/common/logger.py +84 -0
- package/browser/addons/vrm/common/mtoon0.blend +0 -0
- package/browser/addons/vrm/common/mtoon1.blend +0 -0
- package/browser/addons/vrm/common/mtoon1_outline.blend +0 -0
- package/browser/addons/vrm/common/mtoon_auto_setup.blend +0 -0
- package/browser/addons/vrm/common/mtoon_unversioned.py +62 -0
- package/browser/addons/vrm/common/ops/__init__.py +10 -0
- package/browser/addons/vrm/common/ops/export_scene.py +67 -0
- package/browser/addons/vrm/common/ops/icyp.py +53 -0
- package/browser/addons/vrm/common/ops/import_scene.py +57 -0
- package/browser/addons/vrm/common/ops/vrm.py +2380 -0
- package/browser/addons/vrm/common/ops/wm.py +167 -0
- package/browser/addons/vrm/common/preferences.py +362 -0
- package/browser/addons/vrm/common/progress.py +87 -0
- package/browser/addons/vrm/common/rotation.py +89 -0
- package/browser/addons/vrm/common/safe_removal.py +46 -0
- package/browser/addons/vrm/common/scene_watcher.py +230 -0
- package/browser/addons/vrm/common/shader.py +2243 -0
- package/browser/addons/vrm/common/shape_key_mapper/__init__.py +1 -0
- package/browser/addons/vrm/common/shape_key_mapper/arkit_mapping.py +168 -0
- package/browser/addons/vrm/common/shape_key_mapper/mmd_mapping.py +62 -0
- package/browser/addons/vrm/common/shape_key_mapper/ready_player_me_mapping.py +52 -0
- package/browser/addons/vrm/common/shape_key_mapper/vrchat_mapping.py +17 -0
- package/browser/addons/vrm/common/third_party_user_extension.py +214 -0
- package/browser/addons/vrm/common/transparent_z_write.blend +0 -0
- package/browser/addons/vrm/common/version.py +161 -0
- package/browser/addons/vrm/common/vrm0/__init__.py +1 -0
- package/browser/addons/vrm/common/vrm0/human_bone.py +662 -0
- package/browser/addons/vrm/common/vrm0/material_property.py +346 -0
- package/browser/addons/vrm/common/vrm1/__init__.py +1 -0
- package/browser/addons/vrm/common/vrm1/expression_preset.py +207 -0
- package/browser/addons/vrm/common/vrm1/human_bone.py +822 -0
- package/browser/addons/vrm/common/workspace.py +227 -0
- package/browser/addons/vrm/common/writable_context.py +77 -0
- package/browser/addons/vrm/documentation/en-us/animation/index.md +63 -0
- package/browser/addons/vrm/documentation/en-us/create-humanoid-vrm-from-scratch/index.md +131 -0
- package/browser/addons/vrm/documentation/en-us/create-simple-vrm-from-scratch/index.md +30 -0
- package/browser/addons/vrm/documentation/en-us/development/index.md +136 -0
- package/browser/addons/vrm/documentation/en-us/index.md +41 -0
- package/browser/addons/vrm/documentation/en-us/installation/index.md +152 -0
- package/browser/addons/vrm/documentation/en-us/material-mtoon/index.md +122 -0
- package/browser/addons/vrm/documentation/en-us/material-pbr/index.md +94 -0
- package/browser/addons/vrm/documentation/en-us/scripting-api/index.md +391 -0
- package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrm/index.md +57 -0
- package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrma/index.md +17 -0
- package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrm/index.md +61 -0
- package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrma/index.md +13 -0
- package/browser/addons/vrm/documentation/ja-jp/animation/index.md +57 -0
- package/browser/addons/vrm/documentation/ja-jp/create-humanoid-vrm-from-scratch/index.md +129 -0
- package/browser/addons/vrm/documentation/ja-jp/create-simple-vrm-from-scratch/index.md +29 -0
- package/browser/addons/vrm/documentation/ja-jp/development/index.md +137 -0
- package/browser/addons/vrm/documentation/ja-jp/index.md +37 -0
- package/browser/addons/vrm/documentation/ja-jp/installation/index.md +150 -0
- package/browser/addons/vrm/documentation/ja-jp/material-mtoon/index.md +113 -0
- package/browser/addons/vrm/documentation/ja-jp/material-pbr/index.md +90 -0
- package/browser/addons/vrm/documentation/ja-jp/scripting-api/index.md +389 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrm/index.md +55 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrma/index.md +19 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrm/index.md +56 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrma/index.md +14 -0
- package/browser/addons/vrm/editor/__init__.py +1 -0
- package/browser/addons/vrm/editor/extension.py +425 -0
- package/browser/addons/vrm/editor/extension_accessor.py +75 -0
- package/browser/addons/vrm/editor/handler.py +84 -0
- package/browser/addons/vrm/editor/khr_character/__init__.py +1 -0
- package/browser/addons/vrm/editor/khr_character/panel.py +69 -0
- package/browser/addons/vrm/editor/khr_character/property_group.py +27 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/__init__.py +1 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/ops.py +464 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/panel.py +136 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/property_group.py +83 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/ui_list.py +198 -0
- package/browser/addons/vrm/editor/make_armature.py +709 -0
- package/browser/addons/vrm/editor/menu.py +147 -0
- package/browser/addons/vrm/editor/migration.py +297 -0
- package/browser/addons/vrm/editor/mtoon1/__init__.py +1 -0
- package/browser/addons/vrm/editor/mtoon1/handler.py +20 -0
- package/browser/addons/vrm/editor/mtoon1/migration.py +536 -0
- package/browser/addons/vrm/editor/mtoon1/ops.py +304 -0
- package/browser/addons/vrm/editor/mtoon1/panel.py +442 -0
- package/browser/addons/vrm/editor/mtoon1/property_group.py +4977 -0
- package/browser/addons/vrm/editor/mtoon1/scene_watcher.py +381 -0
- package/browser/addons/vrm/editor/node_constraint1/__init__.py +1 -0
- package/browser/addons/vrm/editor/node_constraint1/panel.py +317 -0
- package/browser/addons/vrm/editor/node_constraint1/property_group.py +25 -0
- package/browser/addons/vrm/editor/ops.py +471 -0
- package/browser/addons/vrm/editor/panel.py +315 -0
- package/browser/addons/vrm/editor/property_group.py +888 -0
- package/browser/addons/vrm/editor/search.py +682 -0
- package/browser/addons/vrm/editor/spring_bone1/__init__.py +1 -0
- package/browser/addons/vrm/editor/spring_bone1/handler.py +994 -0
- package/browser/addons/vrm/editor/spring_bone1/menu.py +246 -0
- package/browser/addons/vrm/editor/spring_bone1/migration.py +86 -0
- package/browser/addons/vrm/editor/spring_bone1/ops.py +1679 -0
- package/browser/addons/vrm/editor/spring_bone1/panel.py +605 -0
- package/browser/addons/vrm/editor/spring_bone1/property_group.py +1457 -0
- package/browser/addons/vrm/editor/spring_bone1/ui_list.py +408 -0
- package/browser/addons/vrm/editor/subscription.py +123 -0
- package/browser/addons/vrm/editor/t_pose.py +713 -0
- package/browser/addons/vrm/editor/validation.py +1271 -0
- package/browser/addons/vrm/editor/vrm0/__init__.py +1 -0
- package/browser/addons/vrm/editor/vrm0/gizmo_group.py +63 -0
- package/browser/addons/vrm/editor/vrm0/handler.py +23 -0
- package/browser/addons/vrm/editor/vrm0/menu.py +145 -0
- package/browser/addons/vrm/editor/vrm0/migration.py +761 -0
- package/browser/addons/vrm/editor/vrm0/ops.py +2145 -0
- package/browser/addons/vrm/editor/vrm0/panel.py +1333 -0
- package/browser/addons/vrm/editor/vrm0/property_group.py +1818 -0
- package/browser/addons/vrm/editor/vrm0/ui_list.py +688 -0
- package/browser/addons/vrm/editor/vrm1/__init__.py +1 -0
- package/browser/addons/vrm/editor/vrm1/handler.py +28 -0
- package/browser/addons/vrm/editor/vrm1/menu.py +76 -0
- package/browser/addons/vrm/editor/vrm1/migration.py +321 -0
- package/browser/addons/vrm/editor/vrm1/ops.py +2679 -0
- package/browser/addons/vrm/editor/vrm1/panel.py +1243 -0
- package/browser/addons/vrm/editor/vrm1/property_group.py +2087 -0
- package/browser/addons/vrm/editor/vrm1/scene_watcher.py +90 -0
- package/browser/addons/vrm/editor/vrm1/ui_list.py +533 -0
- package/browser/addons/vrm/exporter/__init__.py +1 -0
- package/browser/addons/vrm/exporter/abstract_base_vrm_exporter.py +628 -0
- package/browser/addons/vrm/exporter/export_scene.py +930 -0
- package/browser/addons/vrm/exporter/gltf2_export_user_extension.py +45 -0
- package/browser/addons/vrm/exporter/khr_character_exporter.py +34 -0
- package/browser/addons/vrm/exporter/vrm0_exporter.py +4020 -0
- package/browser/addons/vrm/exporter/vrm1_exporter.py +3682 -0
- package/browser/addons/vrm/exporter/vrm_animation_exporter.py +1035 -0
- package/browser/addons/vrm/external/__init__.py +1 -0
- package/browser/addons/vrm/external/auto_rig_pro_support.py +3 -0
- package/browser/addons/vrm/external/io_scene_gltf2_support.py +418 -0
- package/browser/addons/vrm/external/rigify_support.py +1 -0
- package/browser/addons/vrm/importer/__init__.py +1 -0
- package/browser/addons/vrm/importer/abstract_base_vrm_importer.py +1548 -0
- package/browser/addons/vrm/importer/file_handler.py +34 -0
- package/browser/addons/vrm/importer/gltf2_import_user_extension.py +268 -0
- package/browser/addons/vrm/importer/import_scene.py +646 -0
- package/browser/addons/vrm/importer/license_validation.py +198 -0
- package/browser/addons/vrm/importer/vrm0_importer.py +1723 -0
- package/browser/addons/vrm/importer/vrm1_importer.py +2014 -0
- package/browser/addons/vrm/importer/vrm_animation_importer.py +763 -0
- package/browser/addons/vrm/importer/vrm_diff.py +316 -0
- package/browser/addons/vrm/locale/__init__.py +1 -0
- package/browser/addons/vrm/locale/ja_jp.py +826 -0
- package/browser/addons/vrm/locale/ruff.toml +8 -0
- package/browser/addons/vrm/locale/translation_dictionary.py +10 -0
- package/browser/addons/vrm/locale/zh_hans.py +928 -0
- package/browser/addons/vrm/registration.py +670 -0
- package/browser/addons/vrm.json +18 -0
- package/browser/blender-emscripten-engine.mts +60 -128
- package/browser/blender-engine.mts +20 -186
- package/browser/blender-wali-engine.mts +35 -81
- package/browser/describe-thrown.mts +3 -6
- package/browser/document-chunks.mts +4 -16
- package/browser/gltf_export.py +3161 -0
- package/browser/heap-growth.mts +23 -0
- package/browser/index.ts +2 -0
- package/browser/protocol.ts +119 -35
- package/browser/pull-job.mts +2 -10
- package/browser/rna.ts +190 -73
- package/browser/runtime.ts +197 -82
- package/browser/session-addons.mts +33 -0
- package/browser/session-frame.mts +5 -24
- package/browser/session.py +1338 -1427
- package/browser/three/attach-presenter.ts +11 -39
- package/browser/three/blender-base64.ts +1 -36
- package/browser/three/blender-corner-normals.ts +1 -23
- package/browser/three/blender-display-shaders.json +1 -1
- package/browser/three/blender-display-shaders.lut +0 -0
- package/browser/three/blender-display-transform.ts +2 -1
- package/browser/three/blender-frame-columns.ts +5 -13
- package/browser/three/blender-graph-material.ts +18 -3
- package/browser/three/blender-node-graph.ts +10 -34
- package/browser/three/blender-physical-material.ts +2 -31
- package/browser/three/blender-runtime-armature.ts +7 -60
- package/browser/three/blender-runtime-frame.ts +9 -23
- package/browser/three/blender-runtime-geometry.ts +22 -26
- package/browser/three/blender-runtime-lighting.ts +48 -178
- package/browser/three/blender-runtime-shadows.ts +187 -1
- package/browser/three/blender-runtime-skeleton.ts +53 -1
- package/browser/three/blender-runtime-view.ts +505 -283
- package/browser/three/blender-runtime-weights.ts +9 -72
- package/browser/three/blender-sky.ts +13 -33
- package/browser/three/blender-triangulate.ts +7 -35
- package/browser/three/blender-viewport-sampling.ts +235 -0
- package/browser/three/blender-workbench-material.ts +25 -29
- package/browser/three/blender-workbench-outline.ts +199 -0
- package/browser/three/blender-world-volume.ts +62 -10
- package/browser/three/presenter.ts +10 -26
- package/browser/three/release.ts +5 -21
- package/browser/three/sky-precompute-worker.ts +1 -18
- package/browser/three/sky-worker.ts +5 -23
- package/browser/three/world-field-sampler.ts +12 -26
- package/browser/volter_three.py +9 -105
- package/browser/worker.ts +156 -151
- package/package.json +5 -2
- package/wasm/BUNDLE.json +17 -17
- package/wasm/DEPENDENCY-LICENSES.txt +14 -0
- package/wasm/blender_browser.data.br +0 -0
- package/wasm/blender_browser.js +1 -1
- package/wasm/blender_browser.wasm.br +0 -0
- package/browser/frame-transfer.mts +0 -19
- package/browser/three/agx-base-srgb.lut +0 -0
- package/browser/three/agx-look-medium-high-contrast.lut +0 -0
- package/browser/three/agx-look-punchy.lut +0 -0
- package/browser/three/blender-agx.ts +0 -235
- package/browser/three/blender-display-lut.ts +0 -145
- package/browser/three/blender-filmic.ts +0 -49
- package/browser/three/blender-standard.ts +0 -68
- package/browser/three/blender-world-visibility.ts +0 -278
- package/browser/three/filmic-srgb.lut +0 -0
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
import math
|
|
5
|
+
from collections.abc import Iterator, Mapping
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from typing import Final, Optional, Union
|
|
9
|
+
|
|
10
|
+
from bpy.types import Armature, Bone, Object
|
|
11
|
+
from mathutils import Matrix
|
|
12
|
+
|
|
13
|
+
from ..deep import Json
|
|
14
|
+
from ..logger import get_logger
|
|
15
|
+
from ..vrm1.human_bone import (
|
|
16
|
+
HumanBoneName,
|
|
17
|
+
HumanBoneSpecification,
|
|
18
|
+
HumanBoneSpecifications,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
CENTRAL_BONE_NAMES = frozenset(
|
|
25
|
+
[
|
|
26
|
+
HumanBoneName.HEAD,
|
|
27
|
+
HumanBoneName.NECK,
|
|
28
|
+
HumanBoneName.UPPER_CHEST,
|
|
29
|
+
HumanBoneName.CHEST,
|
|
30
|
+
HumanBoneName.SPINE,
|
|
31
|
+
HumanBoneName.HIPS,
|
|
32
|
+
]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
HIPS_TO_SPINE_BONE_NAMES = frozenset(
|
|
36
|
+
[
|
|
37
|
+
HumanBoneName.HIPS,
|
|
38
|
+
HumanBoneName.SPINE,
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
SPINE_TO_UPPER_CHEST_BONE_NAMES = frozenset(
|
|
43
|
+
[
|
|
44
|
+
HumanBoneName.SPINE,
|
|
45
|
+
HumanBoneName.CHEST,
|
|
46
|
+
HumanBoneName.UPPER_CHEST,
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
SPINE_TO_NECK_NAMES = frozenset(
|
|
51
|
+
[
|
|
52
|
+
HumanBoneName.SPINE,
|
|
53
|
+
HumanBoneName.CHEST,
|
|
54
|
+
HumanBoneName.UPPER_CHEST,
|
|
55
|
+
HumanBoneName.NECK,
|
|
56
|
+
]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
LEFT_ARM_BONE_NAMES = frozenset(
|
|
60
|
+
[
|
|
61
|
+
HumanBoneName.LEFT_EYE,
|
|
62
|
+
HumanBoneName.LEFT_HAND,
|
|
63
|
+
HumanBoneName.LEFT_SHOULDER,
|
|
64
|
+
HumanBoneName.LEFT_UPPER_ARM,
|
|
65
|
+
HumanBoneName.LEFT_LOWER_ARM,
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
LEFT_LEG_BONE_NAMES = frozenset(
|
|
70
|
+
[
|
|
71
|
+
HumanBoneName.LEFT_UPPER_LEG,
|
|
72
|
+
HumanBoneName.LEFT_LOWER_LEG,
|
|
73
|
+
HumanBoneName.LEFT_FOOT,
|
|
74
|
+
]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
RIGHT_ARM_BONE_NAMES = frozenset(
|
|
78
|
+
[
|
|
79
|
+
HumanBoneName.RIGHT_EYE,
|
|
80
|
+
HumanBoneName.RIGHT_HAND,
|
|
81
|
+
HumanBoneName.RIGHT_SHOULDER,
|
|
82
|
+
HumanBoneName.RIGHT_UPPER_ARM,
|
|
83
|
+
HumanBoneName.RIGHT_LOWER_ARM,
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
RIGHT_LEG_BONE_NAMES = frozenset(
|
|
88
|
+
[
|
|
89
|
+
HumanBoneName.RIGHT_UPPER_LEG,
|
|
90
|
+
HumanBoneName.RIGHT_LOWER_LEG,
|
|
91
|
+
HumanBoneName.RIGHT_FOOT,
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class Counter:
|
|
98
|
+
count: int = 0
|
|
99
|
+
cache_hit: int = 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class NormalizedBone:
|
|
104
|
+
name: str
|
|
105
|
+
x: float
|
|
106
|
+
y: float
|
|
107
|
+
z: float
|
|
108
|
+
children: tuple["NormalizedBone", ...]
|
|
109
|
+
recursive_len: int
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def create(
|
|
113
|
+
cls,
|
|
114
|
+
armature_matrix: Matrix,
|
|
115
|
+
max_x: float,
|
|
116
|
+
max_y: float,
|
|
117
|
+
max_z: float,
|
|
118
|
+
bone: Bone,
|
|
119
|
+
) -> "NormalizedBone":
|
|
120
|
+
x, y, z = (armature_matrix @ bone.matrix_local).translation
|
|
121
|
+
normalized_x = math.copysign(math.sqrt(abs(x) / max_x), x) if max_x > 0 else 0
|
|
122
|
+
normalized_y = math.copysign(math.sqrt(abs(y) / max_y), y) if max_y > 0 else 0
|
|
123
|
+
normalized_z = math.copysign(math.sqrt(abs(z) / max_z), z) if max_z > 0 else 0
|
|
124
|
+
children = tuple(
|
|
125
|
+
cls.create(armature_matrix, max_x, max_y, max_z, child)
|
|
126
|
+
for child in bone.children
|
|
127
|
+
)
|
|
128
|
+
return NormalizedBone(
|
|
129
|
+
name=bone.name,
|
|
130
|
+
x=normalized_x,
|
|
131
|
+
y=normalized_y,
|
|
132
|
+
z=normalized_z,
|
|
133
|
+
children=children,
|
|
134
|
+
recursive_len=1 + sum(child.recursive_len for child in children),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def from_json(cls, data: Json) -> "NormalizedBone":
|
|
139
|
+
if not isinstance(data, Mapping):
|
|
140
|
+
message = "NormalizedBone must be an object"
|
|
141
|
+
raise TypeError(message)
|
|
142
|
+
name = data.get("name")
|
|
143
|
+
if not isinstance(name, str):
|
|
144
|
+
message = "NormalizedBone.name must be a string"
|
|
145
|
+
raise TypeError(message)
|
|
146
|
+
|
|
147
|
+
x = data.get("x")
|
|
148
|
+
y = data.get("y")
|
|
149
|
+
z = data.get("z")
|
|
150
|
+
if not isinstance(x, (int, float)):
|
|
151
|
+
message = "NormalizedBone.x must be a number"
|
|
152
|
+
raise TypeError(message)
|
|
153
|
+
if not isinstance(y, (int, float)):
|
|
154
|
+
message = "NormalizedBone.y must be a number"
|
|
155
|
+
raise TypeError(message)
|
|
156
|
+
if not isinstance(z, (int, float)):
|
|
157
|
+
message = "NormalizedBone.z must be a number"
|
|
158
|
+
raise TypeError(message)
|
|
159
|
+
|
|
160
|
+
children_data = data.get("children")
|
|
161
|
+
if children_data is None:
|
|
162
|
+
children_data = []
|
|
163
|
+
if not isinstance(children_data, list):
|
|
164
|
+
message = "NormalizedBone.children must be a list"
|
|
165
|
+
raise TypeError(message)
|
|
166
|
+
children = tuple(cls.from_json(child) for child in children_data)
|
|
167
|
+
|
|
168
|
+
recursive_len = data.get("recursiveLen")
|
|
169
|
+
if recursive_len is None:
|
|
170
|
+
recursive_len_value = 1 + sum(child.recursive_len for child in children)
|
|
171
|
+
elif isinstance(recursive_len, int):
|
|
172
|
+
recursive_len_value = recursive_len
|
|
173
|
+
else:
|
|
174
|
+
message = "NormalizedBone.recursiveLen must be an int"
|
|
175
|
+
raise ValueError(message)
|
|
176
|
+
|
|
177
|
+
return NormalizedBone(
|
|
178
|
+
name=name,
|
|
179
|
+
x=float(x),
|
|
180
|
+
y=float(y),
|
|
181
|
+
z=float(z),
|
|
182
|
+
children=children,
|
|
183
|
+
recursive_len=recursive_len_value,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def to_json(self) -> Json:
|
|
187
|
+
return {
|
|
188
|
+
"name": self.name,
|
|
189
|
+
"x": self.x,
|
|
190
|
+
"y": self.y,
|
|
191
|
+
"z": self.z,
|
|
192
|
+
"children": [child.to_json() for child in self.children],
|
|
193
|
+
"recursiveLen": self.recursive_len,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
def dump(self) -> str:
|
|
197
|
+
output = f'("{self.name}", ({self.x:.3f}, {self.y:.3f}, {self.z:.3f})): {{\n'
|
|
198
|
+
for child in self.children:
|
|
199
|
+
output += child.dump()
|
|
200
|
+
output += "},\n"
|
|
201
|
+
return output
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
SearchBranch = Union["UnassignedSearchBranch", "AssignedSearchBranch"]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _parent_from_json(
|
|
208
|
+
data: Json,
|
|
209
|
+
) -> Optional[tuple[NormalizedBone, HumanBoneSpecification]]:
|
|
210
|
+
if data is None:
|
|
211
|
+
return None
|
|
212
|
+
if not isinstance(data, Mapping):
|
|
213
|
+
message = "parent must be an object or null"
|
|
214
|
+
raise TypeError(message)
|
|
215
|
+
bone_data = data.get("bone")
|
|
216
|
+
if not isinstance(bone_data, Mapping):
|
|
217
|
+
message = "parent.bone must be an object"
|
|
218
|
+
raise TypeError(message)
|
|
219
|
+
bone = NormalizedBone.from_json(bone_data)
|
|
220
|
+
human_bone_name_str = data.get("humanBoneName")
|
|
221
|
+
if not isinstance(human_bone_name_str, str):
|
|
222
|
+
message = "parent.humanBoneName must be a string"
|
|
223
|
+
raise TypeError(message)
|
|
224
|
+
human_bone_specification = HumanBoneSpecifications.from_name_str(
|
|
225
|
+
human_bone_name_str
|
|
226
|
+
)
|
|
227
|
+
if human_bone_specification is None:
|
|
228
|
+
message = f"Unknown human bone name: {human_bone_name_str}"
|
|
229
|
+
raise ValueError(message)
|
|
230
|
+
return (bone, human_bone_specification)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _search_branch_from_json(data: Json) -> SearchBranch:
|
|
234
|
+
if not isinstance(data, Mapping):
|
|
235
|
+
message = "SearchBranch must be an object"
|
|
236
|
+
raise TypeError(message)
|
|
237
|
+
type_name = data.get("type_name")
|
|
238
|
+
if type_name == AssignedSearchBranch.__name__:
|
|
239
|
+
return AssignedSearchBranch.from_json(data)
|
|
240
|
+
|
|
241
|
+
if type_name == UnassignedSearchBranch.__name__:
|
|
242
|
+
return UnassignedSearchBranch.from_json(data)
|
|
243
|
+
|
|
244
|
+
message = (
|
|
245
|
+
"SearchBranch.type_name must be 'AssignedSearchBranch'"
|
|
246
|
+
+ " or 'UnassignedSearchBranch'"
|
|
247
|
+
)
|
|
248
|
+
raise ValueError(message)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass(frozen=True)
|
|
252
|
+
class UnassignedSearchBranch:
|
|
253
|
+
children: tuple[SearchBranch, ...] = ()
|
|
254
|
+
|
|
255
|
+
def to_json(self) -> Json:
|
|
256
|
+
return {
|
|
257
|
+
"type_name": type(self).__name__,
|
|
258
|
+
"children": [child.to_json() for child in self.children],
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@classmethod
|
|
262
|
+
def from_json(cls, data: Mapping[str, Json]) -> "UnassignedSearchBranch":
|
|
263
|
+
children_data = data.get("children")
|
|
264
|
+
if children_data is None:
|
|
265
|
+
children_data = []
|
|
266
|
+
if not isinstance(children_data, list):
|
|
267
|
+
message = "UnassignedSearchBranch.children must be a list"
|
|
268
|
+
raise TypeError(message)
|
|
269
|
+
return UnassignedSearchBranch(
|
|
270
|
+
children=tuple(_search_branch_from_json(child) for child in children_data)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
@cached_property
|
|
274
|
+
def score(self) -> float:
|
|
275
|
+
return sum((child.score for child in self.children), start=0.0)
|
|
276
|
+
|
|
277
|
+
@cached_property
|
|
278
|
+
def recursive_mappings(self) -> Mapping[HumanBoneSpecification, NormalizedBone]:
|
|
279
|
+
result: dict[HumanBoneSpecification, NormalizedBone] = {}
|
|
280
|
+
for child in self.children:
|
|
281
|
+
result.update(child.recursive_mappings)
|
|
282
|
+
return result
|
|
283
|
+
|
|
284
|
+
def __lt__(self, other: "SearchBranch") -> bool:
|
|
285
|
+
return self.score < other.score
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@dataclass(frozen=True)
|
|
289
|
+
class AssignedSearchBranch:
|
|
290
|
+
depth: int
|
|
291
|
+
bone: NormalizedBone
|
|
292
|
+
human_bone_specification: HumanBoneSpecification
|
|
293
|
+
parent: Optional[tuple[NormalizedBone, HumanBoneSpecification]]
|
|
294
|
+
children: tuple[SearchBranch, ...]
|
|
295
|
+
|
|
296
|
+
def to_json(self) -> Json:
|
|
297
|
+
parent = self.parent
|
|
298
|
+
if parent is None:
|
|
299
|
+
parent_json: Json = None
|
|
300
|
+
else:
|
|
301
|
+
parent_bone, parent_spec = parent
|
|
302
|
+
parent_json = {
|
|
303
|
+
"bone": parent_bone.to_json(),
|
|
304
|
+
"humanBoneName": parent_spec.name.value,
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
"type_name": type(self).__name__,
|
|
308
|
+
"depth": self.depth,
|
|
309
|
+
"bone": self.bone.to_json(),
|
|
310
|
+
"humanBoneName": self.human_bone_specification.name.value,
|
|
311
|
+
"parent": parent_json,
|
|
312
|
+
"children": [child.to_json() for child in self.children],
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@classmethod
|
|
316
|
+
def from_json(cls, data: Mapping[str, Json]) -> "AssignedSearchBranch":
|
|
317
|
+
depth = data.get("depth")
|
|
318
|
+
if not isinstance(depth, int):
|
|
319
|
+
message = "AssignedSearchBranch.depth must be an int"
|
|
320
|
+
raise TypeError(message)
|
|
321
|
+
bone_data = data.get("bone")
|
|
322
|
+
if not isinstance(bone_data, Mapping):
|
|
323
|
+
message = "AssignedSearchBranch.bone must be an object"
|
|
324
|
+
raise TypeError(message)
|
|
325
|
+
bone = NormalizedBone.from_json(bone_data)
|
|
326
|
+
human_bone_name_str = data.get("humanBoneName")
|
|
327
|
+
if not isinstance(human_bone_name_str, str):
|
|
328
|
+
message = "AssignedSearchBranch.humanBoneName must be a string"
|
|
329
|
+
raise TypeError(message)
|
|
330
|
+
human_bone_specification = HumanBoneSpecifications.from_name_str(
|
|
331
|
+
human_bone_name_str
|
|
332
|
+
)
|
|
333
|
+
if human_bone_specification is None:
|
|
334
|
+
message = f"Unknown human bone name: {human_bone_name_str}"
|
|
335
|
+
raise ValueError(message)
|
|
336
|
+
parent = _parent_from_json(data.get("parent"))
|
|
337
|
+
children_data = data.get("children")
|
|
338
|
+
if children_data is None:
|
|
339
|
+
children_data = []
|
|
340
|
+
if not isinstance(children_data, list):
|
|
341
|
+
message = "AssignedSearchBranch.children must be a list"
|
|
342
|
+
raise TypeError(message)
|
|
343
|
+
children = tuple(_search_branch_from_json(child) for child in children_data)
|
|
344
|
+
return AssignedSearchBranch(
|
|
345
|
+
depth=depth,
|
|
346
|
+
bone=bone,
|
|
347
|
+
human_bone_specification=human_bone_specification,
|
|
348
|
+
parent=parent,
|
|
349
|
+
children=children,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
@cached_property
|
|
353
|
+
def score(self) -> float:
|
|
354
|
+
total_score = sum((child.score for child in self.children), start=0.0)
|
|
355
|
+
|
|
356
|
+
total_score -= math.sqrt(self.depth)
|
|
357
|
+
|
|
358
|
+
if self.human_bone_specification.requirement:
|
|
359
|
+
total_score += 1000
|
|
360
|
+
else:
|
|
361
|
+
total_score += 20
|
|
362
|
+
|
|
363
|
+
human_bone_name = self.human_bone_specification.name
|
|
364
|
+
|
|
365
|
+
if self.bone.z > 0:
|
|
366
|
+
total_score += 5
|
|
367
|
+
|
|
368
|
+
if human_bone_name in CENTRAL_BONE_NAMES:
|
|
369
|
+
total_score += self.bone.z - abs(self.bone.x)
|
|
370
|
+
elif human_bone_name in LEFT_ARM_BONE_NAMES:
|
|
371
|
+
total_score += self.bone.x / 2
|
|
372
|
+
elif human_bone_name in LEFT_LEG_BONE_NAMES:
|
|
373
|
+
total_score += 0.2 if self.bone.x > 0 else 0
|
|
374
|
+
elif human_bone_name in RIGHT_ARM_BONE_NAMES:
|
|
375
|
+
total_score += -self.bone.x / 2
|
|
376
|
+
elif human_bone_name in RIGHT_LEG_BONE_NAMES:
|
|
377
|
+
total_score += 0.2 if self.bone.x < 0 else 0
|
|
378
|
+
|
|
379
|
+
parent = self.parent
|
|
380
|
+
if parent is None:
|
|
381
|
+
return total_score
|
|
382
|
+
|
|
383
|
+
parent_bone, parent_human_bone_specification = parent
|
|
384
|
+
human_bone_name = self.human_bone_specification.name
|
|
385
|
+
parent_human_bone_name = parent_human_bone_specification.name
|
|
386
|
+
if human_bone_name == HumanBoneName.SPINE:
|
|
387
|
+
if (
|
|
388
|
+
parent_human_bone_name == HumanBoneName.HIPS
|
|
389
|
+
and parent_bone.z < self.bone.z
|
|
390
|
+
):
|
|
391
|
+
total_score += 5
|
|
392
|
+
elif human_bone_name == HumanBoneName.CHEST:
|
|
393
|
+
if (
|
|
394
|
+
parent_human_bone_name in HIPS_TO_SPINE_BONE_NAMES
|
|
395
|
+
and parent_bone.z < self.bone.z
|
|
396
|
+
):
|
|
397
|
+
total_score += 5
|
|
398
|
+
elif human_bone_name == HumanBoneName.UPPER_CHEST:
|
|
399
|
+
if (
|
|
400
|
+
parent_human_bone_name == HumanBoneName.CHEST
|
|
401
|
+
and parent_bone.z < self.bone.z
|
|
402
|
+
):
|
|
403
|
+
total_score += 5
|
|
404
|
+
elif human_bone_name == HumanBoneName.NECK:
|
|
405
|
+
if (
|
|
406
|
+
parent_human_bone_name in SPINE_TO_UPPER_CHEST_BONE_NAMES
|
|
407
|
+
and parent_bone.z < self.bone.z
|
|
408
|
+
):
|
|
409
|
+
total_score += 5
|
|
410
|
+
elif human_bone_name == HumanBoneName.HEAD:
|
|
411
|
+
if (
|
|
412
|
+
parent_human_bone_name in SPINE_TO_NECK_NAMES
|
|
413
|
+
and parent_bone.z < self.bone.z
|
|
414
|
+
):
|
|
415
|
+
total_score += 5
|
|
416
|
+
else:
|
|
417
|
+
pass
|
|
418
|
+
|
|
419
|
+
return total_score
|
|
420
|
+
|
|
421
|
+
@cached_property
|
|
422
|
+
def recursive_mappings(self) -> Mapping[HumanBoneSpecification, NormalizedBone]:
|
|
423
|
+
result: dict[HumanBoneSpecification, NormalizedBone] = {
|
|
424
|
+
self.human_bone_specification: self.bone
|
|
425
|
+
}
|
|
426
|
+
for child in self.children:
|
|
427
|
+
result.update(child.recursive_mappings)
|
|
428
|
+
return result
|
|
429
|
+
|
|
430
|
+
def __lt__(self, other: "SearchBranch") -> bool:
|
|
431
|
+
return self.score < other.score
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
SEARCH_STOP_BRANCH: Final[SearchBranch] = UnassignedSearchBranch()
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@dataclass(frozen=True)
|
|
438
|
+
class SearchContext:
|
|
439
|
+
max_search_count: int
|
|
440
|
+
require_requirement: int
|
|
441
|
+
only_requirement: bool
|
|
442
|
+
counter: Counter = field(default_factory=Counter)
|
|
443
|
+
cache: dict[
|
|
444
|
+
tuple[tuple[str, ...], tuple[str, ...], Optional[tuple[str, str]], int],
|
|
445
|
+
SearchBranch,
|
|
446
|
+
] = field(
|
|
447
|
+
default_factory=dict[
|
|
448
|
+
tuple[tuple[str, ...], tuple[str, ...], Optional[tuple[str, str]], int],
|
|
449
|
+
SearchBranch,
|
|
450
|
+
]
|
|
451
|
+
)
|
|
452
|
+
max_recursion_depth: int = 50
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
DEFAULT_MAX_SEARCH_COUNT: Final[int] = 1000000
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def create_structure_based_mapping(
|
|
459
|
+
armature: Object,
|
|
460
|
+
human_bone_specification: HumanBoneSpecification = HumanBoneSpecifications.HIPS,
|
|
461
|
+
max_search_count: int = DEFAULT_MAX_SEARCH_COUNT,
|
|
462
|
+
) -> Mapping[str, HumanBoneSpecification]:
|
|
463
|
+
if not isinstance(armature_data := armature.data, Armature):
|
|
464
|
+
message = f"{type(armature.data)} is not an Armature"
|
|
465
|
+
raise TypeError(message)
|
|
466
|
+
|
|
467
|
+
pose_position = armature_data.pose_position
|
|
468
|
+
armature_data.pose_position = "REST"
|
|
469
|
+
|
|
470
|
+
translations = [
|
|
471
|
+
(armature.matrix_world @ bone.matrix_local).translation
|
|
472
|
+
for bone in armature_data.bones
|
|
473
|
+
]
|
|
474
|
+
max_x = max((abs(translation.x) for translation in translations), default=0)
|
|
475
|
+
max_y = max((abs(translation.y) for translation in translations), default=0)
|
|
476
|
+
max_z = max((abs(translation.z) for translation in translations), default=0)
|
|
477
|
+
|
|
478
|
+
root_bones = [
|
|
479
|
+
NormalizedBone.create(
|
|
480
|
+
armature.matrix_world,
|
|
481
|
+
max_x,
|
|
482
|
+
max_y,
|
|
483
|
+
max_z,
|
|
484
|
+
bone,
|
|
485
|
+
)
|
|
486
|
+
for bone in armature_data.bones
|
|
487
|
+
if bone.parent is None
|
|
488
|
+
]
|
|
489
|
+
armature_data.pose_position = pose_position
|
|
490
|
+
counter = Counter()
|
|
491
|
+
max_result: SearchBranch = SEARCH_STOP_BRANCH
|
|
492
|
+
|
|
493
|
+
for require_requirement, only_requirement in ((1, True), (0, False)):
|
|
494
|
+
search_context = SearchContext(
|
|
495
|
+
require_requirement=require_requirement,
|
|
496
|
+
only_requirement=only_requirement,
|
|
497
|
+
max_search_count=max_search_count,
|
|
498
|
+
counter=counter,
|
|
499
|
+
)
|
|
500
|
+
for skip_count in range(1000):
|
|
501
|
+
if counter.count > search_context.max_search_count:
|
|
502
|
+
break
|
|
503
|
+
changed = False
|
|
504
|
+
for root_bone in root_bones:
|
|
505
|
+
if counter.count > search_context.max_search_count:
|
|
506
|
+
break
|
|
507
|
+
if (
|
|
508
|
+
root_bone.recursive_len
|
|
509
|
+
< skip_count
|
|
510
|
+
+ HumanBoneSpecifications.HIPS.recursive_requirement_len
|
|
511
|
+
* require_requirement
|
|
512
|
+
):
|
|
513
|
+
break
|
|
514
|
+
result = _search_structure_based_mapping_step(
|
|
515
|
+
root_bone,
|
|
516
|
+
(human_bone_specification,),
|
|
517
|
+
None,
|
|
518
|
+
1,
|
|
519
|
+
skip_count,
|
|
520
|
+
search_context,
|
|
521
|
+
)
|
|
522
|
+
if max_result < result:
|
|
523
|
+
max_result = result
|
|
524
|
+
changed = True
|
|
525
|
+
|
|
526
|
+
if not changed:
|
|
527
|
+
continue
|
|
528
|
+
|
|
529
|
+
_logger.warning("Structure search context:")
|
|
530
|
+
_logger.warning(" Search Count Limit: %d", search_context.max_search_count)
|
|
531
|
+
_logger.warning(" Recursion Limit: %d", search_context.max_recursion_depth)
|
|
532
|
+
_logger.warning(" Skip Count: %d", skip_count)
|
|
533
|
+
_logger.warning(
|
|
534
|
+
" Require Requirement: %d", search_context.require_requirement
|
|
535
|
+
)
|
|
536
|
+
_logger.warning(" Only Requirement: %d", search_context.only_requirement)
|
|
537
|
+
_logger.warning(
|
|
538
|
+
"Structure search: Total searches: %d, Cache hits: %d",
|
|
539
|
+
counter.count,
|
|
540
|
+
counter.cache_hit,
|
|
541
|
+
)
|
|
542
|
+
_logger.warning(
|
|
543
|
+
"Generated mappings:\n %s",
|
|
544
|
+
"\n ".join(
|
|
545
|
+
f"{spec.name.value}: {bone.name}"
|
|
546
|
+
for spec, bone in max_result.recursive_mappings.items()
|
|
547
|
+
),
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
_logger.warning(
|
|
551
|
+
"Structure search completed:"
|
|
552
|
+
" Total searches: %d (soft limit: %d), Cache hits: %d",
|
|
553
|
+
counter.count,
|
|
554
|
+
max_search_count,
|
|
555
|
+
counter.cache_hit,
|
|
556
|
+
)
|
|
557
|
+
mapping = max_result.recursive_mappings
|
|
558
|
+
return {spec.name: bone for bone, spec in mapping.items()}
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _search_structure_based_mapping_step(
|
|
562
|
+
bone: NormalizedBone,
|
|
563
|
+
human_bone_specifications: tuple[HumanBoneSpecification, ...],
|
|
564
|
+
parent: Optional[tuple[NormalizedBone, HumanBoneSpecification]],
|
|
565
|
+
depth: int,
|
|
566
|
+
skip_count: int,
|
|
567
|
+
search_context: SearchContext,
|
|
568
|
+
) -> SearchBranch:
|
|
569
|
+
if skip_count < 0 or bone.recursive_len < skip_count:
|
|
570
|
+
return SEARCH_STOP_BRANCH
|
|
571
|
+
|
|
572
|
+
if search_context.require_requirement:
|
|
573
|
+
recursive_requirement_len = sum(
|
|
574
|
+
human_bone_specification.recursive_requirement_len
|
|
575
|
+
for human_bone_specification in human_bone_specifications
|
|
576
|
+
)
|
|
577
|
+
if bone.recursive_len < skip_count + recursive_requirement_len:
|
|
578
|
+
return SEARCH_STOP_BRANCH
|
|
579
|
+
|
|
580
|
+
if depth > search_context.max_recursion_depth:
|
|
581
|
+
return SEARCH_STOP_BRANCH
|
|
582
|
+
|
|
583
|
+
counter = search_context.counter
|
|
584
|
+
counter.count += 1
|
|
585
|
+
if counter.count > search_context.max_search_count:
|
|
586
|
+
return SEARCH_STOP_BRANCH
|
|
587
|
+
|
|
588
|
+
cache_key = (
|
|
589
|
+
(bone.name,),
|
|
590
|
+
tuple(spec.name.value for spec in human_bone_specifications),
|
|
591
|
+
None
|
|
592
|
+
if parent is None
|
|
593
|
+
else (
|
|
594
|
+
parent[0].name,
|
|
595
|
+
parent[1].name.value,
|
|
596
|
+
),
|
|
597
|
+
skip_count,
|
|
598
|
+
)
|
|
599
|
+
cache = search_context.cache
|
|
600
|
+
if (search_branchs := cache.get(cache_key)) is not None:
|
|
601
|
+
counter.cache_hit += 1
|
|
602
|
+
return search_branchs
|
|
603
|
+
|
|
604
|
+
result = SEARCH_STOP_BRANCH
|
|
605
|
+
|
|
606
|
+
if len(human_bone_specifications) == 1:
|
|
607
|
+
human_bone_specification = human_bone_specifications[0]
|
|
608
|
+
human_bone_specification_children = (
|
|
609
|
+
human_bone_specification.required_children
|
|
610
|
+
if search_context.only_requirement
|
|
611
|
+
else human_bone_specification.children
|
|
612
|
+
)
|
|
613
|
+
if bone.children and human_bone_specification_children:
|
|
614
|
+
children = (
|
|
615
|
+
_search_structure_based_mapping_product(
|
|
616
|
+
bone.children,
|
|
617
|
+
human_bone_specification_children,
|
|
618
|
+
(bone, human_bone_specification),
|
|
619
|
+
depth + 1,
|
|
620
|
+
skip_count,
|
|
621
|
+
search_context,
|
|
622
|
+
),
|
|
623
|
+
)
|
|
624
|
+
else:
|
|
625
|
+
children = ()
|
|
626
|
+
|
|
627
|
+
result = max(
|
|
628
|
+
result,
|
|
629
|
+
AssignedSearchBranch(
|
|
630
|
+
depth=depth + 1,
|
|
631
|
+
bone=bone,
|
|
632
|
+
human_bone_specification=human_bone_specification,
|
|
633
|
+
parent=parent,
|
|
634
|
+
children=children,
|
|
635
|
+
),
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
if skip_count > 0:
|
|
639
|
+
result = max(
|
|
640
|
+
result,
|
|
641
|
+
_search_structure_based_mapping_product(
|
|
642
|
+
bone.children,
|
|
643
|
+
human_bone_specifications,
|
|
644
|
+
parent,
|
|
645
|
+
depth + 1,
|
|
646
|
+
skip_count - 1,
|
|
647
|
+
search_context,
|
|
648
|
+
),
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
cache[cache_key] = result
|
|
652
|
+
return result
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def _search_structure_based_mapping_product(
|
|
656
|
+
bones: tuple[NormalizedBone, ...],
|
|
657
|
+
human_bone_specifications: tuple[HumanBoneSpecification, ...],
|
|
658
|
+
parent: Optional[tuple[NormalizedBone, HumanBoneSpecification]],
|
|
659
|
+
depth: int,
|
|
660
|
+
skip_count: int,
|
|
661
|
+
search_context: SearchContext,
|
|
662
|
+
) -> SearchBranch:
|
|
663
|
+
if not bones or not human_bone_specifications:
|
|
664
|
+
return SEARCH_STOP_BRANCH
|
|
665
|
+
|
|
666
|
+
if depth > search_context.max_recursion_depth:
|
|
667
|
+
return SEARCH_STOP_BRANCH
|
|
668
|
+
|
|
669
|
+
if skip_count < 0 or sum(bone.recursive_len for bone in bones) < skip_count:
|
|
670
|
+
return SEARCH_STOP_BRANCH
|
|
671
|
+
|
|
672
|
+
counter = search_context.counter
|
|
673
|
+
counter.count += 1
|
|
674
|
+
if counter.count > search_context.max_search_count:
|
|
675
|
+
return SEARCH_STOP_BRANCH
|
|
676
|
+
|
|
677
|
+
cache = search_context.cache
|
|
678
|
+
cache_key = (
|
|
679
|
+
tuple(sorted(bone.name for bone in bones)),
|
|
680
|
+
tuple(spec.name.value for spec in human_bone_specifications),
|
|
681
|
+
None
|
|
682
|
+
if parent is None
|
|
683
|
+
else (
|
|
684
|
+
parent[0].name,
|
|
685
|
+
parent[1].name.value,
|
|
686
|
+
),
|
|
687
|
+
skip_count,
|
|
688
|
+
)
|
|
689
|
+
if (search_branchs := cache.get(cache_key)) is not None:
|
|
690
|
+
counter.cache_hit += 1
|
|
691
|
+
return search_branchs
|
|
692
|
+
|
|
693
|
+
result = SEARCH_STOP_BRANCH
|
|
694
|
+
|
|
695
|
+
for skipping_human_bone_specification in human_bone_specifications:
|
|
696
|
+
if skipping_human_bone_specification.requirement:
|
|
697
|
+
continue
|
|
698
|
+
skipping_children = sum(
|
|
699
|
+
tuple(
|
|
700
|
+
skipping_human_bone_specification.children
|
|
701
|
+
if human_bone_specification == skipping_human_bone_specification
|
|
702
|
+
else (human_bone_specification,)
|
|
703
|
+
for human_bone_specification in human_bone_specifications
|
|
704
|
+
),
|
|
705
|
+
(),
|
|
706
|
+
)
|
|
707
|
+
if skipping_children:
|
|
708
|
+
result = max(
|
|
709
|
+
result,
|
|
710
|
+
_search_structure_based_mapping_product(
|
|
711
|
+
bones,
|
|
712
|
+
skipping_children,
|
|
713
|
+
parent,
|
|
714
|
+
depth + 1,
|
|
715
|
+
skip_count,
|
|
716
|
+
search_context,
|
|
717
|
+
),
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
assignment_combination_len = min(len(human_bone_specifications), len(bones))
|
|
721
|
+
if (
|
|
722
|
+
not search_context.require_requirement
|
|
723
|
+
or sum(int(s.requirement) for s in human_bone_specifications)
|
|
724
|
+
<= assignment_combination_len
|
|
725
|
+
):
|
|
726
|
+
spec_tuples = tuple(
|
|
727
|
+
(human_bone_specification,)
|
|
728
|
+
for human_bone_specification in human_bone_specifications
|
|
729
|
+
)
|
|
730
|
+
for spec_children, bone_children in itertools.product(
|
|
731
|
+
itertools.combinations(spec_tuples, assignment_combination_len),
|
|
732
|
+
itertools.combinations(bones, assignment_combination_len),
|
|
733
|
+
):
|
|
734
|
+
for spec_children_permutation in itertools.permutations(spec_children):
|
|
735
|
+
for bone_packings in _iter_skip_counts_and_bone_combinations(
|
|
736
|
+
skip_count,
|
|
737
|
+
tuple(
|
|
738
|
+
zip(
|
|
739
|
+
bone_children,
|
|
740
|
+
(
|
|
741
|
+
sum(s.recursive_requirement_len for s in ss)
|
|
742
|
+
* search_context.require_requirement
|
|
743
|
+
for ss in spec_children_permutation
|
|
744
|
+
),
|
|
745
|
+
)
|
|
746
|
+
),
|
|
747
|
+
):
|
|
748
|
+
children = tuple(
|
|
749
|
+
_search_structure_based_mapping_step(
|
|
750
|
+
bone_child,
|
|
751
|
+
spec_child,
|
|
752
|
+
parent,
|
|
753
|
+
depth + 1,
|
|
754
|
+
child_skip_count,
|
|
755
|
+
search_context,
|
|
756
|
+
)
|
|
757
|
+
for spec_child, (bone_child, child_skip_count) in zip(
|
|
758
|
+
spec_children_permutation, bone_packings
|
|
759
|
+
)
|
|
760
|
+
)
|
|
761
|
+
result = max(result, UnassignedSearchBranch(children=children))
|
|
762
|
+
if counter.count > search_context.max_search_count:
|
|
763
|
+
break
|
|
764
|
+
|
|
765
|
+
for special_group in (
|
|
766
|
+
(
|
|
767
|
+
HumanBoneSpecifications.LEFT_UPPER_LEG,
|
|
768
|
+
HumanBoneSpecifications.RIGHT_UPPER_LEG,
|
|
769
|
+
),
|
|
770
|
+
(
|
|
771
|
+
HumanBoneSpecifications.LEFT_UPPER_ARM,
|
|
772
|
+
HumanBoneSpecifications.RIGHT_UPPER_ARM,
|
|
773
|
+
),
|
|
774
|
+
(
|
|
775
|
+
HumanBoneSpecifications.LEFT_EYE,
|
|
776
|
+
HumanBoneSpecifications.RIGHT_EYE,
|
|
777
|
+
),
|
|
778
|
+
# (HumanBoneSpecifications.LEFT_THUMB_METACARPAL,),
|
|
779
|
+
# (HumanBoneSpecifications.RIGHT_THUMB_METACARPAL,),
|
|
780
|
+
):
|
|
781
|
+
if not all(s in human_bone_specifications for s in special_group):
|
|
782
|
+
continue
|
|
783
|
+
|
|
784
|
+
remaining_group = [
|
|
785
|
+
s for s in human_bone_specifications if s not in special_group
|
|
786
|
+
]
|
|
787
|
+
|
|
788
|
+
if remaining_group:
|
|
789
|
+
groups = (
|
|
790
|
+
special_group,
|
|
791
|
+
*tuple((remaining,) for remaining in remaining_group),
|
|
792
|
+
)
|
|
793
|
+
else:
|
|
794
|
+
groups = (special_group,)
|
|
795
|
+
|
|
796
|
+
special_assignment_combination_len = min(len(groups), len(bones))
|
|
797
|
+
if (
|
|
798
|
+
search_context.require_requirement
|
|
799
|
+
and special_assignment_combination_len
|
|
800
|
+
< sum(int(any(s.requirement for s in group)) for group in groups)
|
|
801
|
+
):
|
|
802
|
+
break
|
|
803
|
+
|
|
804
|
+
for spec_children, bone_children in itertools.product(
|
|
805
|
+
itertools.combinations(groups, special_assignment_combination_len),
|
|
806
|
+
itertools.combinations(bones, special_assignment_combination_len),
|
|
807
|
+
):
|
|
808
|
+
for spec_children_permutation in itertools.permutations(spec_children):
|
|
809
|
+
for bone_packings in _iter_skip_counts_and_bone_combinations(
|
|
810
|
+
skip_count,
|
|
811
|
+
tuple(
|
|
812
|
+
zip(
|
|
813
|
+
bone_children,
|
|
814
|
+
(
|
|
815
|
+
sum(s.recursive_requirement_len for s in ss)
|
|
816
|
+
* search_context.require_requirement
|
|
817
|
+
for ss in spec_children_permutation
|
|
818
|
+
),
|
|
819
|
+
)
|
|
820
|
+
),
|
|
821
|
+
):
|
|
822
|
+
children = tuple(
|
|
823
|
+
_search_structure_based_mapping_step(
|
|
824
|
+
bone_child,
|
|
825
|
+
spec_child,
|
|
826
|
+
parent,
|
|
827
|
+
depth + 1,
|
|
828
|
+
child_skip_count,
|
|
829
|
+
search_context,
|
|
830
|
+
)
|
|
831
|
+
for spec_child, (bone_child, child_skip_count) in zip(
|
|
832
|
+
spec_children_permutation, bone_packings
|
|
833
|
+
)
|
|
834
|
+
)
|
|
835
|
+
result = max(result, UnassignedSearchBranch(children=children))
|
|
836
|
+
if counter.count > search_context.max_search_count:
|
|
837
|
+
break
|
|
838
|
+
break
|
|
839
|
+
|
|
840
|
+
cache[cache_key] = result
|
|
841
|
+
return result
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
def _backtrack_skip_counts_and_bone_combinations(
|
|
845
|
+
*,
|
|
846
|
+
bones: tuple[tuple[NormalizedBone, int], ...],
|
|
847
|
+
counts: list[int],
|
|
848
|
+
bone_index: int,
|
|
849
|
+
remaining: int,
|
|
850
|
+
) -> Iterator[list[tuple[NormalizedBone, int]]]:
|
|
851
|
+
if bone_index == len(bones):
|
|
852
|
+
if remaining == 0:
|
|
853
|
+
yield [(bone, count) for (bone, _), count in zip(bones, counts)]
|
|
854
|
+
return
|
|
855
|
+
|
|
856
|
+
bone, requirement_len = bones[bone_index]
|
|
857
|
+
max_count = min(bone.recursive_len - requirement_len, remaining)
|
|
858
|
+
for count in range(max_count, -1, -1):
|
|
859
|
+
counts.append(count)
|
|
860
|
+
yield from _backtrack_skip_counts_and_bone_combinations(
|
|
861
|
+
bones=bones,
|
|
862
|
+
counts=counts,
|
|
863
|
+
bone_index=bone_index + 1,
|
|
864
|
+
remaining=remaining - count,
|
|
865
|
+
)
|
|
866
|
+
counts.pop()
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def _iter_skip_counts_and_bone_combinations(
|
|
870
|
+
skip_count: int, bones: tuple[tuple[NormalizedBone, int], ...]
|
|
871
|
+
) -> Iterator[list[tuple[NormalizedBone, int]]]:
|
|
872
|
+
if skip_count < 0:
|
|
873
|
+
return
|
|
874
|
+
|
|
875
|
+
if not bones:
|
|
876
|
+
if skip_count == 0:
|
|
877
|
+
yield []
|
|
878
|
+
return
|
|
879
|
+
|
|
880
|
+
total_recursive_len = sum(
|
|
881
|
+
b.recursive_len - requirement_len for b, requirement_len in bones
|
|
882
|
+
)
|
|
883
|
+
if total_recursive_len < skip_count:
|
|
884
|
+
return
|
|
885
|
+
|
|
886
|
+
yield from _backtrack_skip_counts_and_bone_combinations(
|
|
887
|
+
bones=bones,
|
|
888
|
+
counts=[],
|
|
889
|
+
bone_index=0,
|
|
890
|
+
remaining=skip_count,
|
|
891
|
+
)
|