@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,682 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ from collections.abc import Sequence
3
+ from dataclasses import dataclass
4
+ from typing import Final, Optional, Union
5
+
6
+ from bpy.app.translations import pgettext
7
+ from bpy.types import (
8
+ Armature,
9
+ Collection,
10
+ Constraint,
11
+ Context,
12
+ CopyRotationConstraint,
13
+ Curve,
14
+ DampedTrackConstraint,
15
+ Material,
16
+ Mesh,
17
+ Object,
18
+ ObjectConstraints,
19
+ PoseBoneConstraints,
20
+ )
21
+
22
+ from ..common.logger import get_logger
23
+ from .extension_accessor import get_armature_extension
24
+
25
+ _logger = get_logger(__name__)
26
+
27
+ MESH_CONVERTIBLE_OBJECT_TYPES: Final[tuple[str, ...]] = (
28
+ "CURVE",
29
+ "FONT",
30
+ "MESH",
31
+ # "META", # Disable until the glTF 2.0 add-on supports it
32
+ "SURFACE",
33
+ )
34
+
35
+
36
+ def export_materials(context: Context, objects: Sequence[Object]) -> Sequence[Material]:
37
+ result: list[Material] = []
38
+ for obj in objects:
39
+ if obj.type not in MESH_CONVERTIBLE_OBJECT_TYPES:
40
+ continue
41
+
42
+ mesh_convertible = obj.data
43
+ if isinstance(mesh_convertible, (Curve, Mesh)):
44
+ for material_ref in mesh_convertible.materials:
45
+ if not isinstance(material_ref, Material):
46
+ continue
47
+ material = context.blend_data.materials.get(material_ref.name)
48
+ if not isinstance(material, Material):
49
+ continue
50
+ result.append(material)
51
+ else:
52
+ _logger.error(
53
+ "Unexpected mesh convertible object type: %s", type(mesh_convertible)
54
+ )
55
+
56
+ for material_slot in obj.material_slots:
57
+ if not material_slot:
58
+ continue
59
+ material_ref = material_slot.material
60
+ if not material_ref:
61
+ continue
62
+ material = context.blend_data.materials.get(material_ref.name)
63
+ if not isinstance(material, Material):
64
+ continue
65
+ result.append(material)
66
+
67
+ return list(dict.fromkeys(result)) # Remove duplicates
68
+
69
+
70
+ def object_distance(
71
+ source: Object,
72
+ target: Object,
73
+ collection_child_to_parent: dict[Collection, Optional[Collection]],
74
+ ) -> tuple[int, int, int, int, int, int, int]:
75
+ if source == target:
76
+ return (0, 0, 0, 0, 0, 0, 0)
77
+
78
+ source_collection_path: list[Collection] = []
79
+ source_collections = [
80
+ collection
81
+ for collection in source.users_collection
82
+ if collection in collection_child_to_parent
83
+ ]
84
+ if source_collections:
85
+ source_collection: Optional[Collection] = source_collections[0]
86
+ while source_collection:
87
+ source_collection_path.insert(0, source_collection)
88
+ source_collection = collection_child_to_parent.get(source_collection)
89
+
90
+ target_collection_path: list[Collection] = []
91
+ target_collections = [
92
+ collection
93
+ for collection in target.users_collection
94
+ if collection in collection_child_to_parent
95
+ ]
96
+ if target_collections:
97
+ target_collection: Optional[Collection] = target_collections[0]
98
+ while target_collection:
99
+ target_collection_path.insert(0, target_collection)
100
+ target_collection = collection_child_to_parent.get(target_collection)
101
+
102
+ while (
103
+ source_collection_path
104
+ and target_collection_path
105
+ and source_collection_path[0] == target_collection_path[0]
106
+ ):
107
+ source_collection_path.pop(0)
108
+ target_collection_path.pop(0)
109
+
110
+ source_parent_path: list[Object] = []
111
+ traversing_source: Optional[Object] = source
112
+ while traversing_source:
113
+ source_parent_path.insert(0, traversing_source)
114
+ traversing_source = traversing_source.parent
115
+
116
+ target_parent_path: list[Object] = []
117
+ traversing_target: Optional[Object] = target
118
+ while traversing_target:
119
+ target_parent_path.insert(0, traversing_target)
120
+ traversing_target = traversing_target.parent
121
+
122
+ while (
123
+ source_parent_path
124
+ and target_parent_path
125
+ and source_parent_path[0] == target_parent_path[0]
126
+ ):
127
+ source_parent_path.pop(0)
128
+ target_parent_path.pop(0)
129
+
130
+ return (
131
+ int(not target.select_get()),
132
+ int(target.hide_get()),
133
+ len(source_parent_path),
134
+ len(target_parent_path),
135
+ len(source_collection_path),
136
+ len(target_collection_path),
137
+ int(target.hide_render),
138
+ )
139
+
140
+
141
+ def armature_exists(context: Context) -> bool:
142
+ return any(armature.users > 0 for armature in context.blend_data.armatures) and any(
143
+ obj.type == "ARMATURE" for obj in context.blend_data.objects
144
+ )
145
+
146
+
147
+ def current_armature_is_vrm0(context: Context) -> bool:
148
+ live_armature_datum = [
149
+ armature_data
150
+ for armature_data in context.blend_data.armatures
151
+ if armature_data.users
152
+ ]
153
+ if not live_armature_datum:
154
+ return False
155
+ if all(
156
+ get_armature_extension(armature_data).is_vrm0()
157
+ for armature_data in live_armature_datum
158
+ ) and any(obj.type == "ARMATURE" for obj in context.blend_data.objects):
159
+ return True
160
+ armature = current_armature(context)
161
+ if armature is None:
162
+ return False
163
+ armature_data = armature.data
164
+ if not isinstance(armature_data, Armature):
165
+ return False
166
+ return get_armature_extension(armature_data).is_vrm0()
167
+
168
+
169
+ def current_armature_is_vrm1(context: Context) -> bool:
170
+ live_armature_datum = [
171
+ armature_data
172
+ for armature_data in context.blend_data.armatures
173
+ if armature_data.users
174
+ ]
175
+ if not live_armature_datum:
176
+ return False
177
+ if all(
178
+ get_armature_extension(armature_data).is_vrm1()
179
+ for armature_data in live_armature_datum
180
+ ) and any(obj.type == "ARMATURE" for obj in context.blend_data.objects):
181
+ return True
182
+ armature = current_armature(context)
183
+ if armature is None:
184
+ return False
185
+ armature_data = armature.data
186
+ if not isinstance(armature_data, Armature):
187
+ return False
188
+ return get_armature_extension(armature_data).is_vrm1()
189
+
190
+
191
+ def multiple_armatures_exist(context: Context) -> bool:
192
+ first_data_exists = False
193
+ for data in context.blend_data.armatures:
194
+ if not data.users:
195
+ continue
196
+ if not first_data_exists:
197
+ first_data_exists = True
198
+ continue
199
+
200
+ first_obj_exists = False
201
+ for obj in context.blend_data.objects:
202
+ if obj.type == "ARMATURE":
203
+ if first_obj_exists:
204
+ return True
205
+ first_obj_exists = True
206
+ break
207
+ return False
208
+
209
+
210
+ def current_armature(context: Context) -> Optional[Object]:
211
+ objects = [obj for obj in context.blend_data.objects if obj.type == "ARMATURE"]
212
+ if not objects:
213
+ return None
214
+
215
+ if len(objects) == 1:
216
+ return objects[0]
217
+
218
+ active_object = getattr(context, "active_object", None)
219
+ if not isinstance(active_object, Object):
220
+ active_object = context.view_layer.objects.active
221
+ if not active_object:
222
+ return objects[0]
223
+
224
+ collection_child_to_parent: dict[Collection, Optional[Collection]] = {
225
+ context.scene.collection: None
226
+ }
227
+
228
+ collections = [context.scene.collection]
229
+ while collections:
230
+ parent = collections.pop()
231
+ for child in parent.children:
232
+ collections.append(child)
233
+ collection_child_to_parent[child] = parent
234
+
235
+ min_distance: Optional[tuple[int, int, int, int, int, int, int]] = None
236
+ nearest_object: Optional[Object] = None
237
+ for obj in objects:
238
+ distance = object_distance(active_object, obj, collection_child_to_parent)
239
+ if min_distance is None or min_distance > distance:
240
+ min_distance = distance
241
+ nearest_object = obj
242
+
243
+ return objects[0] if nearest_object is None else nearest_object
244
+
245
+
246
+ def export_objects(
247
+ context: Context,
248
+ armature_object_name: Optional[str],
249
+ *,
250
+ export_invisibles: bool,
251
+ export_only_selections: bool,
252
+ export_lights: bool,
253
+ ) -> list[Object]:
254
+ selected_objects = []
255
+ if export_only_selections:
256
+ selected_objects = list(context.selected_objects)
257
+ elif export_invisibles:
258
+ selected_objects = list(context.blend_data.objects)
259
+ else:
260
+ selected_objects = list(context.selectable_objects)
261
+
262
+ objects: list[Object] = []
263
+
264
+ # https://projects.blender.org/blender/blender/issues/113378
265
+ context.view_layer.update()
266
+
267
+ armature_object = None
268
+ if armature_object_name:
269
+ armature_object = context.blend_data.objects.get(armature_object_name)
270
+ if armature_object and armature_object.name in context.view_layer.objects:
271
+ objects.append(armature_object)
272
+ else:
273
+ armature_object = None
274
+ if not armature_object:
275
+ objects.extend(
276
+ obj
277
+ for obj in context.blend_data.objects
278
+ if obj.type == "ARMATURE" and obj.name in context.view_layer.objects
279
+ )
280
+
281
+ collider_bpy_objects: list[Optional[Object]] = []
282
+ for armature_data in context.blend_data.armatures:
283
+ ext = get_armature_extension(armature_data)
284
+ for spring_bone1_collider in ext.spring_bone1.colliders:
285
+ spring_bone1_collider_bpy_object = spring_bone1_collider.bpy_object
286
+ if not spring_bone1_collider_bpy_object:
287
+ continue
288
+ collider_bpy_objects.append(spring_bone1_collider_bpy_object)
289
+ collider_bpy_objects.extend(spring_bone1_collider_bpy_object.children)
290
+ for collider_group in ext.vrm0.secondary_animation.collider_groups:
291
+ collider_bpy_objects.extend(
292
+ collider.bpy_object for collider in collider_group.colliders
293
+ )
294
+
295
+ for obj in selected_objects:
296
+ if obj.type in {"ARMATURE", "CAMERA"}:
297
+ continue
298
+ if obj.type == "LIGHT" and not export_lights:
299
+ continue
300
+ if obj.name not in context.view_layer.objects:
301
+ continue
302
+ if not export_invisibles and not obj.visible_get():
303
+ continue
304
+ objects.append(obj)
305
+
306
+ return [
307
+ # Remove colliders and duplicates
308
+ obj
309
+ for obj in dict.fromkeys(objects)
310
+ if obj not in collider_bpy_objects
311
+ ]
312
+
313
+
314
+ @dataclass(frozen=True)
315
+ class ExportConstraint:
316
+ roll_constraints: dict[str, CopyRotationConstraint]
317
+ aim_constraints: dict[str, DampedTrackConstraint]
318
+ rotation_constraints: dict[str, CopyRotationConstraint]
319
+
320
+ @property
321
+ def all_constraints(self) -> list[tuple[str, Constraint]]:
322
+ all_constraints: list[tuple[str, Constraint]] = [
323
+ *self.roll_constraints.items(),
324
+ *self.aim_constraints.items(),
325
+ *self.rotation_constraints.items(),
326
+ ]
327
+ return all_constraints
328
+
329
+
330
+ def roll_constraint_or_none(
331
+ constraint: Constraint,
332
+ objs: Sequence[Object],
333
+ armature: Object,
334
+ ) -> Optional[CopyRotationConstraint]:
335
+ if (
336
+ not isinstance(constraint, CopyRotationConstraint)
337
+ or not constraint.is_valid
338
+ or constraint.mute
339
+ or not (constraint_target := constraint.target)
340
+ or constraint_target not in objs
341
+ or constraint.mix_mode != "ADD"
342
+ or (int(constraint.use_x) + int(constraint.use_y) + int(constraint.use_z)) != 1
343
+ or constraint.owner_space != "LOCAL"
344
+ or constraint.target_space != "LOCAL"
345
+ ):
346
+ return None
347
+
348
+ if constraint_target.type != "ARMATURE":
349
+ return constraint
350
+
351
+ if constraint_target != armature:
352
+ return None
353
+
354
+ armature_data = armature.data
355
+ if (
356
+ not isinstance(armature_data, Armature)
357
+ or constraint.subtarget not in armature_data.bones
358
+ ):
359
+ return None
360
+
361
+ return constraint
362
+
363
+
364
+ def aim_constraint_or_none(
365
+ constraint: Constraint,
366
+ objs: Sequence[Object],
367
+ armature: Object,
368
+ ) -> Optional[DampedTrackConstraint]:
369
+ if (
370
+ not isinstance(constraint, DampedTrackConstraint)
371
+ or not constraint.is_valid
372
+ or constraint.mute
373
+ or not (constraint_target := constraint.target)
374
+ or constraint_target not in objs
375
+ ):
376
+ return None
377
+
378
+ if constraint_target.type != "ARMATURE":
379
+ return constraint
380
+
381
+ if constraint_target != armature:
382
+ return None
383
+
384
+ armature_data = armature.data
385
+ if (
386
+ not isinstance(armature_data, Armature)
387
+ or constraint.subtarget not in armature_data.bones
388
+ or abs(constraint.head_tail) > 0
389
+ ):
390
+ return None
391
+
392
+ return constraint
393
+
394
+
395
+ def rotation_constraint_or_none(
396
+ constraint: Constraint,
397
+ objs: Sequence[Object],
398
+ armature: Object,
399
+ ) -> Optional[CopyRotationConstraint]:
400
+ if (
401
+ not isinstance(constraint, CopyRotationConstraint)
402
+ or not constraint.is_valid
403
+ or constraint.mute
404
+ or not (constraint_target := constraint.target)
405
+ or constraint.target not in objs
406
+ or constraint.invert_x
407
+ or constraint.invert_y
408
+ or constraint.invert_z
409
+ or constraint.mix_mode != "ADD"
410
+ or not constraint.use_x
411
+ or not constraint.use_y
412
+ or not constraint.use_z
413
+ or constraint.owner_space != "LOCAL"
414
+ or constraint.target_space != "LOCAL"
415
+ ):
416
+ return None
417
+
418
+ if constraint_target.type != "ARMATURE":
419
+ return constraint
420
+
421
+ if constraint_target != armature:
422
+ return None
423
+
424
+ armature_data = armature.data
425
+ if (
426
+ not isinstance(armature_data, Armature)
427
+ or constraint.subtarget not in armature_data.bones
428
+ ):
429
+ return None
430
+
431
+ return constraint
432
+
433
+
434
+ def export_object_constraints(
435
+ objs: Sequence[Object],
436
+ armature: Object,
437
+ ) -> ExportConstraint:
438
+ roll_constraints: dict[str, CopyRotationConstraint] = {}
439
+ aim_constraints: dict[str, DampedTrackConstraint] = {}
440
+ rotation_constraints: dict[str, CopyRotationConstraint] = {}
441
+
442
+ for obj in objs:
443
+ for constraint in obj.constraints:
444
+ if not isinstance(constraint, Constraint):
445
+ continue
446
+ roll_constraint = roll_constraint_or_none(constraint, objs, armature)
447
+ if roll_constraint:
448
+ roll_constraints[obj.name] = roll_constraint
449
+ break
450
+ aim_constraint = aim_constraint_or_none(constraint, objs, armature)
451
+ if aim_constraint:
452
+ aim_constraints[obj.name] = aim_constraint
453
+ break
454
+ rotation_constraint = rotation_constraint_or_none(
455
+ constraint, objs, armature
456
+ )
457
+ if rotation_constraint:
458
+ rotation_constraints[obj.name] = rotation_constraint
459
+ break
460
+
461
+ return ExportConstraint(
462
+ roll_constraints=roll_constraints,
463
+ aim_constraints=aim_constraints,
464
+ rotation_constraints=rotation_constraints,
465
+ )
466
+
467
+
468
+ def export_bone_constraints(
469
+ objs: Sequence[Object],
470
+ armature: Object,
471
+ ) -> ExportConstraint:
472
+ roll_constraints: dict[str, CopyRotationConstraint] = {}
473
+ aim_constraints: dict[str, DampedTrackConstraint] = {}
474
+ rotation_constraints: dict[str, CopyRotationConstraint] = {}
475
+
476
+ for bone in armature.pose.bones:
477
+ for constraint in bone.constraints:
478
+ if not isinstance(constraint, Constraint):
479
+ continue
480
+ roll_constraint = roll_constraint_or_none(constraint, objs, armature)
481
+ if roll_constraint:
482
+ roll_constraints[bone.name] = roll_constraint
483
+ break
484
+ aim_constraint = aim_constraint_or_none(constraint, objs, armature)
485
+ if aim_constraint:
486
+ aim_constraints[bone.name] = aim_constraint
487
+ break
488
+ rotation_constraint = rotation_constraint_or_none(
489
+ constraint, objs, armature
490
+ )
491
+ if rotation_constraint:
492
+ rotation_constraints[bone.name] = rotation_constraint
493
+ break
494
+
495
+ return ExportConstraint(
496
+ roll_constraints=roll_constraints,
497
+ aim_constraints=aim_constraints,
498
+ rotation_constraints=rotation_constraints,
499
+ )
500
+
501
+
502
+ def export_constraints(
503
+ objs: Sequence[Object],
504
+ armature: Object,
505
+ ) -> tuple[ExportConstraint, ExportConstraint, Sequence[str]]:
506
+ messages: list[str] = []
507
+ object_constraints = export_object_constraints(objs, armature)
508
+ bone_constraints = export_bone_constraints(objs, armature)
509
+
510
+ all_constraints: list[Constraint] = [
511
+ *object_constraints.roll_constraints.values(),
512
+ *bone_constraints.roll_constraints.values(),
513
+ *object_constraints.rotation_constraints.values(),
514
+ *bone_constraints.rotation_constraints.values(),
515
+ # TODO: Aim Constraint's circular dependency detection
516
+ # *object_constraints.aim_constraints.values(),
517
+ # *bone_constraints.aim_constraints.values(),
518
+ ]
519
+
520
+ excluded_constraints: list[
521
+ Union[
522
+ CopyRotationConstraint,
523
+ DampedTrackConstraint,
524
+ ]
525
+ ] = []
526
+
527
+ for search_constraint in all_constraints:
528
+ if not isinstance(search_constraint, CopyRotationConstraint):
529
+ continue
530
+ current_constraints = [
531
+ (
532
+ search_constraint,
533
+ (
534
+ search_constraint.use_x,
535
+ search_constraint.use_y,
536
+ search_constraint.use_z,
537
+ ),
538
+ )
539
+ ]
540
+ iterated_constraints: set[Constraint] = set()
541
+ while current_constraints:
542
+ current_constraint, current_axis = current_constraints.pop()
543
+
544
+ if current_constraint in iterated_constraints:
545
+ break
546
+ iterated_constraints.add(current_constraint)
547
+
548
+ found = False
549
+
550
+ target = current_constraint.target
551
+ if not target:
552
+ continue
553
+
554
+ if target.type == "ARMATURE":
555
+ bone = target.pose.bones[current_constraint.subtarget]
556
+ owner_name = bone.name
557
+ target_constraints: Union[ObjectConstraints, PoseBoneConstraints] = (
558
+ bone.constraints
559
+ )
560
+ else:
561
+ owner_name = target.name
562
+ target_constraints = target.constraints
563
+
564
+ for target_constraint in target_constraints:
565
+ if not isinstance(target_constraint, CopyRotationConstraint):
566
+ continue
567
+ if target_constraint not in all_constraints:
568
+ continue
569
+
570
+ next_target_axis = (
571
+ current_axis[0] and target_constraint.use_x,
572
+ current_axis[1] and target_constraint.use_y,
573
+ current_axis[2] and target_constraint.use_z,
574
+ )
575
+ if next_target_axis == (False, False, False):
576
+ continue
577
+
578
+ if target_constraint != search_constraint:
579
+ current_constraints.append((target_constraint, next_target_axis))
580
+ continue
581
+
582
+ excluded_constraints.append(search_constraint)
583
+ messages.append(
584
+ pgettext(
585
+ 'Node Constraint "{owner_name} / {constraint_name}" has'
586
+ + " a circular dependency"
587
+ ).format(
588
+ owner_name=owner_name,
589
+ constraint_name=search_constraint.name,
590
+ )
591
+ )
592
+ found = True
593
+ break
594
+
595
+ if found:
596
+ break
597
+
598
+ return (
599
+ ExportConstraint(
600
+ roll_constraints={
601
+ k: v
602
+ for k, v in object_constraints.roll_constraints.items()
603
+ if v not in excluded_constraints
604
+ },
605
+ aim_constraints={
606
+ k: v
607
+ for k, v in object_constraints.aim_constraints.items()
608
+ if v not in excluded_constraints
609
+ },
610
+ rotation_constraints={
611
+ k: v
612
+ for k, v in object_constraints.rotation_constraints.items()
613
+ if v not in excluded_constraints
614
+ },
615
+ ),
616
+ ExportConstraint(
617
+ roll_constraints={
618
+ k: v
619
+ for k, v in bone_constraints.roll_constraints.items()
620
+ if v not in excluded_constraints
621
+ },
622
+ aim_constraints={
623
+ k: v
624
+ for k, v in bone_constraints.aim_constraints.items()
625
+ if v not in excluded_constraints
626
+ },
627
+ rotation_constraints={
628
+ k: v
629
+ for k, v in bone_constraints.rotation_constraints.items()
630
+ if v not in excluded_constraints
631
+ },
632
+ ),
633
+ messages,
634
+ )
635
+
636
+
637
+ def active_object_is_vrm1_armature(context: Context) -> bool:
638
+ active_object = context.active_object
639
+ if not active_object:
640
+ return False
641
+ if active_object.type != "ARMATURE":
642
+ return False
643
+ armature_data = active_object.data
644
+ if not isinstance(armature_data, Armature):
645
+ return False
646
+ return get_armature_extension(armature_data).is_vrm1()
647
+
648
+
649
+ def active_object_is_vrm0_armature(context: Context) -> bool:
650
+ active_object = context.active_object
651
+ if not active_object:
652
+ return False
653
+ if active_object.type != "ARMATURE":
654
+ return False
655
+ armature_data = active_object.data
656
+ if not isinstance(armature_data, Armature):
657
+ return False
658
+ return get_armature_extension(armature_data).is_vrm0()
659
+
660
+
661
+ def active_object_is_vrm_armature(context: Context) -> bool:
662
+ active_object = context.active_object
663
+ if not active_object:
664
+ return False
665
+ if active_object.type != "ARMATURE":
666
+ return False
667
+ armature_data = active_object.data
668
+ if not isinstance(armature_data, Armature):
669
+ return False
670
+ return get_armature_extension(armature_data).is_vrm0()
671
+
672
+
673
+ def active_object_is_khr_character_armature(context: Context) -> bool:
674
+ active_object = context.active_object
675
+ if not active_object:
676
+ return False
677
+ if active_object.type != "ARMATURE":
678
+ return False
679
+ armature_data = active_object.data
680
+ if not isinstance(armature_data, Armature):
681
+ return False
682
+ return get_armature_extension(armature_data).is_khr_character()
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later