@volter/blender-engine 0.1.148 → 0.1.150

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 +22 -5
  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 +66 -129
  185. package/browser/blender-engine.mts +20 -186
  186. package/browser/blender-wali-engine.mts +37 -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 +3 -0
  192. package/browser/protocol.ts +130 -43
  193. package/browser/pull-job.mts +2 -10
  194. package/browser/rna.ts +190 -73
  195. package/browser/runtime.ts +271 -106
  196. package/browser/session-addons.mts +33 -0
  197. package/browser/session-frame.mts +5 -24
  198. package/browser/session.py +1389 -1446
  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 +492 -306
  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 +195 -180
  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,381 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import sys
3
+ from dataclasses import dataclass, field
4
+ from typing import Final, Optional
5
+
6
+ import bpy
7
+ from bpy.types import Context, Material, Mesh, ShaderNodeGroup, ShaderNodeOutputMaterial
8
+
9
+ from ...common.logger import get_logger
10
+ from ...common.scene_watcher import RunState, SceneWatcher
11
+ from ...common.shader import MTOON1_AUTO_SETUP_GROUP_NODE_TREE_CUSTOM_KEY
12
+ from ..extension_accessor import get_material_extension
13
+ from .property_group import (
14
+ generate_mtoon1_outline_material_name,
15
+ refresh_mtoon1_outline,
16
+ reset_shader_node_group,
17
+ )
18
+
19
+ _logger = get_logger(__name__)
20
+
21
+
22
+ HAS_AUTO_SMOOTH: Final = tuple(bpy.app.version) < (4, 1)
23
+
24
+
25
+ @dataclass
26
+ class ComparisonMaterial:
27
+ name: str
28
+
29
+
30
+ @dataclass
31
+ class ComparisonObject:
32
+ use_auto_smooth: Optional[bool] = None
33
+ comparison_materials: Final[list[Optional[ComparisonMaterial]]] = field(
34
+ default_factory=list[Optional[ComparisonMaterial]]
35
+ )
36
+
37
+
38
+ @dataclass
39
+ class OutlineUpdater(SceneWatcher):
40
+ comparison_objects: Final[list[ComparisonObject]] = field(
41
+ default_factory=list[ComparisonObject]
42
+ )
43
+ outline_material_key_to_material_name: Final[dict[int, str]] = field(
44
+ default_factory=dict[int, str]
45
+ )
46
+
47
+ object_index: int = 0
48
+ comparison_object_index: int = 0
49
+ material_slot_index: int = 0
50
+
51
+ @staticmethod
52
+ def get_outline_material_key(material: Material) -> int:
53
+ return material.as_pointer() ^ 0x5F5FF5F5
54
+
55
+ def reset_run_progress(self) -> None:
56
+ self.object_index = 0
57
+ self.comparison_object_index = 0
58
+ self.material_slot_index = 0
59
+ self.outline_material_key_to_material_name.clear()
60
+
61
+ def run(self, context: Context) -> RunState:
62
+ """Detect changes in material assignments to objects and assign outlines."""
63
+ # If this value becomes zero, return PREEMPT and interrupt the process.
64
+ # If a change is detected, set a virtually infinite value so that the
65
+ # process proceeds to the end.
66
+ preempt_countdown = 15
67
+
68
+ changed = False
69
+
70
+ create_modifier = False
71
+
72
+ objects = context.visible_objects
73
+ if not objects:
74
+ return RunState.FINISH
75
+
76
+ # If the number of objects is less than the previous state and the index
77
+ # range is exceeded, start over from the beginning
78
+ objects_len = len(objects)
79
+ if self.object_index >= objects_len:
80
+ self.object_index = 0
81
+
82
+ # Scan objects and check for differences with the comparison object
83
+ next_object_index = self.object_index
84
+ for obj in objects[self.object_index : objects_len]:
85
+ self.object_index = next_object_index
86
+ next_object_index += 1
87
+
88
+ preempt_countdown -= 1
89
+ if preempt_countdown <= 0:
90
+ return RunState.PREEMPT
91
+
92
+ # Only mesh objects are subject to investigation.
93
+ # Skip if it is not a mesh object
94
+ obj_data = obj.data
95
+ if not isinstance(obj_data, Mesh):
96
+ continue
97
+ mesh = obj_data
98
+
99
+ # If the number of comparison objects is insufficient, add a new
100
+ # comparison object
101
+ while self.comparison_object_index >= len(self.comparison_objects):
102
+ self.comparison_objects.append(ComparisonObject())
103
+
104
+ # Get a comparison object
105
+ comparison_object = self.comparison_objects[self.comparison_object_index]
106
+
107
+ # Comparison of use_auto_smooth
108
+ if HAS_AUTO_SMOOTH and (
109
+ (use_auto_smooth := comparison_object.use_auto_smooth) is None
110
+ or (use_auto_smooth != mesh.use_auto_smooth)
111
+ ):
112
+ changed, preempt_countdown = True, sys.maxsize
113
+ # Resolve change differences
114
+ comparison_object.use_auto_smooth = mesh.use_auto_smooth
115
+
116
+ # If the number of MaterialSlots is less than the previous state and
117
+ # the index range is exceeded, start over from the beginning
118
+ material_slots = obj.material_slots
119
+ material_slots_len = len(material_slots)
120
+ if self.material_slot_index >= material_slots_len:
121
+ self.material_slot_index = 0
122
+
123
+ # Match the number of MaterialSlots and the number of comparison Materials
124
+ while material_slots_len > len(comparison_object.comparison_materials):
125
+ comparison_object.comparison_materials.append(None)
126
+ while material_slots_len < len(comparison_object.comparison_materials):
127
+ comparison_object.comparison_materials.pop()
128
+
129
+ # Scan MaterialSlots and check for differences with the comparison Material
130
+ next_material_slot_index = self.material_slot_index
131
+ for material_slot in material_slots[
132
+ self.material_slot_index : material_slots_len
133
+ ]:
134
+ material_slot_index = self.material_slot_index = (
135
+ next_material_slot_index
136
+ )
137
+ next_material_slot_index += 1
138
+
139
+ preempt_countdown -= 1
140
+ if preempt_countdown <= 0:
141
+ return RunState.PREEMPT
142
+
143
+ # If the number of comparison objects is insufficient, add a new
144
+ # comparison object
145
+ while material_slot_index >= len(
146
+ comparison_object.comparison_materials
147
+ ):
148
+ comparison_object.comparison_materials.append(None)
149
+ comparison_material = comparison_object.comparison_materials[
150
+ material_slot_index
151
+ ]
152
+
153
+ # Difference check
154
+ if not (
155
+ (material := material_slot.material)
156
+ and (mtoon1 := get_material_extension(material).mtoon1)
157
+ and mtoon1.enabled
158
+ ):
159
+ # MToon of the material in the material slot is disabled
160
+ if comparison_material is None:
161
+ # No updates
162
+ continue
163
+ # if the comparison object is enabled, a change is detected
164
+ changed, preempt_countdown = True, sys.maxsize
165
+ # Resolve change differences
166
+ comparison_object.comparison_materials[material_slot_index] = None
167
+ continue
168
+
169
+ # MToon of the material in the material slot is enabled
170
+
171
+ outline_material = mtoon1.outline_material
172
+ outline_material_name = generate_mtoon1_outline_material_name(material)
173
+
174
+ if (
175
+ comparison_material
176
+ and comparison_material.name == material.name
177
+ and (
178
+ outline_material is None
179
+ or outline_material.name == outline_material_name
180
+ )
181
+ ):
182
+ continue
183
+
184
+ # MToon of the material in the material slot is enabled,
185
+ # but if the comparison object does not exist or the name
186
+ # does not match, a change is detected
187
+ changed, preempt_countdown = True, sys.maxsize
188
+ # Resolve change differences
189
+ comparison_object.comparison_materials[material_slot_index] = (
190
+ ComparisonMaterial(material.name)
191
+ )
192
+ # A material with MToon enabled has been newly assigned
193
+ # to the object so, if necessary, create a new outline
194
+ # modifier.
195
+ # Originally, True/False should be set for each object and
196
+ # material pair, but for now, I think it's okay to fix it
197
+ # for practical use.
198
+ create_modifier = True
199
+
200
+ if outline_material is None:
201
+ continue
202
+
203
+ # When a material is copied, a single outline material may be
204
+ # shared by multiple MToon materials. To resolve this, copy the
205
+ # outline material and reassign it.
206
+ outline_material_key = self.get_outline_material_key(outline_material)
207
+ original_material_name = self.outline_material_key_to_material_name.get(
208
+ outline_material_key
209
+ )
210
+ if original_material_name is None:
211
+ self.outline_material_key_to_material_name[outline_material_key] = (
212
+ material.name
213
+ )
214
+ elif original_material_name != material.name:
215
+ outline_material = outline_material.copy()
216
+ outline_material_key = self.get_outline_material_key(
217
+ outline_material
218
+ )
219
+ self.outline_material_key_to_material_name[outline_material_key] = (
220
+ material.name
221
+ )
222
+ mtoon1.outline_material = outline_material
223
+
224
+ # The outline material name follows the naming changes of
225
+ # the base MToon material.
226
+ if outline_material.name != outline_material_name:
227
+ outline_material.name = outline_material_name
228
+
229
+ # Since the scanning of MaterialSlots is complete,
230
+ # reset the next scanning index to 0.
231
+ self.material_slot_index = 0
232
+
233
+ # Before scanning the next object,
234
+ # advance the index of the next comparison object.
235
+ self.comparison_object_index += 1
236
+
237
+ # Since the number of elements in self.comparison_objects may be
238
+ # unnecessarily large, reduce it to a sufficient size
239
+ while len(self.comparison_objects) > self.comparison_object_index:
240
+ self.comparison_objects.pop()
241
+
242
+ if not changed:
243
+ return RunState.FINISH
244
+
245
+ refresh_mtoon1_outline(context, create_modifier=create_modifier)
246
+ return RunState.FINISH
247
+
248
+ def create_fast_path_performance_test_objects(self, context: Context) -> None:
249
+ blend_data = context.blend_data
250
+
251
+ for i in range(100):
252
+ blend_data.materials.new(f"Material#{i}")
253
+
254
+ for i in range(100):
255
+ mesh = blend_data.meshes.new(f"Mesh#{i}")
256
+ obj = blend_data.objects.new(f"Object#{i}", mesh)
257
+ context.scene.collection.objects.link(obj)
258
+ for k in range(50):
259
+ material = blend_data.materials[(k * 3) % len(blend_data.materials)]
260
+ mesh.materials.append(material)
261
+ if k % 5 == 0:
262
+ get_material_extension(material).mtoon1.enabled = True
263
+
264
+
265
+ @dataclass
266
+ class MToon1AutoSetup(SceneWatcher):
267
+ last_material_index: int = 0
268
+ last_node_index: int = 0
269
+
270
+ def reset_run_progress(self) -> None:
271
+ self.last_material_index: int = 0
272
+ self.last_node_index: int = 0
273
+
274
+ def run(self, context: Context) -> RunState:
275
+ """Monitor the appearance of MToon auto-setup node groups and set them up.
276
+
277
+ Since this function is called frequently, keep the processing lightweight and
278
+ be careful to minimize IO and GC Allocation.
279
+ """
280
+ # If this value becomes 0 or less, interrupt the process
281
+ search_preempt_countdown = 100
282
+
283
+ materials = context.blend_data.materials
284
+
285
+ # Restore the material traversal start position from the last interrupted state.
286
+ end_material_index = len(materials)
287
+ start_material_index = self.last_material_index
288
+ if start_material_index >= end_material_index:
289
+ self.last_material_index = 0
290
+ self.last_node_index = 0
291
+ start_material_index = 0
292
+
293
+ # Traverse the materials and enable MToon if necessary.
294
+ next_material_index = start_material_index
295
+ for material in materials[start_material_index:end_material_index]:
296
+ self.last_material_index = next_material_index
297
+ next_material_index += 1
298
+
299
+ search_preempt_countdown -= 1
300
+ if search_preempt_countdown <= 0:
301
+ return RunState.PREEMPT
302
+
303
+ if bpy.app.version < (5, 0, 0) and not material.use_nodes:
304
+ continue
305
+ node_tree = material.node_tree
306
+ if node_tree is None:
307
+ continue
308
+
309
+ nodes = node_tree.nodes
310
+
311
+ # Restore the node traversal start position from the last interrupted state.
312
+ end_node_index = len(nodes)
313
+ start_node_index = self.last_node_index
314
+ if start_node_index >= end_node_index:
315
+ start_node_index = 0
316
+
317
+ # Traverse the nodes and convert the material to MToon if the MToon
318
+ # placeholder node is connected to ShaderNodeOutputMaterial.
319
+ next_node_index = start_node_index
320
+ for node in nodes[start_node_index:end_node_index]:
321
+ self.last_node_index = next_node_index
322
+ next_node_index += 1
323
+
324
+ search_preempt_countdown -= 1
325
+ if search_preempt_countdown <= 0:
326
+ return RunState.PREEMPT
327
+
328
+ if not isinstance(node, ShaderNodeGroup):
329
+ continue
330
+
331
+ group_node_tree = node.node_tree
332
+ if group_node_tree is None:
333
+ continue
334
+
335
+ if not group_node_tree.get(
336
+ MTOON1_AUTO_SETUP_GROUP_NODE_TREE_CUSTOM_KEY
337
+ ):
338
+ continue
339
+
340
+ found = False
341
+ for output in node.outputs:
342
+ for link in output.links:
343
+ if isinstance(link.to_node, ShaderNodeOutputMaterial):
344
+ found = True
345
+ break
346
+ if found:
347
+ break
348
+ if not found:
349
+ continue
350
+
351
+ mtoon1 = get_material_extension(material).mtoon1
352
+ if mtoon1.enabled:
353
+ reset_shader_node_group(
354
+ context,
355
+ material,
356
+ reset_material_node_tree=True,
357
+ reset_node_groups=True,
358
+ )
359
+ else:
360
+ mtoon1.enabled = True
361
+ break
362
+ self.last_node_index = 0
363
+ self.last_material_index = 0
364
+
365
+ return RunState.FINISH
366
+
367
+ def create_fast_path_performance_test_objects(self, context: Context) -> None:
368
+ blend_data = context.blend_data
369
+
370
+ for i in range(100):
371
+ blend_data.materials.new(f"Material#{i}")
372
+
373
+ for i in range(100):
374
+ mesh = blend_data.meshes.new(f"Mesh#{i}")
375
+ obj = blend_data.objects.new(f"Object#{i}", mesh)
376
+ context.scene.collection.objects.link(obj)
377
+ for k in range(50):
378
+ material = blend_data.materials[(k * 3) % len(blend_data.materials)]
379
+ mesh.materials.append(material)
380
+ if k % 5 == 0:
381
+ get_material_extension(material).mtoon1.enabled = True
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
@@ -0,0 +1,317 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+
3
+ from typing import ClassVar
4
+
5
+ from bpy.app.translations import pgettext
6
+ from bpy.types import (
7
+ Armature,
8
+ Context,
9
+ CopyRotationConstraint,
10
+ DampedTrackConstraint,
11
+ Object,
12
+ Panel,
13
+ UILayout,
14
+ )
15
+
16
+ from ...common.preferences import get_preferences
17
+ from .. import search
18
+ from ..extension_accessor import get_armature_extension
19
+ from ..panel import VRM_PT_vrm_armature_object_property
20
+ from ..search import active_object_is_vrm1_armature
21
+ from .property_group import NodeConstraint1NodeConstraintPropertyGroup
22
+
23
+
24
+ def _draw_roll_constraint_layout(
25
+ layout: UILayout,
26
+ node_constraint: NodeConstraint1NodeConstraintPropertyGroup,
27
+ object_constraints: dict[str, CopyRotationConstraint],
28
+ bone_constraints: dict[str, CopyRotationConstraint],
29
+ ) -> None:
30
+ constraints_box = layout.box()
31
+ constraints_row = constraints_box.row()
32
+ constraints_row.alignment = "LEFT"
33
+ constraints_row.prop(
34
+ node_constraint,
35
+ "show_expanded_roll_constraints",
36
+ icon="TRIA_DOWN"
37
+ if node_constraint.show_expanded_roll_constraints
38
+ else "TRIA_RIGHT",
39
+ emboss=False,
40
+ )
41
+ if not node_constraint.show_expanded_roll_constraints:
42
+ return
43
+
44
+ constraints_column = constraints_box.column()
45
+ if object_constraints or bone_constraints:
46
+ constraints_expanded_column = constraints_column.column()
47
+ for object_name, constraint in object_constraints.items():
48
+ constraint_row = constraints_expanded_column.row()
49
+ constraint_row.alignment = "LEFT"
50
+ constraint_row.label(
51
+ text=object_name + ": " + constraint.name,
52
+ icon="CONSTRAINT",
53
+ translate=False,
54
+ )
55
+ for bone_name, constraint in bone_constraints.items():
56
+ constraint_row = constraints_expanded_column.row()
57
+ constraint_row.alignment = "LEFT"
58
+ constraint_row.label(
59
+ text=bone_name + ": " + constraint.name,
60
+ icon="CONSTRAINT_BONE",
61
+ translate=False,
62
+ )
63
+
64
+ constraints_help_column = constraints_column.box().column(align=True)
65
+ help_message = pgettext(
66
+ "Conditions exported as Roll Constraint\n"
67
+ + " - {copy_rotation}\n"
68
+ + " - Enabled\n"
69
+ + " - No Vertex Group\n"
70
+ + " - {axis} is one of X, Y and Z\n"
71
+ + " - No Inverted\n"
72
+ + " - {mix} is {add}\n"
73
+ + " - {target} is {local_space}\n"
74
+ + " - {owner} is {local_space}\n"
75
+ + " - No circular dependencies\n"
76
+ + " - The one at the top of the list of\n"
77
+ + " those that meet all the conditions\n",
78
+ ).format(
79
+ copy_rotation=pgettext("Copy Rotation"),
80
+ axis=pgettext("Axis"),
81
+ mix=pgettext("Mix"),
82
+ add=pgettext("Add"),
83
+ target=pgettext("Target"),
84
+ local_space=pgettext("Local Space"),
85
+ owner=pgettext("Owner"),
86
+ )
87
+ for index, help_line in enumerate(help_message.splitlines()):
88
+ constraints_help_column.label(
89
+ text=help_line, translate=False, icon="HELP" if index == 0 else "NONE"
90
+ )
91
+
92
+
93
+ def _draw_aim_constraint_layout(
94
+ layout: UILayout,
95
+ node_constraint: NodeConstraint1NodeConstraintPropertyGroup,
96
+ object_constraints: dict[str, DampedTrackConstraint],
97
+ bone_constraints: dict[str, DampedTrackConstraint],
98
+ ) -> None:
99
+ constraints_box = layout.box()
100
+ constraints_row = constraints_box.row()
101
+ constraints_row.alignment = "LEFT"
102
+ constraints_row.prop(
103
+ node_constraint,
104
+ "show_expanded_aim_constraints",
105
+ icon="TRIA_DOWN"
106
+ if node_constraint.show_expanded_aim_constraints
107
+ else "TRIA_RIGHT",
108
+ emboss=False,
109
+ )
110
+ if not node_constraint.show_expanded_aim_constraints:
111
+ return
112
+
113
+ constraints_column = constraints_box.column()
114
+ if object_constraints or bone_constraints:
115
+ constraints_expanded_column = constraints_column.column()
116
+ for object_name, constraint in object_constraints.items():
117
+ constraint_row = constraints_expanded_column.row()
118
+ constraint_row.alignment = "LEFT"
119
+ constraint_row.label(
120
+ text=object_name + ": " + constraint.name,
121
+ icon="CONSTRAINT",
122
+ translate=False,
123
+ )
124
+ for bone_name, constraint in bone_constraints.items():
125
+ constraint_row = constraints_expanded_column.row()
126
+ constraint_row.alignment = "LEFT"
127
+ constraint_row.label(
128
+ text=bone_name + ": " + constraint.name,
129
+ icon="CONSTRAINT_BONE",
130
+ translate=False,
131
+ )
132
+
133
+ constraints_help_column = constraints_column.box().column(align=True)
134
+ help_message = pgettext(
135
+ "Conditions exported as Aim Constraint\n"
136
+ + " - {damped_track}\n"
137
+ + " - Enabled\n"
138
+ + " - Target Bone {head_tail} is 0\n"
139
+ + " - No Follow Target Bone B-Bone\n"
140
+ + " - No circular dependencies\n"
141
+ + " - The one at the top of the list of\n"
142
+ + " those that meet all the conditions\n"
143
+ ).format(
144
+ damped_track=pgettext("Damped Track"),
145
+ head_tail=pgettext("Head/Tail"),
146
+ )
147
+ for index, help_line in enumerate(help_message.splitlines()):
148
+ constraints_help_column.label(
149
+ text=help_line, translate=False, icon="HELP" if index == 0 else "NONE"
150
+ )
151
+
152
+
153
+ def _draw_rotation_constraint_layout(
154
+ layout: UILayout,
155
+ node_constraint: NodeConstraint1NodeConstraintPropertyGroup,
156
+ object_constraints: dict[str, CopyRotationConstraint],
157
+ bone_constraints: dict[str, CopyRotationConstraint],
158
+ ) -> None:
159
+ constraints_box = layout.box()
160
+ constraints_row = constraints_box.row()
161
+ constraints_row.alignment = "LEFT"
162
+ constraints_row.prop(
163
+ node_constraint,
164
+ "show_expanded_rotation_constraints",
165
+ icon="TRIA_DOWN"
166
+ if node_constraint.show_expanded_rotation_constraints
167
+ else "TRIA_RIGHT",
168
+ emboss=False,
169
+ )
170
+ if not node_constraint.show_expanded_rotation_constraints:
171
+ return
172
+
173
+ constraints_column = constraints_box.column()
174
+ if object_constraints or bone_constraints:
175
+ constraints_expanded_column = constraints_column.column()
176
+ for object_name, constraint in object_constraints.items():
177
+ constraint_row = constraints_expanded_column.row()
178
+ constraint_row.alignment = "LEFT"
179
+ constraint_row.label(
180
+ text=object_name + ": " + constraint.name,
181
+ icon="CONSTRAINT",
182
+ translate=False,
183
+ )
184
+ for bone_name, constraint in bone_constraints.items():
185
+ constraint_row = constraints_expanded_column.row()
186
+ constraint_row.alignment = "LEFT"
187
+ constraint_row.label(
188
+ text=bone_name + ": " + constraint.name,
189
+ icon="CONSTRAINT_BONE",
190
+ translate=False,
191
+ )
192
+
193
+ constraints_help_column = constraints_column.box().column(align=True)
194
+ help_message = pgettext(
195
+ "Conditions exported as Rotation Constraint\n"
196
+ + " - {copy_rotation}\n"
197
+ + " - Enabled\n"
198
+ + " - No Vertex Group\n"
199
+ + " - {axis} is X, Y and Z\n"
200
+ + " - No Inverted\n"
201
+ + " - {mix} is {add}\n"
202
+ + " - {target} is {local_space}\n"
203
+ + " - {owner} is {local_space}\n"
204
+ + " - No circular dependencies\n"
205
+ + " - The one at the top of the list of\n"
206
+ + " those that meet all the conditions\n"
207
+ ).format(
208
+ copy_rotation=pgettext("Copy Rotation"),
209
+ axis=pgettext("Axis"),
210
+ mix=pgettext("Mix"),
211
+ add=pgettext("Add"),
212
+ target=pgettext("Target"),
213
+ local_space=pgettext("Local Space"),
214
+ owner=pgettext("Owner"),
215
+ )
216
+ for index, help_line in enumerate(help_message.splitlines()):
217
+ constraints_help_column.label(
218
+ text=help_line, translate=False, icon="HELP" if index == 0 else "NONE"
219
+ )
220
+
221
+
222
+ def _draw_node_constraint1_layout(
223
+ context: Context,
224
+ armature: Object,
225
+ layout: UILayout,
226
+ node_constraint: NodeConstraint1NodeConstraintPropertyGroup,
227
+ ) -> None:
228
+ preferences = get_preferences(context)
229
+ objs = search.export_objects(
230
+ context,
231
+ armature_object_name=armature.name,
232
+ export_invisibles=preferences.export_invisibles,
233
+ export_only_selections=False,
234
+ export_lights=False,
235
+ )
236
+ object_constraints, bone_constraints, _ = search.export_constraints(objs, armature)
237
+ _draw_roll_constraint_layout(
238
+ layout,
239
+ node_constraint,
240
+ object_constraints.roll_constraints,
241
+ bone_constraints.roll_constraints,
242
+ )
243
+ _draw_aim_constraint_layout(
244
+ layout,
245
+ node_constraint,
246
+ object_constraints.aim_constraints,
247
+ bone_constraints.aim_constraints,
248
+ )
249
+ _draw_rotation_constraint_layout(
250
+ layout,
251
+ node_constraint,
252
+ object_constraints.rotation_constraints,
253
+ bone_constraints.rotation_constraints,
254
+ )
255
+
256
+
257
+ class VRM_PT_node_constraint1_armature_object_property(Panel):
258
+ bl_idname = "VRM_PT_node_constraint1_armature_object_property"
259
+ bl_label = "Node Constraint"
260
+ bl_translation_context = "VRM"
261
+ bl_space_type = "PROPERTIES"
262
+ bl_region_type = "WINDOW"
263
+ bl_context = "object"
264
+ bl_options: ClassVar = {"DEFAULT_CLOSED"}
265
+ bl_parent_id = VRM_PT_vrm_armature_object_property.bl_idname
266
+
267
+ @classmethod
268
+ def poll(cls, context: Context) -> bool:
269
+ return active_object_is_vrm1_armature(context)
270
+
271
+ def draw_header(self, _context: Context) -> None:
272
+ self.layout.label(icon="CONSTRAINT")
273
+
274
+ def draw(self, context: Context) -> None:
275
+ active_object = context.active_object
276
+ if not active_object:
277
+ return
278
+ armature_data = active_object.data
279
+ if not isinstance(armature_data, Armature):
280
+ return
281
+ _draw_node_constraint1_layout(
282
+ context,
283
+ active_object,
284
+ self.layout,
285
+ get_armature_extension(armature_data).node_constraint1,
286
+ )
287
+
288
+
289
+ class VRM_PT_node_constraint1_ui(Panel):
290
+ bl_idname = "VRM_PT_node_constraint1_ui"
291
+ bl_label = "Node Constraint"
292
+ bl_translation_context = "VRM"
293
+ bl_space_type = "VIEW_3D"
294
+ bl_region_type = "UI"
295
+ bl_category = "VRM"
296
+ bl_options: ClassVar = {"DEFAULT_CLOSED"}
297
+
298
+ @classmethod
299
+ def poll(cls, context: Context) -> bool:
300
+ return search.current_armature_is_vrm1(context)
301
+
302
+ def draw_header(self, _context: Context) -> None:
303
+ self.layout.label(icon="CONSTRAINT")
304
+
305
+ def draw(self, context: Context) -> None:
306
+ armature = search.current_armature(context)
307
+ if not armature:
308
+ return
309
+ armature_data = armature.data
310
+ if not isinstance(armature_data, Armature):
311
+ return
312
+ _draw_node_constraint1_layout(
313
+ context,
314
+ armature,
315
+ self.layout,
316
+ get_armature_extension(armature_data).node_constraint1,
317
+ )