@volter/blender-engine 0.1.148 → 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.
Files changed (245) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +4 -11
  3. package/browser/addons/vrm/LICENSE_(OPTION1)_MIT.txt +22 -0
  4. package/browser/addons/vrm/LICENSE_(OPTION2)_GPL-3_0.txt +674 -0
  5. package/browser/addons/vrm/LICENSE_MAIN.txt +1 -0
  6. package/browser/addons/vrm/README.txt +3 -0
  7. package/browser/addons/vrm/__init__.py +41 -0
  8. package/browser/addons/vrm/blender_manifest.toml +16 -0
  9. package/browser/addons/vrm/common/__init__.py +1 -0
  10. package/browser/addons/vrm/common/animation.py +58 -0
  11. package/browser/addons/vrm/common/blender_manifest.py +82 -0
  12. package/browser/addons/vrm/common/char.py +16 -0
  13. package/browser/addons/vrm/common/convert.py +287 -0
  14. package/browser/addons/vrm/common/convert_any.py +48 -0
  15. package/browser/addons/vrm/common/debug.py +40 -0
  16. package/browser/addons/vrm/common/deep.py +162 -0
  17. package/browser/addons/vrm/common/error_dialog.py +274 -0
  18. package/browser/addons/vrm/common/fs.py +71 -0
  19. package/browser/addons/vrm/common/gl.py +20 -0
  20. package/browser/addons/vrm/common/gltf.py +1149 -0
  21. package/browser/addons/vrm/common/human_bone_mapper/__init__.py +1 -0
  22. package/browser/addons/vrm/common/human_bone_mapper/biped_mapping.py +84 -0
  23. package/browser/addons/vrm/common/human_bone_mapper/cats_blender_plugin_fix_model_mapping.py +63 -0
  24. package/browser/addons/vrm/common/human_bone_mapper/human_bone_mapper.py +203 -0
  25. package/browser/addons/vrm/common/human_bone_mapper/microsoft_rocketbox_mapping.py +69 -0
  26. package/browser/addons/vrm/common/human_bone_mapper/mixamo_mapping.py +62 -0
  27. package/browser/addons/vrm/common/human_bone_mapper/mmd_mapping.py +115 -0
  28. package/browser/addons/vrm/common/human_bone_mapper/mpfb_mapping.py +120 -0
  29. package/browser/addons/vrm/common/human_bone_mapper/ready_player_me_mapping.py +65 -0
  30. package/browser/addons/vrm/common/human_bone_mapper/reallusion_mapping.py +94 -0
  31. package/browser/addons/vrm/common/human_bone_mapper/rigify_meta_rig_mapping.py +65 -0
  32. package/browser/addons/vrm/common/human_bone_mapper/structure_based_mapping.py +891 -0
  33. package/browser/addons/vrm/common/human_bone_mapper/unreal_mapping.py +62 -0
  34. package/browser/addons/vrm/common/human_bone_mapper/vrm_addon_mapping.py +112 -0
  35. package/browser/addons/vrm/common/human_bone_mapper/vroid_mapping.py +89 -0
  36. package/browser/addons/vrm/common/legacy_gltf.py +16 -0
  37. package/browser/addons/vrm/common/logger.py +84 -0
  38. package/browser/addons/vrm/common/mtoon0.blend +0 -0
  39. package/browser/addons/vrm/common/mtoon1.blend +0 -0
  40. package/browser/addons/vrm/common/mtoon1_outline.blend +0 -0
  41. package/browser/addons/vrm/common/mtoon_auto_setup.blend +0 -0
  42. package/browser/addons/vrm/common/mtoon_unversioned.py +62 -0
  43. package/browser/addons/vrm/common/ops/__init__.py +10 -0
  44. package/browser/addons/vrm/common/ops/export_scene.py +67 -0
  45. package/browser/addons/vrm/common/ops/icyp.py +53 -0
  46. package/browser/addons/vrm/common/ops/import_scene.py +57 -0
  47. package/browser/addons/vrm/common/ops/vrm.py +2380 -0
  48. package/browser/addons/vrm/common/ops/wm.py +167 -0
  49. package/browser/addons/vrm/common/preferences.py +362 -0
  50. package/browser/addons/vrm/common/progress.py +87 -0
  51. package/browser/addons/vrm/common/rotation.py +89 -0
  52. package/browser/addons/vrm/common/safe_removal.py +46 -0
  53. package/browser/addons/vrm/common/scene_watcher.py +230 -0
  54. package/browser/addons/vrm/common/shader.py +2243 -0
  55. package/browser/addons/vrm/common/shape_key_mapper/__init__.py +1 -0
  56. package/browser/addons/vrm/common/shape_key_mapper/arkit_mapping.py +168 -0
  57. package/browser/addons/vrm/common/shape_key_mapper/mmd_mapping.py +62 -0
  58. package/browser/addons/vrm/common/shape_key_mapper/ready_player_me_mapping.py +52 -0
  59. package/browser/addons/vrm/common/shape_key_mapper/vrchat_mapping.py +17 -0
  60. package/browser/addons/vrm/common/third_party_user_extension.py +214 -0
  61. package/browser/addons/vrm/common/transparent_z_write.blend +0 -0
  62. package/browser/addons/vrm/common/version.py +161 -0
  63. package/browser/addons/vrm/common/vrm0/__init__.py +1 -0
  64. package/browser/addons/vrm/common/vrm0/human_bone.py +662 -0
  65. package/browser/addons/vrm/common/vrm0/material_property.py +346 -0
  66. package/browser/addons/vrm/common/vrm1/__init__.py +1 -0
  67. package/browser/addons/vrm/common/vrm1/expression_preset.py +207 -0
  68. package/browser/addons/vrm/common/vrm1/human_bone.py +822 -0
  69. package/browser/addons/vrm/common/workspace.py +227 -0
  70. package/browser/addons/vrm/common/writable_context.py +77 -0
  71. package/browser/addons/vrm/documentation/en-us/animation/index.md +63 -0
  72. package/browser/addons/vrm/documentation/en-us/create-humanoid-vrm-from-scratch/index.md +131 -0
  73. package/browser/addons/vrm/documentation/en-us/create-simple-vrm-from-scratch/index.md +30 -0
  74. package/browser/addons/vrm/documentation/en-us/development/index.md +136 -0
  75. package/browser/addons/vrm/documentation/en-us/index.md +41 -0
  76. package/browser/addons/vrm/documentation/en-us/installation/index.md +152 -0
  77. package/browser/addons/vrm/documentation/en-us/material-mtoon/index.md +122 -0
  78. package/browser/addons/vrm/documentation/en-us/material-pbr/index.md +94 -0
  79. package/browser/addons/vrm/documentation/en-us/scripting-api/index.md +391 -0
  80. package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrm/index.md +57 -0
  81. package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrma/index.md +17 -0
  82. package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrm/index.md +61 -0
  83. package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrma/index.md +13 -0
  84. package/browser/addons/vrm/documentation/ja-jp/animation/index.md +57 -0
  85. package/browser/addons/vrm/documentation/ja-jp/create-humanoid-vrm-from-scratch/index.md +129 -0
  86. package/browser/addons/vrm/documentation/ja-jp/create-simple-vrm-from-scratch/index.md +29 -0
  87. package/browser/addons/vrm/documentation/ja-jp/development/index.md +137 -0
  88. package/browser/addons/vrm/documentation/ja-jp/index.md +37 -0
  89. package/browser/addons/vrm/documentation/ja-jp/installation/index.md +150 -0
  90. package/browser/addons/vrm/documentation/ja-jp/material-mtoon/index.md +113 -0
  91. package/browser/addons/vrm/documentation/ja-jp/material-pbr/index.md +90 -0
  92. package/browser/addons/vrm/documentation/ja-jp/scripting-api/index.md +389 -0
  93. package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrm/index.md +55 -0
  94. package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrma/index.md +19 -0
  95. package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrm/index.md +56 -0
  96. package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrma/index.md +14 -0
  97. package/browser/addons/vrm/editor/__init__.py +1 -0
  98. package/browser/addons/vrm/editor/extension.py +425 -0
  99. package/browser/addons/vrm/editor/extension_accessor.py +75 -0
  100. package/browser/addons/vrm/editor/handler.py +84 -0
  101. package/browser/addons/vrm/editor/khr_character/__init__.py +1 -0
  102. package/browser/addons/vrm/editor/khr_character/panel.py +69 -0
  103. package/browser/addons/vrm/editor/khr_character/property_group.py +27 -0
  104. package/browser/addons/vrm/editor/khr_xmp_json_ld/__init__.py +1 -0
  105. package/browser/addons/vrm/editor/khr_xmp_json_ld/ops.py +464 -0
  106. package/browser/addons/vrm/editor/khr_xmp_json_ld/panel.py +136 -0
  107. package/browser/addons/vrm/editor/khr_xmp_json_ld/property_group.py +83 -0
  108. package/browser/addons/vrm/editor/khr_xmp_json_ld/ui_list.py +198 -0
  109. package/browser/addons/vrm/editor/make_armature.py +709 -0
  110. package/browser/addons/vrm/editor/menu.py +147 -0
  111. package/browser/addons/vrm/editor/migration.py +297 -0
  112. package/browser/addons/vrm/editor/mtoon1/__init__.py +1 -0
  113. package/browser/addons/vrm/editor/mtoon1/handler.py +20 -0
  114. package/browser/addons/vrm/editor/mtoon1/migration.py +536 -0
  115. package/browser/addons/vrm/editor/mtoon1/ops.py +304 -0
  116. package/browser/addons/vrm/editor/mtoon1/panel.py +442 -0
  117. package/browser/addons/vrm/editor/mtoon1/property_group.py +4977 -0
  118. package/browser/addons/vrm/editor/mtoon1/scene_watcher.py +381 -0
  119. package/browser/addons/vrm/editor/node_constraint1/__init__.py +1 -0
  120. package/browser/addons/vrm/editor/node_constraint1/panel.py +317 -0
  121. package/browser/addons/vrm/editor/node_constraint1/property_group.py +25 -0
  122. package/browser/addons/vrm/editor/ops.py +471 -0
  123. package/browser/addons/vrm/editor/panel.py +315 -0
  124. package/browser/addons/vrm/editor/property_group.py +888 -0
  125. package/browser/addons/vrm/editor/search.py +682 -0
  126. package/browser/addons/vrm/editor/spring_bone1/__init__.py +1 -0
  127. package/browser/addons/vrm/editor/spring_bone1/handler.py +994 -0
  128. package/browser/addons/vrm/editor/spring_bone1/menu.py +246 -0
  129. package/browser/addons/vrm/editor/spring_bone1/migration.py +86 -0
  130. package/browser/addons/vrm/editor/spring_bone1/ops.py +1679 -0
  131. package/browser/addons/vrm/editor/spring_bone1/panel.py +605 -0
  132. package/browser/addons/vrm/editor/spring_bone1/property_group.py +1457 -0
  133. package/browser/addons/vrm/editor/spring_bone1/ui_list.py +408 -0
  134. package/browser/addons/vrm/editor/subscription.py +123 -0
  135. package/browser/addons/vrm/editor/t_pose.py +713 -0
  136. package/browser/addons/vrm/editor/validation.py +1271 -0
  137. package/browser/addons/vrm/editor/vrm0/__init__.py +1 -0
  138. package/browser/addons/vrm/editor/vrm0/gizmo_group.py +63 -0
  139. package/browser/addons/vrm/editor/vrm0/handler.py +23 -0
  140. package/browser/addons/vrm/editor/vrm0/menu.py +145 -0
  141. package/browser/addons/vrm/editor/vrm0/migration.py +761 -0
  142. package/browser/addons/vrm/editor/vrm0/ops.py +2145 -0
  143. package/browser/addons/vrm/editor/vrm0/panel.py +1333 -0
  144. package/browser/addons/vrm/editor/vrm0/property_group.py +1818 -0
  145. package/browser/addons/vrm/editor/vrm0/ui_list.py +688 -0
  146. package/browser/addons/vrm/editor/vrm1/__init__.py +1 -0
  147. package/browser/addons/vrm/editor/vrm1/handler.py +28 -0
  148. package/browser/addons/vrm/editor/vrm1/menu.py +76 -0
  149. package/browser/addons/vrm/editor/vrm1/migration.py +321 -0
  150. package/browser/addons/vrm/editor/vrm1/ops.py +2679 -0
  151. package/browser/addons/vrm/editor/vrm1/panel.py +1243 -0
  152. package/browser/addons/vrm/editor/vrm1/property_group.py +2087 -0
  153. package/browser/addons/vrm/editor/vrm1/scene_watcher.py +90 -0
  154. package/browser/addons/vrm/editor/vrm1/ui_list.py +533 -0
  155. package/browser/addons/vrm/exporter/__init__.py +1 -0
  156. package/browser/addons/vrm/exporter/abstract_base_vrm_exporter.py +628 -0
  157. package/browser/addons/vrm/exporter/export_scene.py +930 -0
  158. package/browser/addons/vrm/exporter/gltf2_export_user_extension.py +45 -0
  159. package/browser/addons/vrm/exporter/khr_character_exporter.py +34 -0
  160. package/browser/addons/vrm/exporter/vrm0_exporter.py +4020 -0
  161. package/browser/addons/vrm/exporter/vrm1_exporter.py +3682 -0
  162. package/browser/addons/vrm/exporter/vrm_animation_exporter.py +1035 -0
  163. package/browser/addons/vrm/external/__init__.py +1 -0
  164. package/browser/addons/vrm/external/auto_rig_pro_support.py +3 -0
  165. package/browser/addons/vrm/external/io_scene_gltf2_support.py +418 -0
  166. package/browser/addons/vrm/external/rigify_support.py +1 -0
  167. package/browser/addons/vrm/importer/__init__.py +1 -0
  168. package/browser/addons/vrm/importer/abstract_base_vrm_importer.py +1548 -0
  169. package/browser/addons/vrm/importer/file_handler.py +34 -0
  170. package/browser/addons/vrm/importer/gltf2_import_user_extension.py +268 -0
  171. package/browser/addons/vrm/importer/import_scene.py +646 -0
  172. package/browser/addons/vrm/importer/license_validation.py +198 -0
  173. package/browser/addons/vrm/importer/vrm0_importer.py +1723 -0
  174. package/browser/addons/vrm/importer/vrm1_importer.py +2014 -0
  175. package/browser/addons/vrm/importer/vrm_animation_importer.py +763 -0
  176. package/browser/addons/vrm/importer/vrm_diff.py +316 -0
  177. package/browser/addons/vrm/locale/__init__.py +1 -0
  178. package/browser/addons/vrm/locale/ja_jp.py +826 -0
  179. package/browser/addons/vrm/locale/ruff.toml +8 -0
  180. package/browser/addons/vrm/locale/translation_dictionary.py +10 -0
  181. package/browser/addons/vrm/locale/zh_hans.py +928 -0
  182. package/browser/addons/vrm/registration.py +670 -0
  183. package/browser/addons/vrm.json +18 -0
  184. package/browser/blender-emscripten-engine.mts +60 -128
  185. package/browser/blender-engine.mts +20 -186
  186. package/browser/blender-wali-engine.mts +35 -81
  187. package/browser/describe-thrown.mts +3 -6
  188. package/browser/document-chunks.mts +4 -16
  189. package/browser/gltf_export.py +3161 -0
  190. package/browser/heap-growth.mts +23 -0
  191. package/browser/index.ts +2 -0
  192. package/browser/protocol.ts +119 -35
  193. package/browser/pull-job.mts +2 -10
  194. package/browser/rna.ts +190 -73
  195. package/browser/runtime.ts +197 -82
  196. package/browser/session-addons.mts +33 -0
  197. package/browser/session-frame.mts +5 -24
  198. package/browser/session.py +1338 -1427
  199. package/browser/three/attach-presenter.ts +11 -39
  200. package/browser/three/blender-base64.ts +1 -36
  201. package/browser/three/blender-corner-normals.ts +1 -23
  202. package/browser/three/blender-display-shaders.json +1 -1
  203. package/browser/three/blender-display-shaders.lut +0 -0
  204. package/browser/three/blender-display-transform.ts +2 -1
  205. package/browser/three/blender-frame-columns.ts +5 -13
  206. package/browser/three/blender-graph-material.ts +18 -3
  207. package/browser/three/blender-node-graph.ts +10 -34
  208. package/browser/three/blender-physical-material.ts +2 -31
  209. package/browser/three/blender-runtime-armature.ts +7 -60
  210. package/browser/three/blender-runtime-frame.ts +9 -23
  211. package/browser/three/blender-runtime-geometry.ts +22 -26
  212. package/browser/three/blender-runtime-lighting.ts +48 -178
  213. package/browser/three/blender-runtime-shadows.ts +187 -1
  214. package/browser/three/blender-runtime-skeleton.ts +53 -1
  215. package/browser/three/blender-runtime-view.ts +505 -283
  216. package/browser/three/blender-runtime-weights.ts +9 -72
  217. package/browser/three/blender-sky.ts +13 -33
  218. package/browser/three/blender-triangulate.ts +7 -35
  219. package/browser/three/blender-viewport-sampling.ts +235 -0
  220. package/browser/three/blender-workbench-material.ts +25 -29
  221. package/browser/three/blender-workbench-outline.ts +199 -0
  222. package/browser/three/blender-world-volume.ts +62 -10
  223. package/browser/three/presenter.ts +10 -26
  224. package/browser/three/release.ts +5 -21
  225. package/browser/three/sky-precompute-worker.ts +1 -18
  226. package/browser/three/sky-worker.ts +5 -23
  227. package/browser/three/world-field-sampler.ts +12 -26
  228. package/browser/volter_three.py +9 -105
  229. package/browser/worker.ts +156 -151
  230. package/package.json +5 -2
  231. package/wasm/BUNDLE.json +17 -17
  232. package/wasm/DEPENDENCY-LICENSES.txt +14 -0
  233. package/wasm/blender_browser.data.br +0 -0
  234. package/wasm/blender_browser.js +1 -1
  235. package/wasm/blender_browser.wasm.br +0 -0
  236. package/browser/frame-transfer.mts +0 -19
  237. package/browser/three/agx-base-srgb.lut +0 -0
  238. package/browser/three/agx-look-medium-high-contrast.lut +0 -0
  239. package/browser/three/agx-look-punchy.lut +0 -0
  240. package/browser/three/blender-agx.ts +0 -235
  241. package/browser/three/blender-display-lut.ts +0 -145
  242. package/browser/three/blender-filmic.ts +0 -49
  243. package/browser/three/blender-standard.ts +0 -68
  244. package/browser/three/blender-world-visibility.ts +0 -278
  245. package/browser/three/filmic-srgb.lut +0 -0
@@ -0,0 +1,763 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import itertools
3
+ import math
4
+ from collections.abc import Mapping
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ import bpy
9
+ from bpy.types import Armature, Context, Object, PoseBone
10
+ from mathutils import Matrix, Quaternion, Vector
11
+
12
+ from ..common import convert
13
+ from ..common.convert import Json
14
+ from ..common.gltf import (
15
+ parse_glb,
16
+ parse_gltf_node_matrix,
17
+ read_accessor_as_animation_sampler_input,
18
+ read_accessor_as_animation_sampler_rotation_output,
19
+ read_accessor_as_animation_sampler_translation_output,
20
+ )
21
+ from ..common.logger import get_logger
22
+ from ..common.rotation import (
23
+ get_rotation_as_quaternion,
24
+ insert_rotation_keyframe,
25
+ set_rotation_without_mode_change,
26
+ )
27
+ from ..common.vrm1.human_bone import HumanBoneName
28
+ from ..common.workspace import save_workspace
29
+ from ..editor.extension_accessor import get_armature_extension
30
+ from ..editor.t_pose import setup_humanoid_t_pose
31
+
32
+ _logger = get_logger(__name__)
33
+
34
+
35
+ class VrmAnimationImporter:
36
+ @staticmethod
37
+ def execute(context: Context, path: Path, armature: Object) -> set[str]:
38
+ armature_data = armature.data
39
+ if not isinstance(armature_data, Armature):
40
+ return {"CANCELLED"}
41
+
42
+ ext = get_armature_extension(armature_data)
43
+ humanoid = ext.vrm1.humanoid
44
+ if not humanoid.human_bones.bones_are_correctly_assigned():
45
+ return {"CANCELLED"}
46
+
47
+ with (
48
+ setup_humanoid_t_pose(context, armature),
49
+ save_workspace(context, armature, mode="POSE"),
50
+ ):
51
+ bpy.ops.pose.select_all(action="DESELECT")
52
+ result = _import_vrm_animation(context, path, armature)
53
+ look_at_preview_enabled = ext.vrm1.look_at.enable_preview
54
+
55
+ ext.vrm1.look_at.enable_preview = look_at_preview_enabled
56
+ return result
57
+
58
+
59
+ def _find_root_node_index(
60
+ node_dicts: list[Json], node_index: int, skip_node_indices: set[int]
61
+ ) -> int:
62
+ for parent_node_index, node_dict in enumerate(node_dicts):
63
+ if parent_node_index in skip_node_indices:
64
+ continue
65
+ if not isinstance(node_dict, dict):
66
+ continue
67
+ child_node_indices = node_dict.get("children")
68
+ if not isinstance(child_node_indices, list):
69
+ continue
70
+ for child_node_index in child_node_indices:
71
+ if child_node_index in skip_node_indices:
72
+ continue
73
+ if child_node_index != node_index:
74
+ continue
75
+ skip_node_indices.add(node_index)
76
+ return _find_root_node_index(
77
+ node_dicts, parent_node_index, skip_node_indices
78
+ )
79
+ return node_index
80
+
81
+
82
+ def _import_vrm_animation(context: Context, path: Path, armature: Object) -> set[str]:
83
+ if not path.exists():
84
+ return {"CANCELLED"}
85
+ armature_data = armature.data
86
+ if not isinstance(armature_data, Armature):
87
+ return {"CANCELLED"}
88
+ humanoid = get_armature_extension(armature_data).vrm1.humanoid
89
+ if not humanoid.human_bones.bones_are_correctly_assigned():
90
+ return {"CANCELLED"}
91
+ look_at = get_armature_extension(armature_data).vrm1.look_at
92
+
93
+ vrma_dict, buffer0_bytes = parse_glb(path.read_bytes())
94
+
95
+ node_dicts = vrma_dict.get("nodes")
96
+ if not isinstance(node_dicts, list) or not node_dicts:
97
+ return {"CANCELLED"}
98
+
99
+ animation_dicts = vrma_dict.get("animations")
100
+ if not isinstance(animation_dicts, list) or not animation_dicts:
101
+ return {"CANCELLED"}
102
+ animation_dict = animation_dicts[0]
103
+ if not isinstance(animation_dict, dict):
104
+ return {"CANCELLED"}
105
+ animation_channel_dicts = animation_dict.get("channels")
106
+ if not isinstance(animation_channel_dicts, list) or not animation_channel_dicts:
107
+ return {"CANCELLED"}
108
+ animation_sampler_dicts = animation_dict.get("samplers")
109
+ if not isinstance(animation_sampler_dicts, list) or not animation_sampler_dicts:
110
+ return {"CANCELLED"}
111
+
112
+ extensions_dict = vrma_dict.get("extensions")
113
+ if not isinstance(extensions_dict, dict):
114
+ return {"CANCELLED"}
115
+ vrmc_vrm_animation_dict = extensions_dict.get("VRMC_vrm_animation")
116
+ if not isinstance(vrmc_vrm_animation_dict, dict):
117
+ return {"CANCELLED"}
118
+ humanoid_dict = vrmc_vrm_animation_dict.get("humanoid")
119
+ if not isinstance(humanoid_dict, dict):
120
+ return {"CANCELLED"}
121
+ human_bones_dict = humanoid_dict.get("humanBones")
122
+ if not isinstance(human_bones_dict, dict):
123
+ return {"CANCELLED"}
124
+ hips_dict = human_bones_dict.get("hips")
125
+ if not isinstance(hips_dict, dict):
126
+ return {"CANCELLED"}
127
+ hips_node_index = hips_dict.get("node")
128
+ if not isinstance(hips_node_index, int):
129
+ return {"CANCELLED"}
130
+ if not (0 <= hips_node_index < len(node_dicts)):
131
+ return {"CANCELLED"}
132
+ hips_node_dict = node_dicts[hips_node_index]
133
+ if not isinstance(hips_node_dict, dict):
134
+ return {"CANCELLED"}
135
+
136
+ expression_name_to_node_index: dict[str, int] = {}
137
+ expressions_dict = vrmc_vrm_animation_dict.get("expressions")
138
+ if isinstance(expressions_dict, dict):
139
+ preset_dict = expressions_dict.get("preset")
140
+ if isinstance(preset_dict, dict):
141
+ for name, node_dict in preset_dict.items():
142
+ if not isinstance(node_dict, dict):
143
+ continue
144
+ node_index = node_dict.get("node")
145
+ if not isinstance(node_index, int):
146
+ continue
147
+ expression_name_to_node_index[name] = node_index
148
+
149
+ custom_dict = expressions_dict.get("custom")
150
+ if isinstance(custom_dict, dict):
151
+ for name, node_dict in custom_dict.items():
152
+ if not isinstance(node_dict, dict):
153
+ continue
154
+ if name in expression_name_to_node_index:
155
+ continue
156
+ node_index = node_dict.get("node")
157
+ if not isinstance(node_index, int):
158
+ continue
159
+ expression_name_to_node_index[name] = node_index
160
+
161
+ node_index_to_human_bone_name: dict[int, HumanBoneName] = {}
162
+ for human_bone_name_str, human_bone_dict in human_bones_dict.items():
163
+ if not isinstance(human_bone_dict, dict):
164
+ continue
165
+ node_index = human_bone_dict.get("node")
166
+ if not isinstance(node_index, int):
167
+ continue
168
+ if not (0 <= node_index < len(node_dicts)):
169
+ continue
170
+ human_bone_name = HumanBoneName.from_str(human_bone_name_str)
171
+ if not human_bone_name:
172
+ continue
173
+ node_index_to_human_bone_name[node_index] = human_bone_name
174
+
175
+ root_node_index = _find_root_node_index(node_dicts, hips_node_index, set())
176
+ node_rest_pose_trees = NodeRestPoseTree.build(
177
+ node_dicts, root_node_index, is_root=True
178
+ )
179
+ if len(node_rest_pose_trees) != 1:
180
+ return {"CANCELLED"}
181
+ node_rest_pose_tree = node_rest_pose_trees[0]
182
+
183
+ accessor_dicts = vrma_dict.get("accessors")
184
+ if not isinstance(accessor_dicts, list):
185
+ return {"CANCELLED"}
186
+ buffer_view_dicts = vrma_dict.get("bufferViews")
187
+ if not isinstance(buffer_view_dicts, list):
188
+ return {"CANCELLED"}
189
+ buffer_dicts = vrma_dict.get("buffers")
190
+ if not isinstance(buffer_dicts, list):
191
+ return {"CANCELLED"}
192
+
193
+ humanoid_action = context.blend_data.actions.new(name="Humanoid")
194
+ if not armature.animation_data:
195
+ armature.animation_data_create()
196
+ armature_animation_data = armature.animation_data
197
+ if not armature_animation_data:
198
+ message = "armature.animation_data is None"
199
+ raise ValueError(message)
200
+ armature_animation_data.action = humanoid_action
201
+
202
+ expression_action = context.blend_data.actions.new(name="Expressions")
203
+ if not armature_data.animation_data:
204
+ armature_data.animation_data_create()
205
+ armature_data_animation_data = armature_data.animation_data
206
+ if not armature_data_animation_data:
207
+ message = "armature_data.animation_data is None"
208
+ raise ValueError(message)
209
+ armature_data_animation_data.action = expression_action
210
+
211
+ node_index_to_translation_keyframes: dict[
212
+ int, tuple[tuple[float, Vector], ...]
213
+ ] = {}
214
+ node_index_to_rotation_keyframes: dict[
215
+ int, tuple[tuple[float, Quaternion], ...]
216
+ ] = {}
217
+
218
+ for animation_channel_dict in animation_channel_dicts:
219
+ if not isinstance(animation_channel_dict, dict):
220
+ continue
221
+ target_dict = animation_channel_dict.get("target")
222
+ if not isinstance(target_dict, dict):
223
+ continue
224
+ node_index = target_dict.get("node")
225
+ if not isinstance(node_index, int):
226
+ continue
227
+ if not (0 <= node_index < len(node_dicts)):
228
+ continue
229
+ animation_path = target_dict.get("path")
230
+ animation_sampler_index = animation_channel_dict.get("sampler")
231
+ if not isinstance(animation_sampler_index, int):
232
+ continue
233
+ if not (0 <= animation_sampler_index < len(animation_sampler_dicts)):
234
+ continue
235
+ animation_sampler_dict = animation_sampler_dicts[animation_sampler_index]
236
+ if not isinstance(animation_sampler_dict, dict):
237
+ continue
238
+
239
+ input_index = animation_sampler_dict.get("input")
240
+ if not isinstance(input_index, int):
241
+ continue
242
+ if not (0 <= input_index < len(accessor_dicts)):
243
+ continue
244
+ input_accessor_dict = accessor_dicts[input_index]
245
+ if not isinstance(input_accessor_dict, dict):
246
+ continue
247
+
248
+ output_index = animation_sampler_dict.get("output")
249
+ if not isinstance(output_index, int):
250
+ continue
251
+ if not (0 <= output_index < len(accessor_dicts)):
252
+ continue
253
+ output_accessor_dict = accessor_dicts[output_index]
254
+ if not isinstance(output_accessor_dict, dict):
255
+ continue
256
+
257
+ if animation_path == "translation":
258
+ translation_timestamps = read_accessor_as_animation_sampler_input(
259
+ input_accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
260
+ )
261
+ if translation_timestamps is None:
262
+ continue
263
+ translations = read_accessor_as_animation_sampler_translation_output(
264
+ output_accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
265
+ )
266
+ if translations is None:
267
+ continue
268
+ translation_keyframes = tuple(
269
+ sorted(zip(translation_timestamps, translations))
270
+ )
271
+ node_index_to_translation_keyframes[node_index] = translation_keyframes
272
+ elif animation_path == "rotation":
273
+ rotation_timestamps = read_accessor_as_animation_sampler_input(
274
+ input_accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
275
+ )
276
+ if rotation_timestamps is None:
277
+ continue
278
+ rotations = read_accessor_as_animation_sampler_rotation_output(
279
+ output_accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
280
+ )
281
+ if rotations is None:
282
+ continue
283
+ rotation_keyframes = tuple(sorted(zip(rotation_timestamps, rotations)))
284
+ node_index_to_rotation_keyframes[node_index] = rotation_keyframes
285
+
286
+ expression_name_to_default_preview_value: dict[str, float] = {}
287
+ expression_name_to_translation_keyframes: dict[
288
+ str, tuple[tuple[float, Vector], ...]
289
+ ] = {}
290
+ for expression_name, node_index in expression_name_to_node_index.items():
291
+ if not (0 <= node_index < len(node_dicts)):
292
+ continue
293
+ node_dict = node_dicts[node_index]
294
+ if not isinstance(node_dict, dict):
295
+ continue
296
+
297
+ # https://github.com/vrm-c/vrm-specification/blob/3942748efbc803b258e288e0f6c993c6bb96cebf/specification/VRMC_vrm_animation-1.0/README.md?plain=1#L163-L164
298
+ translation = node_dict.get("translation")
299
+ if isinstance(translation, list) and translation:
300
+ default_preview_value = translation[0]
301
+ if not isinstance(default_preview_value, (float, int)):
302
+ default_preview_value = 0.0
303
+ else:
304
+ default_preview_value = 0.0
305
+ expression_name_to_default_preview_value[expression_name] = (
306
+ default_preview_value
307
+ )
308
+
309
+ expression_translation_keyframes = node_index_to_translation_keyframes.get(
310
+ node_index
311
+ )
312
+ if expression_translation_keyframes is None:
313
+ continue
314
+ expression_name_to_translation_keyframes[expression_name] = (
315
+ expression_translation_keyframes
316
+ )
317
+
318
+ timestamps: list[float] = sorted(
319
+ itertools.chain(
320
+ (
321
+ timestamp
322
+ for keyframes in node_index_to_translation_keyframes.values()
323
+ for (timestamp, _) in keyframes
324
+ ),
325
+ (
326
+ timestamp
327
+ for keyframes in node_index_to_rotation_keyframes.values()
328
+ for (timestamp, _) in keyframes
329
+ ),
330
+ )
331
+ )
332
+ if not timestamps:
333
+ return {"CANCELLED"}
334
+
335
+ first_timestamp = timestamps[0]
336
+ last_timestamp = timestamps[-1]
337
+
338
+ _logger.debug(
339
+ "first_timestamp=%s ... last_timestamp=%s",
340
+ first_timestamp,
341
+ last_timestamp,
342
+ )
343
+
344
+ look_at_target_object = None
345
+ look_at_translation_keyframes = None
346
+ look_at_dict = vrmc_vrm_animation_dict.get("lookAt")
347
+ if isinstance(look_at_dict, dict):
348
+ look_at_target_node_index = look_at_dict.get("node")
349
+ if isinstance(
350
+ look_at_target_node_index, int
351
+ ) and 0 <= look_at_target_node_index < len(node_dicts):
352
+ look_at_translation_keyframes = node_index_to_translation_keyframes.get(
353
+ look_at_target_node_index
354
+ )
355
+ look_at_target_node_dict = node_dicts[look_at_target_node_index]
356
+ if look_at_translation_keyframes and isinstance(
357
+ look_at_target_node_dict, dict
358
+ ):
359
+ look_at_target_translation = convert.float3_or_none(
360
+ look_at_target_node_dict.get("translation")
361
+ )
362
+ look_at_target_name = look_at_target_node_dict.get("name")
363
+ if not isinstance(look_at_target_name, str) or not look_at_target_name:
364
+ look_at_target_name = "LookAtTarget"
365
+ if look_at_target_translation is not None:
366
+ look_at_target_object = context.blend_data.objects.new(
367
+ name=look_at_target_name, object_data=None
368
+ )
369
+ look_at_target_object.empty_display_size = 0.125
370
+ x, y, z = look_at_target_translation
371
+ look_at_target_object.location = Vector((x, -z, y))
372
+ context.scene.collection.objects.link(look_at_target_object)
373
+ look_at.enable_preview = True
374
+ look_at.preview_target_bpy_object = look_at_target_object
375
+
376
+ first_zero_origin_frame_count: int = math.floor(
377
+ first_timestamp * context.scene.render.fps / context.scene.render.fps_base
378
+ )
379
+ if abs(last_timestamp - first_timestamp) > 0:
380
+ last_zero_origin_frame_count: int = math.ceil(
381
+ last_timestamp * context.scene.render.fps / context.scene.render.fps_base
382
+ )
383
+ else:
384
+ last_zero_origin_frame_count = first_zero_origin_frame_count
385
+
386
+ human_bone_name_to_human_bone = humanoid.human_bones.human_bone_name_to_human_bone()
387
+ node_index_to_pose_bone = {
388
+ node_index: bone
389
+ for node_index, human_bone_name in node_index_to_human_bone_name.items()
390
+ if human_bone_name not in {HumanBoneName.LEFT_EYE, HumanBoneName.RIGHT_EYE}
391
+ and (human_bone := human_bone_name_to_human_bone.get(human_bone_name))
392
+ and (bone := armature.pose.bones.get(human_bone.node.bone_name))
393
+ }
394
+ for zero_origin_frame_count in range(
395
+ first_zero_origin_frame_count, last_zero_origin_frame_count + 1
396
+ ):
397
+ timestamp = (
398
+ zero_origin_frame_count
399
+ * context.scene.render.fps_base
400
+ / context.scene.render.fps
401
+ )
402
+ frame_count = zero_origin_frame_count + 1
403
+
404
+ _assign_humanoid_keyframe(
405
+ armature,
406
+ node_index_to_pose_bone,
407
+ node_rest_pose_tree,
408
+ node_index_to_human_bone_name,
409
+ node_index_to_translation_keyframes,
410
+ node_index_to_rotation_keyframes,
411
+ frame_count,
412
+ timestamp,
413
+ humanoid_parent_rest_world_matrix=Matrix(),
414
+ intermediate_rest_local_matrix=Matrix(),
415
+ intermediate_pose_local_matrix=Matrix(),
416
+ )
417
+ _assign_expression_keyframe(
418
+ armature_data,
419
+ expression_name_to_default_preview_value,
420
+ expression_name_to_translation_keyframes,
421
+ frame_count,
422
+ timestamp,
423
+ )
424
+ if look_at_target_object and look_at_translation_keyframes:
425
+ _assign_look_at_keyframe(
426
+ look_at_target_object,
427
+ look_at_translation_keyframes,
428
+ frame_count,
429
+ timestamp,
430
+ )
431
+
432
+ # This process should be done last because bone state changes occur.
433
+ # If translation is assigned to hips and hips is connected to parent
434
+ # with "use_connect", the movement animation will not be reflected, so
435
+ # we need to disconnect it
436
+ if node_index_to_translation_keyframes.get(hips_node_index):
437
+ with save_workspace(context, armature, mode="EDIT"):
438
+ armature_data = armature.data
439
+ if not isinstance(armature_data, Armature):
440
+ raise TypeError
441
+ hips_bone = armature_data.edit_bones.get(
442
+ humanoid.human_bones.hips.node.bone_name
443
+ )
444
+ if hips_bone and hips_bone.use_connect:
445
+ hips_bone.use_connect = False
446
+ return {"FINISHED"}
447
+
448
+
449
+ def _assign_look_at_keyframe(
450
+ look_at_target_object: Object,
451
+ translation_keyframes: tuple[tuple[float, Vector], ...],
452
+ frame_count: int,
453
+ timestamp: float,
454
+ ) -> None:
455
+ if not translation_keyframes:
456
+ return
457
+
458
+ animation_translation = None
459
+ begin_timestamp, begin_translation = translation_keyframes[0]
460
+ for end_timestamp, end_translation in translation_keyframes:
461
+ if end_timestamp >= timestamp:
462
+ timestamp_duration = end_timestamp - begin_timestamp
463
+ if timestamp_duration > 0:
464
+ animation_translation = (
465
+ begin_translation
466
+ + (end_translation - begin_translation)
467
+ * (timestamp - begin_timestamp)
468
+ / timestamp_duration
469
+ )
470
+ else:
471
+ animation_translation = begin_translation
472
+ break
473
+ begin_timestamp = end_timestamp
474
+ begin_translation = end_translation
475
+ if animation_translation is None:
476
+ animation_translation = begin_translation
477
+
478
+ current_location = look_at_target_object.location.copy()
479
+ look_at_target_object.location = animation_translation
480
+ look_at_target_object.keyframe_insert(data_path="location", frame=frame_count)
481
+ look_at_target_object.location = current_location
482
+
483
+
484
+ def _assign_expression_keyframe(
485
+ armature_data: Armature,
486
+ expression_name_to_default_preview_value: dict[str, float],
487
+ expression_name_to_translation_keyframes: dict[
488
+ str, tuple[tuple[float, Vector], ...]
489
+ ],
490
+ frame_count: int,
491
+ timestamp: float,
492
+ ) -> None:
493
+ expressions = get_armature_extension(armature_data).vrm1.expressions
494
+ expression_name_to_expression = expressions.all_name_to_expression_dict()
495
+ for (
496
+ expression_name,
497
+ translation_keyframes,
498
+ ) in expression_name_to_translation_keyframes.items():
499
+ if expression_name in {
500
+ "lookUp",
501
+ "lookDown",
502
+ "lookLeft",
503
+ "lookRight",
504
+ }:
505
+ continue
506
+ expression = expression_name_to_expression.get(expression_name)
507
+ if not expression:
508
+ continue
509
+
510
+ if translation_keyframes:
511
+ preview = None
512
+ begin_timestamp, begin_translation = translation_keyframes[0]
513
+ for end_timestamp, end_translation in translation_keyframes:
514
+ if end_timestamp >= timestamp:
515
+ timestamp_duration = end_timestamp - begin_timestamp
516
+ if timestamp_duration > 0:
517
+ preview = (
518
+ begin_translation.x
519
+ + (end_translation.x - begin_translation.x)
520
+ * (timestamp - begin_timestamp)
521
+ / timestamp_duration
522
+ )
523
+ else:
524
+ preview = begin_translation.x
525
+ break
526
+ begin_timestamp = end_timestamp
527
+ begin_translation = end_translation
528
+ if preview is None:
529
+ preview = begin_translation.x
530
+ else:
531
+ preview = (
532
+ expression_name_to_default_preview_value.get(expression_name) or 0.0
533
+ )
534
+
535
+ current_preview = expression.preview
536
+ expression.preview = preview
537
+ expression.keyframe_insert(data_path="preview", frame=frame_count)
538
+ expression.preview = current_preview
539
+
540
+
541
+ @dataclass(frozen=True)
542
+ class NodeRestPoseTree:
543
+ node_index: int
544
+ local_matrix: Matrix
545
+ children: tuple["NodeRestPoseTree", ...]
546
+ is_root: bool
547
+
548
+ @staticmethod
549
+ def build(
550
+ node_dicts: list[Json],
551
+ node_index: int,
552
+ *,
553
+ is_root: bool,
554
+ ) -> list["NodeRestPoseTree"]:
555
+ if not (0 <= node_index < len(node_dicts)):
556
+ return []
557
+ node_dict = node_dicts[node_index]
558
+ if not isinstance(node_dict, dict):
559
+ return []
560
+
561
+ parsed_matrix = parse_gltf_node_matrix(node_dict)
562
+ parsed_translation, parsed_rotation, parsed_scale = parsed_matrix.decompose()
563
+
564
+ translation = Vector(
565
+ (parsed_translation.x, -parsed_translation.z, parsed_translation.y)
566
+ )
567
+ rotation = Quaternion(
568
+ (
569
+ parsed_rotation.w,
570
+ parsed_rotation.x,
571
+ -parsed_rotation.z,
572
+ parsed_rotation.y,
573
+ )
574
+ ).normalized()
575
+ scale = Vector((parsed_scale.x, parsed_scale.z, parsed_scale.y))
576
+
577
+ local_matrix = (
578
+ Matrix.Translation(translation)
579
+ @ rotation.to_matrix().to_4x4()
580
+ @ Matrix.Diagonal(scale).to_4x4()
581
+ )
582
+
583
+ child_indices = node_dict.get("children")
584
+ if isinstance(child_indices, list):
585
+ children = tuple(
586
+ itertools.chain(
587
+ *(
588
+ NodeRestPoseTree.build(node_dicts, child_index, is_root=False)
589
+ for child_index in child_indices
590
+ if isinstance(child_index, int)
591
+ )
592
+ )
593
+ )
594
+ else:
595
+ children = ()
596
+
597
+ return [
598
+ NodeRestPoseTree(
599
+ node_index=node_index,
600
+ local_matrix=local_matrix,
601
+ children=children,
602
+ is_root=is_root,
603
+ )
604
+ ]
605
+
606
+
607
+ def _assign_humanoid_keyframe(
608
+ armature: Object,
609
+ node_index_to_pose_bone: Mapping[int, PoseBone],
610
+ node_rest_pose_tree: NodeRestPoseTree,
611
+ node_index_to_human_bone_name: Mapping[int, HumanBoneName],
612
+ node_index_to_translation_keyframes: Mapping[int, tuple[tuple[float, Vector], ...]],
613
+ node_index_to_rotation_keyframes: Mapping[
614
+ int, tuple[tuple[float, Quaternion], ...]
615
+ ],
616
+ frame_count: int,
617
+ timestamp: float,
618
+ humanoid_parent_rest_world_matrix: Matrix,
619
+ intermediate_rest_local_matrix: Matrix,
620
+ intermediate_pose_local_matrix: Matrix,
621
+ ) -> None:
622
+ translation_keyframes = node_index_to_translation_keyframes.get(
623
+ node_rest_pose_tree.node_index
624
+ )
625
+
626
+ if translation_keyframes:
627
+ keyframe_translation = None
628
+ begin_timestamp, begin_translation = translation_keyframes[0]
629
+ for end_timestamp, end_translation in translation_keyframes:
630
+ if end_timestamp >= timestamp:
631
+ timestamp_duration = end_timestamp - begin_timestamp
632
+ if timestamp_duration > 0:
633
+ keyframe_translation = begin_translation.lerp(
634
+ end_translation,
635
+ (timestamp - begin_timestamp) / timestamp_duration,
636
+ )
637
+ else:
638
+ keyframe_translation = begin_translation
639
+ break
640
+ begin_timestamp = end_timestamp
641
+ begin_translation = end_translation
642
+ if keyframe_translation is None:
643
+ keyframe_translation = begin_translation
644
+ else:
645
+ keyframe_translation = node_rest_pose_tree.local_matrix.to_translation()
646
+
647
+ rotation_keyframes = node_index_to_rotation_keyframes.get(
648
+ node_rest_pose_tree.node_index
649
+ )
650
+ if rotation_keyframes:
651
+ keyframe_rotation = None
652
+ begin_timestamp, begin_rotation = rotation_keyframes[0]
653
+ for end_timestamp, end_rotation in rotation_keyframes:
654
+ if end_timestamp >= timestamp:
655
+ timestamp_duration = end_timestamp - begin_timestamp
656
+ if timestamp_duration > 0:
657
+ keyframe_rotation = begin_rotation.slerp(
658
+ end_rotation, (timestamp - begin_timestamp) / timestamp_duration
659
+ )
660
+ else:
661
+ keyframe_rotation = begin_rotation
662
+ break
663
+ begin_timestamp = end_timestamp
664
+ begin_rotation = end_rotation
665
+ if keyframe_rotation is None:
666
+ keyframe_rotation = begin_rotation
667
+ else:
668
+ keyframe_rotation = node_rest_pose_tree.local_matrix.to_quaternion()
669
+
670
+ humanoid_rest_world_matrix = humanoid_parent_rest_world_matrix
671
+ rest_local_matrix = (
672
+ intermediate_rest_local_matrix @ node_rest_pose_tree.local_matrix
673
+ )
674
+ pose_local_matrix = (
675
+ intermediate_pose_local_matrix
676
+ @ Matrix.Translation(keyframe_translation)
677
+ @ keyframe_rotation.to_matrix().to_4x4()
678
+ @ Matrix.Diagonal(node_rest_pose_tree.local_matrix.to_scale()).to_4x4()
679
+ )
680
+
681
+ human_bone_name = node_index_to_human_bone_name.get(node_rest_pose_tree.node_index)
682
+ bone = node_index_to_pose_bone.get(node_rest_pose_tree.node_index)
683
+ if bone:
684
+ rest_world_matrix = humanoid_rest_world_matrix @ rest_local_matrix
685
+ pose_world_matrix = humanoid_rest_world_matrix @ pose_local_matrix
686
+ rest_to_pose_matrix = rest_local_matrix.inverted() @ pose_local_matrix
687
+ axis, angle = rest_to_pose_matrix.to_quaternion().to_axis_angle()
688
+ axis.rotate(rest_world_matrix.to_quaternion())
689
+ rest_to_pose_world_rotation = Quaternion(axis, angle).copy()
690
+
691
+ target_axis, target_angle = rest_to_pose_world_rotation.to_axis_angle()
692
+ target_world_rotation = (armature.matrix_world @ bone.matrix).to_quaternion()
693
+ target_axis.rotate(target_world_rotation.inverted())
694
+
695
+ rest_to_pose_target_local_rotation = Quaternion(
696
+ target_axis, target_angle
697
+ ).copy()
698
+
699
+ if rotation_keyframes:
700
+ backup_rotation_quaternion = get_rotation_as_quaternion(bone)
701
+ set_rotation_without_mode_change(
702
+ bone, backup_rotation_quaternion @ rest_to_pose_target_local_rotation
703
+ )
704
+ insert_rotation_keyframe(bone, frame=frame_count)
705
+ set_rotation_without_mode_change(bone, backup_rotation_quaternion)
706
+
707
+ if human_bone_name == HumanBoneName.HIPS and translation_keyframes:
708
+ world_translation = (
709
+ pose_world_matrix.to_translation() - rest_world_matrix.to_translation()
710
+ )
711
+
712
+ # https://github.com/vrm-c/vrm-specification/blob/3942748efbc803b258e288e0f6c993c6bb96cebf/specification/VRMC_vrm_animation-1.0/how_to_transform_human_pose.md?plain=1#L116-L123
713
+ rest_world_translation_z = rest_world_matrix.to_translation().z
714
+ if abs(rest_world_translation_z) > 0:
715
+ # Measure height relative to the armature origin so moving the
716
+ # object in the scene does not change the amount of motion.
717
+ world_height_ratio = (
718
+ armature.matrix_world.to_3x3() @ bone.matrix.to_translation()
719
+ ).z / rest_world_translation_z
720
+ world_translation *= world_height_ratio
721
+
722
+ # Location channels use the rest axes and parent pose, not the bone's
723
+ # own pose rotation or scale. Let Blender handle that conversion,
724
+ # including the Local Location and Inherit Scale settings.
725
+ translated_pose_matrix = bone.matrix.copy()
726
+ translated_pose_matrix.translation += (
727
+ armature.matrix_world.inverted_safe().to_3x3() @ world_translation
728
+ )
729
+ parent_bone = bone.parent
730
+ translated_basis_matrix = bone.bone.convert_local_to_pose(
731
+ translated_pose_matrix,
732
+ bone.bone.matrix_local,
733
+ parent_matrix=parent_bone.matrix if parent_bone else Matrix(),
734
+ parent_matrix_local=(
735
+ parent_bone.bone.matrix_local if parent_bone else Matrix()
736
+ ),
737
+ invert=True,
738
+ )
739
+ backup_translation = bone.location.copy()
740
+ bone.location = translated_basis_matrix.to_translation()
741
+ bone.keyframe_insert(data_path="location", frame=frame_count)
742
+ bone.location = backup_translation
743
+
744
+ humanoid_rest_world_matrix = (
745
+ humanoid_parent_rest_world_matrix @ rest_local_matrix
746
+ )
747
+ rest_local_matrix = Matrix()
748
+ pose_local_matrix = Matrix()
749
+
750
+ for child in node_rest_pose_tree.children:
751
+ _assign_humanoid_keyframe(
752
+ armature,
753
+ node_index_to_pose_bone,
754
+ child,
755
+ node_index_to_human_bone_name,
756
+ node_index_to_translation_keyframes,
757
+ node_index_to_rotation_keyframes,
758
+ frame_count,
759
+ timestamp,
760
+ humanoid_rest_world_matrix,
761
+ rest_local_matrix,
762
+ pose_local_matrix,
763
+ )