@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,2014 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import json
3
+ from collections.abc import Mapping, Sequence
4
+ from sys import float_info
5
+ from types import MappingProxyType
6
+ from typing import Optional, Union
7
+
8
+ import bpy
9
+ from bpy.types import (
10
+ Armature,
11
+ Bone,
12
+ Context,
13
+ CopyRotationConstraint,
14
+ DampedTrackConstraint,
15
+ EditBone,
16
+ Image,
17
+ Material,
18
+ Mesh,
19
+ Object,
20
+ PoseBone,
21
+ )
22
+ from mathutils import Matrix, Quaternion, Vector
23
+
24
+ from ..common import convert, shader
25
+ from ..common.convert import Json, JsonView
26
+ from ..common.deep import make_json_view
27
+ from ..common.logger import get_logger
28
+ from ..common.preferences import ImportPreferencesProtocol, get_preferences
29
+ from ..common.progress import PartialProgress
30
+ from ..common.third_party_user_extension import (
31
+ collect_third_party_user_extensions,
32
+ trigger_third_party_user_extension_hook,
33
+ )
34
+ from ..common.version import get_addon_version
35
+ from ..common.vrm1.human_bone import HumanBoneName, HumanBoneSpecifications
36
+ from ..editor import make_armature, migration
37
+ from ..editor.extension import (
38
+ VrmAddonBoneExtensionPropertyGroup as BoneExtension,
39
+ )
40
+ from ..editor.extension_accessor import (
41
+ get_armature_extension,
42
+ get_bone_extension,
43
+ get_material_extension,
44
+ get_object_extension,
45
+ )
46
+ from ..editor.make_armature import (
47
+ connect_parent_tail_and_child_head_if_very_close_position,
48
+ )
49
+ from ..editor.mtoon1.property_group import (
50
+ Mtoon1KhrTextureTransformPropertyGroup,
51
+ Mtoon1SamplerPropertyGroup,
52
+ Mtoon1TextureInfoPropertyGroup,
53
+ Mtoon1TexturePropertyGroup,
54
+ )
55
+ from ..editor.spring_bone1.property_group import (
56
+ SpringBone1ColliderPropertyGroup,
57
+ SpringBone1JointPropertyGroup,
58
+ SpringBone1SpringBonePropertyGroup,
59
+ )
60
+ from ..editor.vrm0.property_group import Vrm0HumanoidPropertyGroup
61
+ from ..editor.vrm1.property_group import (
62
+ Vrm1ExpressionPropertyGroup,
63
+ Vrm1ExpressionsPropertyGroup,
64
+ Vrm1FirstPersonPropertyGroup,
65
+ Vrm1HumanBonesPropertyGroup,
66
+ Vrm1HumanoidPropertyGroup,
67
+ Vrm1LookAtPropertyGroup,
68
+ Vrm1MaterialColorBindPropertyGroup,
69
+ Vrm1MetaPropertyGroup,
70
+ )
71
+ from .abstract_base_vrm_importer import AbstractBaseVrmImporter, ParseResult
72
+
73
+ _logger = get_logger(__name__)
74
+
75
+
76
+ class Vrm1Importer(AbstractBaseVrmImporter):
77
+ def __init__(
78
+ self,
79
+ context: Context,
80
+ parse_result: ParseResult,
81
+ preferences: ImportPreferencesProtocol,
82
+ ) -> None:
83
+ super().__init__(context, parse_result, preferences)
84
+ self._third_party_user_extensions: Sequence[object] = [
85
+ third_party_user_extension
86
+ for _, third_party_user_extension in collect_third_party_user_extensions(
87
+ context, "Vrm1ImportUserExtension"
88
+ )
89
+ ]
90
+
91
+ @staticmethod
92
+ def assign_texture_colorspace(image: Image, preferred_colorspace: str) -> None:
93
+ colorspaces = [preferred_colorspace]
94
+ if preferred_colorspace == "Non-Color":
95
+ # https://github.com/saturday06/VRM-Addon-for-Blender/issues/336#issuecomment-1760729404
96
+ colorspaces.extend(["Linear", "Generic Data"])
97
+
98
+ colorspace_settings = image.colorspace_settings
99
+ exceptions: list[Exception] = []
100
+ for colorspace in colorspaces:
101
+ # The values that can be set in colorspace_settings.name vary
102
+ # depending on the Blender setup. For example, bpy 3.6.0 from pypi
103
+ # can only choose Linear or sRGB.
104
+ # To detect this, we would ideally want to reference
105
+ # colorspace_settings.bl_rna.properties.get("name").enum_items
106
+ # etc., but it cannot be used because it crashes in Blender 2.93 etc.
107
+ # So we catch exceptions and try until assignment succeeds.
108
+ try:
109
+ colorspace_settings.name = colorspace
110
+ except TypeError as e:
111
+ exceptions.append(e)
112
+ else:
113
+ return
114
+
115
+ _logger.error(
116
+ "image.colorspace_settings.name doesn't support %s:\n%s",
117
+ colorspaces,
118
+ "\n".join(map(str, exceptions)),
119
+ )
120
+
121
+ def assign_texture(
122
+ self,
123
+ texture: Mtoon1TexturePropertyGroup,
124
+ texture_dict: dict[str, Json],
125
+ ) -> None:
126
+ source = texture_dict.get("source")
127
+ if isinstance(source, int):
128
+ image = self._images.get(source)
129
+ if image:
130
+ texture.source = image
131
+ self.assign_texture_colorspace(image, texture.colorspace)
132
+
133
+ sampler = texture_dict.get("sampler")
134
+ samplers = self._parse_result.json_dict.get("samplers")
135
+ if not isinstance(sampler, int) or not isinstance(samplers, list):
136
+ return
137
+ if not (0 <= sampler < len(samplers)):
138
+ return
139
+
140
+ sampler_dict = samplers[sampler]
141
+ if not isinstance(sampler_dict, dict):
142
+ return
143
+
144
+ mag_filter = sampler_dict.get("magFilter")
145
+ if isinstance(mag_filter, int):
146
+ texture.sampler.mag_filter = (
147
+ Mtoon1SamplerPropertyGroup.mag_filter_enum.value_to_identifier(
148
+ mag_filter, Mtoon1SamplerPropertyGroup.MAG_FILTER_DEFAULT.identifier
149
+ )
150
+ )
151
+
152
+ min_filter = sampler_dict.get("minFilter")
153
+ if isinstance(min_filter, int):
154
+ texture.sampler.min_filter = (
155
+ Mtoon1SamplerPropertyGroup.min_filter_enum.value_to_identifier(
156
+ min_filter, Mtoon1SamplerPropertyGroup.MIN_FILTER_DEFAULT.identifier
157
+ )
158
+ )
159
+
160
+ wrap_s = sampler_dict.get("wrapS")
161
+ if isinstance(wrap_s, int):
162
+ texture.sampler.wrap_s = (
163
+ Mtoon1SamplerPropertyGroup.wrap_enum.value_to_identifier(
164
+ wrap_s, Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
165
+ )
166
+ )
167
+
168
+ wrap_t = sampler_dict.get("wrapT")
169
+ if isinstance(wrap_t, int):
170
+ texture.sampler.wrap_t = (
171
+ Mtoon1SamplerPropertyGroup.wrap_enum.value_to_identifier(
172
+ wrap_t, Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
173
+ )
174
+ )
175
+
176
+ def assign_khr_texture_transform(
177
+ self,
178
+ khr_texture_transform: Mtoon1KhrTextureTransformPropertyGroup,
179
+ khr_texture_transform_dict: dict[str, Json],
180
+ ) -> None:
181
+ offset = convert.float2_or_none(khr_texture_transform_dict.get("offset"))
182
+ if offset:
183
+ khr_texture_transform.offset = offset
184
+
185
+ scale = convert.float2_or_none(khr_texture_transform_dict.get("scale"))
186
+ if scale:
187
+ khr_texture_transform.scale = scale
188
+
189
+ def assign_texture_info(
190
+ self,
191
+ texture_info: Mtoon1TextureInfoPropertyGroup,
192
+ texture_info_dict: dict[str, Json],
193
+ ) -> None:
194
+ index = texture_info_dict.get("index")
195
+ if isinstance(index, int):
196
+ texture_dicts = self._parse_result.json_dict.get("textures")
197
+ if isinstance(texture_dicts, list) and 0 <= index < len(texture_dicts):
198
+ texture_dict = texture_dicts[index]
199
+ if isinstance(texture_dict, dict):
200
+ self.assign_texture(texture_info.index, texture_dict)
201
+
202
+ extensions_dict = texture_info_dict.get("extensions")
203
+ if not isinstance(extensions_dict, dict):
204
+ return
205
+
206
+ khr_texture_transform_dict = extensions_dict.get("KHR_texture_transform")
207
+ if not isinstance(khr_texture_transform_dict, dict):
208
+ return
209
+
210
+ self.assign_khr_texture_transform(
211
+ texture_info.extensions.khr_texture_transform,
212
+ khr_texture_transform_dict,
213
+ )
214
+
215
+ def make_mtoon1_material(
216
+ self, material_index: int, gltf_dict: dict[str, Json]
217
+ ) -> None:
218
+ extensions_dict = gltf_dict.get("extensions")
219
+ if not isinstance(extensions_dict, dict):
220
+ return
221
+
222
+ mtoon_dict = extensions_dict.get("VRMC_materials_mtoon")
223
+ if not isinstance(mtoon_dict, dict):
224
+ return
225
+
226
+ material = self._materials.get(material_index)
227
+ if not material:
228
+ name = gltf_dict.get("name")
229
+ if not isinstance(name, str):
230
+ name = "Material"
231
+ material = self._context.blend_data.materials.new(name)
232
+ self.reset_material(material)
233
+ material.use_backface_culling = True
234
+
235
+ gltf = get_material_extension(material).mtoon1
236
+ gltf.addon_version = get_addon_version()
237
+ gltf.enabled = True
238
+ mtoon = gltf.extensions.vrmc_materials_mtoon
239
+
240
+ pbr_metallic_roughness_dict = gltf_dict.get("pbrMetallicRoughness")
241
+ if isinstance(pbr_metallic_roughness_dict, dict):
242
+ base_color_factor = shader.rgba_or_none(
243
+ pbr_metallic_roughness_dict.get("baseColorFactor")
244
+ )
245
+ if base_color_factor:
246
+ gltf.pbr_metallic_roughness.base_color_factor = base_color_factor
247
+
248
+ base_color_texture_dict = pbr_metallic_roughness_dict.get(
249
+ "baseColorTexture"
250
+ )
251
+ if isinstance(base_color_texture_dict, dict):
252
+ self.assign_texture_info(
253
+ gltf.pbr_metallic_roughness.base_color_texture,
254
+ base_color_texture_dict,
255
+ )
256
+
257
+ alpha_mode = gltf_dict.get("alphaMode")
258
+ if (
259
+ isinstance(alpha_mode, str)
260
+ and alpha_mode in gltf.alpha_mode_enum.identifiers()
261
+ ):
262
+ gltf.alpha_mode = alpha_mode
263
+
264
+ alpha_cutoff = gltf_dict.get("alphaCutoff")
265
+ if isinstance(alpha_cutoff, (float, int)):
266
+ gltf.alpha_cutoff = float(alpha_cutoff)
267
+
268
+ double_sided = gltf_dict.get("doubleSided")
269
+ if isinstance(double_sided, bool):
270
+ gltf.double_sided = double_sided
271
+
272
+ normal_texture_dict = gltf_dict.get("normalTexture")
273
+ if isinstance(normal_texture_dict, dict):
274
+ self.assign_texture_info(
275
+ gltf.normal_texture,
276
+ normal_texture_dict,
277
+ )
278
+ normal_texture_scale = normal_texture_dict.get("scale")
279
+ if isinstance(normal_texture_scale, (float, int)):
280
+ gltf.normal_texture.scale = float(normal_texture_scale)
281
+
282
+ emissive_factor = shader.rgb_or_none(gltf_dict.get("emissiveFactor"))
283
+ if emissive_factor:
284
+ gltf.emissive_factor = emissive_factor
285
+
286
+ emissive_texture_dict = gltf_dict.get("emissiveTexture")
287
+ if isinstance(emissive_texture_dict, dict):
288
+ self.assign_texture_info(
289
+ gltf.emissive_texture,
290
+ emissive_texture_dict,
291
+ )
292
+
293
+ emissive_strength_dict = extensions_dict.get("KHR_materials_emissive_strength")
294
+ if isinstance(emissive_strength_dict, dict):
295
+ emissive_strength = convert.float_or_none(
296
+ emissive_strength_dict.get("emissiveStrength")
297
+ )
298
+ if emissive_strength is not None:
299
+ gltf.extensions.khr_materials_emissive_strength.emissive_strength = (
300
+ emissive_strength
301
+ )
302
+
303
+ shade_color_factor = shader.rgb_or_none(mtoon_dict.get("shadeColorFactor"))
304
+ if shade_color_factor:
305
+ mtoon.shade_color_factor = shade_color_factor
306
+
307
+ shade_multiply_texture_dict = mtoon_dict.get("shadeMultiplyTexture")
308
+ if isinstance(shade_multiply_texture_dict, dict):
309
+ self.assign_texture_info(
310
+ mtoon.shade_multiply_texture,
311
+ shade_multiply_texture_dict,
312
+ )
313
+
314
+ transparent_with_z_write = mtoon_dict.get("transparentWithZWrite")
315
+ if isinstance(transparent_with_z_write, bool):
316
+ mtoon.transparent_with_z_write = transparent_with_z_write
317
+
318
+ render_queue_offset_number = mtoon_dict.get("renderQueueOffsetNumber")
319
+ if isinstance(render_queue_offset_number, (float, int)):
320
+ mtoon.render_queue_offset_number = max(
321
+ -9, min(9, int(render_queue_offset_number))
322
+ )
323
+
324
+ shading_shift_factor = mtoon_dict.get("shadingShiftFactor")
325
+ if isinstance(shading_shift_factor, (float, int)):
326
+ mtoon.shading_shift_factor = float(shading_shift_factor)
327
+
328
+ shading_shift_texture_dict = mtoon_dict.get("shadingShiftTexture")
329
+ if isinstance(shading_shift_texture_dict, dict):
330
+ self.assign_texture_info(
331
+ mtoon.shading_shift_texture,
332
+ shading_shift_texture_dict,
333
+ )
334
+ shading_shift_texture_scale = shading_shift_texture_dict.get("scale")
335
+ if isinstance(shading_shift_texture_scale, (float, int)):
336
+ mtoon.shading_shift_texture.scale = float(shading_shift_texture_scale)
337
+
338
+ shading_toony_factor = mtoon_dict.get("shadingToonyFactor")
339
+ if isinstance(shading_toony_factor, (float, int)):
340
+ mtoon.shading_toony_factor = float(shading_toony_factor)
341
+
342
+ gi_equalization_factor = mtoon_dict.get("giEqualizationFactor")
343
+ if isinstance(gi_equalization_factor, (float, int)):
344
+ mtoon.gi_equalization_factor = float(gi_equalization_factor)
345
+
346
+ matcap_factor = shader.rgb_or_none(mtoon_dict.get("matcapFactor"))
347
+ if matcap_factor:
348
+ mtoon.matcap_factor = matcap_factor
349
+
350
+ matcap_texture_dict = mtoon_dict.get("matcapTexture")
351
+ if isinstance(matcap_texture_dict, dict):
352
+ self.assign_texture_info(
353
+ mtoon.matcap_texture,
354
+ matcap_texture_dict,
355
+ )
356
+
357
+ parametric_rim_color_factor = shader.rgb_or_none(
358
+ mtoon_dict.get("parametricRimColorFactor")
359
+ )
360
+ if parametric_rim_color_factor:
361
+ mtoon.parametric_rim_color_factor = parametric_rim_color_factor
362
+
363
+ parametric_rim_fresnel_power_factor = mtoon_dict.get(
364
+ "parametricRimFresnelPowerFactor"
365
+ )
366
+ if isinstance(parametric_rim_fresnel_power_factor, (float, int)):
367
+ mtoon.parametric_rim_fresnel_power_factor = float(
368
+ parametric_rim_fresnel_power_factor
369
+ )
370
+
371
+ parametric_rim_lift_factor = mtoon_dict.get("parametricRimLiftFactor")
372
+ if isinstance(parametric_rim_lift_factor, (float, int)):
373
+ mtoon.parametric_rim_lift_factor = float(parametric_rim_lift_factor)
374
+
375
+ rim_multiply_texture_dict = mtoon_dict.get("rimMultiplyTexture")
376
+ if isinstance(rim_multiply_texture_dict, dict):
377
+ self.assign_texture_info(
378
+ mtoon.rim_multiply_texture,
379
+ rim_multiply_texture_dict,
380
+ )
381
+
382
+ rim_lighting_mix_factor = mtoon_dict.get("rimLightingMixFactor")
383
+ if isinstance(rim_lighting_mix_factor, (float, int)):
384
+ mtoon.rim_lighting_mix_factor = float(rim_lighting_mix_factor)
385
+
386
+ mtoon.enable_outline_preview = get_preferences(
387
+ self._context
388
+ ).enable_mtoon_outline_preview
389
+
390
+ outline_width_mode = mtoon_dict.get("outlineWidthMode")
391
+ if outline_width_mode in mtoon.outline_width_mode_enum.identifiers():
392
+ mtoon.outline_width_mode = outline_width_mode
393
+
394
+ outline_width_factor = mtoon_dict.get("outlineWidthFactor")
395
+ if isinstance(outline_width_factor, (float, int)):
396
+ mtoon.outline_width_factor = float(outline_width_factor)
397
+
398
+ outline_width_multiply_texture_dict = mtoon_dict.get(
399
+ "outlineWidthMultiplyTexture"
400
+ )
401
+ if isinstance(outline_width_multiply_texture_dict, dict):
402
+ self.assign_texture_info(
403
+ mtoon.outline_width_multiply_texture,
404
+ outline_width_multiply_texture_dict,
405
+ )
406
+
407
+ outline_color_factor = shader.rgb_or_none(mtoon_dict.get("outlineColorFactor"))
408
+ if outline_color_factor:
409
+ mtoon.outline_color_factor = outline_color_factor
410
+
411
+ outline_lighting_mix_factor = mtoon_dict.get("outlineLightingMixFactor")
412
+ if isinstance(outline_lighting_mix_factor, (float, int)):
413
+ mtoon.outline_lighting_mix_factor = float(outline_lighting_mix_factor)
414
+
415
+ uv_animation_mask_texture_dict = mtoon_dict.get("uvAnimationMaskTexture")
416
+ if isinstance(uv_animation_mask_texture_dict, dict):
417
+ self.assign_texture_info(
418
+ mtoon.uv_animation_mask_texture,
419
+ uv_animation_mask_texture_dict,
420
+ )
421
+
422
+ uv_animation_rotation_speed_factor = mtoon_dict.get(
423
+ "uvAnimationRotationSpeedFactor"
424
+ )
425
+ if isinstance(uv_animation_rotation_speed_factor, (float, int)):
426
+ mtoon.uv_animation_rotation_speed_factor = float(
427
+ uv_animation_rotation_speed_factor
428
+ )
429
+
430
+ uv_animation_scroll_x_speed_factor = mtoon_dict.get(
431
+ "uvAnimationScrollXSpeedFactor"
432
+ )
433
+ if isinstance(uv_animation_scroll_x_speed_factor, (float, int)):
434
+ mtoon.uv_animation_scroll_x_speed_factor = float(
435
+ uv_animation_scroll_x_speed_factor
436
+ )
437
+
438
+ uv_animation_scroll_y_speed_factor = mtoon_dict.get(
439
+ "uvAnimationScrollYSpeedFactor"
440
+ )
441
+ if isinstance(uv_animation_scroll_y_speed_factor, (float, int)):
442
+ mtoon.uv_animation_scroll_y_speed_factor = float(
443
+ uv_animation_scroll_y_speed_factor
444
+ )
445
+
446
+ def load_materials(self, progress: PartialProgress) -> None:
447
+ material_dicts = self._parse_result.json_dict.get("materials")
448
+ if not isinstance(material_dicts, list):
449
+ progress.update(1)
450
+ return
451
+ for index, material_dict in enumerate(material_dicts):
452
+ if isinstance(material_dict, dict):
453
+ self.make_mtoon1_material(index, material_dict)
454
+ progress.update(float(index) / len(material_dicts))
455
+ progress.update(1)
456
+
457
+ def find_vrm1_bone_node_indices(self) -> list[int]:
458
+ result: list[int] = []
459
+
460
+ vrm1_dict = self._parse_result.vrm1_extension_dict
461
+ if not isinstance(vrm1_dict, dict):
462
+ return result
463
+
464
+ humanoid_dict = vrm1_dict.get("humanoid")
465
+ if not isinstance(humanoid_dict, dict):
466
+ return result
467
+
468
+ human_bones_dict = humanoid_dict.get("humanBones")
469
+ if not isinstance(human_bones_dict, dict):
470
+ return result
471
+
472
+ for human_bone_dict in human_bones_dict.values():
473
+ if not isinstance(human_bone_dict, dict):
474
+ continue
475
+ node_index = human_bone_dict.get("node")
476
+ if isinstance(node_index, int):
477
+ result.append(node_index)
478
+
479
+ return list(dict.fromkeys(result)) # Remove duplicates
480
+
481
+ def find_spring_bone1_bone_node_indices(self) -> list[int]:
482
+ result: list[int] = []
483
+
484
+ extensions_dict = self._parse_result.json_dict.get("extensions")
485
+ if not isinstance(extensions_dict, dict):
486
+ return result
487
+
488
+ spring_bone1_dict = extensions_dict.get("VRMC_springBone")
489
+ if not isinstance(spring_bone1_dict, dict):
490
+ return result
491
+
492
+ collider_dicts = spring_bone1_dict.get("colliders")
493
+ if isinstance(collider_dicts, list):
494
+ for collider_dict in collider_dicts:
495
+ if not isinstance(collider_dict, dict):
496
+ continue
497
+
498
+ node_index = collider_dict.get("node")
499
+ if isinstance(node_index, int):
500
+ result.append(node_index)
501
+
502
+ spring_dicts = spring_bone1_dict.get("springs")
503
+ if isinstance(spring_dicts, list):
504
+ for spring_dict in spring_dicts:
505
+ if not isinstance(spring_dict, dict):
506
+ continue
507
+
508
+ center_index = spring_dict.get("center")
509
+ if isinstance(center_index, int):
510
+ result.append(center_index)
511
+
512
+ joint_dicts = spring_dict.get("joints")
513
+ if not isinstance(joint_dicts, list):
514
+ continue
515
+
516
+ for joint_dict in joint_dicts:
517
+ if not isinstance(joint_dict, dict):
518
+ continue
519
+ node_index = joint_dict.get("node")
520
+ if isinstance(node_index, int):
521
+ result.append(node_index)
522
+
523
+ return list(dict.fromkeys(result)) # Remove duplicates
524
+
525
+ def find_vrm_bone_node_indices(self) -> list[int]:
526
+ # TODO: SkinnedMeshRenderer root <=> skin.skeleton???
527
+ return list(
528
+ dict.fromkeys( # Remove duplicates
529
+ self.find_vrm1_bone_node_indices()
530
+ + self.find_spring_bone1_bone_node_indices()
531
+ )
532
+ )
533
+
534
+ def setup_vrm1_humanoid_bones(self) -> None:
535
+ armature = self._armature
536
+ if not armature:
537
+ return
538
+ addon_extension = get_armature_extension(self.armature_data)
539
+
540
+ Vrm1HumanBonesPropertyGroup.update_all_bone_name_candidates(
541
+ self._context, self.armature_data.name, force=True
542
+ )
543
+
544
+ human_bones = addon_extension.vrm1.humanoid.human_bones
545
+ for name, human_bone in human_bones.human_bone_name_to_human_bone().items():
546
+ if (
547
+ human_bone.node.bone_name
548
+ and human_bone.node.bone_name
549
+ not in human_bone.node.bone_name_candidates
550
+ ):
551
+ # Invalid bone structure
552
+ return
553
+
554
+ spec = HumanBoneSpecifications.get(name)
555
+ if spec.requirement and not human_bone.node.bone_name:
556
+ # Missing required bones
557
+ return
558
+
559
+ with self.save_bone_child_object_transforms(
560
+ self._context, armature
561
+ ) as armature_data:
562
+ bone_name_to_human_bone_name: dict[str, HumanBoneName] = {}
563
+ for (
564
+ human_bone_name,
565
+ human_bone,
566
+ ) in human_bones.human_bone_name_to_human_bone().items():
567
+ if not human_bone.node.bone_name:
568
+ continue
569
+ bone_name_to_human_bone_name[human_bone.node.bone_name] = (
570
+ human_bone_name
571
+ )
572
+
573
+ # When a bone has multiple children
574
+ # Create a dict to pick the child bone name to point the tail to
575
+ # from that bone name
576
+ bone_name_to_main_child_bone_name: dict[str, str] = {}
577
+ for (
578
+ bone_name,
579
+ human_bone_name,
580
+ ) in bone_name_to_human_bone_name.items():
581
+ # The current algorithm cannot handle
582
+ #
583
+ # head ---- node ---- leftEye
584
+ # \
585
+ # -- rightEye
586
+ #
587
+ # well, so we don't process leftEye and rightEye
588
+ if human_bone_name in {HumanBoneName.RIGHT_EYE, HumanBoneName.LEFT_EYE}:
589
+ continue
590
+
591
+ bone = armature_data.edit_bones.get(bone_name)
592
+ if not bone:
593
+ continue
594
+ last_human_bone_name = human_bone_name
595
+ while True:
596
+ parent = bone.parent
597
+ if not parent:
598
+ break
599
+ parent_human_bone_name = bone_name_to_human_bone_name.get(
600
+ parent.name
601
+ )
602
+
603
+ if parent_human_bone_name in (
604
+ HumanBoneName.RIGHT_HAND,
605
+ HumanBoneName.LEFT_HAND,
606
+ ):
607
+ break
608
+
609
+ if (
610
+ parent_human_bone_name == HumanBoneName.UPPER_CHEST
611
+ and last_human_bone_name
612
+ not in {HumanBoneName.HEAD, HumanBoneName.NECK}
613
+ ):
614
+ break
615
+
616
+ if (
617
+ parent_human_bone_name == HumanBoneName.CHEST
618
+ and last_human_bone_name
619
+ not in (
620
+ HumanBoneName.HEAD,
621
+ HumanBoneName.NECK,
622
+ HumanBoneName.UPPER_CHEST,
623
+ )
624
+ ):
625
+ break
626
+
627
+ if (
628
+ parent_human_bone_name == HumanBoneName.SPINE
629
+ and last_human_bone_name
630
+ not in (
631
+ HumanBoneName.HEAD,
632
+ HumanBoneName.NECK,
633
+ HumanBoneName.UPPER_CHEST,
634
+ HumanBoneName.CHEST,
635
+ )
636
+ ):
637
+ break
638
+
639
+ if (
640
+ parent_human_bone_name == HumanBoneName.HIPS
641
+ and last_human_bone_name != HumanBoneName.SPINE
642
+ ):
643
+ break
644
+
645
+ if parent_human_bone_name:
646
+ last_human_bone_name = parent_human_bone_name
647
+
648
+ bone_name_to_main_child_bone_name[parent.name] = bone.name
649
+ bone = parent
650
+
651
+ # Get human bones and their ancestor bones
652
+ human_bone_tree_bone_names: set[str] = set()
653
+ for bone_name in bone_name_to_human_bone_name:
654
+ bone = armature_data.edit_bones.get(bone_name)
655
+ while bone:
656
+ human_bone_tree_bone_names.add(bone.name)
657
+ bone = bone.parent
658
+
659
+ bone_name_to_axis_translation: dict[str, str] = {}
660
+
661
+ human_bone_tree_bones: list[EditBone] = []
662
+ non_human_bone_tree_bones: list[EditBone] = []
663
+ constraint_node_index_to_source_index: dict[int, int] = {}
664
+ constraint_node_index_groups: list[set[int]] = []
665
+ nodes = self._parse_result.json_dict.get("nodes")
666
+ if not isinstance(nodes, list):
667
+ nodes = []
668
+ for node_index, node_dict in enumerate(nodes):
669
+ if not isinstance(node_dict, dict):
670
+ continue
671
+
672
+ node_extensions_dict = node_dict.get("extensions")
673
+ if not isinstance(node_extensions_dict, dict):
674
+ continue
675
+
676
+ vrmc_node_constraint_dict = node_extensions_dict.get(
677
+ "VRMC_node_constraint"
678
+ )
679
+ if not isinstance(vrmc_node_constraint_dict, dict):
680
+ continue
681
+
682
+ constraint_dict = vrmc_node_constraint_dict.get("constraint")
683
+ if not isinstance(constraint_dict, dict):
684
+ continue
685
+
686
+ object_or_bone = self.get_object_or_bone_by_node_index(node_index)
687
+ if not object_or_bone:
688
+ continue
689
+
690
+ roll_dict = constraint_dict.get("roll")
691
+ if isinstance(roll_dict, dict):
692
+ source = roll_dict.get("source")
693
+ if isinstance(source, int):
694
+ constraint_node_index_to_source_index[node_index] = source
695
+ continue
696
+
697
+ aim_dict = constraint_dict.get("aim")
698
+ if isinstance(aim_dict, dict):
699
+ source = aim_dict.get("source")
700
+ if isinstance(source, int):
701
+ constraint_node_index_to_source_index[node_index] = source
702
+ continue
703
+
704
+ rotation_dict = constraint_dict.get("rotation")
705
+ if isinstance(rotation_dict, dict):
706
+ source = rotation_dict.get("source")
707
+ if isinstance(source, int):
708
+ constraint_node_index_to_source_index[node_index] = source
709
+ continue
710
+
711
+ node_indices = list(constraint_node_index_to_source_index.keys())
712
+ while node_indices:
713
+ node_index = node_indices.pop()
714
+ source_index = constraint_node_index_to_source_index.get(node_index)
715
+ if not isinstance(source_index, int):
716
+ continue
717
+ node_indices.append(source_index)
718
+ found = False
719
+ for constraint_node_index_group in constraint_node_index_groups:
720
+ if node_index in constraint_node_index_group:
721
+ constraint_node_index_group.add(source_index)
722
+ found = True
723
+ break
724
+ if source_index in constraint_node_index_group:
725
+ constraint_node_index_group.add(node_index)
726
+ found = True
727
+ break
728
+
729
+ if not found:
730
+ constraint_node_index_groups.append({node_index, source_index})
731
+
732
+ # When constraints are attached during axis conversion, we want to
733
+ # prioritize human bones and their ancestor bones, so we process
734
+ # them first in depth-first order, then process other bones in
735
+ # depth-first order
736
+ unsorted_bones = [
737
+ bone for bone in armature_data.edit_bones if not bone.parent
738
+ ]
739
+ while unsorted_bones:
740
+ bone = unsorted_bones.pop()
741
+ unsorted_bones.extend(bone.children)
742
+ if bone.name in human_bone_tree_bone_names:
743
+ human_bone_tree_bones.append(bone)
744
+ else:
745
+ non_human_bone_tree_bones.append(bone)
746
+
747
+ for bone in human_bone_tree_bones + non_human_bone_tree_bones:
748
+ bone_index = next(
749
+ (i for i, n in self._bone_names.items() if n == bone.name), None
750
+ )
751
+ if isinstance(bone_index, int):
752
+ group_axis_translation: Optional[str] = None
753
+ for node_index in next(
754
+ (g for g in constraint_node_index_groups if bone_index in g),
755
+ set[int](),
756
+ ):
757
+ object_or_bone = self.get_object_or_bone_by_node_index(
758
+ node_index
759
+ )
760
+ if isinstance(object_or_bone, PoseBone):
761
+ group_axis_translation = bone_name_to_axis_translation.get(
762
+ object_or_bone.name
763
+ )
764
+ if group_axis_translation is not None:
765
+ break
766
+ if group_axis_translation is not None:
767
+ bone.matrix = BoneExtension.translate_axis(
768
+ bone.matrix,
769
+ group_axis_translation,
770
+ )
771
+ if bone.parent and not bone.children:
772
+ bone.length = max(
773
+ bone.parent.length / 2, make_armature.MIN_BONE_LENGTH
774
+ )
775
+ bone_name_to_axis_translation[bone.name] = (
776
+ group_axis_translation
777
+ )
778
+ continue
779
+
780
+ main_child_bone_name = bone_name_to_main_child_bone_name.get(bone.name)
781
+ target_vector: Optional[Vector] = None
782
+ if main_child_bone_name:
783
+ main_child = next(
784
+ bone
785
+ for bone in bone.children
786
+ if bone.name == main_child_bone_name
787
+ )
788
+ target_translation = (
789
+ armature.matrix_world @ Matrix.Translation(main_child.head)
790
+ ).to_translation()
791
+ base_translation = (
792
+ armature.matrix_world @ Matrix.Translation(bone.head)
793
+ ).to_translation()
794
+ target_vector = target_translation - base_translation
795
+ elif bone.children:
796
+ target_translation = Vector((0.0, 0.0, 0.0))
797
+ for child in bone.children:
798
+ child_translation = (
799
+ armature.matrix_world @ Matrix.Translation(child.head)
800
+ ).to_translation()
801
+ target_translation += child_translation
802
+ target_translation /= len(bone.children)
803
+ base_translation = (
804
+ armature.matrix_world @ Matrix.Translation(bone.head)
805
+ ).to_translation()
806
+ target_vector = target_translation - base_translation
807
+ elif not bone.parent:
808
+ bone_name_to_axis_translation[bone.name] = (
809
+ BoneExtension.AXIS_TRANSLATION_NONE.identifier
810
+ )
811
+ continue
812
+ elif bone_name_to_human_bone_name.get(bone.name) in (
813
+ HumanBoneName.RIGHT_EYE,
814
+ HumanBoneName.LEFT_EYE,
815
+ HumanBoneName.RIGHT_FOOT,
816
+ HumanBoneName.LEFT_FOOT,
817
+ ):
818
+ target_vector = Vector((0, -1, 0))
819
+ else:
820
+ target_translation = (
821
+ armature.matrix_world @ Matrix.Translation(bone.head)
822
+ ).to_translation()
823
+ base_translation = (
824
+ armature.matrix_world @ Matrix.Translation(bone.parent.head)
825
+ ).to_translation()
826
+ target_vector = target_translation - base_translation
827
+
828
+ base_rotation = (armature.matrix_world @ bone.matrix).to_quaternion()
829
+
830
+ x_vector = Vector((1, 0, 0))
831
+ x_vector.rotate(base_rotation)
832
+ x_negated_vector = x_vector.copy()
833
+ x_negated_vector.negate()
834
+ y_vector = Vector((0, 1, 0))
835
+ y_vector.rotate(base_rotation)
836
+ y_negated_vector = y_vector.copy()
837
+ y_negated_vector.negate()
838
+ z_vector = Vector((0, 0, 1))
839
+ z_vector.rotate(base_rotation)
840
+ z_negated_vector = z_vector.copy()
841
+ z_negated_vector.negate()
842
+
843
+ bone_length, axis_translation = max(
844
+ (
845
+ target_vector.dot(x_vector),
846
+ BoneExtension.AXIS_TRANSLATION_X_TO_Y.identifier,
847
+ ),
848
+ (
849
+ target_vector.dot(y_vector),
850
+ BoneExtension.AXIS_TRANSLATION_NONE.identifier,
851
+ ),
852
+ (
853
+ target_vector.dot(z_vector),
854
+ BoneExtension.AXIS_TRANSLATION_Z_TO_Y.identifier,
855
+ ),
856
+ (
857
+ target_vector.dot(x_negated_vector),
858
+ BoneExtension.AXIS_TRANSLATION_MINUS_X_TO_Y.identifier,
859
+ ),
860
+ (
861
+ target_vector.dot(y_negated_vector),
862
+ BoneExtension.AXIS_TRANSLATION_MINUS_Y_TO_Y_AROUND_Z.identifier,
863
+ ),
864
+ (
865
+ target_vector.dot(z_negated_vector),
866
+ BoneExtension.AXIS_TRANSLATION_MINUS_Z_TO_Y.identifier,
867
+ ),
868
+ )
869
+ bone.matrix = BoneExtension.translate_axis(
870
+ bone.matrix,
871
+ axis_translation,
872
+ )
873
+ if bone.children:
874
+ bone.length = max(bone_length, make_armature.MIN_BONE_LENGTH)
875
+ elif bone.parent:
876
+ bone.length = max(
877
+ bone.parent.length / 2, make_armature.MIN_BONE_LENGTH
878
+ )
879
+ bone_name_to_axis_translation[bone.name] = axis_translation
880
+
881
+ connect_parent_tail_and_child_head_if_very_close_position(armature_data)
882
+
883
+ bpy.ops.object.mode_set(mode="OBJECT")
884
+ for bone_name, axis_translation in bone_name_to_axis_translation.items():
885
+ data_bone = armature_data.bones.get(bone_name)
886
+ if not data_bone:
887
+ continue
888
+ get_bone_extension(
889
+ data_bone
890
+ ).axis_translation = BoneExtension.reverse_axis_translation(
891
+ axis_translation
892
+ )
893
+
894
+ for node_index in set(constraint_node_index_to_source_index.keys()) | set(
895
+ constraint_node_index_to_source_index.values()
896
+ ):
897
+ object_or_bone = self.get_object_or_bone_by_node_index(node_index)
898
+ if not isinstance(object_or_bone, Object):
899
+ continue
900
+ for group_node_index in next(
901
+ (g for g in constraint_node_index_groups if node_index in g),
902
+ set[int](),
903
+ ):
904
+ group_object_or_bone = self.get_object_or_bone_by_node_index(
905
+ group_node_index
906
+ )
907
+ if isinstance(group_object_or_bone, PoseBone):
908
+ self.translate_object_axis(
909
+ object_or_bone,
910
+ BoneExtension.reverse_axis_translation(
911
+ get_bone_extension(
912
+ group_object_or_bone.bone
913
+ ).axis_translation
914
+ ),
915
+ )
916
+ break
917
+
918
+ def translate_object_axis(self, obj: Object, axis_translation: str) -> None:
919
+ if axis_translation != BoneExtension.AXIS_TRANSLATION_AUTO.identifier:
920
+ return
921
+ matrix = BoneExtension.translate_axis(obj.matrix_world, axis_translation)
922
+ location, rotation, _ = matrix.decompose()
923
+ matrix = Matrix.Translation(location) @ rotation.to_matrix().to_4x4()
924
+ bpy.ops.object.select_all(action="DESELECT")
925
+ self._context.scene.cursor.matrix = matrix
926
+ obj.select_set(True)
927
+ bpy.ops.object.origin_set(type="ORIGIN_CURSOR", center="MEDIAN")
928
+
929
+ get_object_extension(
930
+ obj
931
+ ).axis_translation = BoneExtension.reverse_axis_translation(axis_translation)
932
+
933
+ def load_gltf_extensions(self) -> None:
934
+ armature = self._armature
935
+ if not armature:
936
+ return
937
+ addon_extension = get_armature_extension(self.armature_data)
938
+ vrm1 = addon_extension.vrm1
939
+ vrm0 = addon_extension.vrm0
940
+
941
+ addon_extension.spec_version = addon_extension.SPEC_VERSION_VRM1
942
+ vrm1_extension_dict = self._parse_result.vrm1_extension_dict
943
+
944
+ addon_extension.addon_version = get_addon_version()
945
+
946
+ textblock = self._context.blend_data.texts.new(name="vrm.json")
947
+ textblock.write(json.dumps(self._parse_result.json_dict, indent=4))
948
+
949
+ self.load_vrm1_meta(vrm1.meta, vrm1_extension_dict.get("meta"))
950
+ self.load_vrm1_humanoid(
951
+ vrm1.humanoid, vrm1_extension_dict.get("humanoid"), vrm0.humanoid
952
+ )
953
+ self.setup_vrm1_humanoid_bones()
954
+ self.load_vrm1_first_person(
955
+ vrm1.first_person, vrm1_extension_dict.get("firstPerson")
956
+ )
957
+ self.load_vrm1_look_at(vrm1.look_at, vrm1_extension_dict.get("lookAt"))
958
+ self.load_vrm1_expressions(
959
+ vrm1.expressions, vrm1_extension_dict.get("expressions")
960
+ )
961
+
962
+ extensions_dict = self._parse_result.json_dict.get("extensions")
963
+ if isinstance(extensions_dict, dict):
964
+ self.load_spring_bone1(
965
+ addon_extension.spring_bone1, extensions_dict.get("VRMC_springBone")
966
+ )
967
+
968
+ self.load_node_constraint1()
969
+ migration.migrate(self._context, armature.name, heavy_migration=True)
970
+
971
+ def load_vrm1_meta(self, meta: Vrm1MetaPropertyGroup, meta_dict: Json) -> None:
972
+ if not isinstance(meta_dict, dict):
973
+ return
974
+
975
+ name = meta_dict.get("name")
976
+ if name is not None:
977
+ meta.vrm_name = str(name)
978
+
979
+ version = meta_dict.get("version")
980
+ if version is not None:
981
+ meta.version = str(version)
982
+
983
+ authors = meta_dict.get("authors")
984
+ if isinstance(authors, list):
985
+ for author in authors:
986
+ if author is not None:
987
+ meta.authors.add().value = str(author)
988
+
989
+ copyright_information = meta_dict.get("copyrightInformation")
990
+ if copyright_information is not None:
991
+ meta.copyright_information = str(copyright_information)
992
+
993
+ contact_information = meta_dict.get("contactInformation")
994
+ if contact_information is not None:
995
+ meta.contact_information = str(contact_information)
996
+
997
+ references = meta_dict.get("references")
998
+ if isinstance(references, list):
999
+ for reference in references:
1000
+ if reference is not None:
1001
+ meta.references.add().value = str(reference)
1002
+
1003
+ third_party_licenses = meta_dict.get("thirdPartyLicenses")
1004
+ if third_party_licenses is not None:
1005
+ meta.third_party_licenses = str(third_party_licenses)
1006
+
1007
+ thumbnail_image_index = meta_dict.get("thumbnailImage")
1008
+ if isinstance(thumbnail_image_index, int):
1009
+ thumbnail_image = self._images.get(thumbnail_image_index)
1010
+ if thumbnail_image:
1011
+ meta.thumbnail_image = thumbnail_image
1012
+
1013
+ avatar_permission = meta_dict.get("avatarPermission")
1014
+ if (
1015
+ isinstance(avatar_permission, str)
1016
+ and avatar_permission
1017
+ in Vrm1MetaPropertyGroup.avatar_permission_enum.identifiers()
1018
+ ):
1019
+ meta.avatar_permission = avatar_permission
1020
+
1021
+ meta.allow_excessively_violent_usage = bool(
1022
+ meta_dict.get("allowExcessivelyViolentUsage")
1023
+ )
1024
+
1025
+ meta.allow_excessively_sexual_usage = bool(
1026
+ meta_dict.get("allowExcessivelySexualUsage")
1027
+ )
1028
+
1029
+ commercial_usage = meta_dict.get("commercialUsage")
1030
+ if (
1031
+ isinstance(commercial_usage, str)
1032
+ and commercial_usage
1033
+ in Vrm1MetaPropertyGroup.commercial_usage_enum.identifiers()
1034
+ ):
1035
+ meta.commercial_usage = commercial_usage
1036
+
1037
+ meta.allow_political_or_religious_usage = bool(
1038
+ meta_dict.get("allowPoliticalOrReligiousUsage")
1039
+ )
1040
+
1041
+ meta.allow_antisocial_or_hate_usage = bool(
1042
+ meta_dict.get("allowAntisocialOrHateUsage")
1043
+ )
1044
+
1045
+ credit_notation = meta_dict.get("creditNotation")
1046
+ if (
1047
+ isinstance(credit_notation, str)
1048
+ and credit_notation
1049
+ in Vrm1MetaPropertyGroup.credit_notation_enum.identifiers()
1050
+ ):
1051
+ meta.credit_notation = credit_notation
1052
+
1053
+ meta.allow_redistribution = bool(meta_dict.get("allowRedistribution"))
1054
+
1055
+ modification = meta_dict.get("modification")
1056
+ if (
1057
+ isinstance(modification, str)
1058
+ and modification in Vrm1MetaPropertyGroup.modification_enum.identifiers()
1059
+ ):
1060
+ meta.modification = modification
1061
+
1062
+ other_license_url = meta_dict.get("otherLicenseUrl")
1063
+ if other_license_url is not None:
1064
+ meta.other_license_url = str(other_license_url)
1065
+
1066
+ def load_vrm1_humanoid(
1067
+ self,
1068
+ humanoid: Vrm1HumanoidPropertyGroup,
1069
+ humanoid_dict: Json,
1070
+ vrm0_humanoid: Vrm0HumanoidPropertyGroup,
1071
+ ) -> None:
1072
+ if not isinstance(humanoid_dict, dict):
1073
+ return
1074
+
1075
+ human_bones_dict = humanoid_dict.get("humanBones")
1076
+ if not isinstance(human_bones_dict, dict):
1077
+ return
1078
+
1079
+ assigned_bone_names: list[str] = []
1080
+ for (
1081
+ human_bone_name,
1082
+ human_bone,
1083
+ ) in humanoid.human_bones.human_bone_name_to_human_bone().items():
1084
+ human_bone_dict = human_bones_dict.get(human_bone_name.value)
1085
+ if not isinstance(human_bone_dict, dict):
1086
+ continue
1087
+ node_index = human_bone_dict.get("node")
1088
+ if not isinstance(node_index, int):
1089
+ continue
1090
+ bone_name = self._bone_names.get(node_index)
1091
+ if not isinstance(bone_name, str) or bone_name in assigned_bone_names:
1092
+ continue
1093
+ human_bone.node.bone_name = bone_name
1094
+ human_bone_specification = HumanBoneSpecifications.get(human_bone_name)
1095
+
1096
+ vrm0_human_bone_name = human_bone_specification.vrm0_name
1097
+ vrm0_human_bone = next(
1098
+ (
1099
+ vrm0_human_bone
1100
+ for vrm0_human_bone in vrm0_humanoid.human_bones
1101
+ if vrm0_human_bone.bone == vrm0_human_bone_name.value
1102
+ ),
1103
+ None,
1104
+ )
1105
+ if vrm0_human_bone:
1106
+ _logger.warning('Duplicated VRM0 bone: "%s"', vrm0_human_bone_name)
1107
+ else:
1108
+ vrm0_human_bone = vrm0_humanoid.human_bones.add()
1109
+ vrm0_human_bone.bone = vrm0_human_bone_name.value
1110
+ vrm0_human_bone.node.bone_name = bone_name
1111
+
1112
+ assigned_bone_names.append(bone_name)
1113
+
1114
+ def load_vrm1_first_person(
1115
+ self,
1116
+ first_person: Vrm1FirstPersonPropertyGroup,
1117
+ first_person_dict: Json,
1118
+ ) -> None:
1119
+ if not isinstance(first_person_dict, dict):
1120
+ return
1121
+
1122
+ mesh_annotation_dicts = first_person_dict.get("meshAnnotations")
1123
+ if not isinstance(mesh_annotation_dicts, list):
1124
+ mesh_annotation_dicts = []
1125
+
1126
+ for mesh_annotation_dict in mesh_annotation_dicts:
1127
+ mesh_annotation = first_person.mesh_annotations.add()
1128
+ if not isinstance(mesh_annotation_dict, dict):
1129
+ continue
1130
+
1131
+ if (
1132
+ isinstance(node_index := mesh_annotation_dict.get("node"), int)
1133
+ and (mesh_object_name := self._object_names.get(node_index))
1134
+ and (obj := self._context.blend_data.objects.get(mesh_object_name))
1135
+ and isinstance(obj.data, Mesh)
1136
+ ):
1137
+ mesh_annotation.node.mesh_object_name = mesh_object_name
1138
+
1139
+ type_ = mesh_annotation_dict.get("type")
1140
+ if type_ in mesh_annotation.type_enum.identifiers():
1141
+ mesh_annotation.type = type_
1142
+
1143
+ def load_vrm1_look_at(
1144
+ self,
1145
+ look_at: Vrm1LookAtPropertyGroup,
1146
+ look_at_dict: Json,
1147
+ ) -> None:
1148
+ if not isinstance(look_at_dict, dict):
1149
+ return
1150
+ armature = self._armature
1151
+ if armature is None:
1152
+ return
1153
+ offset_from_head_bone = Vector(
1154
+ convert.vrm_json_array_to_float_vector(
1155
+ look_at_dict.get("offsetFromHeadBone"), [0, 0, 0]
1156
+ )
1157
+ )
1158
+
1159
+ humanoid = get_armature_extension(self.armature_data).vrm1.humanoid
1160
+ head_bone_name = humanoid.human_bones.head.node.bone_name
1161
+ head_bone = self.armature_data.bones.get(head_bone_name)
1162
+ if head_bone:
1163
+ offset_from_head_bone = (
1164
+ offset_from_head_bone
1165
+ @ BoneExtension.translate_axis(
1166
+ Matrix(),
1167
+ BoneExtension.reverse_axis_translation(
1168
+ get_bone_extension(head_bone).axis_translation
1169
+ ),
1170
+ )
1171
+ )
1172
+
1173
+ look_at.offset_from_head_bone = offset_from_head_bone
1174
+ type_ = look_at_dict.get("type")
1175
+ if type_ in look_at.type_enum.identifiers():
1176
+ look_at.type = type_
1177
+
1178
+ for range_map, range_map_dict in (
1179
+ (
1180
+ look_at.range_map_horizontal_inner,
1181
+ look_at_dict.get("rangeMapHorizontalInner"),
1182
+ ),
1183
+ (
1184
+ look_at.range_map_horizontal_outer,
1185
+ look_at_dict.get("rangeMapHorizontalOuter"),
1186
+ ),
1187
+ (
1188
+ look_at.range_map_vertical_down,
1189
+ look_at_dict.get("rangeMapVerticalDown"),
1190
+ ),
1191
+ (
1192
+ look_at.range_map_vertical_up,
1193
+ look_at_dict.get("rangeMapVerticalUp"),
1194
+ ),
1195
+ ):
1196
+ if not isinstance(range_map_dict, dict):
1197
+ continue
1198
+
1199
+ input_max_value = range_map_dict.get("inputMaxValue")
1200
+ if isinstance(input_max_value, (float, int)):
1201
+ range_map.input_max_value = float(input_max_value)
1202
+
1203
+ output_scale = range_map_dict.get("outputScale")
1204
+ if isinstance(output_scale, (float, int)):
1205
+ range_map.output_scale = float(output_scale)
1206
+
1207
+ def load_vrm1_expression(
1208
+ self,
1209
+ expression: Vrm1ExpressionPropertyGroup,
1210
+ expression_dict: Json,
1211
+ ) -> None:
1212
+ if not isinstance(expression_dict, dict):
1213
+ return
1214
+
1215
+ morph_target_bind_dicts = expression_dict.get("morphTargetBinds")
1216
+ if not isinstance(morph_target_bind_dicts, list):
1217
+ morph_target_bind_dicts = []
1218
+
1219
+ for morph_target_bind_dict in morph_target_bind_dicts:
1220
+ if not isinstance(morph_target_bind_dict, dict):
1221
+ continue
1222
+
1223
+ morph_target_bind = expression.morph_target_binds.add()
1224
+
1225
+ weight = convert.float_or(morph_target_bind_dict.get("weight"), 0.0)
1226
+
1227
+ morph_target_bind.weight = weight
1228
+
1229
+ node_index = morph_target_bind_dict.get("node")
1230
+ if not isinstance(node_index, int):
1231
+ continue
1232
+ mesh_object_name = self._object_names.get(node_index)
1233
+ if not mesh_object_name:
1234
+ continue
1235
+ mesh_object = self._context.blend_data.objects.get(mesh_object_name)
1236
+ if not mesh_object or not isinstance(mesh_data := mesh_object.data, Mesh):
1237
+ continue
1238
+ morph_target_bind.node.mesh_object_name = mesh_object.name
1239
+ index = morph_target_bind_dict.get("index")
1240
+ if not isinstance(index, int):
1241
+ continue
1242
+ shape_keys = mesh_data.shape_keys
1243
+ if not shape_keys:
1244
+ continue
1245
+ key_blocks = shape_keys.key_blocks
1246
+ if 1 <= (index + 1) < len(key_blocks):
1247
+ morph_target_bind.index = key_blocks[index + 1].name
1248
+
1249
+ material_color_bind_dicts = expression_dict.get("materialColorBinds")
1250
+ if not isinstance(material_color_bind_dicts, list):
1251
+ material_color_bind_dicts = []
1252
+
1253
+ for material_color_bind_dict in material_color_bind_dicts:
1254
+ material_color_bind = expression.material_color_binds.add()
1255
+
1256
+ if not isinstance(material_color_bind_dict, dict):
1257
+ continue
1258
+
1259
+ material_index = material_color_bind_dict.get("material")
1260
+ if isinstance(material_index, int):
1261
+ material = self._materials.get(material_index)
1262
+ if material:
1263
+ material_color_bind.material = material
1264
+
1265
+ type_ = material_color_bind_dict.get("type")
1266
+ if type_ in Vrm1MaterialColorBindPropertyGroup.type_enum.identifiers():
1267
+ material_color_bind.type = type_
1268
+
1269
+ target_value = material_color_bind_dict.get("targetValue")
1270
+ if not isinstance(target_value, list):
1271
+ target_value = []
1272
+ target_value = [
1273
+ float(v) if isinstance(v, (float, int)) else 0.0 for v in target_value
1274
+ ]
1275
+ while len(target_value) < 4:
1276
+ target_value.append(0.0)
1277
+ material_color_bind.target_value = target_value[:4]
1278
+
1279
+ texture_transform_bind_dicts = expression_dict.get("textureTransformBinds")
1280
+ if not isinstance(texture_transform_bind_dicts, list):
1281
+ texture_transform_bind_dicts = []
1282
+ for texture_transform_bind_dict in texture_transform_bind_dicts:
1283
+ texture_transform_bind = expression.texture_transform_binds.add()
1284
+
1285
+ if not isinstance(texture_transform_bind_dict, dict):
1286
+ continue
1287
+
1288
+ material_index = texture_transform_bind_dict.get("material")
1289
+ if isinstance(material_index, int):
1290
+ material = self._materials.get(material_index)
1291
+ if material:
1292
+ texture_transform_bind.material = material
1293
+
1294
+ texture_transform_bind.scale = convert.vrm_json_array_to_float_vector(
1295
+ texture_transform_bind_dict.get("scale"), [1, 1]
1296
+ )
1297
+
1298
+ texture_transform_bind.offset = convert.vrm_json_array_to_float_vector(
1299
+ texture_transform_bind_dict.get("offset"), [0, 0]
1300
+ )
1301
+
1302
+ is_binary = expression_dict.get("isBinary")
1303
+ if isinstance(is_binary, bool):
1304
+ expression.is_binary = is_binary
1305
+
1306
+ override_blink = expression_dict.get("overrideBlink")
1307
+ if (
1308
+ isinstance(override_blink, str)
1309
+ and override_blink
1310
+ in Vrm1ExpressionPropertyGroup.expression_override_type_enum.identifiers()
1311
+ ):
1312
+ expression.override_blink = override_blink
1313
+
1314
+ override_look_at = expression_dict.get("overrideLookAt")
1315
+ if (
1316
+ isinstance(override_look_at, str)
1317
+ and override_look_at
1318
+ in Vrm1ExpressionPropertyGroup.expression_override_type_enum.identifiers()
1319
+ ):
1320
+ expression.override_look_at = override_look_at
1321
+
1322
+ override_mouth = expression_dict.get("overrideMouth")
1323
+ if (
1324
+ isinstance(override_mouth, str)
1325
+ and override_mouth
1326
+ in Vrm1ExpressionPropertyGroup.expression_override_type_enum.identifiers()
1327
+ ):
1328
+ expression.override_mouth = override_mouth
1329
+
1330
+ def load_vrm1_expressions(
1331
+ self,
1332
+ expressions: Vrm1ExpressionsPropertyGroup,
1333
+ expressions_dict: Json,
1334
+ ) -> None:
1335
+ if not isinstance(expressions_dict, dict):
1336
+ return
1337
+
1338
+ preset_dict = expressions_dict.get("preset")
1339
+ if isinstance(preset_dict, dict):
1340
+ for (
1341
+ preset_name,
1342
+ expression,
1343
+ ) in expressions.preset.name_to_expression_dict().items():
1344
+ self.load_vrm1_expression(expression, preset_dict.get(preset_name))
1345
+
1346
+ custom_dict = expressions_dict.get("custom")
1347
+ if isinstance(custom_dict, dict):
1348
+ for custom_name, expression_dict in custom_dict.items():
1349
+ expression = expressions.custom.add()
1350
+ expression.custom_name = custom_name
1351
+ self.load_vrm1_expression(expression, expression_dict)
1352
+
1353
+ def load_spring_bone1_collider(
1354
+ self,
1355
+ collider: SpringBone1ColliderPropertyGroup,
1356
+ collider_dict: dict[str, Json],
1357
+ ) -> None:
1358
+ bone: Optional[Bone] = None
1359
+ node_index = collider_dict.get("node")
1360
+ if isinstance(node_index, int):
1361
+ bone_name = self._bone_names.get(node_index)
1362
+ if isinstance(bone_name, str):
1363
+ collider.node.bone_name = bone_name
1364
+ collider_bpy_object = collider.bpy_object
1365
+ if collider_bpy_object:
1366
+ collider_bpy_object.name = f"{bone_name} Collider"
1367
+ bone = self.armature_data.bones.get(collider.node.bone_name)
1368
+
1369
+ shape_dict = collider_dict.get("shape")
1370
+ if not isinstance(shape_dict, dict):
1371
+ collider.shape.sphere.radius = 0.0001
1372
+ collider.shape.sphere.offset = (0, 0, -10000)
1373
+ return
1374
+
1375
+ shape = collider.shape
1376
+
1377
+ sphere_dict = shape_dict.get("sphere")
1378
+ capsule_dict = shape_dict.get("capsule")
1379
+ if isinstance(sphere_dict, dict):
1380
+ collider.shape_type = collider.SHAPE_TYPE_SPHERE.identifier
1381
+ offset = Vector(
1382
+ convert.vrm_json_array_to_float_vector(
1383
+ sphere_dict.get("offset"), [0, 0, 0]
1384
+ )
1385
+ )
1386
+ if bone:
1387
+ axis_translation = get_bone_extension(bone).axis_translation
1388
+ offset = Vector(offset) @ BoneExtension.translate_axis(
1389
+ Matrix(),
1390
+ BoneExtension.reverse_axis_translation(axis_translation),
1391
+ )
1392
+ shape.sphere.offset = offset
1393
+ radius = sphere_dict.get("radius")
1394
+ if isinstance(radius, (float, int)):
1395
+ shape.sphere.radius = float(radius)
1396
+ elif isinstance(capsule_dict, dict):
1397
+ collider.shape_type = collider.SHAPE_TYPE_CAPSULE.identifier
1398
+
1399
+ offset = Vector(
1400
+ convert.vrm_json_array_to_float_vector(
1401
+ capsule_dict.get("offset"), [0, 0, 0]
1402
+ )
1403
+ )
1404
+ if bone:
1405
+ axis_translation = get_bone_extension(bone).axis_translation
1406
+ offset = Vector(offset) @ BoneExtension.translate_axis(
1407
+ Matrix(),
1408
+ BoneExtension.reverse_axis_translation(axis_translation),
1409
+ )
1410
+ shape.capsule.offset = offset
1411
+
1412
+ radius = capsule_dict.get("radius")
1413
+ if isinstance(radius, (float, int)):
1414
+ shape.capsule.radius = float(radius)
1415
+
1416
+ tail = Vector(
1417
+ convert.vrm_json_array_to_float_vector(
1418
+ capsule_dict.get("tail"), [0, 0, 0]
1419
+ )
1420
+ )
1421
+ if bone:
1422
+ axis_translation = get_bone_extension(bone).axis_translation
1423
+ tail = Vector(tail) @ BoneExtension.translate_axis(
1424
+ Matrix(),
1425
+ BoneExtension.reverse_axis_translation(axis_translation),
1426
+ )
1427
+ shape.capsule.tail = tail
1428
+
1429
+ extensions_dict = collider_dict.get("extensions")
1430
+ if not isinstance(extensions_dict, dict):
1431
+ return
1432
+ extended_collider_dict = extensions_dict.get(
1433
+ "VRMC_springBone_extended_collider"
1434
+ )
1435
+ if not isinstance(extended_collider_dict, dict):
1436
+ return
1437
+ extended_shape_dict = extended_collider_dict.get("shape")
1438
+ if not isinstance(extended_shape_dict, dict):
1439
+ return
1440
+
1441
+ extended_collider = collider.extensions.vrmc_spring_bone_extended_collider
1442
+ extended_shape = extended_collider.shape
1443
+ extended_sphere_dict = extended_shape_dict.get("sphere")
1444
+ extended_capsule_dict = extended_shape_dict.get("capsule")
1445
+ extended_plane_dict = extended_shape_dict.get("plane")
1446
+ if isinstance(extended_sphere_dict, dict):
1447
+ extended_collider.shape_type = (
1448
+ extended_collider.SHAPE_TYPE_EXTENDED_SPHERE.identifier
1449
+ )
1450
+ offset = Vector(
1451
+ convert.vrm_json_array_to_float_vector(
1452
+ extended_sphere_dict.get("offset"), [0, 0, 0]
1453
+ )
1454
+ )
1455
+ if bone:
1456
+ axis_translation = get_bone_extension(bone).axis_translation
1457
+ offset = Vector(offset) @ BoneExtension.translate_axis(
1458
+ Matrix(),
1459
+ BoneExtension.reverse_axis_translation(axis_translation),
1460
+ )
1461
+ extended_shape.sphere.offset = offset
1462
+ radius = extended_sphere_dict.get("radius")
1463
+ if isinstance(radius, (float, int)):
1464
+ extended_shape.sphere.radius = float(radius)
1465
+
1466
+ inside = extended_sphere_dict.get("inside")
1467
+ if isinstance(inside, bool):
1468
+ extended_shape.sphere.inside = inside
1469
+
1470
+ elif isinstance(extended_capsule_dict, dict):
1471
+ extended_collider.shape_type = (
1472
+ extended_collider.SHAPE_TYPE_EXTENDED_CAPSULE.identifier
1473
+ )
1474
+ offset = Vector(
1475
+ convert.vrm_json_array_to_float_vector(
1476
+ extended_capsule_dict.get("offset"), [0, 0, 0]
1477
+ )
1478
+ )
1479
+ if bone:
1480
+ axis_translation = get_bone_extension(bone).axis_translation
1481
+ offset = Vector(offset) @ BoneExtension.translate_axis(
1482
+ Matrix(),
1483
+ BoneExtension.reverse_axis_translation(axis_translation),
1484
+ )
1485
+ extended_shape.capsule.offset = offset
1486
+
1487
+ radius = extended_capsule_dict.get("radius")
1488
+ if isinstance(radius, (float, int)):
1489
+ extended_shape.capsule.radius = float(radius)
1490
+
1491
+ tail = Vector(
1492
+ convert.vrm_json_array_to_float_vector(
1493
+ extended_capsule_dict.get("tail"), [0, 0, 0]
1494
+ )
1495
+ )
1496
+ if bone:
1497
+ axis_translation = get_bone_extension(bone).axis_translation
1498
+ tail = Vector(tail) @ BoneExtension.translate_axis(
1499
+ Matrix(),
1500
+ BoneExtension.reverse_axis_translation(axis_translation),
1501
+ )
1502
+ extended_shape.capsule.tail = tail
1503
+
1504
+ inside = extended_capsule_dict.get("inside")
1505
+ if isinstance(inside, bool):
1506
+ extended_shape.capsule.inside = inside
1507
+
1508
+ elif isinstance(extended_plane_dict, dict):
1509
+ extended_collider.shape_type = (
1510
+ extended_collider.SHAPE_TYPE_EXTENDED_PLANE.identifier
1511
+ )
1512
+
1513
+ offset = Vector(
1514
+ convert.vrm_json_array_to_float_vector(
1515
+ extended_plane_dict.get("offset"), [0, 0, 0]
1516
+ )
1517
+ )
1518
+ if bone:
1519
+ axis_translation = get_bone_extension(bone).axis_translation
1520
+ offset = Vector(offset) @ BoneExtension.translate_axis(
1521
+ Matrix(),
1522
+ BoneExtension.reverse_axis_translation(axis_translation),
1523
+ )
1524
+ extended_shape.plane.offset = offset
1525
+
1526
+ normal = Vector(
1527
+ convert.vrm_json_array_to_float_vector(
1528
+ extended_plane_dict.get("normal"), [0, 0, 0]
1529
+ )
1530
+ )
1531
+ if bone:
1532
+ axis_translation = get_bone_extension(bone).axis_translation
1533
+ normal = Vector(normal) @ BoneExtension.translate_axis(
1534
+ Matrix(),
1535
+ BoneExtension.reverse_axis_translation(axis_translation),
1536
+ )
1537
+ extended_shape.plane.normal = normal
1538
+
1539
+ collider_bpy_object = collider.bpy_object
1540
+ if collider_bpy_object:
1541
+ # Override the fallback collider values with fixed values when loading
1542
+ collider_bpy_object.empty_display_size = 0.125
1543
+
1544
+ else:
1545
+ return
1546
+
1547
+ extended_collider.enabled = True
1548
+ extended_collider.automatic_fallback_generation = False
1549
+
1550
+ def load_spring_bone1_colliders(
1551
+ self,
1552
+ spring_bone: SpringBone1SpringBonePropertyGroup,
1553
+ spring_bone_dict: dict[str, Json],
1554
+ armature: Object,
1555
+ ) -> None:
1556
+ collider_dicts = spring_bone_dict.get("colliders")
1557
+ if not isinstance(collider_dicts, list):
1558
+ collider_dicts = []
1559
+
1560
+ for collider_dict in collider_dicts:
1561
+ collider = spring_bone.add_collider(self._context, armature)
1562
+
1563
+ if not isinstance(collider_dict, dict):
1564
+ collider.shape.sphere.radius = 0.0001
1565
+ collider.shape.sphere.offset = (0, 0, -10000)
1566
+ continue
1567
+
1568
+ self.load_spring_bone1_collider(collider, collider_dict)
1569
+
1570
+ for collider in spring_bone.colliders:
1571
+ collider.reset_bpy_object(self._context, armature)
1572
+
1573
+ collider_object_names: list[str] = []
1574
+ if spring_bone.colliders:
1575
+ colliders_collection = self._context.blend_data.collections.new("Colliders")
1576
+ self._context.scene.collection.children.link(colliders_collection)
1577
+ for collider in spring_bone.colliders:
1578
+ collider_bpy_object = collider.bpy_object
1579
+ if not collider_bpy_object:
1580
+ continue
1581
+ colliders_collection.objects.link(collider_bpy_object)
1582
+ collider_object_names.append(collider_bpy_object.name)
1583
+ if collider_bpy_object.name in self._context.scene.collection.objects:
1584
+ self._context.scene.collection.objects.unlink(collider_bpy_object)
1585
+ for child in collider_bpy_object.children:
1586
+ collider_object_names.append(child.name)
1587
+ colliders_collection.objects.link(child)
1588
+ if child.name in self._context.scene.collection.objects:
1589
+ self._context.scene.collection.objects.unlink(child)
1590
+
1591
+ spring_bone.active_collider_index = 0
1592
+
1593
+ if collider_object_names:
1594
+ imported_object_names = self._imported_object_names
1595
+ if imported_object_names is None:
1596
+ imported_object_names = []
1597
+ self._imported_object_names = imported_object_names
1598
+ imported_object_names.extend(collider_object_names)
1599
+
1600
+ def load_spring_bone1_collider_groups(
1601
+ self,
1602
+ spring_bone: SpringBone1SpringBonePropertyGroup,
1603
+ spring_bone_dict: dict[str, Json],
1604
+ ) -> None:
1605
+ collider_group_dicts = spring_bone_dict.get("colliderGroups")
1606
+ if not isinstance(collider_group_dicts, list):
1607
+ collider_group_dicts = []
1608
+
1609
+ for collider_group_dict in collider_group_dicts:
1610
+ # Since the reference from Spring to ColliderGroup is by index,
1611
+ # create empty data even if the contents of collider_group_dict are invalid
1612
+ collider_group = spring_bone.add_collider_group()
1613
+
1614
+ if not isinstance(collider_group_dict, dict):
1615
+ continue
1616
+
1617
+ name = collider_group_dict.get("name")
1618
+ if isinstance(name, str):
1619
+ collider_group.vrm_name = name
1620
+
1621
+ collider_indices = collider_group_dict.get("colliders")
1622
+ if not isinstance(collider_indices, list):
1623
+ continue
1624
+
1625
+ for collider_index in collider_indices:
1626
+ collider_reference = collider_group.add_collider()
1627
+ if not isinstance(collider_index, int):
1628
+ continue
1629
+ if not (0 <= collider_index < len(spring_bone.colliders)):
1630
+ continue
1631
+ collider = spring_bone.colliders[collider_index]
1632
+ if not collider:
1633
+ continue
1634
+ collider_reference.collider_uuid = collider.uuid
1635
+ collider_group.active_collider_index = 0
1636
+ spring_bone.active_collider_group_index = 0
1637
+
1638
+ def load_spring_bone1_springs(
1639
+ self,
1640
+ spring_bone: SpringBone1SpringBonePropertyGroup,
1641
+ spring_bone_dict: dict[str, Json],
1642
+ ) -> None:
1643
+ spring_dicts = spring_bone_dict.get("springs")
1644
+ if not isinstance(spring_dicts, list):
1645
+ spring_dicts = []
1646
+
1647
+ for spring_dict in spring_dicts:
1648
+ if not isinstance(spring_dict, dict):
1649
+ continue
1650
+
1651
+ spring = spring_bone.add_spring()
1652
+
1653
+ name = spring_dict.get("name")
1654
+ if isinstance(name, str):
1655
+ spring.vrm_name = name
1656
+
1657
+ center_index = spring_dict.get("center")
1658
+ if isinstance(center_index, int):
1659
+ bone_name = self._bone_names.get(center_index)
1660
+ if bone_name:
1661
+ spring.center.bone_name = bone_name
1662
+
1663
+ joint_dicts = spring_dict.get("joints")
1664
+ if not isinstance(joint_dicts, list):
1665
+ joint_dicts = []
1666
+ for joint_index, joint_dict in enumerate(joint_dicts):
1667
+ joint = spring.add_joint()
1668
+
1669
+ if not isinstance(joint_dict, dict):
1670
+ continue
1671
+
1672
+ node_index = joint_dict.get("node")
1673
+ if isinstance(node_index, int):
1674
+ bone_name = self._bone_names.get(node_index)
1675
+ if bone_name:
1676
+ joint.node.bone_name = bone_name
1677
+
1678
+ hit_radius = convert.float_or_none(joint_dict.get("hitRadius"))
1679
+ if hit_radius is not None:
1680
+ joint.hit_radius = hit_radius
1681
+
1682
+ stiffness = convert.float_or_none(joint_dict.get("stiffness"))
1683
+ if stiffness is not None:
1684
+ joint.stiffness = stiffness
1685
+
1686
+ gravity_power = convert.float_or_none(joint_dict.get("gravityPower"))
1687
+ if gravity_power is not None:
1688
+ joint.gravity_power = gravity_power
1689
+
1690
+ gltf_axis_gravity_dir = convert.vrm_json_array_to_float_vector(
1691
+ joint_dict.get("gravityDir"),
1692
+ [0.0, -1.0, 0.0],
1693
+ )
1694
+ joint.gravity_dir = [
1695
+ gltf_axis_gravity_dir[0],
1696
+ -gltf_axis_gravity_dir[2],
1697
+ gltf_axis_gravity_dir[1],
1698
+ ]
1699
+
1700
+ drag_force = convert.float_or_none(joint_dict.get("dragForce"))
1701
+ if drag_force is not None:
1702
+ joint.drag_force = drag_force
1703
+
1704
+ if joint_index + 1 < len(joint_dicts):
1705
+ bone = self.armature_data.bones.get(joint.node.bone_name)
1706
+ tail_bone = None
1707
+ tail_dict = joint_dicts[joint_index + 1]
1708
+ if isinstance(tail_dict, dict):
1709
+ tail_index = tail_dict.get("node")
1710
+ if isinstance(tail_index, int):
1711
+ tail_name = self._bone_names.get(tail_index)
1712
+ if tail_name:
1713
+ tail_bone = self.armature_data.bones.get(tail_name)
1714
+ self.load_spring_bone1_joint_limit(
1715
+ joint, joint_dict, bone, tail_bone
1716
+ )
1717
+ spring.active_joint_index = 0
1718
+
1719
+ collider_group_indices = spring_dict.get("colliderGroups")
1720
+ if not isinstance(collider_group_indices, list):
1721
+ collider_group_indices = []
1722
+ for collider_group_index in collider_group_indices:
1723
+ collider_group_reference = spring.add_collider_group()
1724
+ if not isinstance(collider_group_index, int):
1725
+ continue
1726
+ if not (0 <= collider_group_index < len(spring_bone.collider_groups)):
1727
+ continue
1728
+ collider_group = spring_bone.collider_groups[collider_group_index]
1729
+ if not collider_group:
1730
+ continue
1731
+ collider_group_reference.collider_group_uuid = collider_group.uuid
1732
+ spring.active_collider_group_index = 0
1733
+ spring_bone.active_spring_index = 0
1734
+
1735
+ @staticmethod
1736
+ def load_spring_bone1_joint_limit(
1737
+ joint: SpringBone1JointPropertyGroup,
1738
+ joint_dict: dict[str, Json],
1739
+ bone: Optional[Bone] = None,
1740
+ tail_bone: Optional[Bone] = None,
1741
+ ) -> None:
1742
+ extensions_dict = joint_dict.get("extensions")
1743
+ if not isinstance(extensions_dict, dict):
1744
+ return
1745
+ extension_dict = extensions_dict.get("VRMC_springBone_limit")
1746
+ if not isinstance(extension_dict, dict):
1747
+ return
1748
+ if extension_dict.get("specVersion") != "1.0":
1749
+ return
1750
+ limit_dict = extension_dict.get("limit")
1751
+ if not isinstance(limit_dict, dict):
1752
+ return
1753
+
1754
+ limit = joint.vrmc_spring_bone_limit
1755
+ parameter_dict: Optional[dict[str, Json]] = None
1756
+ if isinstance(cone_dict := limit_dict.get("cone"), dict):
1757
+ limit.limit_type = limit.LIMIT_TYPE_CONE.identifier
1758
+ parameter_dict = cone_dict
1759
+ angle = convert.float_or_none(cone_dict.get("angle"))
1760
+ if angle is not None:
1761
+ limit.cone_angle = angle
1762
+ elif isinstance(hinge_dict := limit_dict.get("hinge"), dict):
1763
+ limit.limit_type = limit.LIMIT_TYPE_HINGE.identifier
1764
+ parameter_dict = hinge_dict
1765
+ angle = convert.float_or_none(hinge_dict.get("angle"))
1766
+ if angle is not None:
1767
+ limit.hinge_angle = angle
1768
+ elif isinstance(spherical_dict := limit_dict.get("spherical"), dict):
1769
+ limit.limit_type = limit.LIMIT_TYPE_SPHERICAL.identifier
1770
+ parameter_dict = spherical_dict
1771
+ pitch = convert.float_or_none(spherical_dict.get("pitch"))
1772
+ if pitch is not None:
1773
+ limit.spherical_pitch = pitch
1774
+ yaw = convert.float_or_none(spherical_dict.get("yaw"))
1775
+ if yaw is not None:
1776
+ limit.spherical_yaw = yaw
1777
+
1778
+ if parameter_dict is None:
1779
+ return
1780
+ rotation = convert.float4_or_none(parameter_dict.get("rotation"))
1781
+ x, y, z, w = rotation if rotation is not None else (0.0, 0.0, 0.0, 1.0)
1782
+ quaternion = Quaternion((w, x, y, z))
1783
+ if sum(component * component for component in quaternion) < float_info.epsilon:
1784
+ quaternion = Quaternion()
1785
+ else:
1786
+ quaternion.normalize()
1787
+ if bone and tail_bone:
1788
+ # Import adjusts node axes to Blender bone axes. The limit rotation
1789
+ # is relative to the shortest rotation from Y+ to the tail, so both
1790
+ # the node basis and that default rotation must be accounted for.
1791
+ bone_axis = bone.matrix_local.inverted_safe() @ tail_bone.head_local
1792
+ original_to_blender = BoneExtension.translate_axis(
1793
+ Matrix(), get_bone_extension(bone).axis_translation
1794
+ ).to_quaternion()
1795
+ original_bone_axis = original_to_blender.inverted() @ bone_axis
1796
+ quaternion = (
1797
+ limit.default_rotation(bone_axis).inverted()
1798
+ @ original_to_blender
1799
+ @ limit.default_rotation(original_bone_axis)
1800
+ @ quaternion
1801
+ ).normalized()
1802
+ limit.rotation = [
1803
+ quaternion.w,
1804
+ quaternion.x,
1805
+ quaternion.y,
1806
+ quaternion.z,
1807
+ ]
1808
+
1809
+ def load_spring_bone1(
1810
+ self,
1811
+ spring_bone: SpringBone1SpringBonePropertyGroup,
1812
+ spring_bone_dict: Json,
1813
+ ) -> None:
1814
+ if not isinstance(spring_bone_dict, dict):
1815
+ return
1816
+ armature = self._armature
1817
+ if armature is None:
1818
+ message = "armature is None"
1819
+ raise ValueError(message)
1820
+
1821
+ self.load_spring_bone1_colliders(spring_bone, spring_bone_dict, armature)
1822
+
1823
+ self.load_spring_bone1_collider_groups(
1824
+ spring_bone,
1825
+ spring_bone_dict,
1826
+ )
1827
+
1828
+ self.load_spring_bone1_springs(
1829
+ spring_bone,
1830
+ spring_bone_dict,
1831
+ )
1832
+
1833
+ def get_object_or_bone_by_node_index(
1834
+ self, node_index: int
1835
+ ) -> Union[Object, PoseBone, None]:
1836
+ object_name = self._object_names.get(node_index)
1837
+ bone_name = self._bone_names.get(node_index)
1838
+ if object_name is not None:
1839
+ return self._context.blend_data.objects.get(object_name)
1840
+ if self._armature and bone_name is not None:
1841
+ return self._armature.pose.bones.get(bone_name)
1842
+ return None
1843
+
1844
+ def load_node_constraint1(
1845
+ self,
1846
+ ) -> None:
1847
+ armature = self._armature
1848
+ if not armature:
1849
+ return
1850
+
1851
+ nodes = self._parse_result.json_dict.get("nodes")
1852
+ if not isinstance(nodes, list):
1853
+ nodes = []
1854
+ for node_index, node_dict in enumerate(nodes):
1855
+ if not isinstance(node_dict, dict):
1856
+ continue
1857
+
1858
+ node_extensions_dict = node_dict.get("extensions")
1859
+ if not isinstance(node_extensions_dict, dict):
1860
+ continue
1861
+
1862
+ vrmc_node_constraint = node_extensions_dict.get("VRMC_node_constraint")
1863
+ if not isinstance(vrmc_node_constraint, dict):
1864
+ continue
1865
+
1866
+ constraint_dict = vrmc_node_constraint.get("constraint")
1867
+ if not isinstance(constraint_dict, dict):
1868
+ continue
1869
+
1870
+ roll_dict = constraint_dict.get("roll")
1871
+ aim_dict = constraint_dict.get("aim")
1872
+ rotation_dict = constraint_dict.get("rotation")
1873
+
1874
+ object_or_bone = self.get_object_or_bone_by_node_index(node_index)
1875
+ if not object_or_bone:
1876
+ continue
1877
+
1878
+ axis_translation = BoneExtension.reverse_axis_translation(
1879
+ get_bone_extension(object_or_bone.bone).axis_translation
1880
+ if isinstance(object_or_bone, PoseBone)
1881
+ else get_object_extension(object_or_bone).axis_translation
1882
+ )
1883
+
1884
+ if isinstance(roll_dict, dict):
1885
+ constraint = object_or_bone.constraints.new(type="COPY_ROTATION")
1886
+ if not isinstance(constraint, CopyRotationConstraint):
1887
+ _logger.error(
1888
+ "%s is not a CopyRotationConstraint", type(constraint)
1889
+ )
1890
+ continue
1891
+ constraint.mix_mode = "ADD"
1892
+ constraint.owner_space = "LOCAL"
1893
+ constraint.target_space = "LOCAL"
1894
+ roll_axis = roll_dict.get("rollAxis")
1895
+ if isinstance(object_or_bone, PoseBone) and isinstance(roll_axis, str):
1896
+ roll_axis = BoneExtension.node_constraint_roll_axis_translation(
1897
+ axis_translation,
1898
+ roll_axis,
1899
+ )
1900
+ constraint.use_x = False
1901
+ constraint.use_y = False
1902
+ constraint.use_z = False
1903
+ if roll_axis == "X":
1904
+ constraint.use_x = True
1905
+ elif roll_axis == "Y":
1906
+ constraint.use_y = True
1907
+ elif roll_axis == "Z":
1908
+ constraint.use_z = True
1909
+ weight = convert.float_or_none(roll_dict.get("weight"))
1910
+ if weight is not None:
1911
+ constraint.influence = weight
1912
+ source_index = roll_dict.get("source")
1913
+ elif isinstance(aim_dict, dict):
1914
+ constraint = object_or_bone.constraints.new(type="DAMPED_TRACK")
1915
+ if not isinstance(constraint, DampedTrackConstraint):
1916
+ _logger.error("%s is not a DampedTrackConstraint", type(constraint))
1917
+ continue
1918
+ aim_axis = aim_dict.get("aimAxis")
1919
+ if isinstance(aim_axis, str) and isinstance(object_or_bone, PoseBone):
1920
+ aim_axis = BoneExtension.node_constraint_aim_axis_translation(
1921
+ axis_translation,
1922
+ aim_axis,
1923
+ )
1924
+ if isinstance(aim_axis, str):
1925
+ track_axis = convert.VRM_AIM_AXIS_TO_BPY_TRACK_AXIS.get(aim_axis)
1926
+ if track_axis:
1927
+ constraint.track_axis = track_axis
1928
+ weight = convert.float_or_none(aim_dict.get("weight"))
1929
+ if weight is not None:
1930
+ constraint.influence = weight
1931
+ source_index = aim_dict.get("source")
1932
+ elif isinstance(rotation_dict, dict):
1933
+ constraint = object_or_bone.constraints.new(type="COPY_ROTATION")
1934
+ if not isinstance(constraint, CopyRotationConstraint):
1935
+ _logger.error(
1936
+ "%s is not a CopyRotationConstraint", type(constraint)
1937
+ )
1938
+ continue
1939
+ constraint.mix_mode = "ADD"
1940
+ constraint.owner_space = "LOCAL"
1941
+ constraint.target_space = "LOCAL"
1942
+ constraint.use_x = True
1943
+ constraint.use_y = True
1944
+ constraint.use_z = True
1945
+ weight = convert.float_or_none(rotation_dict.get("weight"))
1946
+ if weight is not None:
1947
+ constraint.influence = weight
1948
+ source_index = rotation_dict.get("source")
1949
+ else:
1950
+ continue
1951
+
1952
+ if isinstance(source_index, int):
1953
+ source = self.get_object_or_bone_by_node_index(source_index)
1954
+ if isinstance(source, Object):
1955
+ constraint.target = source
1956
+ elif isinstance(source, PoseBone):
1957
+ constraint.target = armature
1958
+ constraint.subtarget = source.name
1959
+
1960
+ def on_post_import(self) -> None:
1961
+ armature = self._armature
1962
+ if not armature:
1963
+ return
1964
+ if not isinstance(armature_data := armature.data, Armature):
1965
+ return
1966
+
1967
+ self.trigger_post_import_hook(
1968
+ MappingProxyType(
1969
+ {k: make_json_view(v) for k, v in self._parse_result.json_dict.items()}
1970
+ ),
1971
+ self._parse_result.bin_chunk,
1972
+ armature,
1973
+ MappingProxyType(
1974
+ {
1975
+ index: obj
1976
+ for index, object_name in self._object_names.items()
1977
+ if (obj := self._context.blend_data.objects.get(object_name))
1978
+ }
1979
+ ),
1980
+ MappingProxyType(
1981
+ {
1982
+ index: bone
1983
+ for index, bone_name in self._bone_names.items()
1984
+ if (bone := armature_data.bones.get(bone_name))
1985
+ }
1986
+ ),
1987
+ MappingProxyType(self._images),
1988
+ MappingProxyType(self._materials),
1989
+ MappingProxyType(self._meshes),
1990
+ )
1991
+
1992
+ def trigger_post_import_hook(
1993
+ self,
1994
+ json_dict: Mapping[str, JsonView],
1995
+ buffer0: bytes,
1996
+ armature: Object,
1997
+ node_index_to_object: Mapping[int, Object],
1998
+ node_index_to_bone: Mapping[int, Bone],
1999
+ image_index_to_image: Mapping[int, Image],
2000
+ material_index_to_material: Mapping[int, Material],
2001
+ mesh_index_to_mesh: Mapping[int, Mesh],
2002
+ ) -> None:
2003
+ trigger_third_party_user_extension_hook(
2004
+ self._third_party_user_extensions,
2005
+ "post_import_hook",
2006
+ json_dict,
2007
+ buffer0,
2008
+ armature,
2009
+ node_index_to_object,
2010
+ node_index_to_bone,
2011
+ image_index_to_image,
2012
+ material_index_to_material,
2013
+ mesh_index_to_mesh,
2014
+ )