@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,1457 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import math
3
+ import statistics
4
+ import uuid
5
+ from collections.abc import Callable, Sequence
6
+ from sys import float_info
7
+ from typing import TYPE_CHECKING, Optional
8
+
9
+ import bpy
10
+ from bpy.props import (
11
+ BoolProperty,
12
+ CollectionProperty,
13
+ EnumProperty,
14
+ FloatProperty,
15
+ FloatVectorProperty,
16
+ IntProperty,
17
+ PointerProperty,
18
+ StringProperty,
19
+ )
20
+ from bpy.types import Armature, Context, Object, PropertyGroup
21
+ from mathutils import Matrix, Quaternion, Vector
22
+
23
+ from ...common import convert, safe_removal
24
+ from ...common.logger import get_logger
25
+ from ...common.rotation import (
26
+ get_rotation_as_quaternion,
27
+ set_rotation_without_mode_change,
28
+ )
29
+ from ..extension_accessor import get_armature_extension
30
+ from ..property_group import BonePropertyGroup, property_group_enum
31
+
32
+ if TYPE_CHECKING:
33
+ from ..property_group import CollectionPropertyProtocol
34
+
35
+ _logger = get_logger(__name__)
36
+
37
+
38
+ def _find_armature_and_collider(
39
+ context: Context,
40
+ match_collider: Callable[["SpringBone1ColliderPropertyGroup"], bool],
41
+ ) -> tuple[Object, "SpringBone1ColliderPropertyGroup"]:
42
+ for obj in context.blend_data.objects:
43
+ if obj.type != "ARMATURE":
44
+ continue
45
+ armature_data = obj.data
46
+ if not isinstance(armature_data, Armature):
47
+ continue
48
+ for collider in get_armature_extension(armature_data).spring_bone1.colliders:
49
+ if match_collider(collider):
50
+ return (obj, collider)
51
+ message = "No armature"
52
+ raise ValueError(message)
53
+
54
+
55
+ # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.shape.schema.json#L7-L27
56
+ class SpringBone1ColliderShapeSpherePropertyGroup(PropertyGroup):
57
+ def find_armature_and_collider(
58
+ self,
59
+ context: Context,
60
+ ) -> tuple[Object, "SpringBone1ColliderPropertyGroup"]:
61
+ return _find_armature_and_collider(
62
+ context, lambda collider: collider.shape.sphere == self
63
+ )
64
+
65
+ def _get_offset(self) -> tuple[float, float, float]:
66
+ context = bpy.context
67
+
68
+ armature, collider = self.find_armature_and_collider(context)
69
+ if not collider.bpy_object:
70
+ _logger.error(
71
+ 'Failed to get bpy object of "%s" in sphere._get_offset()',
72
+ collider.uuid or "unknown",
73
+ )
74
+ return (0, 0, 0)
75
+ bone = armature.pose.bones.get(collider.node.bone_name)
76
+ if bone:
77
+ matrix = (
78
+ bone.matrix.inverted()
79
+ @ armature.matrix_world.inverted()
80
+ @ collider.bpy_object.matrix_world
81
+ )
82
+ else:
83
+ matrix = armature.matrix_world.inverted() @ collider.bpy_object.matrix_world
84
+ return convert.float3_or(matrix.to_translation(), (0.0, 0.0, 0.0))
85
+
86
+ def _set_offset(self, offset: Sequence[float]) -> None:
87
+ context = bpy.context
88
+
89
+ armature, collider = self.find_armature_and_collider(context)
90
+ collider.reset_bpy_object(context, armature)
91
+ bone = armature.pose.bones.get(collider.node.bone_name)
92
+ if not bone:
93
+ if collider.bpy_object:
94
+ collider.bpy_object.matrix_world = (
95
+ armature.matrix_world @ Matrix.Translation(offset)
96
+ )
97
+ return
98
+ if collider.bpy_object:
99
+ collider.bpy_object.matrix_world = (
100
+ armature.matrix_world @ bone.matrix @ Matrix.Translation(offset)
101
+ )
102
+
103
+ def _update_offset(self, _context: Context) -> None:
104
+ self.fallback_offset = self.offset
105
+
106
+ def _get_radius(self) -> float:
107
+ context = bpy.context
108
+
109
+ _, collider = self.find_armature_and_collider(context)
110
+ if not collider.bpy_object:
111
+ _logger.error(
112
+ 'Failed to get bpy object of "%s" in sphere._get_radius()',
113
+ collider.uuid or "unknown",
114
+ )
115
+ return 0.0
116
+ mean_scale = statistics.mean(
117
+ abs(s) for s in collider.bpy_object.matrix_basis.to_scale()
118
+ )
119
+ empty_display_size: float = collider.bpy_object.empty_display_size
120
+ return float(mean_scale) * empty_display_size
121
+
122
+ def _set_radius(self, v: float) -> None:
123
+ context = bpy.context
124
+
125
+ armature, collider = self.find_armature_and_collider(context)
126
+ collider.reset_bpy_object(context, armature)
127
+ if not collider.bpy_object:
128
+ _logger.error(
129
+ 'Failed to reset bpy object of "%s" in sphere._set_radius()',
130
+ collider.uuid or "unknown",
131
+ )
132
+ return
133
+ location, rotation, _ = collider.bpy_object.matrix_basis.decompose()
134
+ collider.bpy_object.matrix_basis = (
135
+ Matrix.Translation(location) @ rotation.to_matrix().to_4x4()
136
+ )
137
+ collider.bpy_object.empty_display_size = v
138
+
139
+ def _update_radius(self, _context: Context) -> None:
140
+ self.fallback_radius = self.radius
141
+
142
+ offset: FloatVectorProperty( # type: ignore[valid-type]
143
+ size=3,
144
+ subtype="TRANSLATION",
145
+ unit="LENGTH",
146
+ default=(0, 0, 0),
147
+ get=_get_offset,
148
+ set=_set_offset,
149
+ update=_update_offset,
150
+ )
151
+
152
+ radius: FloatProperty( # type: ignore[valid-type]
153
+ name="Radius",
154
+ min=0.0,
155
+ default=0.0,
156
+ soft_max=1.0,
157
+ unit="LENGTH",
158
+ get=_get_radius,
159
+ set=_set_radius,
160
+ update=_update_radius,
161
+ )
162
+
163
+ fallback_offset: FloatVectorProperty( # type: ignore[valid-type]
164
+ name="Fallback Sphere Offset",
165
+ size=3,
166
+ subtype="TRANSLATION",
167
+ unit="LENGTH",
168
+ default=(0, 0, 0),
169
+ )
170
+
171
+ fallback_radius: FloatProperty( # type: ignore[valid-type]
172
+ name="Fallback Sphere Radius",
173
+ min=0.0,
174
+ default=0.0,
175
+ soft_max=1.0,
176
+ unit="LENGTH",
177
+ )
178
+
179
+ if TYPE_CHECKING:
180
+ # This code is auto generated.
181
+ # To regenerate, run the `uv run tools/property_typing.py` command.
182
+ offset: Sequence[float] # type: ignore[no-redef]
183
+ radius: float # type: ignore[no-redef]
184
+ fallback_offset: Sequence[float] # type: ignore[no-redef]
185
+ fallback_radius: float # type: ignore[no-redef]
186
+
187
+
188
+ # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.shape.schema.json#L28-L58
189
+ class SpringBone1ColliderShapeCapsulePropertyGroup(PropertyGroup):
190
+ def find_armature_and_collider(
191
+ self,
192
+ context: Context,
193
+ ) -> tuple[Object, "SpringBone1ColliderPropertyGroup"]:
194
+ return _find_armature_and_collider(
195
+ context, lambda collider: collider.shape.capsule == self
196
+ )
197
+
198
+ def _get_offset(self) -> tuple[float, float, float]:
199
+ context = bpy.context
200
+
201
+ armature, collider = self.find_armature_and_collider(context)
202
+ if not collider.bpy_object:
203
+ _logger.error(
204
+ 'Failed to get bpy object of "%s" in capsule._get_offset()',
205
+ collider.uuid or "unknown",
206
+ )
207
+ return (0, 0, 0)
208
+ bone = armature.pose.bones.get(collider.node.bone_name)
209
+ if bone:
210
+ matrix = (
211
+ bone.matrix.inverted()
212
+ @ armature.matrix_world.inverted()
213
+ @ collider.bpy_object.matrix_world
214
+ )
215
+ else:
216
+ matrix = armature.matrix_world.inverted() @ collider.bpy_object.matrix_world
217
+ return convert.float3_or(matrix.to_translation(), (0.0, 0.0, 0.0))
218
+
219
+ def _set_offset(self, offset: Sequence[float]) -> None:
220
+ context = bpy.context
221
+
222
+ armature, collider = self.find_armature_and_collider(context)
223
+ collider.reset_bpy_object(context, armature)
224
+ bone = armature.pose.bones.get(collider.node.bone_name)
225
+ if not bone:
226
+ if collider.bpy_object:
227
+ collider.bpy_object.matrix_world = (
228
+ armature.matrix_world @ Matrix.Translation(offset)
229
+ )
230
+ return
231
+ if collider.bpy_object:
232
+ collider.bpy_object.matrix_world = (
233
+ armature.matrix_world @ bone.matrix @ Matrix.Translation(offset)
234
+ )
235
+
236
+ def _update_offset(self, _context: Context) -> None:
237
+ self.fallback_offset = self.offset
238
+
239
+ def _get_tail(self) -> tuple[float, float, float]:
240
+ context = bpy.context
241
+
242
+ armature, collider = self.find_armature_and_collider(context)
243
+ if not collider.bpy_object:
244
+ _logger.error(
245
+ 'Failed to get bpy object of "%s" in capsule._get_tail()',
246
+ collider.uuid or "unknown",
247
+ )
248
+ return (0, 0, 0)
249
+ if not collider.bpy_object.children:
250
+ _logger.error(
251
+ 'Failed to get bpy object children of "%s" in capsule._get_tail()',
252
+ collider.display_name,
253
+ )
254
+ return (0, 0, 0)
255
+ bone = armature.pose.bones.get(collider.node.bone_name)
256
+ if bone:
257
+ matrix = (
258
+ bone.matrix.inverted()
259
+ @ armature.matrix_world.inverted()
260
+ @ collider.bpy_object.children[0].matrix_world
261
+ )
262
+ else:
263
+ matrix = (
264
+ armature.matrix_world.inverted()
265
+ @ collider.bpy_object.children[0].matrix_world
266
+ )
267
+ return convert.float3_or(matrix.to_translation(), (0.0, 0.0, 0.0))
268
+
269
+ def _set_tail(self, offset: Sequence[float]) -> None:
270
+ context = bpy.context
271
+
272
+ armature, collider = self.find_armature_and_collider(context)
273
+ collider.reset_bpy_object(context, armature)
274
+ bone = armature.pose.bones.get(collider.node.bone_name)
275
+ if not bone:
276
+ if collider.bpy_object:
277
+ collider.bpy_object.children[0].matrix_world = (
278
+ armature.matrix_world @ Matrix.Translation(offset)
279
+ )
280
+ return
281
+ if collider.bpy_object:
282
+ if not collider.bpy_object.children:
283
+ _logger.error(
284
+ 'Failed to set tail bpy object matrix of "%s"'
285
+ " in capsule._set_tail()",
286
+ collider.display_name,
287
+ )
288
+ return
289
+ collider.bpy_object.children[0].matrix_world = (
290
+ armature.matrix_world @ bone.matrix @ Matrix.Translation(offset)
291
+ )
292
+
293
+ def _update_tail(self, _context: Context) -> None:
294
+ self.fallback_tail = self.tail
295
+
296
+ def _get_radius(self) -> float:
297
+ context = bpy.context
298
+
299
+ _, collider = self.find_armature_and_collider(context)
300
+ if not collider.bpy_object:
301
+ _logger.error(
302
+ 'Failed to get bpy object of "%s" in capsule._get_radius()',
303
+ collider.uuid or "unknown",
304
+ )
305
+ return 0.0
306
+ mean_scale: float = statistics.mean(
307
+ abs(s) for s in collider.bpy_object.matrix_basis.to_scale()
308
+ )
309
+ empty_display_size: float = collider.bpy_object.empty_display_size
310
+ return mean_scale * empty_display_size
311
+
312
+ def _set_radius(self, v: float) -> None:
313
+ context = bpy.context
314
+
315
+ armature, collider = self.find_armature_and_collider(context)
316
+ collider.reset_bpy_object(context, armature)
317
+ if not collider.bpy_object:
318
+ _logger.error(
319
+ 'Failed to reset bpy object of "%s" in capsule._set_radius()',
320
+ collider.uuid or "unknown",
321
+ )
322
+ return
323
+ location, rotation, _ = collider.bpy_object.matrix_basis.decompose()
324
+ collider.bpy_object.matrix_basis = (
325
+ Matrix.Translation(location) @ rotation.to_matrix().to_4x4()
326
+ )
327
+ collider.bpy_object.empty_display_size = v
328
+ if not collider.bpy_object.children:
329
+ _logger.error(
330
+ 'Failed to set tail bpy object size of "%s" in capsule._set_radius()',
331
+ collider.display_name,
332
+ )
333
+ return
334
+ collider.bpy_object.children[0].empty_display_size = v
335
+
336
+ def _update_radius(self, _context: Context) -> None:
337
+ self.fallback_radius = self.radius
338
+
339
+ offset: FloatVectorProperty( # type: ignore[valid-type]
340
+ size=3,
341
+ subtype="TRANSLATION",
342
+ unit="LENGTH",
343
+ default=(0, 0, 0),
344
+ get=_get_offset,
345
+ set=_set_offset,
346
+ update=_update_offset,
347
+ )
348
+
349
+ radius: FloatProperty( # type: ignore[valid-type]
350
+ name="Radius",
351
+ min=0.0,
352
+ default=0.0,
353
+ soft_max=1.0,
354
+ unit="LENGTH",
355
+ get=_get_radius,
356
+ set=_set_radius,
357
+ update=_update_radius,
358
+ )
359
+
360
+ tail: FloatVectorProperty( # type: ignore[valid-type]
361
+ size=3,
362
+ subtype="TRANSLATION",
363
+ unit="LENGTH",
364
+ default=(0, 0, 0),
365
+ get=_get_tail,
366
+ set=_set_tail,
367
+ update=_update_tail,
368
+ )
369
+
370
+ fallback_offset: FloatVectorProperty( # type: ignore[valid-type]
371
+ name="Fallback Capsule Head",
372
+ size=3,
373
+ subtype="TRANSLATION",
374
+ unit="LENGTH",
375
+ default=(0, 0, 0),
376
+ )
377
+
378
+ fallback_radius: FloatProperty( # type: ignore[valid-type]
379
+ name="Fallback Capsule Radius",
380
+ min=0.0,
381
+ default=0.0,
382
+ soft_max=1.0,
383
+ unit="LENGTH",
384
+ )
385
+
386
+ fallback_tail: FloatVectorProperty( # type: ignore[valid-type]
387
+ name="Fallback Capsule Tail",
388
+ size=3,
389
+ subtype="TRANSLATION",
390
+ unit="LENGTH",
391
+ default=(0, 0, 0),
392
+ )
393
+
394
+ if TYPE_CHECKING:
395
+ # This code is auto generated.
396
+ # To regenerate, run the `uv run tools/property_typing.py` command.
397
+ offset: Sequence[float] # type: ignore[no-redef]
398
+ radius: float # type: ignore[no-redef]
399
+ tail: Sequence[float] # type: ignore[no-redef]
400
+ fallback_offset: Sequence[float] # type: ignore[no-redef]
401
+ fallback_radius: float # type: ignore[no-redef]
402
+ fallback_tail: Sequence[float] # type: ignore[no-redef]
403
+
404
+
405
+ # https://github.com/vrm-c/vrm-specification/blob/9e8c886a2043639a3c166eb7cc93526be6313147/specification/VRMC_springBone_extended_collider-1.0/schema/VRMC_springBone_extended_collider.shape.schema.json#L9
406
+ class SpringBone1ExtendedColliderShapeSpherePropertyGroup(
407
+ SpringBone1ColliderShapeSpherePropertyGroup
408
+ ):
409
+ def find_armature_and_collider(
410
+ self,
411
+ context: Context,
412
+ ) -> tuple[Object, "SpringBone1ColliderPropertyGroup"]:
413
+ return _find_armature_and_collider(
414
+ context,
415
+ lambda collider: (
416
+ collider.extensions.vrmc_spring_bone_extended_collider.shape.sphere
417
+ == self
418
+ ),
419
+ )
420
+
421
+ inside: BoolProperty() # type: ignore[valid-type]
422
+
423
+ if TYPE_CHECKING:
424
+ # This code is auto generated.
425
+ # To regenerate, run the `uv run tools/property_typing.py` command.
426
+ inside: bool # type: ignore[no-redef]
427
+
428
+
429
+ # https://github.com/vrm-c/vrm-specification/blob/9e8c886a2043639a3c166eb7cc93526be6313147/specification/VRMC_springBone_extended_collider-1.0/schema/VRMC_springBone_extended_collider.shape.schema.json#L36
430
+ class SpringBone1ExtendedColliderShapeCapsulePropertyGroup(
431
+ SpringBone1ColliderShapeCapsulePropertyGroup
432
+ ):
433
+ def find_armature_and_collider(
434
+ self,
435
+ context: Context,
436
+ ) -> tuple[Object, "SpringBone1ColliderPropertyGroup"]:
437
+ return _find_armature_and_collider(
438
+ context,
439
+ lambda collider: (
440
+ collider.extensions.vrmc_spring_bone_extended_collider.shape.capsule
441
+ == self
442
+ ),
443
+ )
444
+
445
+ inside: BoolProperty() # type: ignore[valid-type]
446
+
447
+ if TYPE_CHECKING:
448
+ # This code is auto generated.
449
+ # To regenerate, run the `uv run tools/property_typing.py` command.
450
+ inside: bool # type: ignore[no-redef]
451
+
452
+
453
+ # https://github.com/vrm-c/vrm-specification/blob/9e8c886a2043639a3c166eb7cc93526be6313147/specification/VRMC_springBone_extended_collider-1.0/schema/VRMC_springBone_extended_collider.shape.schema.json#L73
454
+ class SpringBone1ExtendedColliderShapePlanePropertyGroup(PropertyGroup):
455
+ def find_armature_and_collider(
456
+ self,
457
+ context: Context,
458
+ ) -> tuple[Object, "SpringBone1ColliderPropertyGroup"]:
459
+ return _find_armature_and_collider(
460
+ context,
461
+ lambda collider: (
462
+ collider.extensions.vrmc_spring_bone_extended_collider.shape.plane
463
+ == self
464
+ ),
465
+ )
466
+
467
+ def _get_offset(self) -> tuple[float, float, float]:
468
+ context = bpy.context
469
+
470
+ armature, collider = self.find_armature_and_collider(context)
471
+ if not collider.bpy_object:
472
+ _logger.error(
473
+ 'Failed to get bpy object of "%s" in extended_collider._get_offset()',
474
+ collider.uuid or "unknown",
475
+ )
476
+ return (0, 0, 0)
477
+ bone = armature.pose.bones.get(collider.node.bone_name)
478
+ if bone:
479
+ matrix = (
480
+ bone.matrix.inverted()
481
+ @ armature.matrix_world.inverted()
482
+ @ collider.bpy_object.matrix_world
483
+ )
484
+ else:
485
+ matrix = armature.matrix_world.inverted() @ collider.bpy_object.matrix_world
486
+ return convert.float3_or(matrix.to_translation(), (0.0, 0.0, 0.0))
487
+
488
+ def _set_offset(self, offset: Sequence[float]) -> None:
489
+ context = bpy.context
490
+
491
+ armature, collider = self.find_armature_and_collider(context)
492
+ collider.reset_bpy_object(context, armature)
493
+ bone = armature.pose.bones.get(collider.node.bone_name)
494
+ if not bone:
495
+ if collider.bpy_object:
496
+ collider.bpy_object.matrix_world = (
497
+ armature.matrix_world @ Matrix.Translation(offset)
498
+ )
499
+ return
500
+ if collider.bpy_object:
501
+ collider.bpy_object.matrix_world = (
502
+ armature.matrix_world @ bone.matrix @ Matrix.Translation(offset)
503
+ )
504
+
505
+ def _get_normal(self) -> tuple[float, float, float]:
506
+ context = bpy.context
507
+
508
+ _, collider = self.find_armature_and_collider(context)
509
+ bpy_object = collider.bpy_object
510
+ if not bpy_object:
511
+ return (0.0, 1.0, 0.0)
512
+
513
+ result = Vector((0.0, 1.0, 0.0))
514
+ result.rotate(get_rotation_as_quaternion(bpy_object))
515
+
516
+ return (
517
+ # use tuple initializer to make type checkers happy
518
+ result.x,
519
+ result.y,
520
+ result.z,
521
+ )
522
+
523
+ def _set_normal(self, normal: Sequence[float]) -> None:
524
+ context = bpy.context
525
+
526
+ armature, collider = self.find_armature_and_collider(context)
527
+ collider.reset_bpy_object(context, armature)
528
+
529
+ y_up_vec = Vector((0.0, 1.0, 0.0))
530
+ normal_vec = Vector(normal)
531
+ if normal_vec.length_squared > 0:
532
+ rotation = y_up_vec.rotation_difference(normal_vec)
533
+ else:
534
+ rotation = Quaternion()
535
+
536
+ bpy_object = collider.bpy_object
537
+ if bpy_object:
538
+ set_rotation_without_mode_change(bpy_object, rotation)
539
+
540
+ offset: FloatVectorProperty( # type: ignore[valid-type]
541
+ size=3,
542
+ subtype="TRANSLATION",
543
+ unit="LENGTH",
544
+ default=(0, 0, 0),
545
+ get=_get_offset,
546
+ set=_set_offset,
547
+ )
548
+
549
+ normal: FloatVectorProperty( # type: ignore[valid-type]
550
+ size=3,
551
+ default=(0, 0, 1),
552
+ get=_get_normal,
553
+ set=_set_normal,
554
+ )
555
+
556
+ if TYPE_CHECKING:
557
+ # This code is auto generated.
558
+ # To regenerate, run the `uv run tools/property_typing.py` command.
559
+ offset: Sequence[float] # type: ignore[no-redef]
560
+ normal: Sequence[float] # type: ignore[no-redef]
561
+
562
+
563
+ # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.shape.schema.json
564
+ class SpringBone1ColliderShapePropertyGroup(PropertyGroup):
565
+ sphere: PointerProperty( # type: ignore[valid-type]
566
+ type=SpringBone1ColliderShapeSpherePropertyGroup
567
+ )
568
+ capsule: PointerProperty( # type: ignore[valid-type]
569
+ type=SpringBone1ColliderShapeCapsulePropertyGroup
570
+ )
571
+
572
+ if TYPE_CHECKING:
573
+ # This code is auto generated.
574
+ # To regenerate, run the `uv run tools/property_typing.py` command.
575
+ sphere: SpringBone1ColliderShapeSpherePropertyGroup # type: ignore[no-redef]
576
+ capsule: SpringBone1ColliderShapeCapsulePropertyGroup # type: ignore[no-redef]
577
+
578
+
579
+ # https://github.com/vrm-c/vrm-specification/blob/9e8c886a2043639a3c166eb7cc93526be6313147/specification/VRMC_springBone_extended_collider-1.0/schema/VRMC_springBone_extended_collider.shape.schema.json
580
+ class SpringBone1ExtendedColliderShapePropertyGroup(PropertyGroup):
581
+ sphere: PointerProperty( # type: ignore[valid-type]
582
+ type=SpringBone1ExtendedColliderShapeSpherePropertyGroup
583
+ )
584
+ capsule: PointerProperty( # type: ignore[valid-type]
585
+ type=SpringBone1ExtendedColliderShapeCapsulePropertyGroup
586
+ )
587
+ plane: PointerProperty( # type: ignore[valid-type]
588
+ type=SpringBone1ExtendedColliderShapePlanePropertyGroup
589
+ )
590
+
591
+ if TYPE_CHECKING:
592
+ # This code is auto generated.
593
+ # To regenerate, run the `uv run tools/property_typing.py` command.
594
+ sphere: ( # type: ignore[no-redef]
595
+ SpringBone1ExtendedColliderShapeSpherePropertyGroup
596
+ )
597
+ capsule: ( # type: ignore[no-redef]
598
+ SpringBone1ExtendedColliderShapeCapsulePropertyGroup
599
+ )
600
+ plane: ( # type: ignore[no-redef]
601
+ SpringBone1ExtendedColliderShapePlanePropertyGroup
602
+ )
603
+
604
+
605
+ # https://github.com/vrm-c/vrm-specification/blob/9e8c886a2043639a3c166eb7cc93526be6313147/specification/VRMC_springBone_extended_collider-1.0/schema/VRMC_springBone_extended_collider.schema.json
606
+ class SpringBone1VrmcSpringBoneExtendedColliderPropertyGroup(PropertyGroup):
607
+ def _update_shape_type(self, context: Context) -> None:
608
+ armature, collider = _find_armature_and_collider(
609
+ context,
610
+ lambda collider: (
611
+ collider.extensions.vrmc_spring_bone_extended_collider == self
612
+ ),
613
+ )
614
+ collider.reset_bpy_object(context, armature)
615
+
616
+ enabled: BoolProperty( # type: ignore[valid-type]
617
+ name="Extended Collider",
618
+ update=_update_shape_type,
619
+ )
620
+
621
+ automatic_fallback_generation: BoolProperty( # type: ignore[valid-type]
622
+ name="Automatic Fallback Generation",
623
+ default=True,
624
+ )
625
+
626
+ shape: PointerProperty( # type: ignore[valid-type]
627
+ type=SpringBone1ExtendedColliderShapePropertyGroup
628
+ )
629
+
630
+ (
631
+ shape_type_enum,
632
+ (
633
+ SHAPE_TYPE_EXTENDED_SPHERE,
634
+ SHAPE_TYPE_EXTENDED_CAPSULE,
635
+ SHAPE_TYPE_EXTENDED_PLANE,
636
+ ),
637
+ ) = property_group_enum(
638
+ ("extendedSphere", "Sphere", "", "NONE", 0),
639
+ ("extendedCapsule", "Capsule", "", "NONE", 1),
640
+ ("extendedPlane", "Plane", "", "NONE", 2),
641
+ )
642
+
643
+ shape_type: EnumProperty( # type: ignore[valid-type]
644
+ items=shape_type_enum.items(),
645
+ name="Shape",
646
+ update=_update_shape_type,
647
+ )
648
+
649
+ if TYPE_CHECKING:
650
+ # This code is auto generated.
651
+ # To regenerate, run the `uv run tools/property_typing.py` command.
652
+ enabled: bool # type: ignore[no-redef]
653
+ automatic_fallback_generation: bool # type: ignore[no-redef]
654
+ shape: SpringBone1ExtendedColliderShapePropertyGroup # type: ignore[no-redef]
655
+ shape_type: str # type: ignore[no-redef]
656
+
657
+
658
+ class SpringBone1ColliderExtensionsPropertyGroup(PropertyGroup):
659
+ vrmc_spring_bone_extended_collider: PointerProperty( # type: ignore[valid-type]
660
+ type=SpringBone1VrmcSpringBoneExtendedColliderPropertyGroup
661
+ )
662
+
663
+ if TYPE_CHECKING:
664
+ # This code is auto generated.
665
+ # To regenerate, run the `uv run tools/property_typing.py` command.
666
+ vrmc_spring_bone_extended_collider: ( # type: ignore[no-redef]
667
+ SpringBone1VrmcSpringBoneExtendedColliderPropertyGroup
668
+ )
669
+
670
+
671
+ # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.collider.schema.json
672
+ class SpringBone1ColliderPropertyGroup(PropertyGroup):
673
+ @property
674
+ def display_name(self) -> str:
675
+ bpy_object = self.bpy_object
676
+ if not bpy_object:
677
+ return ""
678
+ return bpy_object.name
679
+
680
+ node: PointerProperty( # type: ignore[valid-type]
681
+ type=BonePropertyGroup,
682
+ name="Bone",
683
+ )
684
+ shape: PointerProperty(type=SpringBone1ColliderShapePropertyGroup) # type: ignore[valid-type]
685
+ extensions: PointerProperty(type=SpringBone1ColliderExtensionsPropertyGroup) # type: ignore[valid-type]
686
+
687
+ # for UI
688
+ show_expanded: BoolProperty() # type: ignore[valid-type]
689
+
690
+ (
691
+ shape_type_enum,
692
+ (
693
+ SHAPE_TYPE_SPHERE,
694
+ SHAPE_TYPE_CAPSULE,
695
+ ),
696
+ ) = property_group_enum(
697
+ ("Sphere", "Sphere", "", "NONE", 0),
698
+ ("Capsule", "Capsule", "", "NONE", 1),
699
+ )
700
+
701
+ (
702
+ ui_collider_type_enum,
703
+ (
704
+ UI_COLLIDER_TYPE_SPHERE,
705
+ UI_COLLIDER_TYPE_CAPSULE,
706
+ UI_COLLIDER_TYPE_PLANE,
707
+ UI_COLLIDER_TYPE_SPHERE_INSIDE,
708
+ UI_COLLIDER_TYPE_CAPSULE_INSIDE,
709
+ ),
710
+ ) = property_group_enum(
711
+ ("uiColliderTypeSphere", "Sphere", "", "NONE", 0),
712
+ ("uiColliderTypeCapsule", "Capsule", "", "NONE", 1),
713
+ ("uiColliderTypePlane", "Plane", "", "NONE", 2),
714
+ ("uiColliderTypeSphereInside", "Sphere Inside", "", "NONE", 3),
715
+ ("uiColliderTypeCapsuleInside", "Capsule Inside", "", "NONE", 4),
716
+ )
717
+
718
+ def _get_ui_collider_type(self) -> int:
719
+ extended = self.extensions.vrmc_spring_bone_extended_collider
720
+ if not extended.enabled:
721
+ if self.shape_type == self.SHAPE_TYPE_SPHERE.identifier:
722
+ return self.UI_COLLIDER_TYPE_SPHERE.value
723
+ if self.shape_type == self.SHAPE_TYPE_CAPSULE.identifier:
724
+ return self.UI_COLLIDER_TYPE_CAPSULE.value
725
+ return self.UI_COLLIDER_TYPE_SPHERE.value
726
+
727
+ if extended.shape_type == extended.SHAPE_TYPE_EXTENDED_PLANE.identifier:
728
+ return self.UI_COLLIDER_TYPE_PLANE.value
729
+ if extended.shape_type == extended.SHAPE_TYPE_EXTENDED_SPHERE.identifier:
730
+ if extended.shape.sphere.inside:
731
+ return self.UI_COLLIDER_TYPE_SPHERE_INSIDE.value
732
+ return self.UI_COLLIDER_TYPE_SPHERE.value
733
+ if extended.shape_type == extended.SHAPE_TYPE_EXTENDED_CAPSULE.identifier:
734
+ if extended.shape.capsule.inside:
735
+ return self.UI_COLLIDER_TYPE_CAPSULE_INSIDE.value
736
+ return self.UI_COLLIDER_TYPE_CAPSULE.value
737
+ return self.UI_COLLIDER_TYPE_SPHERE.value
738
+
739
+ def _set_ui_collider_type(self, value: int) -> None:
740
+ extended = self.extensions.vrmc_spring_bone_extended_collider
741
+
742
+ if value in (
743
+ self.UI_COLLIDER_TYPE_SPHERE.value,
744
+ self.UI_COLLIDER_TYPE_SPHERE_INSIDE.value,
745
+ ):
746
+ if self.shape_type != self.SHAPE_TYPE_SPHERE.identifier:
747
+ self.shape_type = self.SHAPE_TYPE_SPHERE.identifier
748
+ if extended.shape_type != extended.SHAPE_TYPE_EXTENDED_SPHERE.identifier:
749
+ extended.shape_type = extended.SHAPE_TYPE_EXTENDED_SPHERE.identifier
750
+ inside = self.UI_COLLIDER_TYPE_SPHERE_INSIDE.value == value
751
+ if extended.shape.sphere.inside != inside:
752
+ extended.shape.sphere.inside = inside
753
+ if extended.enabled != inside:
754
+ extended.enabled = inside
755
+ return
756
+
757
+ if value in (
758
+ self.UI_COLLIDER_TYPE_CAPSULE.value,
759
+ self.UI_COLLIDER_TYPE_CAPSULE_INSIDE.value,
760
+ ):
761
+ if self.shape_type != self.SHAPE_TYPE_CAPSULE.identifier:
762
+ self.shape_type = self.SHAPE_TYPE_CAPSULE.identifier
763
+ if extended.shape_type != extended.SHAPE_TYPE_EXTENDED_CAPSULE.identifier:
764
+ extended.shape_type = extended.SHAPE_TYPE_EXTENDED_CAPSULE.identifier
765
+ inside = self.UI_COLLIDER_TYPE_CAPSULE_INSIDE.value == value
766
+ if extended.shape.capsule.inside != inside:
767
+ extended.shape.capsule.inside = inside
768
+ if extended.enabled != inside:
769
+ extended.enabled = inside
770
+ return
771
+
772
+ if self.UI_COLLIDER_TYPE_PLANE.value == value:
773
+ if not extended.enabled:
774
+ extended.enabled = True
775
+ if extended.shape_type != extended.SHAPE_TYPE_EXTENDED_PLANE.identifier:
776
+ extended.shape_type = extended.SHAPE_TYPE_EXTENDED_PLANE.identifier
777
+
778
+ def _update_shape_type(self, context: Context) -> None:
779
+ if (
780
+ self.bpy_object
781
+ and self.bpy_object.parent
782
+ and self.bpy_object.parent.type == "ARMATURE"
783
+ ):
784
+ self.reset_bpy_object(context, self.bpy_object.parent)
785
+
786
+ shape_type: EnumProperty( # type: ignore[valid-type]
787
+ items=shape_type_enum.items(),
788
+ name="Shape",
789
+ update=_update_shape_type,
790
+ )
791
+
792
+ ui_collider_type: EnumProperty( # type: ignore[valid-type]
793
+ items=ui_collider_type_enum.items(),
794
+ name="Collider Type",
795
+ get=_get_ui_collider_type,
796
+ set=_set_ui_collider_type,
797
+ )
798
+
799
+ # for View3D
800
+ bpy_object: PointerProperty( # type: ignore[valid-type]
801
+ type=Object
802
+ )
803
+
804
+ # for references
805
+ uuid: StringProperty() # type: ignore[valid-type]
806
+
807
+ def reset_bpy_object(self, context: Context, armature: Object) -> None:
808
+ armature_data = armature.data
809
+ if not isinstance(armature_data, Armature):
810
+ return
811
+ collider_prefix = armature_data.name
812
+ if self.node and self.node.bone_name:
813
+ collider_prefix = self.node.bone_name
814
+
815
+ if not self.bpy_object or not self.bpy_object.name:
816
+ obj = context.blend_data.objects.new(
817
+ name=f"{collider_prefix} Collider", object_data=None
818
+ )
819
+ obj.empty_display_size = 0.125
820
+ context.scene.collection.objects.link(obj)
821
+ self.bpy_object = obj
822
+ self.bpy_object.select_set(True)
823
+
824
+ if self.bpy_object.parent != armature:
825
+ self.bpy_object.parent = armature
826
+
827
+ extended = self.extensions.vrmc_spring_bone_extended_collider
828
+ if (
829
+ not extended.enabled
830
+ and self.shape_type == self.SHAPE_TYPE_SPHERE.identifier
831
+ ) or (
832
+ extended.enabled
833
+ and extended.shape_type
834
+ in (
835
+ extended.SHAPE_TYPE_EXTENDED_SPHERE.identifier,
836
+ extended.SHAPE_TYPE_EXTENDED_PLANE.identifier,
837
+ )
838
+ ):
839
+ for child in list(self.bpy_object.children):
840
+ if not safe_removal.remove_object(context, child):
841
+ _logger.warning(
842
+ 'Failed to remove "%s" with %d users'
843
+ " while changing spring bone collider type",
844
+ child.name,
845
+ child.users,
846
+ )
847
+ empty_display_type = "SPHERE"
848
+ if extended.shape_type == extended.SHAPE_TYPE_EXTENDED_PLANE.identifier:
849
+ empty_display_type = "CIRCLE"
850
+ if self.bpy_object.empty_display_type != empty_display_type:
851
+ self.bpy_object.empty_display_type = empty_display_type
852
+ elif (
853
+ not extended.enabled
854
+ and self.shape_type == self.SHAPE_TYPE_CAPSULE.identifier
855
+ ) or (
856
+ extended.enabled
857
+ and extended.shape_type == extended.SHAPE_TYPE_EXTENDED_CAPSULE.identifier
858
+ ):
859
+ if self.bpy_object.children:
860
+ end_object = self.bpy_object.children[0]
861
+ else:
862
+ end_object = context.blend_data.objects.new(
863
+ name=f"{self.bpy_object.name} End", object_data=None
864
+ )
865
+ end_object.empty_display_size = self.bpy_object.empty_display_size
866
+ context.scene.collection.objects.link(end_object)
867
+ end_object.parent = self.bpy_object
868
+ end_object.matrix_local = Matrix.Translation(Vector((0.0, 0.2, 0.0)))
869
+ end_object.select_set(True)
870
+ if self.bpy_object.empty_display_type != "SPHERE":
871
+ self.bpy_object.empty_display_type = "SPHERE"
872
+ if end_object.empty_display_type != "SPHERE":
873
+ end_object.empty_display_type = "SPHERE"
874
+ if end_object.empty_display_size != self.bpy_object.empty_display_size:
875
+ end_object.empty_display_size = self.bpy_object.empty_display_size
876
+ if end_object.scale != Vector((1, 1, 1)):
877
+ end_object.scale = Vector((1, 1, 1))
878
+
879
+ if self.node.bone_name:
880
+ if self.bpy_object.parent_type != "BONE":
881
+ self.bpy_object.parent_type = "BONE"
882
+ if self.bpy_object.parent_bone != self.node.bone_name:
883
+ self.bpy_object.parent_bone = self.node.bone_name
884
+ else:
885
+ if self.bpy_object.parent_type != "OBJECT":
886
+ self.bpy_object.parent_type = "OBJECT"
887
+ if self.bpy_object.parent_bone:
888
+ self.bpy_object.parent_bone = ""
889
+
890
+ if TYPE_CHECKING:
891
+ # This code is auto generated.
892
+ # To regenerate, run the `uv run tools/property_typing.py` command.
893
+ node: BonePropertyGroup # type: ignore[no-redef]
894
+ shape: SpringBone1ColliderShapePropertyGroup # type: ignore[no-redef]
895
+ extensions: SpringBone1ColliderExtensionsPropertyGroup # type: ignore[no-redef]
896
+ show_expanded: bool # type: ignore[no-redef]
897
+ shape_type: str # type: ignore[no-redef]
898
+ ui_collider_type: str # type: ignore[no-redef]
899
+ bpy_object: Optional[Object] # type: ignore[no-redef]
900
+ uuid: str # type: ignore[no-redef]
901
+
902
+
903
+ class SpringBone1ColliderReferencePropertyGroup(PropertyGroup):
904
+ @property
905
+ def collider_display_name(self) -> str:
906
+ armature = self.id_data
907
+ if not isinstance(armature, Armature):
908
+ return ""
909
+ if not self.collider_uuid:
910
+ return ""
911
+
912
+ spring_bone = get_armature_extension(armature).spring_bone1
913
+ for collider in spring_bone.colliders:
914
+ if collider.uuid == self.collider_uuid:
915
+ return collider.display_name
916
+ return ""
917
+
918
+ def _update_collider_uuid(self, _context: Context) -> None:
919
+ if not self.collider_uuid:
920
+ return
921
+ armature_data = self.id_data
922
+ if not isinstance(armature_data, Armature):
923
+ return
924
+ spring_bone1 = get_armature_extension(armature_data).spring_bone1
925
+ for collider in spring_bone1.colliders:
926
+ if self.collider_uuid == collider.uuid:
927
+ return
928
+ self.collider_uuid = ""
929
+
930
+ collider_uuid: StringProperty(update=_update_collider_uuid) # type: ignore[valid-type]
931
+
932
+ if TYPE_CHECKING:
933
+ # This code is auto generated.
934
+ # To regenerate, run the `uv run tools/property_typing.py` command.
935
+ collider_uuid: str # type: ignore[no-redef]
936
+
937
+
938
+ # https://github.com/vrm-c/vrm-specification/blob/f2d8f158297fc883aef9c3071ca68fbe46b03f45/specification/0.0/schema/vrm.secondaryanimation.collidergroup.schema.json
939
+ class SpringBone1ColliderGroupPropertyGroup(PropertyGroup):
940
+ def add_collider(self) -> SpringBone1ColliderReferencePropertyGroup:
941
+ collider = self.colliders.add()
942
+ self.active_collider_index = len(self.colliders) - 1
943
+ return collider
944
+
945
+ def _get_vrm_name(self) -> str:
946
+ value = self.get("vrm_name", "")
947
+ return value if isinstance(value, str) else str(value)
948
+
949
+ def _set_vrm_name(self, vrm_name: object) -> None:
950
+ if not isinstance(vrm_name, str):
951
+ vrm_name = str(vrm_name)
952
+ self["vrm_name"] = vrm_name
953
+
954
+ vrm_name: StringProperty( # type: ignore[valid-type]
955
+ name="Name",
956
+ get=_get_vrm_name,
957
+ set=_set_vrm_name,
958
+ )
959
+
960
+ colliders: CollectionProperty( # type: ignore[valid-type]
961
+ name="Colliders",
962
+ type=SpringBone1ColliderReferencePropertyGroup,
963
+ )
964
+
965
+ def fixup(self) -> None:
966
+ if not isinstance(armature_data := self.id_data, Armature):
967
+ return
968
+ spring_bone1 = get_armature_extension(armature_data).spring_bone1
969
+ collider_uuids = {
970
+ collider.uuid for collider in spring_bone1.colliders if collider.uuid
971
+ }
972
+ for collider_reference in self.colliders:
973
+ collider_uuid = collider_reference.collider_uuid
974
+ if not collider_uuid:
975
+ continue
976
+ if collider_uuid in collider_uuids:
977
+ collider_uuids.remove(collider_uuid)
978
+ continue
979
+ _logger.error(
980
+ 'Collider with uuid "%s" not found or duplicated for'
981
+ ' "%s". Clearing UUID.',
982
+ collider_uuid,
983
+ collider_reference.path_from_id(),
984
+ )
985
+ collider_reference.collider_uuid = ""
986
+
987
+ # for UI
988
+ show_expanded: BoolProperty() # type: ignore[valid-type]
989
+ active_collider_index: IntProperty(min=0) # type: ignore[valid-type]
990
+
991
+ # for reference
992
+ # When copying objects, there may be multiple objects with the same uuid
993
+ uuid: StringProperty() # type: ignore[valid-type]
994
+
995
+ if TYPE_CHECKING:
996
+ # This code is auto generated.
997
+ # To regenerate, run the `uv run tools/property_typing.py` command.
998
+ vrm_name: str # type: ignore[no-redef]
999
+ colliders: CollectionPropertyProtocol[ # type: ignore[no-redef]
1000
+ SpringBone1ColliderReferencePropertyGroup
1001
+ ]
1002
+ show_expanded: bool # type: ignore[no-redef]
1003
+ active_collider_index: int # type: ignore[no-redef]
1004
+ uuid: str # type: ignore[no-redef]
1005
+
1006
+
1007
+ # https://github.com/vrm-c/vrm-specification/tree/993a90a5bda9025f3d9e2923ad6dea7506f88553/specification/VRMC_springBone-1.0#initialization
1008
+ class SpringBone1JointAnimationStatePropertyGroup(PropertyGroup):
1009
+ initialized_as_tail: BoolProperty() # type: ignore[valid-type]
1010
+
1011
+ previous_world_translation: FloatVectorProperty(size=3) # type: ignore[valid-type]
1012
+ current_world_translation: FloatVectorProperty(size=3) # type: ignore[valid-type]
1013
+
1014
+ if TYPE_CHECKING:
1015
+ # This code is auto generated.
1016
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1017
+ initialized_as_tail: bool # type: ignore[no-redef]
1018
+ previous_world_translation: Sequence[float] # type: ignore[no-redef]
1019
+ current_world_translation: Sequence[float] # type: ignore[no-redef]
1020
+
1021
+
1022
+ class SpringBone1VrmcSpringBoneLimitPropertyGroup(PropertyGroup):
1023
+ (
1024
+ limit_type_enum,
1025
+ (
1026
+ LIMIT_TYPE_NONE,
1027
+ LIMIT_TYPE_CONE,
1028
+ LIMIT_TYPE_HINGE,
1029
+ LIMIT_TYPE_SPHERICAL,
1030
+ ),
1031
+ ) = property_group_enum(
1032
+ ("NONE", "None", "Don't apply an angle limit", "NONE", 0),
1033
+ ("CONE", "Cone", "Restrict the joint direction to a cone", "CONE", 1),
1034
+ (
1035
+ "HINGE",
1036
+ "Hinge",
1037
+ "Restrict the joint direction to a hinge plane",
1038
+ "CON_ROTLIKE",
1039
+ 2,
1040
+ ),
1041
+ (
1042
+ "SPHERICAL",
1043
+ "Spherical",
1044
+ "Restrict the joint direction by pitch and yaw",
1045
+ "SPHERE",
1046
+ 3,
1047
+ ),
1048
+ )
1049
+
1050
+ limit_type: EnumProperty( # type: ignore[valid-type]
1051
+ items=limit_type_enum.items(),
1052
+ name="Limit Type",
1053
+ )
1054
+ cone_angle: FloatProperty( # type: ignore[valid-type]
1055
+ name="Angle",
1056
+ subtype="ANGLE",
1057
+ min=0.0,
1058
+ max=math.pi,
1059
+ default=math.pi,
1060
+ )
1061
+ hinge_angle: FloatProperty( # type: ignore[valid-type]
1062
+ name="Angle",
1063
+ subtype="ANGLE",
1064
+ min=0.0,
1065
+ max=math.pi,
1066
+ default=math.pi,
1067
+ )
1068
+ spherical_pitch: FloatProperty( # type: ignore[valid-type]
1069
+ name="Pitch",
1070
+ subtype="ANGLE",
1071
+ min=0.0,
1072
+ max=math.pi,
1073
+ default=math.pi,
1074
+ )
1075
+ spherical_yaw: FloatProperty( # type: ignore[valid-type]
1076
+ name="Yaw",
1077
+ subtype="ANGLE",
1078
+ min=0.0,
1079
+ max=math.pi / 2.0,
1080
+ default=math.pi / 2.0,
1081
+ )
1082
+ rotation: FloatVectorProperty( # type: ignore[valid-type]
1083
+ name="Rotation",
1084
+ description="Relative rotation as a W, X, Y, Z quaternion",
1085
+ size=4,
1086
+ subtype="QUATERNION",
1087
+ min=-1.0,
1088
+ max=1.0,
1089
+ default=(1.0, 0.0, 0.0, 0.0),
1090
+ )
1091
+
1092
+ @staticmethod
1093
+ def default_rotation(bone_axis: Vector) -> Quaternion:
1094
+ bone_axis = bone_axis.normalized()
1095
+ dot = max(-1.0, min(1.0, bone_axis.y))
1096
+ if dot < -1.0 + float_info.epsilon:
1097
+ return Quaternion((0.0, 1.0, 0.0, 0.0))
1098
+ return Quaternion((dot + 1.0, bone_axis.z, 0.0, -bone_axis.x)).normalized()
1099
+
1100
+ def rotation_quaternion(self) -> Quaternion:
1101
+ rotation = Quaternion(self.rotation)
1102
+ if sum(component * component for component in rotation) < float_info.epsilon:
1103
+ return Quaternion()
1104
+ rotation.normalize()
1105
+ return rotation
1106
+
1107
+ if TYPE_CHECKING:
1108
+ limit_type: str # type: ignore[no-redef]
1109
+ cone_angle: float # type: ignore[no-redef]
1110
+ hinge_angle: float # type: ignore[no-redef]
1111
+ spherical_pitch: float # type: ignore[no-redef]
1112
+ spherical_yaw: float # type: ignore[no-redef]
1113
+ rotation: Sequence[float] # type: ignore[no-redef]
1114
+
1115
+
1116
+ # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.joint.schema.json
1117
+ class SpringBone1JointPropertyGroup(PropertyGroup):
1118
+ node: PointerProperty( # type: ignore[valid-type]
1119
+ type=BonePropertyGroup,
1120
+ name="Bone",
1121
+ )
1122
+
1123
+ hit_radius: FloatProperty( # type: ignore[valid-type]
1124
+ name="Joint Radius",
1125
+ min=0.0,
1126
+ default=0.0,
1127
+ soft_max=0.5,
1128
+ )
1129
+
1130
+ stiffness: FloatProperty( # type: ignore[valid-type]
1131
+ name="Stiffness Force",
1132
+ min=0.0,
1133
+ default=1.0,
1134
+ soft_max=4.0,
1135
+ )
1136
+
1137
+ gravity_power: FloatProperty( # type: ignore[valid-type]
1138
+ name="Gravity Power",
1139
+ min=0.0,
1140
+ default=0.0,
1141
+ soft_max=2.0,
1142
+ )
1143
+
1144
+ def _update_gravity_dir(self, _context: Context) -> None:
1145
+ gravity_dir = Vector(self.gravity_dir)
1146
+ normalized_gravity_dir = gravity_dir.normalized()
1147
+ if abs(normalized_gravity_dir.length) < float_info.epsilon:
1148
+ self.gravity_dir = (0, 0, -1)
1149
+ elif (gravity_dir - normalized_gravity_dir).length > 0.0001:
1150
+ self.gravity_dir = normalized_gravity_dir
1151
+
1152
+ gravity_dir: FloatVectorProperty( # type: ignore[valid-type]
1153
+ name="Gravity Dir",
1154
+ size=3,
1155
+ min=-1,
1156
+ max=1,
1157
+ default=(0, 0, -1),
1158
+ subtype="XYZ",
1159
+ update=_update_gravity_dir,
1160
+ )
1161
+
1162
+ drag_force: FloatProperty( # type: ignore[valid-type]
1163
+ name="Drag Force",
1164
+ default=0.5,
1165
+ min=0,
1166
+ max=1.0,
1167
+ )
1168
+
1169
+ animation_state: PointerProperty( # type: ignore[valid-type]
1170
+ type=SpringBone1JointAnimationStatePropertyGroup,
1171
+ )
1172
+
1173
+ vrmc_spring_bone_limit: PointerProperty( # type: ignore[valid-type]
1174
+ type=SpringBone1VrmcSpringBoneLimitPropertyGroup,
1175
+ )
1176
+
1177
+ # for UI
1178
+ show_expanded: BoolProperty() # type: ignore[valid-type]
1179
+
1180
+ if TYPE_CHECKING:
1181
+ # This code is auto generated.
1182
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1183
+ node: BonePropertyGroup # type: ignore[no-redef]
1184
+ hit_radius: float # type: ignore[no-redef]
1185
+ stiffness: float # type: ignore[no-redef]
1186
+ gravity_power: float # type: ignore[no-redef]
1187
+ gravity_dir: Sequence[float] # type: ignore[no-redef]
1188
+ drag_force: float # type: ignore[no-redef]
1189
+ animation_state: ( # type: ignore[no-redef]
1190
+ SpringBone1JointAnimationStatePropertyGroup
1191
+ )
1192
+ vrmc_spring_bone_limit: ( # type: ignore[no-redef]
1193
+ SpringBone1VrmcSpringBoneLimitPropertyGroup
1194
+ )
1195
+ show_expanded: bool # type: ignore[no-redef]
1196
+
1197
+
1198
+ class SpringBone1ColliderGroupReferencePropertyGroup(PropertyGroup):
1199
+ @property
1200
+ def collider_group_display_name(self) -> str:
1201
+ armature = self.id_data
1202
+ if not isinstance(armature, Armature):
1203
+ return ""
1204
+ if not self.collider_group_uuid:
1205
+ return ""
1206
+
1207
+ spring_bone = get_armature_extension(armature).spring_bone1
1208
+ for collider_group in spring_bone.collider_groups:
1209
+ if collider_group.uuid == self.collider_group_uuid:
1210
+ return collider_group.vrm_name
1211
+ return ""
1212
+
1213
+ def _update_collider_group_uuid(self, _context: Context) -> None:
1214
+ if not self.collider_group_uuid:
1215
+ return
1216
+ armature = self.id_data
1217
+ if not isinstance(armature, Armature):
1218
+ return
1219
+
1220
+ spring_bone = get_armature_extension(armature).spring_bone1
1221
+ for collider_group in spring_bone.collider_groups:
1222
+ if self.collider_group_uuid == collider_group.uuid:
1223
+ return
1224
+
1225
+ self.collider_group_uuid = ""
1226
+
1227
+ collider_group_uuid: StringProperty(update=_update_collider_group_uuid) # type: ignore[valid-type]
1228
+
1229
+ if TYPE_CHECKING:
1230
+ # This code is auto generated.
1231
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1232
+ collider_group_uuid: str # type: ignore[no-redef]
1233
+
1234
+
1235
+ class SpringBone1SpringAnimationStatePropertyGroup(PropertyGroup):
1236
+ use_center_space: BoolProperty() # type: ignore[valid-type]
1237
+
1238
+ previous_center_world_translation: FloatVectorProperty(size=3) # type: ignore[valid-type]
1239
+
1240
+ if TYPE_CHECKING:
1241
+ # This code is auto generated.
1242
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1243
+ use_center_space: bool # type: ignore[no-redef]
1244
+ previous_center_world_translation: Sequence[float] # type: ignore[no-redef]
1245
+
1246
+
1247
+ # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.spring.schema.json
1248
+ class SpringBone1SpringPropertyGroup(PropertyGroup):
1249
+ def add_joint(self) -> "SpringBone1JointPropertyGroup":
1250
+ joint = self.joints.add()
1251
+ self.active_joint_index = len(self.joints) - 1
1252
+ return joint
1253
+
1254
+ def add_collider_group(self) -> "SpringBone1ColliderGroupReferencePropertyGroup":
1255
+ collider_group = self.collider_groups.add()
1256
+ self.active_collider_group_index = len(self.collider_groups) - 1
1257
+ return collider_group
1258
+
1259
+ vrm_name: StringProperty( # type: ignore[valid-type]
1260
+ name="Name"
1261
+ )
1262
+ joints: CollectionProperty( # type: ignore[valid-type]
1263
+ type=SpringBone1JointPropertyGroup
1264
+ )
1265
+ collider_groups: CollectionProperty( # type: ignore[valid-type]
1266
+ type=SpringBone1ColliderGroupReferencePropertyGroup,
1267
+ )
1268
+ center: PointerProperty( # type: ignore[valid-type]
1269
+ type=BonePropertyGroup,
1270
+ )
1271
+
1272
+ def fixup(self) -> None:
1273
+ if not isinstance(armature_data := self.id_data, Armature):
1274
+ return
1275
+ spring_bone1 = get_armature_extension(armature_data).spring_bone1
1276
+ collider_group_uuids = {
1277
+ collider_group.uuid
1278
+ for collider_group in spring_bone1.collider_groups
1279
+ if collider_group.uuid
1280
+ }
1281
+ for collider_group_reference in self.collider_groups:
1282
+ collider_group_uuid = collider_group_reference.collider_group_uuid
1283
+ if not collider_group_uuid:
1284
+ continue
1285
+ if collider_group_uuid in collider_group_uuids:
1286
+ collider_group_uuids.remove(collider_group_uuid)
1287
+ continue
1288
+ _logger.error(
1289
+ 'Collider group with uuid "%s" not found or duplicated for'
1290
+ ' "%s". Clearing UUID.',
1291
+ collider_group_uuid,
1292
+ collider_group_reference.path_from_id(),
1293
+ )
1294
+ collider_group_reference.collider_group_uuid = ""
1295
+
1296
+ # for UI
1297
+ show_expanded: BoolProperty() # type: ignore[valid-type]
1298
+ show_expanded_bones: BoolProperty( # type: ignore[valid-type]
1299
+ name="Bones"
1300
+ )
1301
+ show_expanded_collider_groups: BoolProperty( # type: ignore[valid-type]
1302
+ name="Collider Groups"
1303
+ )
1304
+
1305
+ active_joint_index: IntProperty(min=0) # type: ignore[valid-type]
1306
+ active_collider_group_index: IntProperty(min=0) # type: ignore[valid-type]
1307
+
1308
+ animation_state: PointerProperty( # type: ignore[valid-type]
1309
+ type=SpringBone1SpringAnimationStatePropertyGroup
1310
+ )
1311
+
1312
+ if TYPE_CHECKING:
1313
+ # This code is auto generated.
1314
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1315
+ vrm_name: str # type: ignore[no-redef]
1316
+ joints: CollectionPropertyProtocol[ # type: ignore[no-redef]
1317
+ SpringBone1JointPropertyGroup
1318
+ ]
1319
+ collider_groups: CollectionPropertyProtocol[ # type: ignore[no-redef]
1320
+ SpringBone1ColliderGroupReferencePropertyGroup
1321
+ ]
1322
+ center: BonePropertyGroup # type: ignore[no-redef]
1323
+ show_expanded: bool # type: ignore[no-redef]
1324
+ show_expanded_bones: bool # type: ignore[no-redef]
1325
+ show_expanded_collider_groups: bool # type: ignore[no-redef]
1326
+ active_joint_index: int # type: ignore[no-redef]
1327
+ active_collider_group_index: int # type: ignore[no-redef]
1328
+ animation_state: ( # type: ignore[no-redef]
1329
+ SpringBone1SpringAnimationStatePropertyGroup
1330
+ )
1331
+
1332
+
1333
+ # https://github.com/vrm-c/vrm-specification/blob/6fb6baaf9b9095a84fb82c8384db36e1afeb3558/specification/VRMC_springBone-1.0-beta/schema/VRMC_springBone.schema.json
1334
+ class SpringBone1SpringBonePropertyGroup(PropertyGroup):
1335
+ def add_collider(
1336
+ self, context: Context, armature: Object
1337
+ ) -> SpringBone1ColliderPropertyGroup:
1338
+ collider = self.colliders.add()
1339
+ collider.uuid = uuid.uuid4().hex
1340
+ collider.shape.sphere.radius = 0.125
1341
+ collider.reset_bpy_object(context, armature)
1342
+ self.active_collider_index = len(self.colliders) - 1
1343
+ return collider
1344
+
1345
+ def add_collider_group(self) -> SpringBone1ColliderGroupPropertyGroup:
1346
+ collider_group = self.collider_groups.add()
1347
+ collider_group.uuid = uuid.uuid4().hex
1348
+ collider_group.vrm_name = "Collider Group"
1349
+ self.active_collider_group_index = len(self.collider_groups) - 1
1350
+ return collider_group
1351
+
1352
+ def add_spring(self) -> SpringBone1SpringPropertyGroup:
1353
+ spring = self.springs.add()
1354
+ spring.vrm_name = "Spring"
1355
+ self.active_spring_index = len(self.springs) - 1
1356
+ return spring
1357
+
1358
+ colliders: CollectionProperty( # type: ignore[valid-type]
1359
+ type=SpringBone1ColliderPropertyGroup,
1360
+ )
1361
+ collider_groups: CollectionProperty( # type: ignore[valid-type]
1362
+ type=SpringBone1ColliderGroupPropertyGroup,
1363
+ )
1364
+ springs: CollectionProperty( # type: ignore[valid-type]
1365
+ type=SpringBone1SpringPropertyGroup,
1366
+ )
1367
+
1368
+ def _update_enable_animation(self, _context: Context) -> None:
1369
+ for spring in self.springs:
1370
+ for joint in spring.joints:
1371
+ joint.animation_state.initialized_as_tail = False
1372
+
1373
+ enable_animation: BoolProperty( # type: ignore[valid-type]
1374
+ name="Enable Animation",
1375
+ update=_update_enable_animation,
1376
+ )
1377
+
1378
+ def fixup(self) -> None:
1379
+ found_collider_uuids = set[str]()
1380
+ for collider in self.colliders:
1381
+ if not collider.uuid:
1382
+ new_uuid = uuid.uuid4().hex
1383
+ _logger.error(
1384
+ "Collider without uuid found. Generating a new uuid %s.", new_uuid
1385
+ )
1386
+ collider.uuid = new_uuid
1387
+ if collider.uuid in found_collider_uuids:
1388
+ new_uuid = uuid.uuid4().hex
1389
+ _logger.error(
1390
+ "Collider with duplicated uuid found. Generating a new uuid %s.",
1391
+ new_uuid,
1392
+ )
1393
+ collider.uuid = new_uuid
1394
+ found_collider_uuids.add(collider.uuid)
1395
+
1396
+ found_collider_group_uuids = set[str]()
1397
+ for collider_group in self.collider_groups:
1398
+ if not collider_group.uuid:
1399
+ new_uuid = uuid.uuid4().hex
1400
+ _logger.error(
1401
+ "Collider group without uuid found. Generating a new uuid %s.",
1402
+ new_uuid,
1403
+ )
1404
+ collider_group.uuid = new_uuid
1405
+ if collider_group.uuid in found_collider_group_uuids:
1406
+ new_uuid = uuid.uuid4().hex
1407
+ _logger.error(
1408
+ "Collider group with duplicated uuid found."
1409
+ " Generating a new uuid %s.",
1410
+ new_uuid,
1411
+ )
1412
+ collider_group.uuid = new_uuid
1413
+ found_collider_group_uuids.add(collider_group.uuid)
1414
+ collider_group.fixup()
1415
+
1416
+ for spring in self.springs:
1417
+ spring.fixup()
1418
+
1419
+ initial_automatic_spring_bone_assignment: BoolProperty( # type: ignore[valid-type]
1420
+ default=True,
1421
+ )
1422
+
1423
+ # for UI
1424
+ show_expanded_colliders: BoolProperty( # type: ignore[valid-type]
1425
+ name="Spring Bone Colliders"
1426
+ )
1427
+ show_expanded_collider_groups: BoolProperty( # type: ignore[valid-type]
1428
+ name="Spring Bone Collider Groups"
1429
+ )
1430
+ show_expanded_springs: BoolProperty( # type: ignore[valid-type]
1431
+ name="Spring Bone Springs"
1432
+ )
1433
+
1434
+ active_collider_index: IntProperty(min=0) # type: ignore[valid-type]
1435
+ active_collider_group_index: IntProperty(min=0) # type: ignore[valid-type]
1436
+ active_spring_index: IntProperty(min=0) # type: ignore[valid-type]
1437
+
1438
+ if TYPE_CHECKING:
1439
+ # This code is auto generated.
1440
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1441
+ colliders: CollectionPropertyProtocol[ # type: ignore[no-redef]
1442
+ SpringBone1ColliderPropertyGroup
1443
+ ]
1444
+ collider_groups: CollectionPropertyProtocol[ # type: ignore[no-redef]
1445
+ SpringBone1ColliderGroupPropertyGroup
1446
+ ]
1447
+ springs: CollectionPropertyProtocol[ # type: ignore[no-redef]
1448
+ SpringBone1SpringPropertyGroup
1449
+ ]
1450
+ enable_animation: bool # type: ignore[no-redef]
1451
+ initial_automatic_spring_bone_assignment: bool # type: ignore[no-redef]
1452
+ show_expanded_colliders: bool # type: ignore[no-redef]
1453
+ show_expanded_collider_groups: bool # type: ignore[no-redef]
1454
+ show_expanded_springs: bool # type: ignore[no-redef]
1455
+ active_collider_index: int # type: ignore[no-redef]
1456
+ active_collider_group_index: int # type: ignore[no-redef]
1457
+ active_spring_index: int # type: ignore[no-redef]