@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,1149 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import base64
3
+ import json
4
+ import math
5
+ import struct
6
+ from dataclasses import dataclass
7
+ from io import BytesIO
8
+ from typing import Final, Optional, Union
9
+ from urllib.parse import unquote, urlsplit
10
+
11
+ from mathutils import Matrix, Quaternion, Vector
12
+
13
+ from . import convert
14
+ from .convert import Json
15
+ from .deep import make_json
16
+ from .gl import (
17
+ GL_BYTE,
18
+ GL_FLOAT,
19
+ GL_INT,
20
+ GL_SHORT,
21
+ GL_UNSIGNED_BYTE,
22
+ GL_UNSIGNED_INT,
23
+ GL_UNSIGNED_SHORT,
24
+ )
25
+
26
+ # https://www.khronos.org/opengl/wiki/Small_Float_Formats#Numeric_limits_and_precision
27
+ FLOAT_POSITIVE_MAX: Final = 3.4028237e38
28
+ FLOAT_NEGATIVE_MAX: Final = -FLOAT_POSITIVE_MAX
29
+
30
+ # https://github.com/KhronosGroup/glTF/blob/b6e0fcc6d8e9f83347aa8b2e3df085b81590a65c/specification/2.0/schema/glTF.schema.json
31
+ ROOT_NON_EMPTY_ARRAY_KEYS: Final = (
32
+ "extensionsUsed",
33
+ "extensionsRequired",
34
+ "accessors",
35
+ "animations",
36
+ "buffers",
37
+ "bufferViews",
38
+ "cameras",
39
+ "images",
40
+ "materials",
41
+ "meshes",
42
+ "nodes",
43
+ "samplers",
44
+ "scenes",
45
+ "skins",
46
+ "textures",
47
+ )
48
+
49
+
50
+ def remove_empty_root_arrays(json_dict: dict[str, Json]) -> None:
51
+ for key in ROOT_NON_EMPTY_ARRAY_KEYS:
52
+ if not json_dict.get(key):
53
+ json_dict.pop(key, None)
54
+
55
+
56
+ def parse_glb(data: bytes) -> tuple[dict[str, Json], bytes]:
57
+ with BytesIO(data) as glb:
58
+ header_struct = struct.Struct("<4sII")
59
+ header_bytes = glb.read(header_struct.size)
60
+ header_dump = "[" + ", ".join(f"0x{b:02x}" for b in header_bytes) + "]"
61
+ if len(header_bytes) != header_struct.size:
62
+ message = f"Failed to read VRM glTF header: {header_dump}"
63
+ raise ValueError(message)
64
+
65
+ header: tuple[bytes, int, int] = header_struct.unpack(header_bytes)
66
+ magic, version, length = header
67
+ if magic != b"glTF":
68
+ message = f"Invalid VRM glTF magic bytes: {header_dump}"
69
+ raise ValueError(message)
70
+
71
+ if version != 2:
72
+ message = f"Unsupported VRM glTF Version: {version}"
73
+ raise ValueError(message)
74
+
75
+ chunks_bytes_length = length - header_struct.size
76
+ if chunks_bytes_length < 0:
77
+ message = f"Invalid VRM glTF length: {length}"
78
+ raise ValueError(message)
79
+
80
+ chunks_bytes = glb.read(chunks_bytes_length)
81
+ if len(chunks_bytes) != chunks_bytes_length:
82
+ message = "Failed to read VRM chunks bytes"
83
+ raise ValueError(message)
84
+
85
+ with BytesIO(chunks_bytes) as chunks:
86
+ json_chunk_length_bytes = chunks.read(4)
87
+ if len(json_chunk_length_bytes) != 4:
88
+ message = "Failed to read VRM json chunk length bytes"
89
+ raise ValueError(message)
90
+
91
+ json_chunk_type_bytes = chunks.read(4)
92
+ if len(json_chunk_type_bytes) != 4:
93
+ message = "Failed to read VRM json chunk type bytes"
94
+ raise ValueError(message)
95
+
96
+ if json_chunk_type_bytes != b"JSON":
97
+ message = "Invalid VRM json chunk type bytes: " + ",".join(
98
+ chr(b) for b in json_chunk_type_bytes
99
+ )
100
+ raise ValueError(message)
101
+
102
+ json_chunk_data_length: int = struct.unpack("<I", json_chunk_length_bytes)[0]
103
+ json_chunk_data_bytes = chunks.read(json_chunk_data_length)
104
+ if len(json_chunk_data_bytes) != json_chunk_data_length:
105
+ message = "Failed to read VRM json chunk"
106
+ raise ValueError(message)
107
+
108
+ raw_json = json.loads(json_chunk_data_bytes) # raises json.JSONDecodeError
109
+ json_obj = make_json(raw_json)
110
+ if not isinstance(json_obj, dict):
111
+ message = f"Unexpected VRM json format: {type(json_obj)}"
112
+ raise TypeError(message)
113
+
114
+ bin_chunk_length_bytes = chunks.read(4)
115
+ if not bin_chunk_length_bytes:
116
+ return json_obj, b""
117
+ if len(bin_chunk_length_bytes) != 4:
118
+ message = "Failed to read VRM bin chunk length bytes"
119
+ raise ValueError(message)
120
+
121
+ bin_chunk_type_bytes = chunks.read(4)
122
+ if len(bin_chunk_type_bytes) != 4:
123
+ message = "Failed to read VRM bin chunk type bytes"
124
+ raise ValueError(message)
125
+
126
+ if bin_chunk_type_bytes != b"BIN\x00":
127
+ message = "Invalid VRM bin chunk type bytes: " + ",".join(
128
+ chr(b) for b in bin_chunk_type_bytes
129
+ )
130
+ raise ValueError(message)
131
+
132
+ bin_chunk_data_length: int = struct.unpack("<I", bin_chunk_length_bytes)[0]
133
+ bin_chunk_data_bytes = chunks.read(bin_chunk_data_length)
134
+ if len(bin_chunk_data_bytes) != bin_chunk_data_length:
135
+ message = "Failed to read VRM bin chunk"
136
+ raise ValueError(message)
137
+
138
+ return json_obj, bin_chunk_data_bytes
139
+
140
+
141
+ def pack_glb(
142
+ json_dict: dict[str, Json], bin_chunk_bytes: Union[bytes, bytearray]
143
+ ) -> bytes:
144
+ json_dict = dict(json_dict) # Shallow copy for excluding keys
145
+ remove_empty_root_arrays(json_dict)
146
+
147
+ # https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#binary-gltf-layout
148
+ json_chunk_bytes = json.dumps(
149
+ json_dict,
150
+ separators=(",", ":"),
151
+ sort_keys=True,
152
+ # UniVRM 0.56.3 cannot import a json containing unicode escape chars into
153
+ # Unity Editor.
154
+ ensure_ascii=False,
155
+ ).encode()
156
+
157
+ while len(json_chunk_bytes) % 4:
158
+ json_chunk_bytes += b"\x20"
159
+
160
+ while len(bin_chunk_bytes) % 4:
161
+ bin_chunk_bytes += b"\x00"
162
+
163
+ glb = bytearray()
164
+
165
+ glb.extend(b"glTF") # magic
166
+ glb.extend(struct.pack("<I", 2)) # version
167
+ glb.extend( # length
168
+ struct.pack(
169
+ "<I",
170
+ # header
171
+ 12
172
+ # json chunk
173
+ + 8
174
+ + len(json_chunk_bytes)
175
+ # binary chunk
176
+ + 8
177
+ + len(bin_chunk_bytes),
178
+ )
179
+ )
180
+
181
+ glb.extend(struct.pack("<I", len(json_chunk_bytes)))
182
+ glb.extend(b"JSON")
183
+ glb.extend(json_chunk_bytes)
184
+
185
+ glb.extend(struct.pack("<I", len(bin_chunk_bytes)))
186
+ glb.extend(b"BIN\x00")
187
+ glb.extend(bin_chunk_bytes)
188
+
189
+ return bytes(glb)
190
+
191
+
192
+ @dataclass(frozen=True)
193
+ class Component:
194
+ component_type: int
195
+ unpack_symbol: str
196
+ byte_length: int
197
+
198
+ @staticmethod
199
+ def from_component_type(component_type: int) -> Optional["Component"]:
200
+ for component in (
201
+ BYTE_COMPONENT,
202
+ UNSIGNED_BYTE_COMPONENT,
203
+ SHORT_COMPONENT,
204
+ UNSIGNED_SHORT_COMPONENT,
205
+ INT_COMPONENT,
206
+ UNSIGNED_INT_COMPONENT,
207
+ FLOAT_COMPONENT,
208
+ ):
209
+ if component.component_type == component_type:
210
+ return component
211
+ return None
212
+
213
+
214
+ BYTE_COMPONENT: Final = Component(GL_BYTE, "b", 1)
215
+ UNSIGNED_BYTE_COMPONENT: Final = Component(GL_UNSIGNED_BYTE, "B", 1)
216
+ SHORT_COMPONENT: Final = Component(GL_SHORT, "h", 2)
217
+ UNSIGNED_SHORT_COMPONENT: Final = Component(GL_UNSIGNED_SHORT, "H", 2)
218
+ INT_COMPONENT: Final = Component(GL_INT, "i", 4)
219
+ UNSIGNED_INT_COMPONENT: Final = Component(GL_UNSIGNED_INT, "I", 4)
220
+ FLOAT_COMPONENT: Final = Component(GL_FLOAT, "f", 4)
221
+
222
+
223
+ def _accessor_type_component_count(accessor_type: str) -> Optional[int]:
224
+ if accessor_type == "SCALAR":
225
+ return 1
226
+ if accessor_type == "VEC2":
227
+ return 2
228
+ if accessor_type == "VEC3":
229
+ return 3
230
+ if accessor_type == "VEC4":
231
+ return 4
232
+ if accessor_type == "MAT4":
233
+ return 16
234
+ return None
235
+
236
+
237
+ def read_buffer_view_as_bytes(
238
+ buffer_view_dict: Json,
239
+ buffer_dicts: list[Json],
240
+ bin_chunk_bytes: Optional[bytes],
241
+ ) -> Optional[bytes]:
242
+ if not isinstance(buffer_view_dict, dict):
243
+ return None
244
+
245
+ buffer_index = buffer_view_dict.get("buffer")
246
+ if not isinstance(buffer_index, int):
247
+ return None
248
+ if not (0 <= buffer_index < len(buffer_dicts)):
249
+ return None
250
+
251
+ if buffer_index == 0 and bin_chunk_bytes is not None:
252
+ buffer_bytes = bin_chunk_bytes
253
+ else:
254
+ buffer_dict = buffer_dicts[buffer_index]
255
+ if not isinstance(buffer_dict, dict):
256
+ return None
257
+ uri = buffer_dict.get("uri")
258
+
259
+ if not isinstance(uri, str):
260
+ return None
261
+ try:
262
+ parsed_uri = urlsplit(uri)
263
+ except ValueError:
264
+ return None
265
+ if parsed_uri.scheme != "data":
266
+ return None
267
+
268
+ path_components = parsed_uri.path.split(",", 1)
269
+ if len(path_components) != 2:
270
+ return None
271
+
272
+ header, data = path_components
273
+ if not header.endswith(";base64"):
274
+ return None
275
+ unquoted_data = unquote(data)
276
+ try:
277
+ buffer_bytes = base64.b64decode(unquoted_data)
278
+ except ValueError:
279
+ return None
280
+
281
+ byte_offset = buffer_view_dict.get("byteOffset", 0)
282
+ if not isinstance(byte_offset, int):
283
+ return None
284
+ if not (0 <= byte_offset < len(buffer_bytes)):
285
+ return None
286
+ byte_length = buffer_view_dict.get("byteLength")
287
+ if not isinstance(byte_length, int):
288
+ return None
289
+ if not (0 <= byte_offset + byte_length <= len(buffer_bytes)):
290
+ return None
291
+ return buffer_bytes[byte_offset : byte_offset + byte_length]
292
+
293
+
294
+ def _remove_byte_stride_padding(
295
+ buffer_view_dict: Json,
296
+ buffer_view_bytes: bytes,
297
+ accessor_byte_offset: int,
298
+ accessor_count: int,
299
+ accessor_element_byte_length: int,
300
+ ) -> Optional[bytes]:
301
+ if not isinstance(buffer_view_dict, dict):
302
+ return None
303
+ if not (0 <= accessor_byte_offset <= len(buffer_view_bytes)):
304
+ return None
305
+
306
+ byte_stride = buffer_view_dict.get("byteStride")
307
+ if byte_stride is None:
308
+ return buffer_view_bytes[accessor_byte_offset:]
309
+
310
+ if not isinstance(byte_stride, int):
311
+ return None
312
+ if byte_stride < accessor_element_byte_length:
313
+ return None
314
+ if accessor_count == 0:
315
+ return b""
316
+
317
+ accessor_byte_end = (
318
+ accessor_byte_offset
319
+ + byte_stride * (accessor_count - 1)
320
+ + accessor_element_byte_length
321
+ )
322
+ if not (0 <= accessor_byte_end <= len(buffer_view_bytes)):
323
+ return None
324
+
325
+ accessor_bytes = bytearray(accessor_count * accessor_element_byte_length)
326
+ for accessor_index in range(accessor_count):
327
+ src_start = accessor_byte_offset + byte_stride * accessor_index
328
+ src_end = src_start + accessor_element_byte_length
329
+ dest_start = accessor_index * accessor_element_byte_length
330
+ dest_end = dest_start + accessor_element_byte_length
331
+ accessor_bytes[dest_start:dest_end] = buffer_view_bytes[src_start:src_end]
332
+ return bytes(accessor_bytes)
333
+
334
+
335
+ def _read_accessor_as_bytes(
336
+ accessor_dict: dict[str, Json],
337
+ buffer_view_dicts: list[Json],
338
+ buffer_dicts: list[Json],
339
+ bin_chunk_bytes: Optional[bytes],
340
+ ) -> Optional[bytes]:
341
+ if not isinstance(accessor_type := accessor_dict.get("type"), str):
342
+ return None
343
+ if not isinstance(component_type := accessor_dict.get("componentType"), int):
344
+ return None
345
+ if (
346
+ not isinstance(accessor_count := accessor_dict.get("count"), int)
347
+ or accessor_count < 0
348
+ ):
349
+ return None
350
+ if (
351
+ not isinstance(accessor_byte_offset := accessor_dict.get("byteOffset", 0), int)
352
+ or accessor_byte_offset < 0
353
+ ):
354
+ return None
355
+ accessor_component_count = _accessor_type_component_count(accessor_type)
356
+ if accessor_component_count is None:
357
+ return None
358
+ component = Component.from_component_type(component_type)
359
+ if component is None:
360
+ return None
361
+
362
+ element_byte_length = accessor_component_count * component.byte_length
363
+ required_byte_length = accessor_count * element_byte_length
364
+
365
+ base_bytes: Optional[bytes] = None
366
+ key_not_found = object()
367
+ buffer_view_index = accessor_dict.get("bufferView", key_not_found)
368
+ if buffer_view_index == key_not_found:
369
+ base_bytes = b"\x00" * required_byte_length
370
+ elif isinstance(buffer_view_index, int) and 0 <= buffer_view_index < len(
371
+ buffer_view_dicts
372
+ ):
373
+ buffer_view_dict = buffer_view_dicts[buffer_view_index]
374
+ base_buffer_view_bytes = read_buffer_view_as_bytes(
375
+ buffer_view_dict, buffer_dicts, bin_chunk_bytes
376
+ )
377
+ if base_buffer_view_bytes is None:
378
+ return None
379
+ base_bytes = _remove_byte_stride_padding(
380
+ buffer_view_dict,
381
+ base_buffer_view_bytes,
382
+ accessor_byte_offset,
383
+ accessor_count,
384
+ element_byte_length,
385
+ )
386
+
387
+ if base_bytes is None:
388
+ return None
389
+
390
+ sparse_dict = accessor_dict.get("sparse")
391
+ if sparse_dict is None:
392
+ return base_bytes
393
+
394
+ if not isinstance(sparse_dict, dict):
395
+ return None
396
+
397
+ sparse_count = sparse_dict.get("count")
398
+ if not isinstance(sparse_count, int):
399
+ return None
400
+ if sparse_count < 1:
401
+ return None
402
+
403
+ if len(base_bytes) < required_byte_length:
404
+ return None
405
+
406
+ if not isinstance(indices_dict := sparse_dict.get("indices"), dict):
407
+ return None
408
+
409
+ if not isinstance(values_dict := sparse_dict.get("values"), dict):
410
+ return None
411
+
412
+ indices_buffer_view_index = indices_dict.get("bufferView")
413
+ if not isinstance(indices_buffer_view_index, int):
414
+ return None
415
+ if not (0 <= indices_buffer_view_index < len(buffer_view_dicts)):
416
+ return None
417
+ indices_raw_bytes = read_buffer_view_as_bytes(
418
+ buffer_view_dicts[indices_buffer_view_index], buffer_dicts, bin_chunk_bytes
419
+ )
420
+ if indices_raw_bytes is None:
421
+ return None
422
+ indices_byte_offset = indices_dict.get("byteOffset")
423
+ if isinstance(indices_byte_offset, int):
424
+ if not (0 <= indices_byte_offset < len(indices_raw_bytes)):
425
+ return None
426
+ indices_bytes = indices_raw_bytes[indices_byte_offset:]
427
+ else:
428
+ indices_bytes = indices_raw_bytes
429
+
430
+ if not isinstance(indices_component_type := indices_dict.get("componentType"), int):
431
+ return None
432
+ indices = _unpack_component(indices_component_type, sparse_count, indices_bytes)
433
+ if indices is None:
434
+ return None
435
+ previous_index = -1
436
+ for index in indices:
437
+ if not isinstance(index, int):
438
+ return None
439
+ if not (previous_index < index < accessor_count):
440
+ return None
441
+ previous_index = index
442
+
443
+ values_buffer_view_index = values_dict.get("bufferView")
444
+ if not isinstance(values_buffer_view_index, int):
445
+ return None
446
+ if not (0 <= values_buffer_view_index < len(buffer_view_dicts)):
447
+ return None
448
+ values_raw_bytes = read_buffer_view_as_bytes(
449
+ buffer_view_dicts[values_buffer_view_index], buffer_dicts, bin_chunk_bytes
450
+ )
451
+ if values_raw_bytes is None:
452
+ return None
453
+ values_byte_offset = values_dict.get("byteOffset")
454
+ if isinstance(values_byte_offset, int):
455
+ if not (0 <= values_byte_offset < len(values_raw_bytes)):
456
+ return None
457
+ values_bytes = values_raw_bytes[values_byte_offset:]
458
+ else:
459
+ values_bytes = values_raw_bytes
460
+
461
+ values_byte_length = sparse_count * element_byte_length
462
+ if len(values_bytes) < values_byte_length:
463
+ return None
464
+
465
+ updated_bytes = bytearray(base_bytes[:required_byte_length])
466
+ for sparse_index, index in enumerate(indices):
467
+ index_int = int(index)
468
+ start = index_int * element_byte_length
469
+ end = start + element_byte_length
470
+ if end > len(updated_bytes):
471
+ return None
472
+ value_start = sparse_index * element_byte_length
473
+ value_end = value_start + element_byte_length
474
+ updated_bytes[start:end] = values_bytes[value_start:value_end]
475
+
476
+ return bytes(updated_bytes)
477
+
478
+
479
+ def _unpack_component(
480
+ component_type: int, unpack_count: int, buffer_bytes: bytes
481
+ ) -> Optional[Union[tuple[int, ...], tuple[float, ...]]]:
482
+ component = Component.from_component_type(component_type)
483
+ if component is None:
484
+ return None
485
+ if unpack_count == 0:
486
+ return ()
487
+ unpack_byte_length = unpack_count * component.byte_length
488
+ if not (1 <= unpack_byte_length <= len(buffer_bytes)):
489
+ return None
490
+ return struct.unpack(
491
+ f"<{unpack_count}{component.unpack_symbol}",
492
+ buffer_bytes[:unpack_byte_length],
493
+ )
494
+
495
+
496
+ def _merge_duplicate_primitive_vertex_skinning_weights(
497
+ accessor_dicts: list[Json],
498
+ buffer_view_dicts: list[Json],
499
+ buffer_dicts: list[Json],
500
+ buffer0_bytes: bytes,
501
+ primitive_dict: dict[str, Json],
502
+ ) -> None:
503
+ attributes_dict = primitive_dict.get("attributes")
504
+ if not isinstance(attributes_dict, dict):
505
+ return
506
+
507
+ joints_and_weights: list[
508
+ tuple[
509
+ list[tuple[int, int, int, int]],
510
+ list[tuple[float, float, float, float]],
511
+ ]
512
+ ] = []
513
+
514
+ joints_weights_index = 0
515
+ while True:
516
+ joints_index = attributes_dict.get(f"JOINTS_{joints_weights_index}")
517
+ if joints_index is None:
518
+ break
519
+ if not isinstance(joints_index, int):
520
+ return
521
+ if not (0 <= joints_index < len(accessor_dicts)):
522
+ return
523
+ joints_accessor_dict = accessor_dicts[joints_index]
524
+ if not isinstance(joints_accessor_dict, dict):
525
+ return
526
+ if not isinstance(
527
+ joints_component_type := joints_accessor_dict.get("componentType"), int
528
+ ):
529
+ return
530
+ if joints_component_type not in {GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT}:
531
+ return
532
+ joints_n = _read_vec4_accessor(
533
+ joints_accessor_dict,
534
+ buffer_view_dicts,
535
+ buffer_dicts,
536
+ buffer0_bytes,
537
+ )
538
+ if joints_n is None:
539
+ return
540
+ joints_n = [(int(j0), int(j1), int(j2), int(j3)) for j0, j1, j2, j3 in joints_n]
541
+
542
+ weights_index = attributes_dict.get(f"WEIGHTS_{joints_weights_index}")
543
+ if not isinstance(weights_index, int):
544
+ return
545
+ if not (0 <= weights_index < len(accessor_dicts)):
546
+ return
547
+ weights_accessor_dict = accessor_dicts[weights_index]
548
+ if not isinstance(weights_accessor_dict, dict):
549
+ return
550
+ if not isinstance(
551
+ weights_component_type := weights_accessor_dict.get("componentType"), int
552
+ ):
553
+ return
554
+ weights_n = _read_vec4_accessor(
555
+ weights_accessor_dict,
556
+ buffer_view_dicts,
557
+ buffer_dicts,
558
+ buffer0_bytes,
559
+ )
560
+ if weights_n is None:
561
+ return
562
+ if len(joints_n) != len(weights_n):
563
+ return
564
+ if weights_component_type == GL_FLOAT:
565
+ denominator = 1.0
566
+ elif weights_component_type == GL_UNSIGNED_BYTE:
567
+ denominator = 255.0
568
+ elif weights_component_type == GL_UNSIGNED_SHORT:
569
+ denominator = 65535.0
570
+ else:
571
+ return
572
+ weights_n = [
573
+ (
574
+ max(0.0, min((0.0 if math.isnan(w0) else w0) / denominator, 1.0)),
575
+ max(0.0, min((0.0 if math.isnan(w1) else w1) / denominator, 1.0)),
576
+ max(0.0, min((0.0 if math.isnan(w2) else w2) / denominator, 1.0)),
577
+ max(0.0, min((0.0 if math.isnan(w3) else w3) / denominator, 1.0)),
578
+ )
579
+ for w0, w1, w2, w3 in weights_n
580
+ ]
581
+
582
+ joints_and_weights.append((joints_n, weights_n))
583
+ joints_weights_index += 1
584
+
585
+ if not joints_and_weights:
586
+ return
587
+
588
+ have_joint_duplication = False
589
+ vertex_index = 0
590
+ all_vertices_processed = False
591
+ while not all_vertices_processed:
592
+ joint_to_weight_by_vertex_dict: dict[int, float] = {}
593
+ need_writeback_in_this_loop = False
594
+ for joints_n, weights_n in joints_and_weights:
595
+ if vertex_index >= len(joints_n) or vertex_index >= len(weights_n):
596
+ all_vertices_processed = True
597
+ break
598
+ joint_n = joints_n[vertex_index]
599
+ weight_n = weights_n[vertex_index]
600
+ for joint, weight in zip(joint_n, weight_n):
601
+ if joint == 0 and not (weight > 0):
602
+ continue
603
+ if joint in joint_to_weight_by_vertex_dict:
604
+ joint_to_weight_by_vertex_dict[joint] += weight
605
+ have_joint_duplication = True
606
+ need_writeback_in_this_loop = True
607
+ else:
608
+ joint_to_weight_by_vertex_dict[joint] = weight
609
+
610
+ if need_writeback_in_this_loop:
611
+ joint_to_weight_by_vertex: list[tuple[int, float]] = sorted(
612
+ joint_to_weight_by_vertex_dict.items(),
613
+ key=lambda item: (item[1], item[0]),
614
+ reverse=True,
615
+ )
616
+ denominator = sum(weight for _joint, weight in joint_to_weight_by_vertex)
617
+ if not (denominator > 0):
618
+ denominator = 1
619
+ joint_to_weight_by_vertex = []
620
+ for joints_n, weights_n in joints_and_weights:
621
+ (j0, w0), (j1, w1), (j2, w2), (j3, w3) = (
622
+ joint_to_weight_by_vertex.pop(0)
623
+ if joint_to_weight_by_vertex
624
+ else (0, 0.0),
625
+ joint_to_weight_by_vertex.pop(0)
626
+ if joint_to_weight_by_vertex
627
+ else (0, 0.0),
628
+ joint_to_weight_by_vertex.pop(0)
629
+ if joint_to_weight_by_vertex
630
+ else (0, 0.0),
631
+ joint_to_weight_by_vertex.pop(0)
632
+ if joint_to_weight_by_vertex
633
+ else (0, 0.0),
634
+ )
635
+ joints_n[vertex_index] = (j0, j1, j2, j3)
636
+ weights_n[vertex_index] = (
637
+ w0 / denominator,
638
+ w1 / denominator,
639
+ w2 / denominator,
640
+ w3 / denominator,
641
+ )
642
+
643
+ vertex_index += 1
644
+
645
+ if not have_joint_duplication:
646
+ return
647
+
648
+ for index, (joints_n, weights_n) in enumerate(joints_and_weights):
649
+ joints_n_bytearray = bytearray()
650
+ for j in joints_n:
651
+ joints_n_bytearray.extend(struct.pack("<4H", *j))
652
+
653
+ joints_buffer_index = len(buffer_dicts)
654
+ buffer_dicts.append(
655
+ {
656
+ "uri": "data:application/gltf-buffer;base64,"
657
+ + base64.b64encode(joints_n_bytearray).decode("ascii"),
658
+ "byteLength": len(joints_n_bytearray),
659
+ }
660
+ )
661
+
662
+ joints_buffer_view_dict: Json = {
663
+ "buffer": joints_buffer_index,
664
+ "byteLength": len(joints_n_bytearray),
665
+ }
666
+ joints_buffer_view_index = len(buffer_view_dicts)
667
+ buffer_view_dicts.append(joints_buffer_view_dict)
668
+ joints_accessor_dict: Json = {
669
+ "bufferView": joints_buffer_view_index,
670
+ "byteOffset": 0,
671
+ "componentType": GL_UNSIGNED_SHORT,
672
+ "count": len(joints_n),
673
+ "type": "VEC4",
674
+ }
675
+ joints_accessor_index = len(accessor_dicts)
676
+ accessor_dicts.append(joints_accessor_dict)
677
+ attributes_dict[f"JOINTS_{index}"] = joints_accessor_index
678
+
679
+ weights_n_bytearray = bytearray()
680
+ for w in weights_n:
681
+ weights_n_bytearray.extend(struct.pack("<4f", *w))
682
+
683
+ weights_buffer_index = len(buffer_dicts)
684
+ buffer_dicts.append(
685
+ {
686
+ "uri": "data:application/gltf-buffer;base64,"
687
+ + base64.b64encode(weights_n_bytearray).decode("ascii"),
688
+ "byteLength": len(weights_n_bytearray),
689
+ }
690
+ )
691
+
692
+ weights_buffer_view_dict: Json = {
693
+ "buffer": weights_buffer_index,
694
+ "byteLength": len(weights_n_bytearray),
695
+ }
696
+ weights_buffer_view_index = len(buffer_view_dicts)
697
+ buffer_view_dicts.append(weights_buffer_view_dict)
698
+ weights_accessor_dict: Json = {
699
+ "bufferView": weights_buffer_view_index,
700
+ "byteOffset": 0,
701
+ "componentType": GL_FLOAT,
702
+ "count": len(weights_n),
703
+ "type": "VEC4",
704
+ }
705
+
706
+ weights_accessor_index = len(accessor_dicts)
707
+ accessor_dicts.append(weights_accessor_dict)
708
+ attributes_dict[f"WEIGHTS_{index}"] = weights_accessor_index
709
+
710
+
711
+ def merge_duplicate_vertex_skinning_weights(
712
+ json_dict: dict[str, Json],
713
+ buffer0_bytes: bytes,
714
+ ) -> None:
715
+ """Merge duplicated vertex skinning weights in the glTF.
716
+
717
+ Some VRM models may contain multiple skinning weights for a single joint
718
+ on a single vertex. Since this violates the glTF specification, we merge
719
+ and fix these duplicates before passing them to the official glTF importer.
720
+ """
721
+ if not isinstance(accessor_dicts := json_dict.get("accessors"), list):
722
+ return
723
+ if not isinstance(buffer_view_dicts := json_dict.get("bufferViews"), list):
724
+ return
725
+ if not isinstance(buffer_dicts := json_dict.get("buffers"), list):
726
+ return
727
+ if not isinstance(mesh_dicts := json_dict.get("meshes"), list):
728
+ return
729
+
730
+ for mesh_dict in mesh_dicts:
731
+ if not isinstance(mesh_dict, dict):
732
+ continue
733
+ if not isinstance(primitive_dicts := mesh_dict.get("primitives"), list):
734
+ continue
735
+
736
+ for primitive_dict in primitive_dicts:
737
+ if not isinstance(primitive_dict, dict):
738
+ continue
739
+ _merge_duplicate_primitive_vertex_skinning_weights(
740
+ accessor_dicts,
741
+ buffer_view_dicts,
742
+ buffer_dicts,
743
+ buffer0_bytes,
744
+ primitive_dict,
745
+ )
746
+
747
+
748
+ def _unpack_accessor_as_scalar_components(
749
+ accessor_dict: dict[str, Json],
750
+ buffer_view_dicts: list[Json],
751
+ buffer_dicts: list[Json],
752
+ buffer0_bytes: bytes,
753
+ unpack_count: int,
754
+ ) -> Union[tuple[int, ...], tuple[float, ...], None]:
755
+ component_type = accessor_dict.get("componentType")
756
+ if not isinstance(component_type, int):
757
+ return None
758
+
759
+ raw_bytes = _read_accessor_as_bytes(
760
+ accessor_dict,
761
+ buffer_view_dicts,
762
+ buffer_dicts,
763
+ buffer0_bytes,
764
+ )
765
+ if not raw_bytes:
766
+ return None
767
+
768
+ return _unpack_component(component_type, unpack_count, raw_bytes)
769
+
770
+
771
+ def _read_scalar_accessor(
772
+ accessor_dict: dict[str, Json],
773
+ buffer_view_dicts: list[Json],
774
+ buffer_dicts: list[Json],
775
+ buffer0_bytes: bytes,
776
+ ) -> Union[tuple[int, ...], tuple[float, ...], None]:
777
+ accessor_type = accessor_dict.get("type")
778
+ if accessor_type != "SCALAR":
779
+ return None
780
+ count = accessor_dict.get("count")
781
+ if not isinstance(count, int):
782
+ return None
783
+ return _unpack_accessor_as_scalar_components(
784
+ accessor_dict,
785
+ buffer_view_dicts,
786
+ buffer_dicts,
787
+ buffer0_bytes,
788
+ count,
789
+ )
790
+
791
+
792
+ def _read_vec2_accessor(
793
+ accessor_dict: dict[str, Json],
794
+ buffer_view_dicts: list[Json],
795
+ buffer_dicts: list[Json],
796
+ buffer0_bytes: bytes,
797
+ ) -> Union[tuple[tuple[int, int], ...], tuple[tuple[float, float], ...], None]:
798
+ accessor_type = accessor_dict.get("type")
799
+ if accessor_type != "VEC2":
800
+ return None
801
+ count = accessor_dict.get("count")
802
+ if not isinstance(count, int):
803
+ return None
804
+ components = _unpack_accessor_as_scalar_components(
805
+ accessor_dict,
806
+ buffer_view_dicts,
807
+ buffer_dicts,
808
+ buffer0_bytes,
809
+ count * 2,
810
+ )
811
+ if components is None:
812
+ return None
813
+ return tuple(
814
+ (
815
+ components[i],
816
+ components[i + 1],
817
+ )
818
+ for i in range(0, count * 2, 2)
819
+ )
820
+
821
+
822
+ def _read_vec3_accessor(
823
+ accessor_dict: dict[str, Json],
824
+ buffer_view_dicts: list[Json],
825
+ buffer_dicts: list[Json],
826
+ buffer0_bytes: bytes,
827
+ ) -> Union[
828
+ tuple[tuple[int, int, int], ...], tuple[tuple[float, float, float], ...], None
829
+ ]:
830
+ accessor_type = accessor_dict.get("type")
831
+ if accessor_type != "VEC3":
832
+ return None
833
+ count = accessor_dict.get("count")
834
+ if not isinstance(count, int):
835
+ return None
836
+ components = _unpack_accessor_as_scalar_components(
837
+ accessor_dict,
838
+ buffer_view_dicts,
839
+ buffer_dicts,
840
+ buffer0_bytes,
841
+ count * 3,
842
+ )
843
+ if components is None:
844
+ return None
845
+ return tuple(
846
+ (
847
+ components[i],
848
+ components[i + 1],
849
+ components[i + 2],
850
+ )
851
+ for i in range(0, count * 3, 3)
852
+ )
853
+
854
+
855
+ def _read_vec4_accessor(
856
+ accessor_dict: dict[str, Json],
857
+ buffer_view_dicts: list[Json],
858
+ buffer_dicts: list[Json],
859
+ buffer0_bytes: bytes,
860
+ ) -> Union[
861
+ tuple[tuple[int, int, int, int], ...],
862
+ tuple[tuple[float, float, float, float], ...],
863
+ None,
864
+ ]:
865
+ accessor_type = accessor_dict.get("type")
866
+ if accessor_type != "VEC4":
867
+ return None
868
+ count = accessor_dict.get("count")
869
+ if not isinstance(count, int):
870
+ return None
871
+ components = _unpack_accessor_as_scalar_components(
872
+ accessor_dict,
873
+ buffer_view_dicts,
874
+ buffer_dicts,
875
+ buffer0_bytes,
876
+ count * 4,
877
+ )
878
+ if components is None:
879
+ return None
880
+ return tuple(
881
+ (
882
+ components[i],
883
+ components[i + 1],
884
+ components[i + 2],
885
+ components[i + 3],
886
+ )
887
+ for i in range(0, count * 4, 4)
888
+ )
889
+
890
+
891
+ def _read_mat4_accessor(
892
+ accessor_dict: dict[str, Json],
893
+ buffer_view_dicts: list[Json],
894
+ buffer_dicts: list[Json],
895
+ buffer0_bytes: bytes,
896
+ ) -> Union[
897
+ tuple[
898
+ tuple[
899
+ tuple[int, int, int, int],
900
+ tuple[int, int, int, int],
901
+ tuple[int, int, int, int],
902
+ tuple[int, int, int, int],
903
+ ],
904
+ ...,
905
+ ],
906
+ tuple[
907
+ tuple[
908
+ tuple[float, float, float, float],
909
+ tuple[float, float, float, float],
910
+ tuple[float, float, float, float],
911
+ tuple[float, float, float, float],
912
+ ],
913
+ ...,
914
+ ],
915
+ None,
916
+ ]:
917
+ accessor_type = accessor_dict.get("type")
918
+ if accessor_type != "MAT4":
919
+ return None
920
+ count = accessor_dict.get("count")
921
+ if not isinstance(count, int):
922
+ return None
923
+ components = _unpack_accessor_as_scalar_components(
924
+ accessor_dict,
925
+ buffer_view_dicts,
926
+ buffer_dicts,
927
+ buffer0_bytes,
928
+ count * 16,
929
+ )
930
+ if components is None:
931
+ return None
932
+ return tuple(
933
+ (
934
+ (
935
+ components[i],
936
+ components[i + 1],
937
+ components[i + 2],
938
+ components[i + 3],
939
+ ),
940
+ (
941
+ components[i + 4],
942
+ components[i + 5],
943
+ components[i + 6],
944
+ components[i + 7],
945
+ ),
946
+ (
947
+ components[i + 8],
948
+ components[i + 9],
949
+ components[i + 10],
950
+ components[i + 11],
951
+ ),
952
+ (
953
+ components[i + 12],
954
+ components[i + 13],
955
+ components[i + 14],
956
+ components[i + 15],
957
+ ),
958
+ )
959
+ for i in range(0, count * 16, 16)
960
+ )
961
+
962
+
963
+ def _read_accessor(
964
+ accessor_dict: dict[str, Json],
965
+ buffer_view_dicts: list[Json],
966
+ buffer_dicts: list[Json],
967
+ buffer0_bytes: bytes,
968
+ ) -> Union[
969
+ tuple[int, ...],
970
+ tuple[float, ...],
971
+ tuple[tuple[int, int], ...],
972
+ tuple[tuple[float, float], ...],
973
+ tuple[tuple[int, int, int], ...],
974
+ tuple[tuple[float, float, float], ...],
975
+ tuple[tuple[int, int, int, int], ...],
976
+ tuple[tuple[float, float, float, float], ...],
977
+ tuple[
978
+ tuple[
979
+ tuple[int, int, int, int],
980
+ tuple[int, int, int, int],
981
+ tuple[int, int, int, int],
982
+ tuple[int, int, int, int],
983
+ ],
984
+ ...,
985
+ ],
986
+ tuple[
987
+ tuple[
988
+ tuple[float, float, float, float],
989
+ tuple[float, float, float, float],
990
+ tuple[float, float, float, float],
991
+ tuple[float, float, float, float],
992
+ ],
993
+ ...,
994
+ ],
995
+ None,
996
+ ]:
997
+ accessor_type = accessor_dict.get("type")
998
+ if accessor_type == "SCALAR":
999
+ return _read_scalar_accessor(
1000
+ accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
1001
+ )
1002
+ if accessor_type == "VEC2":
1003
+ return _read_vec2_accessor(
1004
+ accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
1005
+ )
1006
+ if accessor_type == "VEC3":
1007
+ return _read_vec3_accessor(
1008
+ accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
1009
+ )
1010
+ if accessor_type == "VEC4":
1011
+ return _read_vec4_accessor(
1012
+ accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
1013
+ )
1014
+ if accessor_type == "MAT4":
1015
+ return _read_mat4_accessor(
1016
+ accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
1017
+ )
1018
+ return None
1019
+
1020
+
1021
+ def read_accessors(
1022
+ json_dict: dict[str, Json],
1023
+ buffer0_bytes: bytes,
1024
+ ) -> tuple[
1025
+ Union[
1026
+ tuple[int, ...],
1027
+ tuple[float, ...],
1028
+ tuple[tuple[int, int], ...],
1029
+ tuple[tuple[float, float], ...],
1030
+ tuple[tuple[int, int, int], ...],
1031
+ tuple[tuple[float, float, float], ...],
1032
+ tuple[tuple[int, int, int, int], ...],
1033
+ tuple[tuple[float, float, float, float], ...],
1034
+ tuple[
1035
+ tuple[
1036
+ tuple[int, int, int, int],
1037
+ tuple[int, int, int, int],
1038
+ tuple[int, int, int, int],
1039
+ tuple[int, int, int, int],
1040
+ ],
1041
+ ...,
1042
+ ],
1043
+ tuple[
1044
+ tuple[
1045
+ tuple[float, float, float, float],
1046
+ tuple[float, float, float, float],
1047
+ tuple[float, float, float, float],
1048
+ tuple[float, float, float, float],
1049
+ ],
1050
+ ...,
1051
+ ],
1052
+ None,
1053
+ ],
1054
+ ...,
1055
+ ]:
1056
+ accessor_dicts = json_dict.get("accessors")
1057
+ if not isinstance(accessor_dicts, list):
1058
+ accessor_dicts = []
1059
+
1060
+ buffer_view_dicts = json_dict.get("bufferViews")
1061
+ if not isinstance(buffer_view_dicts, list):
1062
+ buffer_view_dicts = []
1063
+
1064
+ buffer_dicts = json_dict.get("buffers")
1065
+ if not isinstance(buffer_dicts, list):
1066
+ buffer_dicts = []
1067
+
1068
+ return tuple(
1069
+ _read_accessor(accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes)
1070
+ for accessor_dict in accessor_dicts
1071
+ if isinstance(accessor_dict, dict)
1072
+ )
1073
+
1074
+
1075
+ def read_accessor_as_animation_sampler_input(
1076
+ accessor_dict: dict[str, Json],
1077
+ buffer_view_dicts: list[Json],
1078
+ buffer_dicts: list[Json],
1079
+ buffer0_bytes: bytes,
1080
+ ) -> Optional[list[float]]:
1081
+ scalar_accessor = _read_scalar_accessor(
1082
+ accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
1083
+ )
1084
+ if scalar_accessor is None:
1085
+ return None
1086
+ return [float(v) for v in scalar_accessor]
1087
+
1088
+
1089
+ def read_accessor_as_animation_sampler_translation_output(
1090
+ accessor_dict: dict[str, Json],
1091
+ buffer_view_dicts: list[Json],
1092
+ buffer_dicts: list[Json],
1093
+ buffer0_bytes: bytes,
1094
+ ) -> Optional[list[Vector]]:
1095
+ vec3_accessor = _read_vec3_accessor(
1096
+ accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
1097
+ )
1098
+ if vec3_accessor is None:
1099
+ return None
1100
+ return [Vector((x, -z, y)) for x, y, z in vec3_accessor]
1101
+
1102
+
1103
+ def read_accessor_as_animation_sampler_rotation_output(
1104
+ accessor_dict: dict[str, Json],
1105
+ buffer_view_dicts: list[Json],
1106
+ buffer_dicts: list[Json],
1107
+ buffer0_bytes: bytes,
1108
+ ) -> Optional[list[Quaternion]]:
1109
+ vec4_accessor = _read_vec4_accessor(
1110
+ accessor_dict, buffer_view_dicts, buffer_dicts, buffer0_bytes
1111
+ )
1112
+ if vec4_accessor is None:
1113
+ return None
1114
+ return [Quaternion((w, x, -z, y)).normalized() for x, y, z, w in vec4_accessor]
1115
+
1116
+
1117
+ def parse_gltf_node_matrix(node_dict: dict[str, Json]) -> Matrix:
1118
+ matrix = node_dict.get("matrix")
1119
+ if isinstance(matrix, list):
1120
+ if len(matrix) != 16:
1121
+ return Matrix()
1122
+
1123
+ row0 = convert.float4_or_none((matrix[0], matrix[4], matrix[8], matrix[12]))
1124
+ row1 = convert.float4_or_none((matrix[1], matrix[5], matrix[9], matrix[13]))
1125
+ row2 = convert.float4_or_none((matrix[2], matrix[6], matrix[10], matrix[14]))
1126
+ row3 = convert.float4_or_none((matrix[3], matrix[7], matrix[11], matrix[15]))
1127
+ if not (row0 and row1 and row2 and row3):
1128
+ return Matrix()
1129
+
1130
+ return Matrix((row0, row1, row2, row3))
1131
+
1132
+ location_matrix = Matrix()
1133
+ location = convert.float3_or_none(node_dict.get("translation"))
1134
+ if location:
1135
+ location_matrix = Matrix.Translation(location)
1136
+
1137
+ rotation_matrix = Matrix()
1138
+ rotation = convert.float4_or_none(node_dict.get("rotation"))
1139
+ if rotation:
1140
+ x, y, z, w = rotation
1141
+ quaternion = Quaternion((w, x, y, z))
1142
+ rotation_matrix = quaternion.to_matrix().to_4x4()
1143
+
1144
+ scale_matrix = Matrix()
1145
+ scale = convert.float3_or_none(node_dict.get("scale"))
1146
+ if scale:
1147
+ scale_matrix = Matrix.Diagonal(scale).to_4x4()
1148
+
1149
+ return location_matrix @ rotation_matrix @ scale_matrix