@volter/blender-engine 0.1.147 → 0.1.149

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,1723 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ # SPDX-FileCopyrightText: 2018 iCyP
3
+
4
+ import functools
5
+ import json
6
+ import math
7
+ import uuid
8
+ from collections.abc import Callable, Mapping, Sequence
9
+ from dataclasses import dataclass
10
+ from sys import float_info
11
+ from typing import Optional, Union
12
+
13
+ from bpy.types import (
14
+ Armature,
15
+ Context,
16
+ Material,
17
+ Object,
18
+ )
19
+ from mathutils import Matrix, Vector
20
+
21
+ from ..common import convert, shader
22
+ from ..common.convert import Json
23
+ from ..common.logger import get_logger
24
+ from ..common.preferences import get_preferences
25
+ from ..common.progress import PartialProgress
26
+ from ..common.version import get_addon_version
27
+ from ..common.vrm0.human_bone import HumanBoneName, HumanBoneSpecifications
28
+ from ..common.vrm1.human_bone import (
29
+ HumanBoneSpecifications as Vrm1HumanBoneSpecifications,
30
+ )
31
+ from ..editor import make_armature, migration
32
+ from ..editor.extension_accessor import get_armature_extension, get_material_extension
33
+ from ..editor.make_armature import (
34
+ connect_parent_tail_and_child_head_if_very_close_position,
35
+ )
36
+ from ..editor.mtoon1.property_group import (
37
+ Mtoon0TexturePropertyGroup,
38
+ Mtoon1MaterialPropertyGroup,
39
+ Mtoon1SamplerPropertyGroup,
40
+ Mtoon1TextureInfoPropertyGroup,
41
+ Mtoon1TexturePropertyGroup,
42
+ )
43
+ from ..editor.vrm0.property_group import (
44
+ Vrm0BlendShapeMasterPropertyGroup,
45
+ Vrm0FirstPersonPropertyGroup,
46
+ Vrm0HumanoidBonePropertyGroup,
47
+ Vrm0HumanoidPropertyGroup,
48
+ Vrm0MetaPropertyGroup,
49
+ Vrm0SecondaryAnimationPropertyGroup,
50
+ )
51
+ from ..editor.vrm1.property_group import Vrm1HumanBonesPropertyGroup
52
+ from .abstract_base_vrm_importer import AbstractBaseVrmImporter
53
+
54
+ _logger = get_logger(__name__)
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class MaterialProperty:
59
+ name: str
60
+ shader: str
61
+ render_queue: Optional[int]
62
+ keyword_map: Mapping[str, bool]
63
+ tag_map: Mapping[str, str]
64
+ float_properties: Mapping[str, float]
65
+ vector_properties: Mapping[str, Sequence[float]]
66
+ texture_properties: Mapping[str, int]
67
+
68
+ @staticmethod
69
+ def create(json_dict: Mapping[str, Json], i: int) -> "MaterialProperty":
70
+ fallback = MaterialProperty(
71
+ name="Undefined",
72
+ shader="VRM_USE_GLTFSHADER",
73
+ render_queue=None,
74
+ keyword_map={},
75
+ tag_map={},
76
+ float_properties={},
77
+ vector_properties={},
78
+ texture_properties={},
79
+ )
80
+
81
+ extensions_dict = json_dict.get("extensions")
82
+ if not isinstance(extensions_dict, dict):
83
+ return fallback
84
+
85
+ vrm_dict = extensions_dict.get("VRM")
86
+ if not isinstance(vrm_dict, dict):
87
+ return fallback
88
+
89
+ material_property_dicts = vrm_dict.get("materialProperties")
90
+ if not isinstance(material_property_dicts, list):
91
+ return fallback
92
+
93
+ if not (0 <= i < len(material_property_dicts)):
94
+ return fallback
95
+
96
+ material_property_dict = material_property_dicts[i]
97
+ if not isinstance(material_property_dict, dict):
98
+ return fallback
99
+
100
+ name = material_property_dict.get("name")
101
+ if not isinstance(name, str):
102
+ name = fallback.name
103
+
104
+ shader = material_property_dict.get("shader")
105
+ if not isinstance(shader, str):
106
+ shader = fallback.shader
107
+
108
+ render_queue = material_property_dict.get("renderQueue")
109
+ if not isinstance(render_queue, int):
110
+ render_queue = fallback.render_queue
111
+
112
+ raw_keyword_map = material_property_dict.get("keywordMap")
113
+ if isinstance(raw_keyword_map, dict):
114
+ keyword_map: Mapping[str, bool] = {
115
+ k: v for k, v in raw_keyword_map.items() if isinstance(v, bool)
116
+ }
117
+ else:
118
+ keyword_map = fallback.keyword_map
119
+
120
+ raw_tag_map = material_property_dict.get("tagMap")
121
+ if isinstance(raw_tag_map, dict):
122
+ tag_map: Mapping[str, str] = {
123
+ k: v for k, v in raw_tag_map.items() if isinstance(v, str)
124
+ }
125
+ else:
126
+ tag_map = fallback.tag_map
127
+
128
+ raw_float_properties = material_property_dict.get("floatProperties")
129
+ if isinstance(raw_float_properties, dict):
130
+ float_properties: Mapping[str, float] = {
131
+ k: float(v)
132
+ for k, v in raw_float_properties.items()
133
+ if isinstance(v, (float, int))
134
+ }
135
+ else:
136
+ float_properties = fallback.float_properties
137
+
138
+ raw_vector_properties = material_property_dict.get("vectorProperties")
139
+ if isinstance(raw_vector_properties, dict):
140
+ vector_properties: Mapping[str, Sequence[float]] = {
141
+ k: [
142
+ vector_element
143
+ for vector_element in vector
144
+ if isinstance(vector_element, (float, int))
145
+ ]
146
+ for k, vector in raw_vector_properties.items()
147
+ if isinstance(vector, list)
148
+ }
149
+ else:
150
+ vector_properties = fallback.vector_properties
151
+
152
+ raw_texture_properties = material_property_dict.get("textureProperties")
153
+ if isinstance(raw_texture_properties, dict):
154
+ texture_properties: Mapping[str, int] = {
155
+ k: v for k, v in raw_texture_properties.items() if isinstance(v, int)
156
+ }
157
+ else:
158
+ texture_properties = fallback.texture_properties
159
+
160
+ return MaterialProperty(
161
+ name=name,
162
+ shader=shader,
163
+ render_queue=render_queue,
164
+ keyword_map=keyword_map,
165
+ tag_map=tag_map,
166
+ float_properties=float_properties,
167
+ vector_properties=vector_properties,
168
+ texture_properties=texture_properties,
169
+ )
170
+
171
+
172
+ def _calculate_mtoon0_render_queue_offset_maps(
173
+ material_properties: Sequence[MaterialProperty],
174
+ ) -> tuple[Mapping[int, int], Mapping[int, int]]:
175
+ """Build render queue offset maps for MToon0 materials.
176
+
177
+ Based on UniVRM's migration logic:
178
+ https://github.com/vrm-c/UniVRM/blob/v0.131.0/Packages/VRM10/Runtime/Migration/Materials/MigrationMToonMaterial.cs#L33-L131
179
+
180
+ Returns a tuple of (transparent_queue_map, transparent_z_write_queue_map).
181
+ The maps convert raw VRM0 render queue values to MToon1 render queue offset numbers.
182
+ For Transparent materials, the highest raw queue maps to 0, then -1, -2, ...
183
+ For TransparentWithZWrite materials, the lowest raw queue maps to 0, then 1, 2, ...
184
+ """
185
+ transparent_render_queues: set[int] = set()
186
+ transparent_z_write_render_queues: set[int] = set()
187
+ for material_property in material_properties:
188
+ if (
189
+ material_property.shader != "VRM/MToon"
190
+ or material_property.render_queue is None
191
+ ):
192
+ continue
193
+ blend_mode = material_property.float_properties.get("_BlendMode")
194
+ if blend_mode is None:
195
+ continue
196
+ if math.fabs(blend_mode - 2) < 0.001:
197
+ transparent_render_queues.add(material_property.render_queue)
198
+ elif math.fabs(blend_mode - 3) < 0.001:
199
+ transparent_z_write_render_queues.add(material_property.render_queue)
200
+ transparent_queue_map = {
201
+ q: -i for i, q in enumerate(sorted(transparent_render_queues, reverse=True))
202
+ }
203
+ transparent_z_write_queue_map = {
204
+ q: i for i, q in enumerate(sorted(transparent_z_write_render_queues))
205
+ }
206
+ return transparent_queue_map, transparent_z_write_queue_map
207
+
208
+
209
+ class Vrm0Importer(AbstractBaseVrmImporter):
210
+ def load_materials(self, progress: PartialProgress) -> None:
211
+ material_dicts = self._parse_result.json_dict.get("materials")
212
+ if not isinstance(material_dicts, list):
213
+ return
214
+
215
+ material_properties = [
216
+ MaterialProperty.create(self._parse_result.json_dict, index)
217
+ for index in range(len(material_dicts))
218
+ ]
219
+
220
+ transparent_queue_map, transparent_z_write_queue_map = (
221
+ _calculate_mtoon0_render_queue_offset_maps(material_properties)
222
+ )
223
+
224
+ shader_to_assignment_method: Mapping[
225
+ str,
226
+ Callable[[Material, MaterialProperty], None],
227
+ ] = {
228
+ "VRM/MToon": functools.partial(
229
+ self.assign_mtoon0_property,
230
+ transparent_queue_map=transparent_queue_map,
231
+ transparent_z_write_queue_map=transparent_z_write_queue_map,
232
+ ),
233
+ "VRM/UnlitTexture": self.assign_unlit_texture_property,
234
+ "VRM/UnlitCutout": self.assign_unlit_cutout_property,
235
+ "VRM/UnlitTransparent": self.assign_unlit_transparent_property,
236
+ "VRM/UnlitTransparentZWrite": (
237
+ self.assign_unlit_transparent_z_write_property
238
+ ),
239
+ }
240
+
241
+ for index, material_property in enumerate(material_properties):
242
+ assignment_method = shader_to_assignment_method.get(
243
+ material_property.shader
244
+ )
245
+ if not assignment_method:
246
+ continue
247
+
248
+ material = self._materials.get(index)
249
+ if not material:
250
+ material = self._context.blend_data.materials.new(
251
+ material_property.name
252
+ )
253
+ self._materials[index] = material
254
+
255
+ self.reset_material(material)
256
+ assignment_method(material, material_property)
257
+ progress.update(float(index) / len(material_properties))
258
+
259
+ progress.update(1)
260
+
261
+ def assign_mtoon0_texture(
262
+ self,
263
+ texture: Union[Mtoon0TexturePropertyGroup, Mtoon1TexturePropertyGroup],
264
+ texture_index: Optional[int],
265
+ ) -> bool:
266
+ if texture_index is None:
267
+ return False
268
+ texture_dicts = self._parse_result.json_dict.get("textures")
269
+ if not isinstance(texture_dicts, list):
270
+ return False
271
+ if not (0 <= texture_index < len(texture_dicts)):
272
+ return False
273
+ texture_dict = texture_dicts[texture_index]
274
+ if not isinstance(texture_dict, dict):
275
+ return False
276
+
277
+ source = texture_dict.get("source")
278
+ if isinstance(source, int):
279
+ image = self._images.get(source)
280
+ if image:
281
+ image.colorspace_settings.name = texture.colorspace
282
+ texture.source = image
283
+
284
+ sampler = texture_dict.get("sampler")
285
+ samplers = self._parse_result.json_dict.get("samplers")
286
+ if not isinstance(sampler, int) or not isinstance(samplers, list):
287
+ return True
288
+ if not (0 <= sampler < len(samplers)):
289
+ return True
290
+
291
+ sampler_dict = samplers[sampler]
292
+ if not isinstance(sampler_dict, dict):
293
+ return True
294
+
295
+ mag_filter = sampler_dict.get("magFilter")
296
+ if isinstance(mag_filter, int):
297
+ texture.sampler.mag_filter = (
298
+ Mtoon1SamplerPropertyGroup.mag_filter_enum.value_to_identifier(
299
+ mag_filter, Mtoon1SamplerPropertyGroup.MAG_FILTER_DEFAULT.identifier
300
+ )
301
+ )
302
+
303
+ min_filter = sampler_dict.get("minFilter")
304
+ if isinstance(min_filter, int):
305
+ texture.sampler.min_filter = (
306
+ Mtoon1SamplerPropertyGroup.min_filter_enum.value_to_identifier(
307
+ min_filter, Mtoon1SamplerPropertyGroup.MIN_FILTER_DEFAULT.identifier
308
+ )
309
+ )
310
+
311
+ wrap_s = sampler_dict.get("wrapS")
312
+ if isinstance(wrap_s, int):
313
+ texture.sampler.wrap_s = (
314
+ Mtoon1SamplerPropertyGroup.wrap_enum.value_to_identifier(
315
+ wrap_s, Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
316
+ )
317
+ )
318
+
319
+ wrap_t = sampler_dict.get("wrapT")
320
+ if isinstance(wrap_t, int):
321
+ texture.sampler.wrap_t = (
322
+ Mtoon1SamplerPropertyGroup.wrap_enum.value_to_identifier(
323
+ wrap_t, Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
324
+ )
325
+ )
326
+
327
+ return True
328
+
329
+ def assign_mtoon0_texture_info(
330
+ self,
331
+ texture_info: Mtoon1TextureInfoPropertyGroup,
332
+ texture_index: Optional[int],
333
+ uv_transform: tuple[float, float, float, float],
334
+ ) -> None:
335
+ if not self.assign_mtoon0_texture(texture_info.index, texture_index):
336
+ return
337
+
338
+ texture_info.extensions.khr_texture_transform.offset = (
339
+ uv_transform[0],
340
+ uv_transform[1],
341
+ )
342
+ texture_info.extensions.khr_texture_transform.scale = (
343
+ uv_transform[2],
344
+ uv_transform[3],
345
+ )
346
+
347
+ def assign_mtoon0_property(
348
+ self,
349
+ material: Material,
350
+ material_property: MaterialProperty,
351
+ transparent_queue_map: Mapping[int, int],
352
+ transparent_z_write_queue_map: Mapping[int, int],
353
+ ) -> None:
354
+ # https://github.com/saturday06/VRM-Addon-for-Blender/blob/2_15_26/io_scene_vrm/editor/mtoon1/ops.py#L98
355
+ material.use_backface_culling = True
356
+
357
+ gltf = get_material_extension(material).mtoon1
358
+ gltf.addon_version = get_addon_version()
359
+ gltf.enabled = True
360
+ gltf.show_expanded_mtoon0 = True
361
+ mtoon = gltf.extensions.vrmc_materials_mtoon
362
+
363
+ gltf.alpha_mode = gltf.ALPHA_MODE_OPAQUE.identifier
364
+ blend_mode = material_property.float_properties.get("_BlendMode")
365
+ if blend_mode is not None:
366
+ if math.fabs(blend_mode - 1) < 0.001:
367
+ gltf.alpha_mode = gltf.ALPHA_MODE_MASK.identifier
368
+ elif math.fabs(blend_mode - 2) < 0.001:
369
+ gltf.alpha_mode = gltf.ALPHA_MODE_BLEND.identifier
370
+ elif math.fabs(blend_mode - 3) < 0.001:
371
+ gltf.alpha_mode = gltf.ALPHA_MODE_BLEND.identifier
372
+ mtoon.transparent_with_z_write = True
373
+
374
+ cutoff = material_property.float_properties.get("_Cutoff")
375
+ if cutoff is not None:
376
+ gltf.alpha_cutoff = cutoff
377
+
378
+ gltf.double_sided = True
379
+ cull_mode = material_property.float_properties.get("_CullMode")
380
+ if cull_mode is not None:
381
+ if math.fabs(cull_mode - 1) < 0.001:
382
+ gltf.mtoon0_front_cull_mode = True
383
+ elif math.fabs(cull_mode - 2) < 0.001:
384
+ gltf.double_sided = False
385
+
386
+ base_color_factor = shader.rgba_or_none(
387
+ material_property.vector_properties.get("_Color")
388
+ )
389
+ if base_color_factor:
390
+ gltf.pbr_metallic_roughness.base_color_factor = convert.srgb_to_linear(
391
+ base_color_factor
392
+ )
393
+
394
+ (
395
+ texture_offset_u,
396
+ raw_texture_offset_v,
397
+ raw_texture_scale_u,
398
+ raw_texture_scale_v,
399
+ ) = convert.float4_or(
400
+ material_property.vector_properties.get("_MainTex"), (0.0, 0.0, 1.0, 1.0)
401
+ )
402
+
403
+ if abs(raw_texture_scale_u) < float_info.epsilon:
404
+ texture_scale_u = 1.0
405
+ else:
406
+ texture_scale_u = raw_texture_scale_u
407
+
408
+ if abs(raw_texture_scale_v) < float_info.epsilon:
409
+ texture_scale_v = 1.0
410
+ else:
411
+ texture_scale_v = raw_texture_scale_v
412
+
413
+ # https://github.com/vrm-c/UniVRM/issues/930
414
+ texture_offset_v = 1 - raw_texture_offset_v - texture_scale_v
415
+
416
+ uv_transform = (
417
+ texture_offset_u,
418
+ texture_offset_v,
419
+ texture_scale_u,
420
+ texture_scale_v,
421
+ )
422
+
423
+ self.assign_mtoon0_texture_info(
424
+ gltf.pbr_metallic_roughness.base_color_texture,
425
+ material_property.texture_properties.get("_MainTex"),
426
+ uv_transform,
427
+ )
428
+
429
+ shade_color_factor = shader.rgb_or_none(
430
+ material_property.vector_properties.get("_ShadeColor")
431
+ )
432
+ if shade_color_factor:
433
+ mtoon.shade_color_factor = convert.srgb_to_linear(shade_color_factor)
434
+
435
+ self.assign_mtoon0_texture_info(
436
+ mtoon.shade_multiply_texture,
437
+ material_property.texture_properties.get("_ShadeTexture"),
438
+ uv_transform,
439
+ )
440
+
441
+ self.assign_mtoon0_texture_info(
442
+ gltf.normal_texture,
443
+ material_property.texture_properties.get("_BumpMap"),
444
+ uv_transform,
445
+ )
446
+ normal_texture_scale = material_property.float_properties.get("_BumpScale")
447
+ if isinstance(normal_texture_scale, (float, int)):
448
+ gltf.normal_texture.scale = float(normal_texture_scale)
449
+
450
+ shading_shift_0x = material_property.float_properties.get("_ShadeShift")
451
+ if shading_shift_0x is None:
452
+ shading_shift_0x = 0
453
+
454
+ shading_toony_0x = material_property.float_properties.get("_ShadeToony")
455
+ if shading_toony_0x is None:
456
+ shading_toony_0x = 0
457
+
458
+ mtoon.shading_shift_factor = convert.mtoon_shading_shift_0_to_1(
459
+ shading_toony_0x, shading_shift_0x
460
+ )
461
+
462
+ mtoon.shading_toony_factor = convert.mtoon_shading_toony_0_to_1(
463
+ shading_toony_0x, shading_shift_0x
464
+ )
465
+
466
+ indirect_light_intensity = material_property.float_properties.get(
467
+ "_IndirectLightIntensity"
468
+ )
469
+ if indirect_light_intensity is not None:
470
+ mtoon.gi_equalization_factor = convert.mtoon_intensity_to_gi_equalization(
471
+ indirect_light_intensity
472
+ )
473
+ else:
474
+ mtoon.gi_equalization_factor = 0.5
475
+
476
+ raw_emissive_factor = shader.rgb_or_none(
477
+ material_property.vector_properties.get("_EmissionColor")
478
+ ) or (0, 0, 0)
479
+ max_color_component_value = max(raw_emissive_factor)
480
+ if max_color_component_value > 1:
481
+ gltf.emissive_factor = convert.srgb_to_linear(
482
+ Vector(raw_emissive_factor) / max_color_component_value
483
+ )
484
+ gltf.extensions.khr_materials_emissive_strength.emissive_strength = (
485
+ max_color_component_value
486
+ )
487
+ else:
488
+ gltf.emissive_factor = convert.srgb_to_linear(raw_emissive_factor)
489
+
490
+ self.assign_mtoon0_texture_info(
491
+ gltf.emissive_texture,
492
+ material_property.texture_properties.get("_EmissionMap"),
493
+ uv_transform,
494
+ )
495
+
496
+ self.assign_mtoon0_texture_info(
497
+ mtoon.matcap_texture,
498
+ material_property.texture_properties.get("_SphereAdd"),
499
+ (0, 0, 1, 1),
500
+ )
501
+
502
+ parametric_rim_color_factor = shader.rgb_or_none(
503
+ material_property.vector_properties.get("_RimColor")
504
+ )
505
+ if parametric_rim_color_factor:
506
+ mtoon.parametric_rim_color_factor = convert.srgb_to_linear(
507
+ parametric_rim_color_factor
508
+ )
509
+
510
+ parametric_rim_fresnel_power_factor = material_property.float_properties.get(
511
+ "_RimFresnelPower"
512
+ )
513
+ if isinstance(parametric_rim_fresnel_power_factor, (float, int)):
514
+ mtoon.parametric_rim_fresnel_power_factor = (
515
+ parametric_rim_fresnel_power_factor
516
+ )
517
+
518
+ mtoon.parametric_rim_lift_factor = material_property.float_properties.get(
519
+ "_RimLift", 0
520
+ )
521
+
522
+ self.assign_mtoon0_texture_info(
523
+ mtoon.rim_multiply_texture,
524
+ material_property.texture_properties.get("_RimTexture"),
525
+ uv_transform,
526
+ )
527
+
528
+ # https://github.com/vrm-c/UniVRM/blob/7c9919ef47a25c04100a2dcbe6a75dff49ef4857/Assets/VRM10/Runtime/Migration/Materials/MigrationMToonMaterial.cs#L287-L290
529
+ mtoon.rim_lighting_mix_factor = 1.0
530
+ rim_lighting_mix = material_property.float_properties.get("_RimLightingMix")
531
+ if rim_lighting_mix is not None:
532
+ gltf.mtoon0_rim_lighting_mix = rim_lighting_mix
533
+
534
+ mtoon.enable_outline_preview = get_preferences(
535
+ self._context
536
+ ).enable_mtoon_outline_preview
537
+
538
+ centimeter_to_meter = 0.01
539
+ one_hundredth = 0.01
540
+
541
+ outline_width_mode = round(
542
+ material_property.float_properties.get("_OutlineWidthMode", 0)
543
+ )
544
+
545
+ outline_width = material_property.float_properties.get("_OutlineWidth")
546
+ if outline_width is None:
547
+ outline_width = 0.0
548
+
549
+ if outline_width_mode == 0:
550
+ mtoon.outline_width_mode = mtoon.OUTLINE_WIDTH_MODE_NONE.identifier
551
+ elif outline_width_mode == 1:
552
+ mtoon.outline_width_mode = (
553
+ mtoon.OUTLINE_WIDTH_MODE_WORLD_COORDINATES.identifier
554
+ )
555
+ mtoon.outline_width_factor = max(0.0, outline_width * centimeter_to_meter)
556
+ else:
557
+ mtoon.outline_width_mode = (
558
+ mtoon.OUTLINE_WIDTH_MODE_SCREEN_COORDINATES.identifier
559
+ )
560
+ mtoon.outline_width_factor = max(0.0, outline_width * one_hundredth * 0.5)
561
+ outline_scaled_max_distance = material_property.float_properties.get(
562
+ "_OutlineScaledMaxDistance"
563
+ )
564
+ if outline_scaled_max_distance is not None:
565
+ gltf.mtoon0_outline_scaled_max_distance = outline_scaled_max_distance
566
+
567
+ self.assign_mtoon0_texture_info(
568
+ mtoon.outline_width_multiply_texture,
569
+ material_property.texture_properties.get("_OutlineWidthTexture"),
570
+ uv_transform,
571
+ )
572
+
573
+ outline_color_factor = shader.rgb_or_none(
574
+ material_property.vector_properties.get("_OutlineColor")
575
+ )
576
+ if outline_color_factor:
577
+ mtoon.outline_color_factor = convert.srgb_to_linear(outline_color_factor)
578
+
579
+ outline_color_mode = material_property.float_properties.get("_OutlineColorMode")
580
+ if (
581
+ outline_color_mode is not None
582
+ and math.fabs(outline_color_mode - 1) < 0.00001
583
+ ):
584
+ mtoon.outline_lighting_mix_factor = (
585
+ material_property.float_properties.get("_OutlineLightingMix") or 1
586
+ )
587
+ else:
588
+ mtoon.outline_lighting_mix_factor = 0 # Fixed Color
589
+
590
+ self.assign_mtoon0_texture_info(
591
+ mtoon.uv_animation_mask_texture,
592
+ material_property.texture_properties.get("_UvAnimMaskTexture"),
593
+ uv_transform,
594
+ )
595
+
596
+ uv_animation_rotation_speed_factor = material_property.float_properties.get(
597
+ "_UvAnimRotation"
598
+ )
599
+ if isinstance(uv_animation_rotation_speed_factor, (float, int)):
600
+ mtoon.uv_animation_rotation_speed_factor = (
601
+ uv_animation_rotation_speed_factor
602
+ )
603
+
604
+ uv_animation_scroll_x_speed_factor = material_property.float_properties.get(
605
+ "_UvAnimScrollX"
606
+ )
607
+ if isinstance(uv_animation_scroll_x_speed_factor, (float, int)):
608
+ mtoon.uv_animation_scroll_x_speed_factor = (
609
+ uv_animation_scroll_x_speed_factor
610
+ )
611
+
612
+ uv_animation_scroll_y_speed_factor = material_property.float_properties.get(
613
+ "_UvAnimScrollY"
614
+ )
615
+ if isinstance(uv_animation_scroll_y_speed_factor, (float, int)):
616
+ mtoon.uv_animation_scroll_y_speed_factor = (
617
+ -uv_animation_scroll_y_speed_factor
618
+ )
619
+
620
+ light_color_attenuation = material_property.float_properties.get(
621
+ "_LightColorAttenuation"
622
+ )
623
+ if light_color_attenuation is not None:
624
+ gltf.mtoon0_light_color_attenuation = light_color_attenuation
625
+
626
+ self.assign_mtoon0_texture(
627
+ gltf.mtoon0_receive_shadow_texture,
628
+ material_property.texture_properties.get("_ReceiveShadowTexture"),
629
+ )
630
+
631
+ gltf.mtoon0_receive_shadow_rate = material_property.float_properties.get(
632
+ "_ReceiveShadowRate", 0.5
633
+ )
634
+
635
+ self.assign_mtoon0_texture(
636
+ gltf.mtoon0_shading_grade_texture,
637
+ material_property.texture_properties.get("_ShadingGradeTexture"),
638
+ )
639
+
640
+ gltf.mtoon0_shading_grade_rate = material_property.float_properties.get(
641
+ "_ShadingGradeRate", 0.5
642
+ )
643
+
644
+ render_queue = material_property.render_queue
645
+ if render_queue is not None:
646
+ gltf.mtoon0_render_queue = render_queue
647
+
648
+ # https://github.com/vrm-c/UniVRM/blob/v0.131.0/Packages/VRM10/Runtime/Migration/Materials/MigrationMToonMaterial.cs#L100-L131
649
+ if render_queue is not None:
650
+ if blend_mode is not None and math.fabs(blend_mode - 2) < 0.001:
651
+ mtoon.render_queue_offset_number = max(
652
+ -9, min(0, transparent_queue_map.get(render_queue, 0))
653
+ )
654
+ elif blend_mode is not None and math.fabs(blend_mode - 3) < 0.001:
655
+ mtoon.render_queue_offset_number = max(
656
+ 0, min(9, transparent_z_write_queue_map.get(render_queue, 0))
657
+ )
658
+
659
+ def assign_unlit_common_property(
660
+ self,
661
+ material: Material,
662
+ material_property: MaterialProperty,
663
+ ) -> None:
664
+ gltf = get_material_extension(material).mtoon1
665
+ mtoon = gltf.extensions.vrmc_materials_mtoon
666
+
667
+ main_texture_index = material_property.texture_properties.get("_MainTex")
668
+ main_texture_image = None
669
+ if isinstance(main_texture_index, int):
670
+ texture_dicts = self._parse_result.json_dict.get("textures")
671
+ if isinstance(texture_dicts, list) and 0 <= main_texture_index < len(
672
+ texture_dicts
673
+ ):
674
+ texture_dict = texture_dicts[main_texture_index]
675
+ if isinstance(texture_dict, dict):
676
+ image_index = texture_dict.get("source")
677
+ if isinstance(image_index, int):
678
+ main_texture_image = self._images.get(image_index)
679
+
680
+ (
681
+ texture_offset_u,
682
+ raw_texture_offset_v,
683
+ raw_texture_scale_u,
684
+ raw_texture_scale_v,
685
+ ) = convert.float4_or(
686
+ material_property.vector_properties.get("_MainTex"), (0.0, 0.0, 1.0, 1.0)
687
+ )
688
+
689
+ if abs(raw_texture_scale_u) < float_info.epsilon:
690
+ texture_scale_u = 1.0
691
+ else:
692
+ texture_scale_u = raw_texture_scale_u
693
+
694
+ if abs(raw_texture_scale_v) < float_info.epsilon:
695
+ texture_scale_v = 1.0
696
+ else:
697
+ texture_scale_v = raw_texture_scale_v
698
+
699
+ # https://github.com/vrm-c/UniVRM/issues/930
700
+ texture_offset_v = 1 - raw_texture_offset_v - texture_scale_v
701
+
702
+ if main_texture_image:
703
+ for texture in (
704
+ gltf.pbr_metallic_roughness.base_color_texture,
705
+ gltf.emissive_texture,
706
+ mtoon.shade_multiply_texture,
707
+ ):
708
+ texture.index.source = main_texture_image
709
+ khr_texture_transform = texture.extensions.khr_texture_transform
710
+ khr_texture_transform.offset = (texture_offset_u, texture_offset_v)
711
+ khr_texture_transform.scale = (texture_scale_u, texture_scale_v)
712
+
713
+ gltf.pbr_metallic_roughness.base_color_factor = (0, 0, 0, 1)
714
+ gltf.emissive_factor = (1, 1, 1)
715
+
716
+ gltf.double_sided = False
717
+ mtoon.shade_color_factor = (0, 0, 0)
718
+ mtoon.shading_toony_factor = 0.95
719
+ mtoon.shading_shift_factor = -0.05
720
+ mtoon.rim_lighting_mix_factor = 1
721
+ mtoon.parametric_rim_fresnel_power_factor = 5
722
+ mtoon.parametric_rim_lift_factor = 0
723
+
724
+ render_queue = material_property.render_queue
725
+ if render_queue is not None:
726
+ gltf.mtoon0_render_queue = render_queue
727
+
728
+ def assign_unlit_texture_property(
729
+ self,
730
+ material: Material,
731
+ material_property: MaterialProperty,
732
+ ) -> None:
733
+ gltf = get_material_extension(material).mtoon1
734
+ gltf.enabled = True
735
+ gltf.alpha_mode = Mtoon1MaterialPropertyGroup.ALPHA_MODE_OPAQUE.identifier
736
+
737
+ self.assign_unlit_common_property(material, material_property)
738
+
739
+ def assign_unlit_cutout_property(
740
+ self,
741
+ material: Material,
742
+ material_property: MaterialProperty,
743
+ ) -> None:
744
+ gltf = get_material_extension(material).mtoon1
745
+ gltf.enabled = True
746
+ gltf.alpha_mode = Mtoon1MaterialPropertyGroup.ALPHA_MODE_MASK.identifier
747
+
748
+ cutoff = material_property.float_properties.get("_Cutoff")
749
+ if isinstance(cutoff, (int, float)):
750
+ gltf.alpha_cutoff = cutoff
751
+
752
+ self.assign_unlit_common_property(material, material_property)
753
+
754
+ def assign_unlit_transparent_property(
755
+ self,
756
+ material: Material,
757
+ material_property: MaterialProperty,
758
+ ) -> None:
759
+ gltf = get_material_extension(material).mtoon1
760
+ gltf.enabled = True
761
+ gltf.alpha_mode = Mtoon1MaterialPropertyGroup.ALPHA_MODE_BLEND.identifier
762
+
763
+ self.assign_unlit_common_property(material, material_property)
764
+
765
+ def assign_unlit_transparent_z_write_property(
766
+ self,
767
+ material: Material,
768
+ material_property: MaterialProperty,
769
+ ) -> None:
770
+ gltf = get_material_extension(material).mtoon1
771
+ gltf.enabled = True
772
+ gltf.alpha_mode = Mtoon1MaterialPropertyGroup.ALPHA_MODE_BLEND.identifier
773
+
774
+ mtoon = gltf.extensions.vrmc_materials_mtoon
775
+ mtoon.transparent_with_z_write = True
776
+
777
+ self.assign_unlit_common_property(material, material_property)
778
+
779
+ def load_gltf_extensions(self) -> None:
780
+ armature = self._armature
781
+ if not armature:
782
+ return
783
+ addon_extension = get_armature_extension(self.armature_data)
784
+ addon_extension.spec_version = addon_extension.SPEC_VERSION_VRM0
785
+ vrm0 = addon_extension.vrm0
786
+ vrm1 = addon_extension.vrm1
787
+
788
+ if self._parse_result.spec_version_number >= (1, 0):
789
+ return
790
+
791
+ vrm0_extension = self._parse_result.vrm0_extension_dict
792
+
793
+ addon_extension.addon_version = get_addon_version()
794
+
795
+ textblock = self._context.blend_data.texts.new(name="vrm.json")
796
+ textblock.write(json.dumps(self._parse_result.json_dict, indent=4))
797
+
798
+ self.load_vrm0_meta(vrm0.meta, vrm0_extension.get("meta"))
799
+ self.load_vrm0_humanoid(
800
+ vrm0.humanoid, vrm0_extension.get("humanoid"), vrm1.humanoid.human_bones
801
+ )
802
+ _setup_bones(self._context, armature)
803
+ self.load_vrm0_first_person(
804
+ vrm0.first_person, vrm0_extension.get("firstPerson")
805
+ )
806
+ self.load_vrm0_blend_shape_master(
807
+ vrm0.blend_shape_master, vrm0_extension.get("blendShapeMaster")
808
+ )
809
+ self.load_vrm0_secondary_animation(
810
+ vrm0.secondary_animation, vrm0_extension.get("secondaryAnimation")
811
+ )
812
+ migration.migrate(self._context, armature.name, heavy_migration=True)
813
+
814
+ def load_vrm0_meta(self, meta: Vrm0MetaPropertyGroup, meta_dict: Json) -> None:
815
+ if not isinstance(meta_dict, dict):
816
+ return
817
+
818
+ title = meta_dict.get("title")
819
+ if isinstance(title, str):
820
+ meta.title = title
821
+
822
+ version = meta_dict.get("version")
823
+ if isinstance(version, str):
824
+ meta.version = version
825
+
826
+ author = meta_dict.get("author")
827
+ if isinstance(author, str):
828
+ meta.author = author
829
+
830
+ contact_information = meta_dict.get("contactInformation")
831
+ if isinstance(contact_information, str):
832
+ meta.contact_information = contact_information
833
+
834
+ reference = meta_dict.get("reference")
835
+ if isinstance(reference, str):
836
+ meta.reference = reference
837
+
838
+ allowed_user_name = meta_dict.get("allowedUserName")
839
+ if (
840
+ isinstance(allowed_user_name, str)
841
+ and allowed_user_name in meta.allowed_user_name_enum.identifiers()
842
+ ):
843
+ meta.allowed_user_name = allowed_user_name
844
+
845
+ violent_ussage_name = meta_dict.get("violentUssageName")
846
+ if (
847
+ isinstance(violent_ussage_name, str)
848
+ and violent_ussage_name in meta.violent_ussage_name_enum.identifiers()
849
+ ):
850
+ meta.violent_ussage_name = violent_ussage_name
851
+
852
+ sexual_ussage_name = meta_dict.get("sexualUssageName")
853
+ if (
854
+ isinstance(sexual_ussage_name, str)
855
+ and sexual_ussage_name in meta.sexual_ussage_name_enum.identifiers()
856
+ ):
857
+ meta.sexual_ussage_name = sexual_ussage_name
858
+
859
+ commercial_ussage_name = meta_dict.get("commercialUssageName")
860
+ if (
861
+ isinstance(commercial_ussage_name, str)
862
+ and commercial_ussage_name in meta.commercial_ussage_name_enum.identifiers()
863
+ ):
864
+ meta.commercial_ussage_name = commercial_ussage_name
865
+
866
+ other_permission_url = meta_dict.get("otherPermissionUrl")
867
+ if isinstance(other_permission_url, str):
868
+ meta.other_permission_url = other_permission_url
869
+
870
+ license_name = meta_dict.get("licenseName")
871
+ if (
872
+ isinstance(license_name, str)
873
+ and license_name in meta.license_name_enum.identifiers()
874
+ ):
875
+ meta.license_name = license_name
876
+
877
+ other_license_url = meta_dict.get("otherLicenseUrl")
878
+ if isinstance(other_license_url, str):
879
+ meta.other_license_url = other_license_url
880
+
881
+ texture = meta_dict.get("texture")
882
+ texture_dicts = self._parse_result.json_dict.get("textures")
883
+ if (
884
+ isinstance(texture, int)
885
+ and isinstance(texture_dicts, list)
886
+ # extensions.VRM.meta.texture could be -1
887
+ # https://github.com/vrm-c/UniVRM/issues/91#issuecomment-454284964
888
+ and 0 <= texture < len(texture_dicts)
889
+ ):
890
+ texture_dict = texture_dicts[texture]
891
+ if isinstance(texture_dict, dict):
892
+ image_index = texture_dict.get("source")
893
+ if isinstance(image_index, int) and image_index in self._images:
894
+ meta.texture = self._images[image_index]
895
+
896
+ def load_vrm0_humanoid(
897
+ self,
898
+ humanoid: Vrm0HumanoidPropertyGroup,
899
+ humanoid_dict: Json,
900
+ vrm1_human_bones: Vrm1HumanBonesPropertyGroup,
901
+ ) -> None:
902
+ if not isinstance(humanoid_dict, dict):
903
+ return
904
+ human_bone_dicts = humanoid_dict.get("humanBones")
905
+ if isinstance(human_bone_dicts, list):
906
+ for human_bone_dict in human_bone_dicts:
907
+ if not isinstance(human_bone_dict, dict):
908
+ continue
909
+
910
+ bone = human_bone_dict.get("bone")
911
+ if bone not in HumanBoneSpecifications.all_names:
912
+ continue
913
+
914
+ node = human_bone_dict.get("node")
915
+ if not isinstance(node, int):
916
+ continue
917
+
918
+ node_bone_name = self._bone_names.get(node)
919
+ if node_bone_name is None:
920
+ continue
921
+
922
+ human_bone = next(
923
+ (
924
+ human_bone
925
+ for human_bone in humanoid.human_bones
926
+ if human_bone.bone == bone
927
+ ),
928
+ None,
929
+ )
930
+ if human_bone:
931
+ _logger.warning('Duplicated bone: "%s"', bone)
932
+ else:
933
+ human_bone = humanoid.human_bones.add()
934
+ human_bone.bone = bone
935
+ human_bone.node.bone_name = node_bone_name
936
+
937
+ vrm1_human_bone_specification = (
938
+ Vrm1HumanBoneSpecifications.from_vrm0_name_str(bone)
939
+ )
940
+ if vrm1_human_bone_specification:
941
+ vrm1_human_bone = (
942
+ vrm1_human_bones.human_bone_name_to_human_bone().get(
943
+ vrm1_human_bone_specification.name
944
+ )
945
+ )
946
+ if vrm1_human_bone:
947
+ vrm1_human_bone.node.bone_name = node_bone_name
948
+
949
+ use_default_values = human_bone_dict.get("useDefaultValues")
950
+ if isinstance(use_default_values, bool):
951
+ human_bone.use_default_values = use_default_values
952
+
953
+ min_ = convert.vrm_json_vector3_to_tuple(human_bone_dict.get("min"))
954
+ if min_ is not None:
955
+ human_bone.min = min_
956
+
957
+ max_ = convert.vrm_json_vector3_to_tuple(human_bone_dict.get("max"))
958
+ if max_ is not None:
959
+ human_bone.max = max_
960
+
961
+ center = convert.vrm_json_vector3_to_tuple(
962
+ human_bone_dict.get("center")
963
+ )
964
+ if center is not None:
965
+ human_bone.center = center
966
+
967
+ axis_length = convert.float_or_none(human_bone_dict.get("axisLength"))
968
+ if axis_length is not None:
969
+ human_bone.axis_length = axis_length
970
+
971
+ arm_stretch = convert.float_or_none(humanoid_dict.get("armStretch"))
972
+ if arm_stretch is not None:
973
+ humanoid.arm_stretch = arm_stretch
974
+
975
+ leg_stretch = convert.float_or_none(humanoid_dict.get("legStretch"))
976
+ if leg_stretch is not None:
977
+ humanoid.leg_stretch = leg_stretch
978
+
979
+ upper_arm_twist = convert.float_or_none(humanoid_dict.get("upperArmTwist"))
980
+ if upper_arm_twist is not None:
981
+ humanoid.upper_arm_twist = upper_arm_twist
982
+
983
+ lower_arm_twist = convert.float_or_none(humanoid_dict.get("lowerArmTwist"))
984
+ if lower_arm_twist is not None:
985
+ humanoid.lower_arm_twist = lower_arm_twist
986
+
987
+ upper_leg_twist = convert.float_or_none(humanoid_dict.get("upperLegTwist"))
988
+ if upper_leg_twist is not None:
989
+ humanoid.upper_leg_twist = upper_leg_twist
990
+
991
+ lower_leg_twist = convert.float_or_none(humanoid_dict.get("lowerLegTwist"))
992
+ if lower_leg_twist is not None:
993
+ humanoid.lower_leg_twist = lower_leg_twist
994
+
995
+ feet_spacing = convert.float_or_none(humanoid_dict.get("feetSpacing"))
996
+ if feet_spacing is not None:
997
+ humanoid.feet_spacing = feet_spacing
998
+
999
+ has_translation_dof = humanoid_dict.get("hasTranslationDoF")
1000
+ if isinstance(has_translation_dof, bool):
1001
+ humanoid.has_translation_dof = has_translation_dof
1002
+
1003
+ def load_vrm0_first_person(
1004
+ self,
1005
+ first_person: Vrm0FirstPersonPropertyGroup,
1006
+ first_person_dict: Json,
1007
+ ) -> None:
1008
+ if not isinstance(first_person_dict, dict):
1009
+ return
1010
+
1011
+ first_person_bone = first_person_dict.get("firstPersonBone")
1012
+ if isinstance(first_person_bone, int) and (
1013
+ first_person_bone_name := self._bone_names.get(first_person_bone)
1014
+ ):
1015
+ first_person.first_person_bone.bone_name = first_person_bone_name
1016
+
1017
+ first_person_bone_offset = convert.vrm_json_vector3_to_tuple(
1018
+ first_person_dict.get("firstPersonBoneOffset")
1019
+ )
1020
+ if first_person_bone_offset is not None:
1021
+ # Perform a special axis transformation for VRM 0.0.
1022
+ # https://github.com/vrm-c/vrm-specification/issues/205#issuecomment-753582597
1023
+ (x, y, z) = first_person_bone_offset
1024
+ first_person.first_person_bone_offset = (x, z, y)
1025
+
1026
+ mesh_annotation_dicts = first_person_dict.get("meshAnnotations")
1027
+ if isinstance(mesh_annotation_dicts, list):
1028
+ for mesh_annotation_dict in mesh_annotation_dicts:
1029
+ mesh_annotation = first_person.mesh_annotations.add()
1030
+
1031
+ if not isinstance(mesh_annotation_dict, dict):
1032
+ continue
1033
+
1034
+ first_person_flag = mesh_annotation_dict.get("firstPersonFlag")
1035
+ if (
1036
+ isinstance(first_person_flag, str)
1037
+ and first_person_flag
1038
+ in mesh_annotation.first_person_flag_enum.identifiers()
1039
+ ):
1040
+ mesh_annotation.first_person_flag = first_person_flag
1041
+
1042
+ mesh_index = mesh_annotation_dict.get("mesh")
1043
+ if not isinstance(mesh_index, int):
1044
+ continue
1045
+
1046
+ mesh_data = self._meshes.get(mesh_index)
1047
+ if not mesh_data:
1048
+ continue
1049
+
1050
+ mesh_object = next(
1051
+ (
1052
+ obj
1053
+ for object_name in self._object_names.values()
1054
+ if (obj := self._context.blend_data.objects.get(object_name))
1055
+ and obj.type == "MESH"
1056
+ and obj.data == mesh_data
1057
+ ),
1058
+ None,
1059
+ )
1060
+ if not mesh_object:
1061
+ continue
1062
+
1063
+ mesh_annotation.mesh.mesh_object_name = mesh_object.name
1064
+
1065
+ look_at_type_name = first_person_dict.get("lookAtTypeName")
1066
+ if (
1067
+ isinstance(look_at_type_name, str)
1068
+ and look_at_type_name in first_person.look_at_type_name_enum.identifiers()
1069
+ ):
1070
+ first_person.look_at_type_name = look_at_type_name
1071
+
1072
+ for look_at, look_at_dict in (
1073
+ (
1074
+ first_person.look_at_horizontal_inner,
1075
+ first_person_dict.get("lookAtHorizontalInner"),
1076
+ ),
1077
+ (
1078
+ first_person.look_at_horizontal_outer,
1079
+ first_person_dict.get("lookAtHorizontalOuter"),
1080
+ ),
1081
+ (
1082
+ first_person.look_at_vertical_down,
1083
+ first_person_dict.get("lookAtVerticalDown"),
1084
+ ),
1085
+ (
1086
+ first_person.look_at_vertical_up,
1087
+ first_person_dict.get("lookAtVerticalUp"),
1088
+ ),
1089
+ ):
1090
+ if not isinstance(look_at_dict, dict):
1091
+ continue
1092
+
1093
+ curve = convert.vrm_json_curve_to_list(look_at_dict.get("curve"))
1094
+ if curve is not None:
1095
+ look_at.curve = curve
1096
+
1097
+ x_range = look_at_dict.get("xRange")
1098
+ if isinstance(x_range, (float, int)):
1099
+ look_at.x_range = x_range
1100
+
1101
+ y_range = look_at_dict.get("yRange")
1102
+ if isinstance(y_range, (float, int)):
1103
+ look_at.y_range = y_range
1104
+
1105
+ def load_vrm0_blend_shape_master(
1106
+ self,
1107
+ blend_shape_master: Vrm0BlendShapeMasterPropertyGroup,
1108
+ blend_shape_master_dict: Json,
1109
+ ) -> None:
1110
+ if not isinstance(blend_shape_master_dict, dict):
1111
+ return
1112
+ blend_shape_group_dicts = blend_shape_master_dict.get("blendShapeGroups")
1113
+ if not isinstance(blend_shape_group_dicts, list):
1114
+ return
1115
+
1116
+ for blend_shape_group_dict in blend_shape_group_dicts:
1117
+ blend_shape_group = blend_shape_master.blend_shape_groups.add()
1118
+
1119
+ if not isinstance(blend_shape_group_dict, dict):
1120
+ continue
1121
+
1122
+ name = blend_shape_group_dict.get("name")
1123
+ if isinstance(name, str):
1124
+ blend_shape_group.name = name
1125
+
1126
+ preset_name = blend_shape_group_dict.get("presetName")
1127
+ if (
1128
+ isinstance(preset_name, str)
1129
+ and preset_name in blend_shape_group.preset_name_enum.identifiers()
1130
+ ):
1131
+ blend_shape_group.preset_name = preset_name
1132
+
1133
+ bind_dicts = blend_shape_group_dict.get("binds")
1134
+ if isinstance(bind_dicts, list):
1135
+ for bind_dict in bind_dicts:
1136
+ bind = blend_shape_group.binds.add()
1137
+
1138
+ if not isinstance(bind_dict, dict):
1139
+ continue
1140
+
1141
+ weight = convert.float_or(bind_dict.get("weight"), 0.0)
1142
+ bind.weight = min(max(weight / 100.0, 0), 1)
1143
+
1144
+ mesh_index = bind_dict.get("mesh")
1145
+ if not isinstance(mesh_index, int):
1146
+ continue
1147
+
1148
+ mesh_data = self._meshes.get(mesh_index)
1149
+ if not mesh_data:
1150
+ continue
1151
+
1152
+ mesh_object = next(
1153
+ (
1154
+ obj
1155
+ for object_name in self._object_names.values()
1156
+ if (
1157
+ obj := self._context.blend_data.objects.get(object_name)
1158
+ )
1159
+ and obj.type == "MESH"
1160
+ and obj.data == mesh_data
1161
+ ),
1162
+ None,
1163
+ )
1164
+ if not mesh_object:
1165
+ continue
1166
+
1167
+ bind.mesh.mesh_object_name = mesh_object.name
1168
+
1169
+ shape_keys = mesh_data.shape_keys
1170
+ if not shape_keys:
1171
+ continue
1172
+ key_blocks = shape_keys.key_blocks
1173
+
1174
+ bind_index = bind_dict.get("index")
1175
+ if not isinstance(bind_index, int) or bind_index < 0:
1176
+ continue
1177
+
1178
+ key_blocks_index = bind_index + 1
1179
+ if not (1 <= key_blocks_index < len(key_blocks)):
1180
+ continue
1181
+
1182
+ bind.index = key_blocks[key_blocks_index].name
1183
+
1184
+ material_value_dicts = blend_shape_group_dict.get("materialValues")
1185
+ if isinstance(material_value_dicts, list):
1186
+ for material_value_dict in material_value_dicts:
1187
+ material_value = blend_shape_group.material_values.add()
1188
+
1189
+ if not isinstance(material_value_dict, dict):
1190
+ continue
1191
+
1192
+ material_name = material_value_dict.get("materialName")
1193
+ if (
1194
+ isinstance(material_name, str)
1195
+ and material_name in self._context.blend_data.materials
1196
+ ):
1197
+ material_value.material = self._context.blend_data.materials[
1198
+ material_name
1199
+ ]
1200
+
1201
+ property_name = material_value_dict.get("propertyName")
1202
+ if isinstance(property_name, str):
1203
+ material_value.property_name = property_name
1204
+
1205
+ target_value_vector = material_value_dict.get("targetValue")
1206
+ if isinstance(target_value_vector, list):
1207
+ for v in target_value_vector:
1208
+ material_value.target_value.add().value = convert.float_or(
1209
+ v, 0.0
1210
+ )
1211
+
1212
+ is_binary = blend_shape_group_dict.get("isBinary")
1213
+ if isinstance(is_binary, bool):
1214
+ blend_shape_group.is_binary = is_binary
1215
+
1216
+ def load_vrm0_secondary_animation(
1217
+ self,
1218
+ secondary_animation: Vrm0SecondaryAnimationPropertyGroup,
1219
+ secondary_animation_dict: Json,
1220
+ ) -> None:
1221
+ if not isinstance(secondary_animation_dict, dict):
1222
+ return
1223
+ armature = self._armature
1224
+ if armature is None:
1225
+ message = "armature is None"
1226
+ raise ValueError(message)
1227
+
1228
+ collider_group_dicts = secondary_animation_dict.get("colliderGroups")
1229
+ if not isinstance(collider_group_dicts, list):
1230
+ collider_group_dicts = []
1231
+
1232
+ self._context.view_layer.depsgraph.update()
1233
+ self._context.scene.view_layers.update()
1234
+ collider_objs: list[Object] = []
1235
+ for collider_group_dict in collider_group_dicts:
1236
+ collider_group = secondary_animation.collider_groups.add()
1237
+ collider_group.uuid = uuid.uuid4().hex
1238
+ collider_group.fixup(armature)
1239
+
1240
+ if not isinstance(collider_group_dict, dict):
1241
+ continue
1242
+
1243
+ node = collider_group_dict.get("node")
1244
+ if not isinstance(node, int):
1245
+ continue
1246
+ bone_name = self._bone_names.get(node)
1247
+ if bone_name is None:
1248
+ continue
1249
+ collider_group.node.bone_name = bone_name
1250
+ collider_dicts = collider_group_dict.get("colliders")
1251
+ if not isinstance(collider_dicts, list):
1252
+ continue
1253
+
1254
+ for collider_index, collider_dict in enumerate(collider_dicts):
1255
+ collider = collider_group.colliders.add()
1256
+
1257
+ if not isinstance(collider_dict, dict):
1258
+ continue
1259
+
1260
+ offset = convert.vrm_json_vector3_to_tuple(collider_dict.get("offset"))
1261
+ if offset is None:
1262
+ offset = (0, 0, 0)
1263
+
1264
+ radius = convert.float_or(collider_dict.get("radius"), 0.0)
1265
+
1266
+ collider_name = f"{bone_name}_collider_{collider_index}"
1267
+ obj = self._context.blend_data.objects.new(
1268
+ name=collider_name, object_data=None
1269
+ )
1270
+ collider.bpy_object = obj
1271
+ obj.parent = self._armature
1272
+ obj.parent_type = "BONE"
1273
+ obj.parent_bone = bone_name
1274
+
1275
+ # Perform a special axis transformation for VRM 0.0.
1276
+ # https://github.com/vrm-c/UniVRM/issues/65
1277
+ fixed_offset = (-offset[0], -offset[2], offset[1])
1278
+
1279
+ obj.matrix_world = Matrix.Translation(
1280
+ (
1281
+ armature.matrix_world @ armature.pose.bones[bone_name].matrix
1282
+ ).to_translation()
1283
+ + Vector(fixed_offset)
1284
+ )
1285
+ obj.empty_display_size = radius
1286
+ obj.empty_display_type = "SPHERE"
1287
+ collider_objs.append(obj)
1288
+ if collider_objs:
1289
+ colliders_collection = self._context.blend_data.collections.new("Colliders")
1290
+ self._context.scene.collection.children.link(colliders_collection)
1291
+ for collider_obj in collider_objs:
1292
+ colliders_collection.objects.link(collider_obj)
1293
+
1294
+ for collider_group in secondary_animation.collider_groups:
1295
+ collider_group.fixup(armature)
1296
+
1297
+ bone_group_dicts = secondary_animation_dict.get("boneGroups")
1298
+ if not isinstance(bone_group_dicts, list):
1299
+ bone_group_dicts = []
1300
+
1301
+ for bone_group_dict in bone_group_dicts:
1302
+ bone_group = secondary_animation.bone_groups.add()
1303
+
1304
+ if not isinstance(bone_group_dict, dict):
1305
+ bone_group.fixup()
1306
+ continue
1307
+
1308
+ comment = bone_group_dict.get("comment")
1309
+ if isinstance(comment, str):
1310
+ bone_group.comment = comment
1311
+
1312
+ stiffiness = convert.float_or_none(bone_group_dict.get("stiffiness"))
1313
+ if stiffiness is not None:
1314
+ bone_group.stiffiness = stiffiness
1315
+
1316
+ gravity_power = convert.float_or_none(bone_group_dict.get("gravityPower"))
1317
+ if gravity_power is not None:
1318
+ bone_group.gravity_power = gravity_power
1319
+
1320
+ gravity_dir = convert.vrm_json_vector3_to_tuple(
1321
+ bone_group_dict.get("gravityDir")
1322
+ )
1323
+ if gravity_dir is not None:
1324
+ # Perform a special axis transformation for VRM 0.0.
1325
+ # https://github.com/vrm-c/vrm-specification/issues/205#issuecomment-753582597
1326
+ (x, y, z) = gravity_dir
1327
+ bone_group.gravity_dir = (x, z, y)
1328
+
1329
+ drag_force = convert.float_or_none(bone_group_dict.get("dragForce"))
1330
+ if drag_force is not None:
1331
+ bone_group.drag_force = drag_force
1332
+
1333
+ center = bone_group_dict.get("center")
1334
+ if isinstance(center, int) and (
1335
+ center_bone_name := self._bone_names.get(center)
1336
+ ):
1337
+ bone_group.center.bone_name = center_bone_name
1338
+
1339
+ hit_radius = convert.float_or_none(bone_group_dict.get("hitRadius"))
1340
+ if hit_radius is not None:
1341
+ bone_group.hit_radius = hit_radius
1342
+
1343
+ bones = bone_group_dict.get("bones")
1344
+ if isinstance(bones, list):
1345
+ for bone in bones:
1346
+ bone_prop = bone_group.bones.add()
1347
+ if not isinstance(bone, int):
1348
+ continue
1349
+ bone_name = self._bone_names.get(bone)
1350
+ if bone_name is None:
1351
+ continue
1352
+ bone_prop.bone_name = bone_name
1353
+
1354
+ collider_group_indices = bone_group_dict.get("colliderGroups")
1355
+ if isinstance(collider_group_indices, list):
1356
+ for collider_group_index in collider_group_indices:
1357
+ if not isinstance(collider_group_index, int) or not (
1358
+ 0
1359
+ <= collider_group_index
1360
+ < len(secondary_animation.collider_groups)
1361
+ ):
1362
+ continue
1363
+ collider_group = secondary_animation.collider_groups[
1364
+ collider_group_index
1365
+ ]
1366
+ collider_group_reference = bone_group.collider_groups.add()
1367
+ collider_group_reference.collider_group_uuid = collider_group.uuid
1368
+
1369
+ for bone_group in secondary_animation.bone_groups:
1370
+ bone_group.fixup()
1371
+
1372
+ collider_object_names = [
1373
+ collider_bpy_object.name
1374
+ for collider_group in secondary_animation.collider_groups
1375
+ for collider in collider_group.colliders
1376
+ if (collider_bpy_object := collider.bpy_object)
1377
+ ]
1378
+ if collider_object_names:
1379
+ imported_object_names = self._imported_object_names
1380
+ if imported_object_names is None:
1381
+ imported_object_names = []
1382
+ self._imported_object_names = imported_object_names
1383
+ imported_object_names.extend(collider_object_names)
1384
+
1385
+ def find_vrm0_bone_node_indices(self) -> list[int]:
1386
+ result: list[int] = []
1387
+ vrm0_dict = self._parse_result.vrm0_extension_dict
1388
+
1389
+ first_person_dict = vrm0_dict.get("firstPerson")
1390
+ if isinstance(first_person_dict, dict):
1391
+ first_person_bone_index = first_person_dict.get("firstPersonBone")
1392
+ if isinstance(first_person_bone_index, int):
1393
+ result.append(first_person_bone_index)
1394
+
1395
+ humanoid_dict = vrm0_dict.get("humanoid")
1396
+ if isinstance(humanoid_dict, dict):
1397
+ human_bone_dicts = humanoid_dict.get("humanBones")
1398
+ if isinstance(human_bone_dicts, list):
1399
+ for human_bone_dict in human_bone_dicts:
1400
+ if not isinstance(human_bone_dict, dict):
1401
+ continue
1402
+ node_index = human_bone_dict.get("node")
1403
+ if isinstance(node_index, int):
1404
+ result.append(node_index)
1405
+
1406
+ secondary_animation_dict = vrm0_dict.get("secondaryAnimation")
1407
+ if isinstance(secondary_animation_dict, dict):
1408
+ collider_group_dicts = secondary_animation_dict.get("colliderGroups")
1409
+ if isinstance(collider_group_dicts, list):
1410
+ for collider_group_dict in collider_group_dicts:
1411
+ if not isinstance(collider_group_dict, dict):
1412
+ continue
1413
+ node_index = collider_group_dict.get("node")
1414
+ if isinstance(node_index, int):
1415
+ result.append(node_index)
1416
+
1417
+ bone_group_dicts = secondary_animation_dict.get("boneGroups")
1418
+ if isinstance(bone_group_dicts, list):
1419
+ for bone_group_dict in bone_group_dicts:
1420
+ if not isinstance(bone_group_dict, dict):
1421
+ continue
1422
+ center_index = bone_group_dict.get("center")
1423
+ if isinstance(center_index, int):
1424
+ result.append(center_index)
1425
+ bone_indices = bone_group_dict.get("bones")
1426
+ if isinstance(bone_indices, list):
1427
+ result.extend(
1428
+ bone_index
1429
+ for bone_index in bone_indices
1430
+ if isinstance(bone_index, int)
1431
+ )
1432
+ return list(dict.fromkeys(result)) # Remove duplicates
1433
+
1434
+ def find_vrm_bone_node_indices(self) -> list[int]:
1435
+ # TODO: SkinnedMeshRenderer root <=> skin.skeleton???
1436
+ return list(
1437
+ dict.fromkeys( # Remove duplicates
1438
+ self.find_vrm0_bone_node_indices()
1439
+ )
1440
+ )
1441
+
1442
+ def on_post_import(self) -> None:
1443
+ pass
1444
+
1445
+
1446
+ def _setup_bones(context: Context, armature: Object) -> None:
1447
+ """Set the direction and length of Human Bones.
1448
+
1449
+ VRM0 does not have bone direction and length, so currently FORTUNE's
1450
+ direction and length determination is performed at import time. However,
1451
+ since FORTUNE does not consider human body structure, it can result in
1452
+ unnatural bones. Therefore, we determine direction and length using a
1453
+ heuristic method that considers human body structure.
1454
+
1455
+ Things to do someday:
1456
+ Limit the Head angle to within 30 degrees from the parent angle.
1457
+ Add elevation angle when Toes or Foot are not touching the ground.
1458
+ """
1459
+ armature_data = armature.data
1460
+ if not isinstance(armature_data, Armature):
1461
+ return
1462
+ addon_extension = get_armature_extension(armature_data)
1463
+
1464
+ Vrm0HumanoidPropertyGroup.fixup_human_bones(armature)
1465
+ Vrm0HumanoidPropertyGroup.update_all_bone_name_candidates(
1466
+ context,
1467
+ armature_data.name,
1468
+ force=True,
1469
+ )
1470
+
1471
+ human_bones = addon_extension.vrm0.humanoid.human_bones
1472
+ for human_bone in human_bones:
1473
+ if (
1474
+ human_bone.node.bone_name
1475
+ and human_bone.node.bone_name not in human_bone.node.bone_name_candidates
1476
+ ):
1477
+ # has error
1478
+ return
1479
+
1480
+ for humanoid_name in HumanBoneSpecifications.required_names:
1481
+ if not any(
1482
+ human_bone.bone == humanoid_name and human_bone.node.bone_name
1483
+ for human_bone in human_bones
1484
+ ):
1485
+ # has error
1486
+ return
1487
+
1488
+ with AbstractBaseVrmImporter.save_bone_child_object_transforms(
1489
+ context, armature
1490
+ ) as armature_data:
1491
+ bone_name_to_human_bone_name: dict[str, HumanBoneName] = {}
1492
+ for human_bone in human_bones:
1493
+ if not human_bone.node.bone_name:
1494
+ continue
1495
+ name = HumanBoneName.from_str(human_bone.bone)
1496
+ if not name:
1497
+ continue
1498
+ bone_name_to_human_bone_name[human_bone.node.bone_name] = name
1499
+
1500
+ for bone_name in bone_name_to_human_bone_name:
1501
+ bone = armature_data.edit_bones.get(bone_name)
1502
+ while bone:
1503
+ bone.roll = 0.0
1504
+ bone = bone.parent
1505
+
1506
+ for (
1507
+ bone_name,
1508
+ human_bone_name,
1509
+ ) in bone_name_to_human_bone_name.items():
1510
+ # The current algorithm cannot handle
1511
+ #
1512
+ # head ---- node ---- leftEye
1513
+ # \
1514
+ # -- rightEye
1515
+ #
1516
+ # well, so we don't process leftEye and rightEye
1517
+ if human_bone_name in {HumanBoneName.RIGHT_EYE, HumanBoneName.LEFT_EYE}:
1518
+ continue
1519
+
1520
+ bone = armature_data.edit_bones.get(bone_name)
1521
+ if not bone:
1522
+ continue
1523
+ last_human_bone_name = human_bone_name
1524
+ while True:
1525
+ parent = bone.parent
1526
+ if not parent:
1527
+ break
1528
+ parent_human_bone_name = bone_name_to_human_bone_name.get(parent.name)
1529
+
1530
+ if parent_human_bone_name in (
1531
+ HumanBoneName.RIGHT_HAND,
1532
+ HumanBoneName.LEFT_HAND,
1533
+ ):
1534
+ break
1535
+
1536
+ if (
1537
+ parent_human_bone_name == HumanBoneName.UPPER_CHEST
1538
+ and last_human_bone_name
1539
+ not in {HumanBoneName.HEAD, HumanBoneName.NECK}
1540
+ ):
1541
+ break
1542
+
1543
+ if (
1544
+ parent_human_bone_name == HumanBoneName.CHEST
1545
+ and last_human_bone_name
1546
+ not in (
1547
+ HumanBoneName.HEAD,
1548
+ HumanBoneName.NECK,
1549
+ HumanBoneName.UPPER_CHEST,
1550
+ )
1551
+ ):
1552
+ break
1553
+
1554
+ if (
1555
+ parent_human_bone_name == HumanBoneName.SPINE
1556
+ and last_human_bone_name
1557
+ not in (
1558
+ HumanBoneName.HEAD,
1559
+ HumanBoneName.NECK,
1560
+ HumanBoneName.UPPER_CHEST,
1561
+ HumanBoneName.CHEST,
1562
+ )
1563
+ ):
1564
+ break
1565
+
1566
+ if (
1567
+ parent_human_bone_name == HumanBoneName.HIPS
1568
+ and last_human_bone_name != HumanBoneName.SPINE
1569
+ ):
1570
+ break
1571
+
1572
+ if parent_human_bone_name:
1573
+ last_human_bone_name = parent_human_bone_name
1574
+
1575
+ if (parent.head - bone.head).length >= make_armature.MIN_BONE_LENGTH:
1576
+ parent.tail = bone.head
1577
+
1578
+ bone = parent
1579
+
1580
+ human_bone_name_to_human_bone: dict[
1581
+ HumanBoneName, Vrm0HumanoidBonePropertyGroup
1582
+ ] = {}
1583
+ for human_bone in human_bones:
1584
+ n = HumanBoneName.from_str(human_bone.bone)
1585
+ if not n:
1586
+ continue
1587
+ human_bone_name_to_human_bone[n] = human_bone
1588
+
1589
+ # When VRM Humanoid terminal bones other than eye and head bones have
1590
+ # multiple children, point them in the same direction as the parent.
1591
+ # This results in a more natural direction than the default.
1592
+ # Since it might be a direction specification by the VRM creator,
1593
+ # do nothing when there is only one child.
1594
+ for tip_bone_name in (
1595
+ HumanBoneName.JAW,
1596
+ HumanBoneName.LEFT_THUMB_DISTAL,
1597
+ HumanBoneName.LEFT_INDEX_DISTAL,
1598
+ HumanBoneName.LEFT_MIDDLE_DISTAL,
1599
+ HumanBoneName.LEFT_RING_DISTAL,
1600
+ HumanBoneName.LEFT_LITTLE_DISTAL,
1601
+ HumanBoneName.RIGHT_THUMB_DISTAL,
1602
+ HumanBoneName.RIGHT_INDEX_DISTAL,
1603
+ HumanBoneName.RIGHT_MIDDLE_DISTAL,
1604
+ HumanBoneName.RIGHT_RING_DISTAL,
1605
+ HumanBoneName.RIGHT_LITTLE_DISTAL,
1606
+ HumanBoneName.LEFT_TOES,
1607
+ HumanBoneName.RIGHT_TOES,
1608
+ ):
1609
+ bone = None
1610
+ searching_tip_bone_name: Optional[HumanBoneName] = tip_bone_name
1611
+ while searching_tip_bone_name:
1612
+ # Break if there is a corresponding bone
1613
+ human_bone = human_bone_name_to_human_bone.get(searching_tip_bone_name)
1614
+ if human_bone:
1615
+ bone = armature_data.edit_bones.get(human_bone.node.bone_name)
1616
+ if bone:
1617
+ break
1618
+
1619
+ # If there is no corresponding bone and it was a required bone,
1620
+ # abort due to data error
1621
+ specification = HumanBoneSpecifications.get(searching_tip_bone_name)
1622
+ if specification.requirement:
1623
+ break
1624
+ parent_specification = specification.parent
1625
+ if not parent_specification:
1626
+ _logger.error(
1627
+ 'logic error: "%s" has no parent', searching_tip_bone_name
1628
+ )
1629
+ break
1630
+
1631
+ # Do nothing if there are already assigned bones in the parent's
1632
+ # descendants
1633
+ assigned_parent_descendant_found = False
1634
+ for parent_descendant in parent_specification.descendants:
1635
+ parent_descendant_human_bone = human_bone_name_to_human_bone.get(
1636
+ parent_descendant.name
1637
+ )
1638
+ if not parent_descendant_human_bone:
1639
+ continue
1640
+ if (
1641
+ parent_descendant_human_bone.node.bone_name
1642
+ in armature_data.edit_bones
1643
+ ):
1644
+ assigned_parent_descendant_found = True
1645
+ break
1646
+ if assigned_parent_descendant_found:
1647
+ break
1648
+
1649
+ searching_tip_bone_name = specification.parent_name
1650
+ if not bone:
1651
+ continue
1652
+ if len(bone.children) <= 1:
1653
+ continue
1654
+
1655
+ parent_bone = bone.parent
1656
+ if not parent_bone:
1657
+ continue
1658
+
1659
+ bone.roll = parent_bone.roll
1660
+ bone.tail = bone.head + parent_bone.vector / 2
1661
+
1662
+ # When eye bones have no children or multiple children, point them forward.
1663
+ # This results in a more natural direction than the default.
1664
+ # Since it might be a direction specification by the VRM creator,
1665
+ # do nothing when there is only one child.
1666
+ for eye_human_bone in (
1667
+ human_bone_name_to_human_bone.get(HumanBoneName.LEFT_EYE),
1668
+ human_bone_name_to_human_bone.get(HumanBoneName.RIGHT_EYE),
1669
+ ):
1670
+ if not eye_human_bone or not eye_human_bone.node.bone_name:
1671
+ continue
1672
+
1673
+ bone = armature_data.edit_bones.get(eye_human_bone.node.bone_name)
1674
+ if not bone:
1675
+ continue
1676
+ if len(bone.children) == 1:
1677
+ continue
1678
+
1679
+ world_head = (
1680
+ armature.matrix_world @ Matrix.Translation(bone.head)
1681
+ ).to_translation()
1682
+
1683
+ world_tail = list(world_head)
1684
+ world_tail[1] -= 0.03125
1685
+
1686
+ world_inv = armature.matrix_world.inverted()
1687
+ if not world_inv:
1688
+ continue
1689
+ bone.tail = (Matrix.Translation(world_tail) @ world_inv).to_translation()
1690
+
1691
+ # When head bones have no children or multiple children, point them upward.
1692
+ # This results in a more natural direction than the default.
1693
+ # Since it might be a direction specification by the VRM creator,
1694
+ # do nothing when there is only one child.
1695
+ for head_human_bone in (human_bone_name_to_human_bone.get(HumanBoneName.HEAD),):
1696
+ if not head_human_bone or not head_human_bone.node.bone_name:
1697
+ continue
1698
+
1699
+ bone = armature_data.edit_bones.get(head_human_bone.node.bone_name)
1700
+ if not bone:
1701
+ continue
1702
+ if len(bone.children) == 1:
1703
+ continue
1704
+
1705
+ world_head = (
1706
+ armature.matrix_world @ Matrix.Translation(bone.head)
1707
+ ).to_translation()
1708
+
1709
+ world_tail = list(world_head)
1710
+
1711
+ parent_bone = bone.parent
1712
+ world_tail[2] += parent_bone.length if parent_bone else 0.2
1713
+
1714
+ world_inv = armature.matrix_world.inverted()
1715
+ if not world_inv:
1716
+ continue
1717
+
1718
+ parent_bone = bone.parent
1719
+ if parent_bone:
1720
+ bone.roll = parent_bone.roll
1721
+ bone.tail = (Matrix.Translation(world_tail) @ world_inv).to_translation()
1722
+
1723
+ connect_parent_tail_and_child_head_if_very_close_position(armature_data)