@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,2243 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import math
3
+ import secrets
4
+ import string
5
+ import time
6
+ from copy import deepcopy
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from sys import float_info
10
+ from typing import Final, Optional, TypeVar, Union
11
+
12
+ import bpy
13
+ from bpy.types import (
14
+ Context,
15
+ Driver,
16
+ FCurve,
17
+ Image,
18
+ Material,
19
+ Node,
20
+ NodeFrame,
21
+ NodeGroup,
22
+ NodeGroupOutput,
23
+ NodeReroute,
24
+ NodeSocket,
25
+ NodeSocketBool,
26
+ NodeSocketColor,
27
+ NodeSocketFloat,
28
+ NodeSocketFloatAngle,
29
+ NodeSocketFloatFactor,
30
+ NodeSocketFloatPercentage,
31
+ NodeSocketFloatTime,
32
+ NodeSocketFloatUnsigned,
33
+ NodeSocketInt,
34
+ NodeSocketIntFactor,
35
+ NodeSocketIntPercentage,
36
+ NodeSocketIntUnsigned,
37
+ NodeSocketString,
38
+ NodeSocketVector,
39
+ NodeSocketVectorAcceleration,
40
+ NodeSocketVectorDirection,
41
+ NodeSocketVectorEuler,
42
+ NodeSocketVectorTranslation,
43
+ NodeSocketVectorVelocity,
44
+ NodeSocketVectorXYZ,
45
+ NodeTree,
46
+ ShaderNodeAmbientOcclusion,
47
+ ShaderNodeAttribute,
48
+ ShaderNodeBevel,
49
+ ShaderNodeBsdfAnisotropic,
50
+ ShaderNodeBsdfGlass,
51
+ ShaderNodeBsdfHair,
52
+ ShaderNodeBsdfHairPrincipled,
53
+ ShaderNodeBsdfPrincipled,
54
+ ShaderNodeBsdfRefraction,
55
+ ShaderNodeBsdfToon,
56
+ ShaderNodeBump,
57
+ ShaderNodeClamp,
58
+ ShaderNodeCustomGroup,
59
+ ShaderNodeDisplacement,
60
+ ShaderNodeGroup,
61
+ ShaderNodeMapping,
62
+ ShaderNodeMapRange,
63
+ ShaderNodeMath,
64
+ ShaderNodeMixRGB,
65
+ ShaderNodeNormalMap,
66
+ ShaderNodeOutputAOV,
67
+ ShaderNodeOutputLight,
68
+ ShaderNodeOutputLineStyle,
69
+ ShaderNodeOutputMaterial,
70
+ ShaderNodeOutputWorld,
71
+ ShaderNodeScript,
72
+ ShaderNodeSubsurfaceScattering,
73
+ ShaderNodeTangent,
74
+ ShaderNodeTexBrick,
75
+ ShaderNodeTexCoord,
76
+ ShaderNodeTexEnvironment,
77
+ ShaderNodeTexGradient,
78
+ ShaderNodeTexIES,
79
+ ShaderNodeTexImage,
80
+ ShaderNodeTexMagic,
81
+ ShaderNodeTexNoise,
82
+ ShaderNodeTexSky,
83
+ ShaderNodeTexVoronoi,
84
+ ShaderNodeTexWave,
85
+ ShaderNodeTexWhiteNoise,
86
+ ShaderNodeUVAlongStroke,
87
+ ShaderNodeUVMap,
88
+ ShaderNodeVectorDisplacement,
89
+ ShaderNodeVectorMath,
90
+ ShaderNodeVectorRotate,
91
+ ShaderNodeVectorTransform,
92
+ ShaderNodeVertexColor,
93
+ ShaderNodeWireframe,
94
+ )
95
+ from mathutils import Color, Euler, Vector
96
+
97
+ from ..editor.extension_accessor import get_material_extension, get_node_tree_extension
98
+ from . import convert
99
+ from .char import INTERNAL_NAME_PREFIX
100
+ from .gl import GL_CLAMP_TO_EDGE, GL_LINEAR, GL_NEAREST, GL_REPEAT
101
+ from .logger import get_logger
102
+ from .version import get_addon_version
103
+ from .workspace import wm_append_without_library
104
+
105
+ _logger = get_logger(__name__)
106
+
107
+ LAST_MODIFIED_VERSION: Final = (3, 18, 1)
108
+
109
+ BOOL_SOCKET_CLASSES: Final = (NodeSocketBool,)
110
+ FLOAT_SOCKET_CLASSES: Final = (
111
+ NodeSocketFloat,
112
+ NodeSocketFloatAngle,
113
+ NodeSocketFloatFactor,
114
+ NodeSocketFloatPercentage,
115
+ NodeSocketFloatTime,
116
+ NodeSocketFloatUnsigned,
117
+ )
118
+ INT_SOCKET_CLASSES: Final = (
119
+ NodeSocketInt,
120
+ NodeSocketIntFactor,
121
+ NodeSocketIntPercentage,
122
+ NodeSocketIntUnsigned,
123
+ )
124
+ SCALAR_SOCKET_CLASSES: Final = (
125
+ *BOOL_SOCKET_CLASSES,
126
+ *FLOAT_SOCKET_CLASSES,
127
+ *INT_SOCKET_CLASSES,
128
+ )
129
+ VECTOR_SOCKET_CLASSES: Final = (
130
+ NodeSocketVector,
131
+ NodeSocketVectorAcceleration,
132
+ NodeSocketVectorDirection,
133
+ NodeSocketVectorEuler,
134
+ NodeSocketVectorTranslation,
135
+ NodeSocketVectorVelocity,
136
+ NodeSocketVectorXYZ,
137
+ )
138
+ COLOR_SOCKET_CLASSES: Final = (NodeSocketColor,)
139
+ STRING_SOCKET_CLASSES: Final = (NodeSocketString,)
140
+
141
+ MTOON1_AUTO_SETUP_GROUP_NAME: Final = "MToon"
142
+ MTOON1_AUTO_SETUP_GROUP_NODE_TREE_CUSTOM_KEY: Final = (
143
+ "VRM Add-on MToon1 Auto Setup Placeholder"
144
+ )
145
+
146
+ OUTLINE_GEOMETRY_GROUP_NAME: Final = "VRM Add-on MToon 1.0 Outline Geometry Revision 1"
147
+
148
+ UV_GROUP_NAME: Final = "VRM Add-on MToon 1.0 UV Revision 1"
149
+ UV_GROUP_UV_SCALE_X_LABEL: Final = "UV Scale X"
150
+ UV_GROUP_UV_SCALE_X_DEFAULT: Final = 1
151
+ UV_GROUP_UV_SCALE_Y_LABEL: Final = "UV Scale Y"
152
+ UV_GROUP_UV_SCALE_Y_DEFAULT: Final = 1
153
+ UV_GROUP_UV_OFFSET_X_LABEL: Final = "UV Offset X"
154
+ UV_GROUP_UV_OFFSET_X_DEFAULT: Final = 0
155
+ UV_GROUP_UV_OFFSET_Y_LABEL: Final = "UV Offset Y"
156
+ UV_GROUP_UV_OFFSET_Y_DEFAULT: Final = 0
157
+ UV_GROUP_WRAP_S_LABEL: Final = "Wrap S"
158
+ UV_GROUP_WRAP_S_DEFAULT: Final = GL_REPEAT
159
+ UV_GROUP_WRAP_T_LABEL: Final = "Wrap T"
160
+ UV_GROUP_WRAP_T_DEFAULT: Final = GL_REPEAT
161
+ UV_GROUP_IMAGE_WIDTH_LABEL: Final = "Image Width"
162
+ UV_GROUP_IMAGE_HEIGHT_LABEL: Final = "Image Height"
163
+ UV_GROUP_EXPRESSION_UV_SCALE_BIND_LABEL: Final = "Expression UV Scale Bind"
164
+ UV_GROUP_EXPRESSION_UV_OFFSET_BIND_LABEL: Final = "Expression UV Offset Bind"
165
+
166
+ UV_ANIMATION_GROUP_NAME: Final = "VRM Add-on MToon 1.0 UV Animation Revision 1"
167
+ UV_ANIMATION_GROUP_TRANSLATE_X_LABEL: Final = "Translate X"
168
+ UV_ANIMATION_GROUP_TRANSLATE_X_DEFAULT: Final = 0.0
169
+ UV_ANIMATION_GROUP_TRANSLATE_Y_LABEL: Final = "Translate Y"
170
+ UV_ANIMATION_GROUP_TRANSLATE_Y_DEFAULT: Final = 0.0
171
+ UV_ANIMATION_GROUP_ROTATION_LABEL: Final = "Rotation"
172
+ UV_ANIMATION_GROUP_ROTATION_DEFAULT: Final = 0.0
173
+ UV_ANIMATION_GROUP_FPS_NODE_NAME: Final = "Scene.Render.Fps"
174
+ UV_ANIMATION_GROUP_FPS_BASE_NODE_NAME: Final = "Scene.Render.FpsBase"
175
+ UV_ANIMATION_GROUP_FRAME_CURRENT_NODE_NAME: Final = "Scene.FrameCurrent"
176
+
177
+ NORMAL_GROUP_NAME: Final = "VRM Add-on MToon 1.0 Normal Revision 1"
178
+ NORMAL_GROUP_SCALE_LABEL: Final = "Normal Map Texture Scale"
179
+ NORMAL_GROUP_SCALE_DEFAULT: Final = 1.0
180
+ NORMAL_GROUP_IS_OUTLINE_LABEL: Final = "Is Outline"
181
+
182
+ MATCAP_UV_GROUP_NAME: Final = "VRM Add-on MToon 1.0 MatCap UV Revision 1"
183
+
184
+ OUTPUT_GROUP_NAME: Final = "VRM Add-on MToon 1.0 Output Revision 1"
185
+ OUTPUT_GROUP_DOUBLE_SIDED_LABEL: Final = "Double Sided"
186
+ OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_LABEL: Final = "Transparent With Z-Write"
187
+ OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_DEFAULT: Final = False
188
+ OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_LABEL: Final = "GI Equalization Factor"
189
+ OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_DEFAULT: Final = 0.9
190
+ OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_LABEL: Final = "Render Queue Offset Number"
191
+ OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_MIN: Final = -9
192
+ OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_DEFAULT: Final = 0
193
+ OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_MAX: Final = 9
194
+ OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_LABEL: Final = "Lit Color"
195
+ OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_LABEL: Final = "Lit Color Alpha"
196
+ OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_DEFAULT: Final = (1.0, 1.0, 1.0)
197
+ OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_DEFAULT: Final = 1.0
198
+ OUTPUT_GROUP_SHADE_COLOR_FACTOR_LABEL: Final = "Shade Color"
199
+ OUTPUT_GROUP_SHADE_COLOR_FACTOR_DEFAULT: Final = (1.0, 1.0, 1.0)
200
+ OUTPUT_GROUP_SHADING_TOONY_FACTOR_LABEL: Final = "Shading Toony"
201
+ OUTPUT_GROUP_SHADING_TOONY_FACTOR_DEFAULT: Final = 0.9
202
+ OUTPUT_GROUP_SHADING_SHIFT_FACTOR_LABEL: Final = "Shading Shift"
203
+ OUTPUT_GROUP_SHADING_SHIFT_FACTOR_DEFAULT: Final = -0.2
204
+ OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_LABEL: Final = "Shading Shift Texture Scale"
205
+ OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_DEFAULT: Final = 1.0
206
+ OUTPUT_GROUP_EMISSIVE_FACTOR_LABEL: Final = "Emissive Factor"
207
+ OUTPUT_GROUP_EMISSIVE_FACTOR_DEFAULT: Final = (0, 0, 0)
208
+ OUTPUT_GROUP_EMISSIVE_STRENGTH_LABEL: Final = "Emissive Strength"
209
+ OUTPUT_GROUP_EMISSIVE_STRENGTH_DEFAULT: Final = 1.0
210
+ OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_LABEL: Final = "Parametric Rim Color"
211
+ OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_DEFAULT: Final = (0, 0, 0)
212
+ OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_LABEL: Final = "Parametric Rim Fresnel Power"
213
+ OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_DEFAULT: Final = 1.0
214
+ OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_LABEL: Final = "Parametric Rim Lift"
215
+ OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_DEFAULT: Final = 1.0
216
+ OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_LABEL: Final = "Rim LightingMix"
217
+ OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_DEFAULT: Final = 0
218
+ OUTPUT_GROUP_IS_OUTLINE_LABEL: Final = NORMAL_GROUP_IS_OUTLINE_LABEL
219
+ OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_LABEL: Final = "Outline LightingMix"
220
+ OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_DEFAULT: Final = 1.0
221
+ OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_LABEL: Final = "Outline Color"
222
+ OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_DEFAULT: Final = (0, 0, 0)
223
+ OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_LABEL: Final = "Outline Width"
224
+ OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_DEFAULT: Final = 0
225
+ OUTPUT_GROUP_OUTLINE_WIDTH_MODE_LABEL: Final = "Outline Width Mode"
226
+ OUTPUT_GROUP_OUTLINE_WIDTH_MODE_MIN: Final = 0
227
+ OUTPUT_GROUP_OUTLINE_WIDTH_MODE_DEFAULT: Final = 0
228
+ OUTPUT_GROUP_OUTLINE_WIDTH_MODE_MAX: Final = 2
229
+ OUTPUT_GROUP_MATCAP_FACTOR_LABEL: Final = "MatCap Factor"
230
+ OUTPUT_GROUP_MATCAP_FACTOR_DEFAULT: Final = (1, 1, 1)
231
+ OUTPUT_GROUP_ALPHA_MODE_LABEL: Final = "Alpha Mode"
232
+ OUTPUT_GROUP_ALPHA_CUTOFF_LABEL: Final = "Alpha Cutoff"
233
+ OUTPUT_GROUP_ALPHA_CUTOFF_DEFAULT: Final = 0.5
234
+ OUTPUT_GROUP_EXPRESSION_COLOR_BIND_LABEL: Final = "Expression Color Bind"
235
+ OUTPUT_GROUP_EXPRESSION_COLOR_ALPHA_BIND_LABEL: Final = "Expression Color Alpha Bind"
236
+ OUTPUT_GROUP_EXPRESSION_EMISSION_COLOR_BIND_LABEL: Final = (
237
+ "Expression Emission Color Bind"
238
+ )
239
+ OUTPUT_GROUP_EXPRESSION_SHADE_COLOR_BIND_LABEL: Final = "Expression Shade Color Bind"
240
+ OUTPUT_GROUP_EXPRESSION_MATCAP_COLOR_BIND_LABEL: Final = "Expression MatCap Color Bind"
241
+ OUTPUT_GROUP_EXPRESSION_RIM_COLOR_BIND_LABEL: Final = "Expression Rim Color Bind"
242
+ OUTPUT_GROUP_EXPRESSION_OUTLINE_COLOR_BIND_LABEL: Final = (
243
+ "Expression Outline Color Bind"
244
+ )
245
+
246
+ SHADER_NODE_GROUP_NAMES: Final = (
247
+ MATCAP_UV_GROUP_NAME,
248
+ UV_GROUP_NAME,
249
+ UV_ANIMATION_GROUP_NAME,
250
+ NORMAL_GROUP_NAME,
251
+ OUTPUT_GROUP_NAME,
252
+ )
253
+
254
+ LEGACY_SHADER_NAMES: Final = (
255
+ "MToon_unversioned",
256
+ "GLTF",
257
+ "TRANSPARENT_ZWRITE",
258
+ )
259
+
260
+
261
+ def _template_name(name: str) -> str:
262
+ return INTERNAL_NAME_PREFIX + name + " Template"
263
+
264
+
265
+ def _backup_name(name: str, backup_suffix: str) -> str:
266
+ return name.removesuffix(" Template") + backup_suffix
267
+
268
+
269
+ def _generate_backup_suffix() -> str:
270
+ # There is a possibility of duplicates in very rare cases. To be exact, duplicates
271
+ # must be regenerated if any are found.
272
+ return " " + "".join(
273
+ secrets.choice(string.ascii_letters + string.digits)
274
+ for _ in range(8) # Be careful not to exceed a total of 64 characters
275
+ )
276
+
277
+
278
+ def _get_mtoon1_auto_setup_shader_node_group(context: Context) -> Optional[NodeTree]:
279
+ node_group = context.blend_data.node_groups.get(MTOON1_AUTO_SETUP_GROUP_NAME)
280
+ if not node_group:
281
+ return None
282
+ if node_group.get(MTOON1_AUTO_SETUP_GROUP_NODE_TREE_CUSTOM_KEY):
283
+ return node_group
284
+ return None
285
+
286
+
287
+ def add_mtoon1_auto_setup_shader_node_group(context: Context) -> None:
288
+ blend_path = Path(__file__).with_name("mtoon_auto_setup.blend")
289
+ node_tree_path = str(blend_path) + "/NodeTree"
290
+
291
+ if _get_mtoon1_auto_setup_shader_node_group(context):
292
+ return
293
+
294
+ wm_append_without_library(
295
+ context,
296
+ blend_path,
297
+ append_filepath=node_tree_path + "/" + MTOON1_AUTO_SETUP_GROUP_NAME,
298
+ append_filename=MTOON1_AUTO_SETUP_GROUP_NAME,
299
+ append_directory=node_tree_path,
300
+ )
301
+
302
+
303
+ def remove_mtoon1_auto_setup_shader_node_group(context: Context) -> None:
304
+ node_group = _get_mtoon1_auto_setup_shader_node_group(context)
305
+ if not node_group or node_group.users:
306
+ return
307
+ context.blend_data.node_groups.remove(node_group)
308
+
309
+
310
+ def _load_mtoon1_node_group(
311
+ context: Context,
312
+ blend_file_path: Path,
313
+ node_group_name: str,
314
+ node_group_type: str,
315
+ node_tree_type: str,
316
+ *,
317
+ reset_node_groups: bool,
318
+ ) -> None:
319
+ start_time = time.perf_counter()
320
+
321
+ if not blend_file_path.exists():
322
+ _logger.error("File not found: %s", blend_file_path)
323
+ return
324
+
325
+ if not reset_node_groups:
326
+ checking_node_group = context.blend_data.node_groups.get(node_group_name)
327
+ if checking_node_group:
328
+ if (
329
+ tuple(get_node_tree_extension(checking_node_group).addon_version)
330
+ >= LAST_MODIFIED_VERSION
331
+ ):
332
+ return
333
+ if checking_node_group.type != node_tree_type:
334
+ message = (
335
+ f'Node Group "{node_group_name}" already exists'
336
+ + f' with different type "{checking_node_group.type}"'
337
+ )
338
+ raise TypeError(message)
339
+
340
+ backup_suffix = _generate_backup_suffix()
341
+
342
+ template_node_group_name = _template_name(node_group_name)
343
+ old_template_node_group = context.blend_data.node_groups.get(
344
+ template_node_group_name
345
+ )
346
+ if old_template_node_group:
347
+ _logger.error('Node Group "%s" already exists', template_node_group_name)
348
+ old_template_node_group.name = _backup_name(
349
+ old_template_node_group.name, backup_suffix
350
+ )
351
+
352
+ node_tree_path = str(blend_file_path) + "/NodeTree"
353
+ node_tree_append_result = wm_append_without_library(
354
+ context,
355
+ blend_file_path,
356
+ append_filepath=(node_tree_path + "/" + template_node_group_name),
357
+ append_filename=template_node_group_name,
358
+ append_directory=node_tree_path,
359
+ )
360
+ if node_tree_append_result != {"FINISHED"}:
361
+ raise RuntimeError(
362
+ "Failed to append MToon 1.0 template node group "
363
+ + f'"{template_node_group_name}": {node_tree_append_result}'
364
+ )
365
+
366
+ template_node_group = None
367
+ try:
368
+ template_node_group = context.blend_data.node_groups.get(
369
+ template_node_group_name
370
+ )
371
+ if not template_node_group:
372
+ raise ValueError("No " + template_node_group_name)
373
+
374
+ node_group = context.blend_data.node_groups.get(node_group_name)
375
+ if not node_group:
376
+ node_group = context.blend_data.node_groups.new(
377
+ node_group_name, node_group_type
378
+ )
379
+ clear_node_tree(node_group, clear_inputs_outputs=True)
380
+ copy_node_tree(context, template_node_group, node_group)
381
+ get_node_tree_extension(node_group).addon_version = get_addon_version()
382
+ finally:
383
+ if template_node_group:
384
+ if template_node_group.users:
385
+ _logger.warning(
386
+ 'Failed to remove "%s" with %d users while loading mtoon shader',
387
+ template_node_group.name,
388
+ template_node_group.users,
389
+ )
390
+ else:
391
+ context.blend_data.node_groups.remove(template_node_group)
392
+
393
+ # Logically, it may have already been removed, so retrieve it again
394
+ old_template_node_group = context.blend_data.node_groups.get(
395
+ _backup_name(template_node_group_name, backup_suffix)
396
+ )
397
+ if old_template_node_group:
398
+ old_template_node_group.name = template_node_group_name
399
+
400
+ end_time = time.perf_counter()
401
+ _logger.debug(
402
+ 'Loaded NodeTree "%s": %.9f seconds', node_group_name, end_time - start_time
403
+ )
404
+
405
+
406
+ def load_mtoon1_outline_geometry_node_group(
407
+ context: Context, *, reset_node_groups: bool
408
+ ) -> None:
409
+ if bpy.app.version < (3, 3):
410
+ return
411
+ _load_mtoon1_node_group(
412
+ context,
413
+ Path(__file__).with_name("mtoon1_outline.blend"),
414
+ OUTLINE_GEOMETRY_GROUP_NAME,
415
+ "GeometryNodeTree",
416
+ "GEOMETRY",
417
+ reset_node_groups=reset_node_groups,
418
+ )
419
+
420
+
421
+ def load_mtoon1_shader_node_groups(
422
+ context: Context, *, reset_node_groups: bool
423
+ ) -> None:
424
+ for shader_node_group_name in SHADER_NODE_GROUP_NAMES:
425
+ _load_mtoon1_node_group(
426
+ context,
427
+ Path(__file__).with_name("mtoon1.blend"),
428
+ shader_node_group_name,
429
+ "ShaderNodeTree",
430
+ "SHADER",
431
+ reset_node_groups=reset_node_groups,
432
+ )
433
+ setup_frame_count_driver(context)
434
+
435
+
436
+ def load_mtoon1_shader(
437
+ context: Context,
438
+ material: Material,
439
+ *,
440
+ reset_node_groups: bool,
441
+ ) -> None:
442
+ if bpy.app.version < (5, 0, 0) and not material.use_nodes:
443
+ material.use_nodes = True
444
+
445
+ load_mtoon1_outline_geometry_node_group(
446
+ context, reset_node_groups=reset_node_groups
447
+ )
448
+ load_mtoon1_shader_node_groups(context, reset_node_groups=reset_node_groups)
449
+
450
+ start_time = time.perf_counter()
451
+
452
+ backup_suffix = _generate_backup_suffix()
453
+
454
+ # Back up if there are materials with the same name as the one being appended.
455
+ # In the future, this will be replaced with append(do_reuse_local_id=True).
456
+ template_material_name = _template_name("VRM Add-on MToon 1.0")
457
+ old_material = context.blend_data.materials.get(template_material_name)
458
+ if old_material:
459
+ _logger.error('Material "%s" already exists', template_material_name)
460
+ old_material.name = _backup_name(old_material.name, backup_suffix)
461
+
462
+ # When appending a Material, NodeTree is also appended simultaneously.
463
+ # Back up if NodeTree with the same name exists.
464
+ for shader_node_group_name in SHADER_NODE_GROUP_NAMES:
465
+ name = _template_name(shader_node_group_name)
466
+ old_template_group = context.blend_data.node_groups.get(name)
467
+ if old_template_group:
468
+ _logger.error('Node Group "%s" already exists', name)
469
+ old_template_group.name = _backup_name(
470
+ old_template_group.name, backup_suffix
471
+ )
472
+
473
+ blend_file_path = Path(__file__).with_name("mtoon1.blend")
474
+ material_path = str(blend_file_path) + "/Material"
475
+ material_append_result = wm_append_without_library(
476
+ context,
477
+ blend_file_path,
478
+ append_filepath=material_path + "/" + template_material_name,
479
+ append_filename=template_material_name,
480
+ append_directory=material_path,
481
+ # do_reuse_local_id=True
482
+ )
483
+ if material_append_result != {"FINISHED"}:
484
+ raise RuntimeError(
485
+ "Failed to append MToon 1.0 template material: "
486
+ + f"{material_append_result}"
487
+ )
488
+
489
+ template_material = None
490
+ try:
491
+ template_material = context.blend_data.materials.get(template_material_name)
492
+ if not template_material:
493
+ raise ValueError("No " + template_material_name)
494
+ template_material_node_tree = template_material.node_tree
495
+ material_node_tree = material.node_tree
496
+ if template_material_node_tree is None:
497
+ _logger.error("MToon template material node tree is None")
498
+ elif material_node_tree is None:
499
+ _logger.error("MToon copy target material node tree is None")
500
+ else:
501
+ copy_node_tree(context, template_material_node_tree, material_node_tree)
502
+ finally:
503
+ if template_material:
504
+ if template_material.users:
505
+ _logger.warning(
506
+ 'Failed to remove "%s" with %d users while loading mtoon shader',
507
+ template_material.name,
508
+ template_material.users,
509
+ )
510
+ else:
511
+ context.blend_data.materials.remove(template_material)
512
+
513
+ # Remove NodeTree that was appended simultaneously when appending Material
514
+ for shader_node_group_name in SHADER_NODE_GROUP_NAMES:
515
+ shader_node_group_template_name = _template_name(shader_node_group_name)
516
+ template_group = context.blend_data.node_groups.get(
517
+ shader_node_group_template_name
518
+ )
519
+ if template_group:
520
+ if template_group.users:
521
+ _logger.warning(
522
+ 'Failed to remove "%s" with %d users'
523
+ " while loading mtoon shader",
524
+ template_group.name,
525
+ template_group.users,
526
+ )
527
+ else:
528
+ context.blend_data.node_groups.remove(template_group)
529
+
530
+ # Logically, it may have already been removed, so retrieve it again
531
+ old_material = context.blend_data.materials.get(
532
+ _backup_name(template_material_name, backup_suffix)
533
+ )
534
+ if old_material:
535
+ old_material.name = template_material_name
536
+
537
+ # Restore the backed up NodeTree
538
+ for shader_node_group_name in SHADER_NODE_GROUP_NAMES:
539
+ name = _template_name(shader_node_group_name)
540
+ old_template_group = context.blend_data.node_groups.get(
541
+ _backup_name(name, backup_suffix)
542
+ )
543
+ if old_template_group:
544
+ old_template_group.name = name
545
+
546
+ ext = get_material_extension(material)
547
+ ext.mtoon1.setup_drivers()
548
+
549
+ end_time = time.perf_counter()
550
+ _logger.debug(
551
+ 'Loaded Material "%s": %.9f seconds', material.name, end_time - start_time
552
+ )
553
+
554
+
555
+ def copy_socket(from_socket: NodeSocket, to_socket: NodeSocket) -> None:
556
+ if (
557
+ bpy.app.version < (5, 0)
558
+ and to_socket.display_shape != from_socket.display_shape
559
+ ):
560
+ to_socket.display_shape = from_socket.display_shape
561
+ if to_socket.enabled != from_socket.enabled:
562
+ to_socket.enabled = from_socket.enabled
563
+ if to_socket.hide != from_socket.hide:
564
+ to_socket.hide = from_socket.hide
565
+ if to_socket.hide_value != from_socket.hide_value:
566
+ to_socket.hide_value = from_socket.hide_value
567
+ if to_socket.link_limit != from_socket.link_limit:
568
+ to_socket.link_limit = from_socket.link_limit
569
+ if to_socket.name != from_socket.name:
570
+ to_socket.name = from_socket.name
571
+ if to_socket.show_expanded != from_socket.show_expanded:
572
+ to_socket.show_expanded = from_socket.show_expanded
573
+ if to_socket.type != from_socket.type:
574
+ to_socket.type = from_socket.type
575
+
576
+
577
+ def copy_socket_interface(
578
+ from_socket: "bpy.types.NodeSocketInterface",
579
+ to_socket: "bpy.types.NodeSocketInterface",
580
+ ) -> None:
581
+ # bpy.app.version < (4,)
582
+ from bpy.types import (
583
+ NodeSocketInterfaceColor,
584
+ NodeSocketInterfaceFloat,
585
+ NodeSocketInterfaceFloatAngle,
586
+ NodeSocketInterfaceFloatDistance,
587
+ NodeSocketInterfaceFloatFactor,
588
+ NodeSocketInterfaceFloatPercentage,
589
+ NodeSocketInterfaceFloatTime,
590
+ NodeSocketInterfaceFloatUnsigned,
591
+ NodeSocketInterfaceVector,
592
+ NodeSocketInterfaceVectorAcceleration,
593
+ NodeSocketInterfaceVectorDirection,
594
+ NodeSocketInterfaceVectorEuler,
595
+ NodeSocketInterfaceVectorTranslation,
596
+ NodeSocketInterfaceVectorVelocity,
597
+ NodeSocketInterfaceVectorXYZ,
598
+ )
599
+
600
+ if bpy.app.version >= (3, 0, 0):
601
+ to_socket.attribute_domain = from_socket.attribute_domain
602
+ to_socket.bl_label = from_socket.bl_label
603
+ to_socket.description = from_socket.description
604
+ to_socket.name = from_socket.name
605
+ to_socket.hide_value = from_socket.hide_value
606
+
607
+ float_classes = (
608
+ NodeSocketInterfaceFloat,
609
+ NodeSocketInterfaceFloatAngle,
610
+ NodeSocketInterfaceFloatDistance,
611
+ NodeSocketInterfaceFloatFactor,
612
+ NodeSocketInterfaceFloatPercentage,
613
+ NodeSocketInterfaceFloatTime,
614
+ NodeSocketInterfaceFloatUnsigned,
615
+ )
616
+ color_classes = (NodeSocketInterfaceColor,)
617
+ vector_classes = (
618
+ NodeSocketInterfaceVector,
619
+ NodeSocketInterfaceVectorAcceleration,
620
+ NodeSocketInterfaceVectorDirection,
621
+ NodeSocketInterfaceVectorEuler,
622
+ NodeSocketInterfaceVectorTranslation,
623
+ NodeSocketInterfaceVectorVelocity,
624
+ NodeSocketInterfaceVectorXYZ,
625
+ )
626
+
627
+ if isinstance(from_socket, float_classes) and isinstance(to_socket, float_classes):
628
+ to_socket.default_value = from_socket.default_value
629
+ to_socket.min_value = from_socket.min_value
630
+ to_socket.max_value = from_socket.max_value
631
+ elif isinstance(from_socket, color_classes) and isinstance(
632
+ to_socket, color_classes
633
+ ):
634
+ to_socket.default_value = deepcopy(
635
+ (
636
+ from_socket.default_value[0],
637
+ from_socket.default_value[1],
638
+ from_socket.default_value[2],
639
+ from_socket.default_value[3],
640
+ )
641
+ )
642
+ elif isinstance(from_socket, vector_classes) and isinstance(
643
+ to_socket, vector_classes
644
+ ):
645
+ to_socket.default_value = deepcopy(
646
+ (
647
+ from_socket.default_value[0],
648
+ from_socket.default_value[1],
649
+ from_socket.default_value[2],
650
+ )
651
+ )
652
+ to_socket.min_value = from_socket.min_value
653
+ to_socket.max_value = from_socket.max_value
654
+
655
+
656
+ def copy_socket_default_value(
657
+ from_socket: NodeSocket,
658
+ to_socket: NodeSocket,
659
+ ) -> None:
660
+ if isinstance(from_socket, SCALAR_SOCKET_CLASSES):
661
+ if isinstance(to_socket, BOOL_SOCKET_CLASSES):
662
+ default_bool_value = bool(from_socket.default_value)
663
+ if to_socket.default_value != default_bool_value:
664
+ to_socket.default_value = default_bool_value
665
+ elif isinstance(to_socket, FLOAT_SOCKET_CLASSES):
666
+ default_float_value = float(from_socket.default_value)
667
+ if to_socket.default_value != default_float_value:
668
+ to_socket.default_value = default_float_value
669
+ elif isinstance(to_socket, INT_SOCKET_CLASSES):
670
+ default_int_value = int(from_socket.default_value)
671
+ if to_socket.default_value != default_int_value:
672
+ to_socket.default_value = default_int_value
673
+ elif isinstance(from_socket, VECTOR_SOCKET_CLASSES) and isinstance(
674
+ to_socket, VECTOR_SOCKET_CLASSES
675
+ ):
676
+ default_vector_value = (
677
+ from_socket.default_value[0],
678
+ from_socket.default_value[1],
679
+ from_socket.default_value[2],
680
+ )
681
+ if tuple(to_socket.default_value) != default_vector_value:
682
+ to_socket.default_value = default_vector_value
683
+ elif isinstance(from_socket, COLOR_SOCKET_CLASSES) and isinstance(
684
+ to_socket, COLOR_SOCKET_CLASSES
685
+ ):
686
+ default_color_value = (
687
+ from_socket.default_value[0],
688
+ from_socket.default_value[1],
689
+ from_socket.default_value[2],
690
+ from_socket.default_value[3],
691
+ )
692
+ if tuple(to_socket.default_value) != default_color_value:
693
+ to_socket.default_value = default_color_value
694
+ elif isinstance(from_socket, STRING_SOCKET_CLASSES) and isinstance(
695
+ to_socket, STRING_SOCKET_CLASSES
696
+ ):
697
+ default_string_value = from_socket.default_value
698
+ if to_socket.default_value != default_string_value:
699
+ to_socket.default_value = default_string_value
700
+
701
+
702
+ def copy_node_socket_default_value(
703
+ from_node: Node,
704
+ to_node: Node,
705
+ ) -> None:
706
+ for index, from_input in enumerate(from_node.inputs):
707
+ if 0 <= index < len(to_node.inputs):
708
+ to_input = to_node.inputs[index]
709
+ copy_socket_default_value(from_input, to_input)
710
+ for index, from_output in enumerate(from_node.outputs):
711
+ if 0 <= index < len(to_node.outputs):
712
+ to_output = to_node.outputs[index]
713
+ copy_socket_default_value(from_output, to_output)
714
+
715
+
716
+ def copy_shader_node_group(
717
+ context: Context,
718
+ from_node: ShaderNodeGroup,
719
+ to_node: ShaderNodeGroup,
720
+ ) -> None:
721
+ node_tree = from_node.node_tree
722
+ if not node_tree:
723
+ _logger.error("No node_tree in ShaderNodeGroup: %s", from_node.name)
724
+ return
725
+
726
+ for shader_node_group_name in SHADER_NODE_GROUP_NAMES:
727
+ shader_node_group_template_name = _template_name(shader_node_group_name)
728
+ if not node_tree.name.startswith(shader_node_group_template_name):
729
+ continue
730
+
731
+ group = context.blend_data.node_groups.get(shader_node_group_name)
732
+ if not group:
733
+ _logger.error('"%s" Not Found', shader_node_group_name)
734
+ continue
735
+
736
+ to_node.node_tree = group
737
+ return
738
+
739
+ _logger.error(
740
+ "Importing ShaderNodeGroup doesn't be supported yet: %s", node_tree.name
741
+ )
742
+
743
+
744
+ def copy_node(
745
+ context: Context,
746
+ from_node: Node,
747
+ to_node: Node,
748
+ from_to: dict[Node, Node],
749
+ ) -> None:
750
+ # ruff: noqa: SIM102 Disable SIM102 only in this function to improve if statement readability
751
+
752
+ from_node_color = (
753
+ from_node.color[0],
754
+ from_node.color[1],
755
+ from_node.color[2],
756
+ )
757
+ if tuple(to_node.color) != from_node_color:
758
+ to_node.color = from_node_color
759
+
760
+ if to_node.height != from_node.height:
761
+ to_node.height = from_node.height
762
+
763
+ if to_node.hide != from_node.hide:
764
+ to_node.hide = from_node.hide
765
+
766
+ for index, from_input in enumerate(from_node.inputs):
767
+ if 0 <= index < len(to_node.inputs):
768
+ to_input = to_node.inputs[index]
769
+ copy_socket(from_input, to_input)
770
+
771
+ if to_node.label != from_node.label:
772
+ to_node.label = from_node.label
773
+
774
+ from_node_location = (
775
+ from_node.location[0],
776
+ from_node.location[1],
777
+ )
778
+ if tuple(to_node.location) != from_node_location:
779
+ to_node.location = from_node_location
780
+
781
+ if to_node.mute != from_node.mute:
782
+ to_node.mute = from_node.mute
783
+
784
+ if to_node.name != from_node.name:
785
+ to_node.name = from_node.name
786
+
787
+ for index, from_output in enumerate(from_node.outputs):
788
+ if 0 <= index < len(to_node.outputs):
789
+ to_output = to_node.outputs[index]
790
+ copy_socket(from_output, to_output)
791
+
792
+ from_node_parent = from_node.parent
793
+ if from_node_parent:
794
+ to_node_parent = from_to.get(from_node_parent)
795
+ if to_node.parent != to_node_parent:
796
+ to_node.parent = to_node_parent
797
+
798
+ if to_node.select:
799
+ to_node.select = False
800
+
801
+ if to_node.show_options != from_node.show_options:
802
+ to_node.show_options = from_node.show_options
803
+
804
+ if to_node.show_preview != from_node.show_preview:
805
+ to_node.show_preview = from_node.show_preview
806
+
807
+ if to_node.show_texture:
808
+ to_node.show_texture = False
809
+
810
+ if to_node.use_custom_color != from_node.use_custom_color:
811
+ to_node.use_custom_color = from_node.use_custom_color
812
+
813
+ if to_node.width != from_node.width:
814
+ to_node.width = from_node.width
815
+
816
+ if isinstance(from_node, NodeFrame) and isinstance(to_node, NodeFrame):
817
+ if to_node.shrink != from_node.shrink:
818
+ to_node.shrink = from_node.shrink
819
+ if to_node.label_size != from_node.label_size:
820
+ to_node.label_size = from_node.label_size
821
+ if to_node.text != from_node.text:
822
+ to_node.text = from_node.text
823
+ if isinstance(from_node, NodeGroup):
824
+ _logger.error("Importing NodeGroup doesn't be supported yet")
825
+ if isinstance(from_node, NodeGroupOutput) and isinstance(to_node, NodeGroupOutput):
826
+ if to_node.is_active_output != from_node.is_active_output:
827
+ to_node.is_active_output = from_node.is_active_output
828
+ if isinstance(from_node, ShaderNodeWireframe) and isinstance(
829
+ to_node, ShaderNodeWireframe
830
+ ):
831
+ if to_node.use_pixel_size != from_node.use_pixel_size:
832
+ to_node.use_pixel_size = from_node.use_pixel_size
833
+ if isinstance(from_node, ShaderNodeVertexColor) and isinstance(
834
+ to_node, ShaderNodeVertexColor
835
+ ):
836
+ if to_node.layer_name != from_node.layer_name:
837
+ to_node.layer_name = from_node.layer_name
838
+ if isinstance(from_node, ShaderNodeVectorTransform) and isinstance(
839
+ to_node, ShaderNodeVectorTransform
840
+ ):
841
+ if to_node.convert_from != from_node.convert_from:
842
+ to_node.convert_from = from_node.convert_from
843
+ if to_node.convert_to != from_node.convert_to:
844
+ to_node.convert_to = from_node.convert_to
845
+ if to_node.vector_type != from_node.vector_type:
846
+ to_node.vector_type = from_node.vector_type
847
+ if isinstance(from_node, ShaderNodeVectorRotate) and isinstance(
848
+ to_node, ShaderNodeVectorRotate
849
+ ):
850
+ if to_node.invert != from_node.invert:
851
+ to_node.invert = from_node.invert
852
+ if to_node.rotation_type != from_node.rotation_type:
853
+ to_node.rotation_type = from_node.rotation_type
854
+ if isinstance(from_node, ShaderNodeVectorMath) and isinstance(
855
+ to_node, ShaderNodeVectorMath
856
+ ):
857
+ if to_node.operation != from_node.operation:
858
+ to_node.operation = from_node.operation
859
+ if isinstance(from_node, ShaderNodeVectorDisplacement) and isinstance(
860
+ to_node, ShaderNodeVectorDisplacement
861
+ ):
862
+ if to_node.space != from_node.space:
863
+ to_node.space = from_node.space
864
+ if isinstance(from_node, ShaderNodeUVMap) and isinstance(to_node, ShaderNodeUVMap):
865
+ if to_node.from_instancer != from_node.from_instancer:
866
+ to_node.from_instancer = from_node.from_instancer
867
+ if to_node.uv_map != from_node.uv_map:
868
+ to_node.uv_map = from_node.uv_map
869
+ if isinstance(from_node, ShaderNodeUVAlongStroke) and isinstance(
870
+ to_node, ShaderNodeUVAlongStroke
871
+ ):
872
+ if to_node.use_tips != from_node.use_tips:
873
+ to_node.use_tips = from_node.use_tips
874
+ if isinstance(from_node, ShaderNodeTexWhiteNoise) and isinstance(
875
+ to_node, ShaderNodeTexWhiteNoise
876
+ ):
877
+ if to_node.noise_dimensions != from_node.noise_dimensions:
878
+ to_node.noise_dimensions = from_node.noise_dimensions
879
+ if isinstance(from_node, ShaderNodeTexWave) and isinstance(
880
+ to_node, ShaderNodeTexWave
881
+ ):
882
+ # incomplete
883
+ if to_node.bands_direction != from_node.bands_direction:
884
+ to_node.bands_direction = from_node.bands_direction
885
+ from_node_color_mapping_blend_color = (
886
+ from_node.color_mapping.blend_color[0],
887
+ from_node.color_mapping.blend_color[1],
888
+ from_node.color_mapping.blend_color[2],
889
+ )
890
+ if (
891
+ tuple(to_node.color_mapping.blend_color)
892
+ != from_node_color_mapping_blend_color
893
+ ):
894
+ to_node.color_mapping.blend_color = from_node_color_mapping_blend_color
895
+ if to_node.color_mapping.blend_type != from_node.color_mapping.blend_type:
896
+ to_node.color_mapping.blend_type = from_node.color_mapping.blend_type
897
+ if to_node.color_mapping.brightness != from_node.color_mapping.brightness:
898
+ to_node.color_mapping.brightness = from_node.color_mapping.brightness
899
+ if to_node.color_mapping.color_ramp.color_mode != (
900
+ from_node.color_mapping.color_ramp.color_mode
901
+ ):
902
+ to_node.color_mapping.color_ramp.color_mode = (
903
+ from_node.color_mapping.color_ramp.color_mode
904
+ )
905
+ if to_node.color_mapping.contrast != from_node.color_mapping.contrast:
906
+ to_node.color_mapping.contrast = from_node.color_mapping.contrast
907
+ if to_node.color_mapping.saturation != from_node.color_mapping.saturation:
908
+ to_node.color_mapping.saturation = from_node.color_mapping.saturation
909
+ if (
910
+ to_node.color_mapping.use_color_ramp
911
+ != from_node.color_mapping.use_color_ramp
912
+ ):
913
+ to_node.color_mapping.use_color_ramp = (
914
+ from_node.color_mapping.use_color_ramp
915
+ )
916
+ if to_node.rings_direction != from_node.rings_direction:
917
+ to_node.rings_direction = from_node.rings_direction
918
+ if to_node.wave_profile != from_node.wave_profile:
919
+ to_node.wave_profile = from_node.wave_profile
920
+ if to_node.wave_type != from_node.wave_type:
921
+ to_node.wave_type = from_node.wave_type
922
+ if isinstance(from_node, ShaderNodeTexVoronoi) and isinstance(
923
+ to_node, ShaderNodeTexVoronoi
924
+ ):
925
+ if to_node.distance != from_node.distance:
926
+ to_node.distance = from_node.distance
927
+ if to_node.feature != from_node.feature:
928
+ to_node.feature = from_node.feature
929
+ if to_node.voronoi_dimensions != from_node.voronoi_dimensions:
930
+ to_node.voronoi_dimensions = from_node.voronoi_dimensions
931
+ if isinstance(from_node, ShaderNodeTexSky) and isinstance(
932
+ to_node, ShaderNodeTexSky
933
+ ):
934
+ if to_node.ground_albedo != from_node.ground_albedo:
935
+ to_node.ground_albedo = from_node.ground_albedo
936
+ if to_node.sky_type != from_node.sky_type:
937
+ to_node.sky_type = from_node.sky_type
938
+ from_node_sun_direction = (
939
+ from_node.sun_direction[0],
940
+ from_node.sun_direction[1],
941
+ from_node.sun_direction[2],
942
+ )
943
+ if tuple(to_node.sun_direction) != from_node_sun_direction:
944
+ to_node.sun_direction = from_node_sun_direction
945
+ if to_node.turbidity != from_node.turbidity:
946
+ to_node.turbidity = from_node.turbidity
947
+ if isinstance(from_node, ShaderNodeTexNoise) and isinstance(
948
+ to_node, ShaderNodeTexNoise
949
+ ):
950
+ if to_node.noise_dimensions != from_node.noise_dimensions:
951
+ to_node.noise_dimensions = from_node.noise_dimensions
952
+ if isinstance(from_node, ShaderNodeTexMagic) and isinstance(
953
+ to_node, ShaderNodeTexMagic
954
+ ):
955
+ if to_node.turbulence_depth != from_node.turbulence_depth:
956
+ to_node.turbulence_depth = from_node.turbulence_depth
957
+ if isinstance(from_node, ShaderNodeTexImage) and isinstance(
958
+ to_node, ShaderNodeTexImage
959
+ ):
960
+ if to_node.extension != from_node.extension:
961
+ to_node.extension = from_node.extension
962
+ # to_node.image = from_node.image
963
+ if to_node.interpolation != from_node.interpolation:
964
+ to_node.interpolation = from_node.interpolation
965
+ if to_node.projection != from_node.projection:
966
+ to_node.projection = from_node.projection
967
+ if to_node.projection_blend != from_node.projection_blend:
968
+ to_node.projection_blend = from_node.projection_blend
969
+ if to_node.name == "Mtoon1BaseColorTexture.Image":
970
+ if not to_node.select:
971
+ to_node.select = True
972
+ if not to_node.show_texture:
973
+ to_node.show_texture = True
974
+ if isinstance(from_node, ShaderNodeTexIES) and isinstance(
975
+ to_node, ShaderNodeTexIES
976
+ ):
977
+ if to_node.filepath != from_node.filepath:
978
+ to_node.filepath = from_node.filepath
979
+ if to_node.ies != from_node.ies:
980
+ to_node.ies = from_node.ies
981
+ if to_node.mode != from_node.mode:
982
+ to_node.mode = from_node.mode
983
+ if isinstance(from_node, ShaderNodeTexGradient) and isinstance(
984
+ to_node, ShaderNodeTexGradient
985
+ ):
986
+ if to_node.gradient_type != from_node.gradient_type:
987
+ to_node.gradient_type = from_node.gradient_type
988
+ if isinstance(from_node, ShaderNodeTexEnvironment) and isinstance(
989
+ to_node, ShaderNodeTexEnvironment
990
+ ):
991
+ if to_node.image != from_node.image:
992
+ to_node.image = from_node.image
993
+ if to_node.interpolation != from_node.interpolation:
994
+ to_node.interpolation = from_node.interpolation
995
+ if to_node.projection != from_node.projection:
996
+ to_node.projection = from_node.projection
997
+ if isinstance(from_node, ShaderNodeTexCoord) and isinstance(
998
+ to_node, ShaderNodeTexCoord
999
+ ):
1000
+ if to_node.from_instancer != from_node.from_instancer:
1001
+ to_node.from_instancer = from_node.from_instancer
1002
+ if to_node.object != from_node.object:
1003
+ to_node.object = from_node.object
1004
+ if isinstance(from_node, ShaderNodeTexBrick) and isinstance(
1005
+ to_node, ShaderNodeTexBrick
1006
+ ):
1007
+ if to_node.offset != from_node.offset:
1008
+ to_node.offset = from_node.offset
1009
+ if to_node.offset_frequency != from_node.offset_frequency:
1010
+ to_node.offset_frequency = from_node.offset_frequency
1011
+ if to_node.squash != from_node.squash:
1012
+ to_node.squash = from_node.squash
1013
+ if to_node.squash_frequency != from_node.squash_frequency:
1014
+ to_node.squash_frequency = from_node.squash_frequency
1015
+ if isinstance(from_node, ShaderNodeTangent) and isinstance(
1016
+ to_node, ShaderNodeTangent
1017
+ ):
1018
+ if to_node.axis != from_node.axis:
1019
+ to_node.axis = from_node.axis
1020
+ if to_node.direction_type != from_node.direction_type:
1021
+ to_node.direction_type = from_node.direction_type
1022
+ if to_node.uv_map != from_node.uv_map:
1023
+ to_node.uv_map = from_node.uv_map
1024
+ if isinstance(from_node, ShaderNodeSubsurfaceScattering) and isinstance(
1025
+ to_node, ShaderNodeSubsurfaceScattering
1026
+ ):
1027
+ if to_node.falloff != from_node.falloff:
1028
+ to_node.falloff = from_node.falloff
1029
+ if isinstance(from_node, ShaderNodeScript) and isinstance(
1030
+ to_node, ShaderNodeScript
1031
+ ):
1032
+ if to_node.bytecode != from_node.bytecode:
1033
+ to_node.bytecode = from_node.bytecode
1034
+ if to_node.bytecode_hash != from_node.bytecode_hash:
1035
+ to_node.bytecode_hash = from_node.bytecode_hash
1036
+ if to_node.filepath != from_node.filepath:
1037
+ to_node.filepath = from_node.filepath
1038
+ if to_node.mode != from_node.mode:
1039
+ to_node.mode = from_node.mode
1040
+ if to_node.script != from_node.script:
1041
+ to_node.script = from_node.script
1042
+ if to_node.use_auto_update != from_node.use_auto_update:
1043
+ to_node.use_auto_update = from_node.use_auto_update
1044
+ if isinstance(from_node, ShaderNodeOutputWorld) and isinstance(
1045
+ to_node, ShaderNodeOutputWorld
1046
+ ):
1047
+ if to_node.is_active_output != from_node.is_active_output:
1048
+ to_node.is_active_output = from_node.is_active_output
1049
+ if to_node.target != from_node.target:
1050
+ to_node.target = from_node.target
1051
+ if isinstance(from_node, ShaderNodeOutputMaterial) and isinstance(
1052
+ to_node, ShaderNodeOutputMaterial
1053
+ ):
1054
+ if to_node.is_active_output != from_node.is_active_output:
1055
+ to_node.is_active_output = from_node.is_active_output
1056
+ if to_node.target != from_node.target:
1057
+ to_node.target = from_node.target
1058
+ if isinstance(from_node, ShaderNodeOutputLineStyle) and isinstance(
1059
+ to_node, ShaderNodeOutputLineStyle
1060
+ ):
1061
+ if to_node.blend_type != from_node.blend_type:
1062
+ to_node.blend_type = from_node.blend_type
1063
+ if to_node.is_active_output != from_node.is_active_output:
1064
+ to_node.is_active_output = from_node.is_active_output
1065
+ if to_node.target != from_node.target:
1066
+ to_node.target = from_node.target
1067
+ if to_node.use_alpha != from_node.use_alpha:
1068
+ to_node.use_alpha = from_node.use_alpha
1069
+ if to_node.use_clamp != from_node.use_clamp:
1070
+ to_node.use_clamp = from_node.use_clamp
1071
+ if isinstance(from_node, ShaderNodeOutputLight) and isinstance(
1072
+ to_node, ShaderNodeOutputLight
1073
+ ):
1074
+ if to_node.is_active_output != from_node.is_active_output:
1075
+ to_node.is_active_output = from_node.is_active_output
1076
+ if to_node.target != from_node.target:
1077
+ to_node.target = from_node.target
1078
+ if isinstance(from_node, ShaderNodeOutputAOV) and isinstance(
1079
+ to_node, ShaderNodeOutputAOV
1080
+ ):
1081
+ if to_node.name != from_node.name:
1082
+ to_node.name = from_node.name
1083
+ if isinstance(from_node, ShaderNodeNormalMap) and isinstance(
1084
+ to_node, ShaderNodeNormalMap
1085
+ ):
1086
+ if to_node.space != from_node.space:
1087
+ to_node.space = from_node.space
1088
+ if to_node.uv_map != from_node.uv_map:
1089
+ to_node.uv_map = from_node.uv_map
1090
+ if isinstance(from_node, ShaderNodeMixRGB) and isinstance(
1091
+ to_node, ShaderNodeMixRGB
1092
+ ):
1093
+ if to_node.blend_type != from_node.blend_type:
1094
+ to_node.blend_type = from_node.blend_type
1095
+ if to_node.use_alpha != from_node.use_alpha:
1096
+ to_node.use_alpha = from_node.use_alpha
1097
+ if to_node.use_clamp != from_node.use_clamp:
1098
+ to_node.use_clamp = from_node.use_clamp
1099
+ if isinstance(from_node, ShaderNodeMath) and isinstance(to_node, ShaderNodeMath):
1100
+ if to_node.operation != from_node.operation:
1101
+ to_node.operation = from_node.operation
1102
+ if to_node.use_clamp != from_node.use_clamp:
1103
+ to_node.use_clamp = from_node.use_clamp
1104
+ if isinstance(from_node, ShaderNodeMapping) and isinstance(
1105
+ to_node, ShaderNodeMapping
1106
+ ):
1107
+ if to_node.vector_type != from_node.vector_type:
1108
+ to_node.vector_type = from_node.vector_type
1109
+ if isinstance(from_node, ShaderNodeMapRange) and isinstance(
1110
+ to_node, ShaderNodeMapRange
1111
+ ):
1112
+ if to_node.clamp != from_node.clamp:
1113
+ to_node.clamp = from_node.clamp
1114
+ if to_node.interpolation_type != from_node.interpolation_type:
1115
+ to_node.interpolation_type = from_node.interpolation_type
1116
+ if isinstance(from_node, ShaderNodeGroup) and isinstance(to_node, ShaderNodeGroup):
1117
+ copy_shader_node_group(context, from_node, to_node)
1118
+ if isinstance(from_node, ShaderNodeDisplacement) and isinstance(
1119
+ to_node, ShaderNodeDisplacement
1120
+ ):
1121
+ if to_node.space != from_node.space:
1122
+ to_node.space = from_node.space
1123
+ if isinstance(from_node, ShaderNodeCustomGroup) and isinstance(
1124
+ to_node, ShaderNodeCustomGroup
1125
+ ):
1126
+ # to_node.node_tree = from_node.node_tree
1127
+ _logger.error("Importing ShaderNodeCustomGroup doesn't be supported yet")
1128
+ if isinstance(from_node, ShaderNodeClamp) and isinstance(to_node, ShaderNodeClamp):
1129
+ if to_node.clamp_type != from_node.clamp_type:
1130
+ to_node.clamp_type = from_node.clamp_type
1131
+ if isinstance(from_node, ShaderNodeBump) and isinstance(to_node, ShaderNodeBump):
1132
+ if to_node.invert != from_node.invert:
1133
+ to_node.invert = from_node.invert
1134
+ if isinstance(from_node, ShaderNodeBsdfToon) and isinstance(
1135
+ to_node, ShaderNodeBsdfToon
1136
+ ):
1137
+ if to_node.component != from_node.component:
1138
+ to_node.component = from_node.component
1139
+ if isinstance(from_node, ShaderNodeBsdfRefraction) and isinstance(
1140
+ to_node, ShaderNodeBsdfRefraction
1141
+ ):
1142
+ if to_node.distribution != from_node.distribution:
1143
+ to_node.distribution = from_node.distribution
1144
+ if isinstance(from_node, ShaderNodeBsdfPrincipled) and isinstance(
1145
+ to_node, ShaderNodeBsdfPrincipled
1146
+ ):
1147
+ if to_node.distribution != from_node.distribution:
1148
+ to_node.distribution = from_node.distribution
1149
+ if to_node.subsurface_method != from_node.subsurface_method:
1150
+ to_node.subsurface_method = from_node.subsurface_method
1151
+ if isinstance(from_node, ShaderNodeBsdfHairPrincipled) and isinstance(
1152
+ to_node, ShaderNodeBsdfHairPrincipled
1153
+ ):
1154
+ if to_node.parametrization != from_node.parametrization:
1155
+ to_node.parametrization = from_node.parametrization
1156
+ if isinstance(from_node, ShaderNodeBsdfHair) and isinstance(
1157
+ to_node, ShaderNodeBsdfHair
1158
+ ):
1159
+ if to_node.component != from_node.component:
1160
+ to_node.component = from_node.component
1161
+ if isinstance(from_node, ShaderNodeBsdfGlass) and isinstance(
1162
+ to_node, ShaderNodeBsdfGlass
1163
+ ):
1164
+ if to_node.distribution != from_node.distribution:
1165
+ to_node.distribution = from_node.distribution
1166
+ if isinstance(from_node, ShaderNodeBsdfAnisotropic) and isinstance(
1167
+ to_node, ShaderNodeBsdfAnisotropic
1168
+ ):
1169
+ if to_node.distribution != from_node.distribution:
1170
+ to_node.distribution = from_node.distribution
1171
+ if isinstance(from_node, ShaderNodeBevel) and isinstance(to_node, ShaderNodeBevel):
1172
+ if to_node.samples != from_node.samples:
1173
+ to_node.samples = from_node.samples
1174
+ if isinstance(from_node, ShaderNodeAttribute) and isinstance(
1175
+ to_node, ShaderNodeAttribute
1176
+ ):
1177
+ if to_node.attribute_name != from_node.attribute_name:
1178
+ to_node.attribute_name = from_node.attribute_name
1179
+ if isinstance(from_node, ShaderNodeAmbientOcclusion) and isinstance(
1180
+ to_node, ShaderNodeAmbientOcclusion
1181
+ ):
1182
+ if to_node.inside != from_node.inside:
1183
+ to_node.inside = from_node.inside
1184
+ if to_node.only_local != from_node.only_local:
1185
+ to_node.only_local = from_node.only_local
1186
+ if to_node.samples != from_node.samples:
1187
+ to_node.samples = from_node.samples
1188
+
1189
+ if bpy.app.version >= (3, 3):
1190
+ from bpy.types import (
1191
+ GeometryNodeDeleteGeometry,
1192
+ GeometryNodeExtrudeMesh,
1193
+ GeometryNodeSeparateGeometry,
1194
+ GeometryNodeSwitch,
1195
+ ShaderNodeCombineColor,
1196
+ ShaderNodeSeparateColor,
1197
+ )
1198
+
1199
+ if isinstance(from_node, ShaderNodeCombineColor) and isinstance(
1200
+ to_node, ShaderNodeCombineColor
1201
+ ):
1202
+ if to_node.mode != from_node.mode:
1203
+ to_node.mode = from_node.mode
1204
+ if isinstance(from_node, ShaderNodeSeparateColor) and isinstance(
1205
+ to_node, ShaderNodeSeparateColor
1206
+ ):
1207
+ if to_node.mode != from_node.mode:
1208
+ to_node.mode = from_node.mode
1209
+
1210
+ if isinstance(from_node, GeometryNodeSwitch) and isinstance(
1211
+ to_node, GeometryNodeSwitch
1212
+ ):
1213
+ if to_node.input_type != from_node.input_type:
1214
+ to_node.input_type = from_node.input_type
1215
+ if isinstance(from_node, GeometryNodeExtrudeMesh) and isinstance(
1216
+ to_node, GeometryNodeExtrudeMesh
1217
+ ):
1218
+ if to_node.mode != from_node.mode:
1219
+ to_node.mode = from_node.mode
1220
+ if isinstance(from_node, GeometryNodeDeleteGeometry) and isinstance(
1221
+ to_node, GeometryNodeDeleteGeometry
1222
+ ):
1223
+ if to_node.domain != from_node.domain:
1224
+ to_node.domain = from_node.domain
1225
+ if to_node.mode != from_node.mode:
1226
+ to_node.mode = from_node.mode
1227
+ if isinstance(from_node, GeometryNodeSeparateGeometry) and isinstance(
1228
+ to_node, GeometryNodeSeparateGeometry
1229
+ ):
1230
+ if to_node.domain != from_node.domain:
1231
+ to_node.domain = from_node.domain
1232
+
1233
+ if bpy.app.version >= (3, 4):
1234
+ from bpy.types import ShaderNodeMix
1235
+
1236
+ if isinstance(from_node, ShaderNodeMix) and isinstance(to_node, ShaderNodeMix):
1237
+ if to_node.blend_type != from_node.blend_type:
1238
+ to_node.blend_type = from_node.blend_type
1239
+ if to_node.clamp_factor != from_node.clamp_factor:
1240
+ to_node.clamp_factor = from_node.clamp_factor
1241
+ if to_node.clamp_result != from_node.clamp_result:
1242
+ to_node.clamp_result = from_node.clamp_result
1243
+ if to_node.data_type != from_node.data_type:
1244
+ to_node.data_type = from_node.data_type
1245
+ if to_node.factor_mode != from_node.factor_mode:
1246
+ to_node.factor_mode = from_node.factor_mode
1247
+
1248
+ if bpy.app.version < (4, 0):
1249
+ from bpy.types import ShaderNodeBsdfGlossy
1250
+
1251
+ if to_node.width_hidden != from_node.width_hidden:
1252
+ to_node.width_hidden = from_node.width_hidden
1253
+ if isinstance(from_node, ShaderNodeBsdfGlossy) and isinstance(
1254
+ to_node, ShaderNodeBsdfGlossy
1255
+ ):
1256
+ if to_node.distribution != from_node.distribution:
1257
+ to_node.distribution = from_node.distribution
1258
+
1259
+ if bpy.app.version < (4, 1):
1260
+ from bpy.types import ShaderNodeTexMusgrave
1261
+
1262
+ if isinstance(from_node, ShaderNodeTexMusgrave) and isinstance(
1263
+ to_node, ShaderNodeTexMusgrave
1264
+ ):
1265
+ if to_node.musgrave_dimensions != from_node.musgrave_dimensions:
1266
+ to_node.musgrave_dimensions = from_node.musgrave_dimensions
1267
+ if to_node.musgrave_type != from_node.musgrave_type:
1268
+ to_node.musgrave_type = from_node.musgrave_type
1269
+
1270
+ if bpy.app.version < (5, 0):
1271
+ from bpy.types import ShaderNodeTexPointDensity
1272
+
1273
+ if isinstance(from_node, ShaderNodeTexPointDensity) and isinstance(
1274
+ to_node, ShaderNodeTexPointDensity
1275
+ ):
1276
+ if to_node.interpolation != from_node.interpolation:
1277
+ to_node.interpolation = from_node.interpolation
1278
+ if to_node.object != from_node.object:
1279
+ to_node.object = from_node.object
1280
+ if to_node.particle_color_source != from_node.particle_color_source:
1281
+ to_node.particle_color_source = from_node.particle_color_source
1282
+ if to_node.point_source != from_node.point_source:
1283
+ to_node.point_source = from_node.point_source
1284
+ if to_node.radius != from_node.radius:
1285
+ to_node.radius = from_node.radius
1286
+ if to_node.resolution != from_node.resolution:
1287
+ to_node.resolution = from_node.resolution
1288
+ if to_node.space != from_node.space:
1289
+ to_node.space = from_node.space
1290
+ if to_node.vertex_attribute_name != from_node.vertex_attribute_name:
1291
+ to_node.vertex_attribute_name = from_node.vertex_attribute_name
1292
+ if to_node.vertex_color_source != from_node.vertex_color_source:
1293
+ to_node.vertex_color_source = from_node.vertex_color_source
1294
+
1295
+
1296
+ def clear_node_tree(
1297
+ node_tree: Optional[NodeTree], *, clear_inputs_outputs: bool = False
1298
+ ) -> None:
1299
+ if node_tree is None:
1300
+ return
1301
+
1302
+ # node_tree.links.clear()
1303
+ while node_tree.links:
1304
+ node_tree.links.remove(node_tree.links[0])
1305
+
1306
+ # node_tree.nodes.clear()
1307
+ while node_tree.nodes:
1308
+ node_tree.nodes.remove(node_tree.nodes[0])
1309
+
1310
+ if not clear_inputs_outputs:
1311
+ return
1312
+
1313
+ if bpy.app.version < (4, 0):
1314
+ # node_tree.inputs.clear()
1315
+ while node_tree.inputs:
1316
+ node_tree.inputs.remove(node_tree.inputs[0])
1317
+
1318
+ # node_tree.outputs.clear()
1319
+ while node_tree.outputs:
1320
+ node_tree.outputs.remove(node_tree.outputs[0])
1321
+
1322
+ return
1323
+
1324
+ node_tree.interface.clear()
1325
+
1326
+
1327
+ def copy_node_tree_inputs_outputs(
1328
+ from_node_tree: NodeTree, to_node_tree: NodeTree
1329
+ ) -> None:
1330
+ # bpy.app.version < (4,)
1331
+ from bpy.types import NodeSocketInterfaceStandard
1332
+
1333
+ while len(to_node_tree.inputs) > len(from_node_tree.inputs):
1334
+ to_node_tree.inputs.remove(to_node_tree.inputs[-1])
1335
+ for index, from_input in enumerate(from_node_tree.inputs):
1336
+ to_input = None
1337
+ if isinstance(from_input, NodeSocketInterfaceStandard) and 0 <= index < len(
1338
+ to_node_tree.inputs
1339
+ ):
1340
+ to_input = to_node_tree.inputs[index]
1341
+ if (
1342
+ isinstance(to_input, NodeSocketInterfaceStandard)
1343
+ and to_input.type != from_input.type
1344
+ ):
1345
+ to_input = None
1346
+ while len(to_node_tree.inputs) > index:
1347
+ to_node_tree.inputs.remove(to_node_tree.inputs[-1])
1348
+ if not to_input:
1349
+ to_input = to_node_tree.inputs.new(
1350
+ from_input.bl_socket_idname, from_input.name
1351
+ )
1352
+ copy_socket_interface(from_input, to_input)
1353
+
1354
+ while len(to_node_tree.outputs) > len(from_node_tree.outputs):
1355
+ to_node_tree.outputs.remove(to_node_tree.outputs[-1])
1356
+ for index, from_output in enumerate(from_node_tree.outputs):
1357
+ to_output = None
1358
+ if isinstance(from_output, NodeSocketInterfaceStandard) and 0 <= index < len(
1359
+ to_node_tree.outputs
1360
+ ):
1361
+ to_output = to_node_tree.outputs[index]
1362
+ if (
1363
+ isinstance(to_output, NodeSocketInterfaceStandard)
1364
+ and to_output.type != from_output.type
1365
+ ):
1366
+ to_output = None
1367
+ while len(to_node_tree.outputs) > index:
1368
+ to_node_tree.outputs.remove(to_node_tree.outputs[-1])
1369
+ if not to_output:
1370
+ to_output = to_node_tree.outputs.new(
1371
+ from_output.bl_socket_idname, from_output.name
1372
+ )
1373
+ copy_socket_interface(from_output, to_output)
1374
+
1375
+
1376
+ def copy_node_tree_interface_socket(
1377
+ from_socket: "bpy.types.NodeTreeInterfaceSocket",
1378
+ to_socket: "bpy.types.NodeTreeInterfaceSocket",
1379
+ ) -> None:
1380
+ from bpy.types import (
1381
+ NodeTreeInterfaceSocketColor,
1382
+ NodeTreeInterfaceSocketFloat,
1383
+ NodeTreeInterfaceSocketFloatAngle,
1384
+ NodeTreeInterfaceSocketFloatDistance,
1385
+ NodeTreeInterfaceSocketFloatFactor,
1386
+ NodeTreeInterfaceSocketFloatPercentage,
1387
+ NodeTreeInterfaceSocketFloatTime,
1388
+ NodeTreeInterfaceSocketFloatTimeAbsolute,
1389
+ NodeTreeInterfaceSocketFloatUnsigned,
1390
+ NodeTreeInterfaceSocketInt,
1391
+ NodeTreeInterfaceSocketIntFactor,
1392
+ NodeTreeInterfaceSocketIntPercentage,
1393
+ NodeTreeInterfaceSocketVector,
1394
+ NodeTreeInterfaceSocketVectorAcceleration,
1395
+ NodeTreeInterfaceSocketVectorDirection,
1396
+ NodeTreeInterfaceSocketVectorEuler,
1397
+ NodeTreeInterfaceSocketVectorTranslation,
1398
+ NodeTreeInterfaceSocketVectorVelocity,
1399
+ NodeTreeInterfaceSocketVectorXYZ,
1400
+ )
1401
+
1402
+ float_classes = (
1403
+ NodeTreeInterfaceSocketFloat,
1404
+ NodeTreeInterfaceSocketFloatAngle,
1405
+ NodeTreeInterfaceSocketFloatDistance,
1406
+ NodeTreeInterfaceSocketFloatFactor,
1407
+ NodeTreeInterfaceSocketFloatPercentage,
1408
+ NodeTreeInterfaceSocketFloatTime,
1409
+ NodeTreeInterfaceSocketFloatTimeAbsolute,
1410
+ NodeTreeInterfaceSocketFloatUnsigned,
1411
+ )
1412
+ int_classes = (
1413
+ NodeTreeInterfaceSocketInt,
1414
+ NodeTreeInterfaceSocketIntFactor,
1415
+ NodeTreeInterfaceSocketIntPercentage,
1416
+ )
1417
+ color_classes = (NodeTreeInterfaceSocketColor,)
1418
+ vector_classes = (
1419
+ NodeTreeInterfaceSocketVector,
1420
+ NodeTreeInterfaceSocketVectorAcceleration,
1421
+ NodeTreeInterfaceSocketVectorDirection,
1422
+ NodeTreeInterfaceSocketVectorEuler,
1423
+ NodeTreeInterfaceSocketVectorTranslation,
1424
+ NodeTreeInterfaceSocketVectorVelocity,
1425
+ NodeTreeInterfaceSocketVectorXYZ,
1426
+ )
1427
+
1428
+ to_socket.attribute_domain = from_socket.attribute_domain
1429
+ to_socket.default_attribute_name = from_socket.default_attribute_name
1430
+ to_socket.hide_value = from_socket.hide_value
1431
+ to_socket.hide_in_modifier = (
1432
+ True # For internal use only, always hide from modifier
1433
+ )
1434
+ to_socket.force_non_field = from_socket.force_non_field
1435
+
1436
+ if isinstance(from_socket, float_classes) and isinstance(to_socket, float_classes):
1437
+ to_socket.default_value = from_socket.default_value
1438
+ to_socket.min_value = from_socket.min_value
1439
+ to_socket.max_value = from_socket.max_value
1440
+ elif isinstance(from_socket, float_classes) and isinstance(to_socket, int_classes):
1441
+ max_int = 1_000_000_000
1442
+ min_int = -1_000_000_000
1443
+ to_socket.default_value = min(
1444
+ max(min_int, math.floor(from_socket.default_value)), max_int
1445
+ )
1446
+ to_socket.min_value = min(
1447
+ max(min_int, math.floor(from_socket.min_value)), max_int
1448
+ )
1449
+ to_socket.max_value = min(
1450
+ max(min_int, math.floor(from_socket.max_value)), max_int
1451
+ )
1452
+ elif isinstance(from_socket, color_classes) and isinstance(
1453
+ to_socket, color_classes
1454
+ ):
1455
+ to_socket.default_value = deepcopy(
1456
+ (
1457
+ from_socket.default_value[0],
1458
+ from_socket.default_value[1],
1459
+ from_socket.default_value[2],
1460
+ from_socket.default_value[3],
1461
+ )
1462
+ )
1463
+ elif isinstance(from_socket, vector_classes) and isinstance(
1464
+ to_socket, vector_classes
1465
+ ):
1466
+ if isinstance(to_socket, NodeTreeInterfaceSocketVector):
1467
+ to_socket.default_value = deepcopy(
1468
+ (
1469
+ from_socket.default_value[0],
1470
+ from_socket.default_value[1],
1471
+ from_socket.default_value[2],
1472
+ )
1473
+ )
1474
+ elif isinstance(to_socket, NodeTreeInterfaceSocketVectorEuler):
1475
+ if isinstance(from_socket, NodeTreeInterfaceSocketVectorEuler):
1476
+ to_socket.default_value = from_socket.default_value.copy()
1477
+ else:
1478
+ to_socket.default_value = Euler(
1479
+ deepcopy(
1480
+ (
1481
+ from_socket.default_value[0],
1482
+ from_socket.default_value[1],
1483
+ from_socket.default_value[2],
1484
+ )
1485
+ ),
1486
+ "XYZ",
1487
+ )
1488
+ elif isinstance(from_socket, NodeTreeInterfaceSocketVectorEuler):
1489
+ to_socket.default_value = Vector(
1490
+ (
1491
+ from_socket.default_value.x,
1492
+ from_socket.default_value.y,
1493
+ from_socket.default_value.z,
1494
+ )
1495
+ )
1496
+ else:
1497
+ to_socket.default_value = Vector(
1498
+ (
1499
+ from_socket.default_value[0],
1500
+ from_socket.default_value[1],
1501
+ from_socket.default_value[2],
1502
+ )
1503
+ )
1504
+
1505
+ to_socket.min_value = from_socket.min_value
1506
+ to_socket.max_value = from_socket.max_value
1507
+
1508
+
1509
+ def copy_node_tree_interface(from_node_tree: NodeTree, to_node_tree: NodeTree) -> None:
1510
+ from bpy.types import (
1511
+ NodeTreeInterfaceSocket,
1512
+ NodeTreeInterfaceSocketFloatFactor,
1513
+ )
1514
+
1515
+ socket_type_to_group_name_to_socket_names: dict[str, dict[str, list[str]]] = {
1516
+ "NodeSocketBool": {
1517
+ OUTPUT_GROUP_NAME: [
1518
+ OUTPUT_GROUP_DOUBLE_SIDED_LABEL,
1519
+ OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_LABEL,
1520
+ OUTPUT_GROUP_IS_OUTLINE_LABEL,
1521
+ ],
1522
+ NORMAL_GROUP_NAME: [
1523
+ NORMAL_GROUP_IS_OUTLINE_LABEL,
1524
+ ],
1525
+ },
1526
+ "NodeSocketInt": {
1527
+ OUTPUT_GROUP_NAME: [
1528
+ OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_LABEL,
1529
+ OUTPUT_GROUP_OUTLINE_WIDTH_MODE_LABEL,
1530
+ OUTPUT_GROUP_ALPHA_MODE_LABEL,
1531
+ ],
1532
+ UV_GROUP_NAME: [
1533
+ UV_GROUP_WRAP_S_LABEL,
1534
+ UV_GROUP_WRAP_T_LABEL,
1535
+ UV_GROUP_IMAGE_WIDTH_LABEL,
1536
+ UV_GROUP_IMAGE_HEIGHT_LABEL,
1537
+ ],
1538
+ },
1539
+ }
1540
+
1541
+ to_items_index = 0
1542
+
1543
+ for from_item in from_node_tree.interface.items_tree:
1544
+ if from_item.item_type != "SOCKET" or not isinstance(
1545
+ from_item, NodeTreeInterfaceSocket
1546
+ ):
1547
+ continue
1548
+ from_socket_type = from_item.socket_type
1549
+ if not from_socket_type:
1550
+ _logger.error(
1551
+ 'Socket source "%s" has empty socket_type. type=%s',
1552
+ from_item.name,
1553
+ type(from_item).__name__,
1554
+ )
1555
+ if isinstance(from_item, NodeTreeInterfaceSocketFloatFactor):
1556
+ from_socket_type = "NodeSocketFloat"
1557
+ else:
1558
+ continue
1559
+
1560
+ translated_from_socket_type = from_socket_type
1561
+ if bpy.app.version >= (4, 1):
1562
+ for (
1563
+ socket_type,
1564
+ group_name_to_socket_names,
1565
+ ) in socket_type_to_group_name_to_socket_names.items():
1566
+ socket_names = group_name_to_socket_names.get(to_node_tree.name)
1567
+ if not socket_names:
1568
+ continue
1569
+ if from_item.name in socket_names:
1570
+ translated_from_socket_type = socket_type
1571
+ break
1572
+
1573
+ to_socket: Optional[NodeTreeInterfaceSocket] = None
1574
+ while len(to_node_tree.interface.items_tree) > to_items_index:
1575
+ to_item = list(to_node_tree.interface.items_tree.values())[to_items_index]
1576
+ if (
1577
+ to_item.item_type != "SOCKET"
1578
+ or not isinstance(to_item, NodeTreeInterfaceSocket)
1579
+ or to_item.in_out != from_item.in_out
1580
+ ):
1581
+ to_node_tree.interface.remove(to_item)
1582
+ continue
1583
+
1584
+ if not to_item.socket_type:
1585
+ _logger.error(
1586
+ 'Socket destination "%s" has empty socket_type. type=%s',
1587
+ to_item.name,
1588
+ type(to_item).__name__,
1589
+ )
1590
+ if isinstance(to_item, NodeTreeInterfaceSocketFloatFactor):
1591
+ to_item.socket_type = "NodeSocketFloat"
1592
+
1593
+ if to_item.socket_type not in (
1594
+ from_socket_type,
1595
+ translated_from_socket_type,
1596
+ ):
1597
+ to_node_tree.interface.remove(to_item)
1598
+ continue
1599
+
1600
+ if to_item.name != from_item.name:
1601
+ to_item.name = from_item.name
1602
+ if to_item.description != from_item.description:
1603
+ to_item.description = from_item.description
1604
+ to_socket = to_item
1605
+ break
1606
+
1607
+ if to_socket is None:
1608
+ to_socket = to_node_tree.interface.new_socket(
1609
+ from_item.name,
1610
+ description=from_item.description,
1611
+ in_out=from_item.in_out,
1612
+ socket_type=translated_from_socket_type,
1613
+ )
1614
+
1615
+ copy_node_tree_interface_socket(from_item, to_socket)
1616
+ to_items_index += 1
1617
+
1618
+ while len(to_node_tree.interface.items_tree) > to_items_index:
1619
+ to_node_tree.interface.remove(
1620
+ list(to_node_tree.interface.items_tree.values())[-1]
1621
+ )
1622
+
1623
+
1624
+ def copy_node_tree(
1625
+ context: Context, from_node_tree: NodeTree, to_node_tree: NodeTree
1626
+ ) -> None:
1627
+ clear_node_tree(to_node_tree, clear_inputs_outputs=False)
1628
+
1629
+ if bpy.app.version < (4, 0):
1630
+ copy_node_tree_inputs_outputs(from_node_tree, to_node_tree)
1631
+ else:
1632
+ copy_node_tree_interface(from_node_tree, to_node_tree)
1633
+
1634
+ from_to: dict[Node, Node] = {}
1635
+
1636
+ # Use sorted because glTF add-on uses index-aware logic.
1637
+ # https://github.com/KhronosGroup/glTF-Blender-IO/blob/86453b20c28d7c78a3f9106911df69fd0138fb2a/addons/io_scene_gltf2/blender/exp/material/gltf2_blender_search_node_tree.py#L806
1638
+ for from_node in sorted(from_node_tree.nodes, key=lambda n: n.name):
1639
+ to_node = to_node_tree.nodes.new(from_node.bl_idname)
1640
+ from_to[from_node] = to_node
1641
+
1642
+ for from_node, to_node in from_to.items():
1643
+ copy_node(context, from_node, to_node, from_to)
1644
+
1645
+ for from_link in from_node_tree.links:
1646
+ if not from_link.is_valid:
1647
+ continue
1648
+
1649
+ input_socket_index = next(
1650
+ (
1651
+ i
1652
+ for i, s in enumerate(from_link.to_node.inputs)
1653
+ if s == from_link.to_socket
1654
+ ),
1655
+ None,
1656
+ )
1657
+ if input_socket_index is None:
1658
+ continue
1659
+ input_node = from_to.get(from_link.to_node)
1660
+ if input_node is None:
1661
+ continue
1662
+ if not (0 <= input_socket_index < len(input_node.inputs)):
1663
+ _logger.error(
1664
+ "Input socket out of range: %d < %d",
1665
+ input_socket_index,
1666
+ len(input_node.inputs),
1667
+ )
1668
+ continue
1669
+ input_socket = input_node.inputs[input_socket_index]
1670
+ if not input_socket:
1671
+ _logger.error("No input socket: %s", from_link.to_socket.name)
1672
+ continue
1673
+
1674
+ output_socket_index = next(
1675
+ (
1676
+ i
1677
+ for i, s in enumerate(from_link.from_node.outputs)
1678
+ if s == from_link.from_socket
1679
+ ),
1680
+ None,
1681
+ )
1682
+ if output_socket_index is None:
1683
+ continue
1684
+ output_node = from_to.get(from_link.from_node)
1685
+ if output_node is None:
1686
+ continue
1687
+ if not (0 <= output_socket_index < len(output_node.outputs)):
1688
+ _logger.error(
1689
+ "Output socket out of range: %d < %d",
1690
+ output_socket_index,
1691
+ len(output_node.outputs),
1692
+ )
1693
+ continue
1694
+ output_socket = output_node.outputs[output_socket_index]
1695
+ if not output_socket:
1696
+ _logger.error("No output socket: %s", from_link.from_socket.name)
1697
+ continue
1698
+
1699
+ to_node_tree.links.new(input_socket, output_socket)
1700
+
1701
+ for from_node, to_node in from_to.items():
1702
+ copy_node_socket_default_value(from_node, to_node)
1703
+
1704
+ for from_node, to_node in from_to.items():
1705
+ # The location of the node can be completely restored by setting the location
1706
+ # again with a consistent parent-child relationship.
1707
+ from_node_location = (
1708
+ from_node.location[0],
1709
+ from_node.location[1],
1710
+ )
1711
+ if tuple(to_node.location) != from_node_location:
1712
+ to_node.location = from_node_location
1713
+
1714
+
1715
+ def get_image_name_and_sampler_type(
1716
+ shader_node: Node, input_socket_name: str
1717
+ ) -> Optional[tuple[str, int, int]]:
1718
+ if (
1719
+ input_socket_name == "NormalmapTexture"
1720
+ and "NormalmapTexture" not in shader_node.inputs
1721
+ and "NomalmapTexture" in shader_node.inputs
1722
+ ):
1723
+ input_socket_name = "NomalmapTexture"
1724
+
1725
+ socket = shader_node.inputs.get(input_socket_name)
1726
+ if not socket:
1727
+ return None
1728
+
1729
+ links = socket.links
1730
+ if not links:
1731
+ return None
1732
+
1733
+ from_node = links[0].from_node
1734
+ if not from_node:
1735
+ return None
1736
+
1737
+ if not isinstance(from_node, ShaderNodeTexImage):
1738
+ return None
1739
+
1740
+ image = from_node.image
1741
+ if not image:
1742
+ return None
1743
+
1744
+ image_name = image.name
1745
+ if not image_name:
1746
+ return None
1747
+
1748
+ # blender is ('Linear', 'Closest', 'Cubic', 'Smart')
1749
+ # glTF is Linear, Closest
1750
+ filter_type = GL_NEAREST if from_node.interpolation == "Closest" else GL_LINEAR
1751
+
1752
+ # blender is ('REPEAT', 'EXTEND', 'CLIP')
1753
+ # glTF is CLAMP_TO_EDGE, MIRRORED_REPEAT, REPEAT
1754
+ wrap_type = GL_REPEAT if from_node.extension == "REPEAT" else GL_CLAMP_TO_EDGE
1755
+
1756
+ return image_name, wrap_type, filter_type
1757
+
1758
+
1759
+ def get_float_value(
1760
+ shader_node: Node,
1761
+ input_socket_name: str,
1762
+ min_value: float = -float_info.max,
1763
+ max_value: float = float_info.max,
1764
+ ) -> Optional[float]:
1765
+ socket = shader_node.inputs.get(input_socket_name)
1766
+ if not socket:
1767
+ return None
1768
+
1769
+ default_value = convert.float_or_none(
1770
+ getattr(socket, "default_value", None), min_value, max_value
1771
+ )
1772
+
1773
+ links = socket.links
1774
+ if not links:
1775
+ return default_value
1776
+
1777
+ from_node = links[0].from_node
1778
+ if not from_node:
1779
+ return default_value
1780
+
1781
+ outputs = from_node.outputs
1782
+ if not outputs:
1783
+ return default_value
1784
+
1785
+ return convert.float_or_none(
1786
+ getattr(outputs[0], "default_value", None), min_value, max_value
1787
+ )
1788
+
1789
+
1790
+ def get_float_value_or(
1791
+ shader_node: Node,
1792
+ input_socket_name: str,
1793
+ min_value: float = -float_info.max,
1794
+ max_value: float = float_info.max,
1795
+ default_value: float = 0.0,
1796
+ ) -> float:
1797
+ float_value = get_float_value(shader_node, input_socket_name, min_value, max_value)
1798
+ if float_value is None:
1799
+ return default_value
1800
+ return float_value
1801
+
1802
+
1803
+ def rgba_or_none(
1804
+ vs: object, min_value: float = -float_info.max, max_value: float = float_info.max
1805
+ ) -> Optional[tuple[float, float, float, float]]:
1806
+ default_alpha_value = max(min_value, min(1.0, max_value))
1807
+ if isinstance(vs, Color):
1808
+ return (
1809
+ max(min_value, min(vs.r, max_value)),
1810
+ max(min_value, min(vs.g, max_value)),
1811
+ max(min_value, min(vs.b, max_value)),
1812
+ default_alpha_value,
1813
+ )
1814
+
1815
+ iterator = convert.iterator_or_none(vs)
1816
+ if iterator is None:
1817
+ return None
1818
+
1819
+ rgba: list[float] = []
1820
+ for v in iterator:
1821
+ f = convert.float_or_none(v, min_value, max_value)
1822
+ if f is None:
1823
+ return None
1824
+ rgba.append(f)
1825
+ if len(rgba) > 4:
1826
+ return None
1827
+
1828
+ if len(rgba) == 3:
1829
+ rgba.append(default_alpha_value)
1830
+ if len(rgba) != 4:
1831
+ return None
1832
+
1833
+ return (rgba[0], rgba[1], rgba[2], rgba[3])
1834
+
1835
+
1836
+ def get_rgba_value(
1837
+ shader_node: Node,
1838
+ input_socket_name: str,
1839
+ min_value: float = -float_info.max,
1840
+ max_value: float = float_info.max,
1841
+ ) -> Optional[tuple[float, float, float, float]]:
1842
+ socket = shader_node.inputs.get(input_socket_name)
1843
+ if not socket:
1844
+ return None
1845
+
1846
+ default_value = rgba_or_none(
1847
+ getattr(socket, "default_value", None), min_value, max_value
1848
+ )
1849
+
1850
+ links = socket.links
1851
+ if not links:
1852
+ return default_value
1853
+
1854
+ from_node = links[0].from_node
1855
+ if not from_node:
1856
+ return default_value
1857
+
1858
+ outputs = from_node.outputs
1859
+ if not outputs:
1860
+ return default_value
1861
+
1862
+ return rgba_or_none(
1863
+ getattr(outputs[0], "default_value", None), min_value, max_value
1864
+ )
1865
+
1866
+
1867
+ def get_rgba_value_or(
1868
+ shader_node: Node,
1869
+ input_socket_name: str,
1870
+ min_value: float = -float_info.max,
1871
+ max_value: float = float_info.max,
1872
+ default_value: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
1873
+ ) -> tuple[float, float, float, float]:
1874
+ rgba_value = get_rgba_value(shader_node, input_socket_name, min_value, max_value)
1875
+ if rgba_value is None:
1876
+ return default_value
1877
+ return rgba_value
1878
+
1879
+
1880
+ def rgb_or_none(
1881
+ vs: object, min_value: float = -float_info.max, max_value: float = float_info.max
1882
+ ) -> Optional[tuple[float, float, float]]:
1883
+ if isinstance(vs, Color):
1884
+ return (
1885
+ max(min_value, min(vs.r, max_value)),
1886
+ max(min_value, min(vs.g, max_value)),
1887
+ max(min_value, min(vs.b, max_value)),
1888
+ )
1889
+
1890
+ iterator = convert.iterator_or_none(vs)
1891
+ if iterator is None:
1892
+ return None
1893
+
1894
+ rgb: list[float] = []
1895
+ for v in iterator:
1896
+ f = convert.float_or_none(v, min_value, max_value)
1897
+ if f is None:
1898
+ return None
1899
+ rgb.append(f)
1900
+ if len(rgb) > 4:
1901
+ return None
1902
+
1903
+ if len(rgb) == 4:
1904
+ rgb.pop()
1905
+ if len(rgb) != 3:
1906
+ return None
1907
+
1908
+ return (rgb[0], rgb[1], rgb[2])
1909
+
1910
+
1911
+ def get_rgb_value(
1912
+ shader_node: Node,
1913
+ input_socket_name: str,
1914
+ min_value: float = -float_info.max,
1915
+ max_value: float = float_info.max,
1916
+ ) -> Optional[tuple[float, float, float]]:
1917
+ socket = shader_node.inputs.get(input_socket_name)
1918
+ if not socket:
1919
+ return None
1920
+
1921
+ default_value = rgb_or_none(
1922
+ getattr(socket, "default_value", None), min_value, max_value
1923
+ )
1924
+
1925
+ links = socket.links
1926
+ if not links:
1927
+ return default_value
1928
+
1929
+ from_node = links[0].from_node
1930
+ if not from_node:
1931
+ return default_value
1932
+
1933
+ outputs = from_node.outputs
1934
+ if not outputs:
1935
+ return default_value
1936
+
1937
+ return rgb_or_none(getattr(outputs[0], "default_value", None), min_value, max_value)
1938
+
1939
+
1940
+ def get_rgb_value_or(
1941
+ shader_node: Node,
1942
+ input_socket_name: str,
1943
+ min_value: float = -float_info.max,
1944
+ max_value: float = float_info.max,
1945
+ default: tuple[float, float, float] = (0.0, 0.0, 0.0),
1946
+ ) -> tuple[float, float, float]:
1947
+ rgb_value = get_rgb_value(shader_node, input_socket_name, min_value, max_value)
1948
+ if rgb_value is None:
1949
+ return default
1950
+ return rgb_value
1951
+
1952
+
1953
+ def setup_frame_count_driver(context: Context) -> None:
1954
+ if bpy.app.version < (3, 6):
1955
+ return
1956
+
1957
+ node_group = context.blend_data.node_groups.get(UV_ANIMATION_GROUP_NAME)
1958
+ if not node_group:
1959
+ return
1960
+ animation_data = node_group.animation_data
1961
+ if not animation_data:
1962
+ animation_data = node_group.animation_data_create()
1963
+ if not animation_data:
1964
+ _logger.error(
1965
+ 'Failed to create anomation data for node group "%s"', node_group.name
1966
+ )
1967
+ return
1968
+ for data_path, target_data_path, enabled in (
1969
+ (
1970
+ f'nodes["{UV_ANIMATION_GROUP_FPS_BASE_NODE_NAME}"]'
1971
+ + ".outputs[0]"
1972
+ + ".default_value",
1973
+ "render.fps_base",
1974
+ True,
1975
+ ),
1976
+ (
1977
+ f'nodes["{UV_ANIMATION_GROUP_FPS_NODE_NAME}"]'
1978
+ + ".outputs[0]"
1979
+ + ".default_value",
1980
+ "render.fps",
1981
+ True,
1982
+ ),
1983
+ (
1984
+ f'nodes["{UV_ANIMATION_GROUP_FRAME_CURRENT_NODE_NAME}"]'
1985
+ + ".outputs[0]"
1986
+ + ".default_value",
1987
+ "frame_current",
1988
+ False,
1989
+ ),
1990
+ ):
1991
+ fcurve: Optional[Union[FCurve, list[FCurve]]] = next(
1992
+ iter(
1993
+ fcurve
1994
+ for fcurve in animation_data.drivers
1995
+ if fcurve.data_path == data_path
1996
+ ),
1997
+ None,
1998
+ )
1999
+ if not enabled:
2000
+ if fcurve is not None:
2001
+ node_group.driver_remove(data_path)
2002
+ continue
2003
+ if fcurve is None:
2004
+ try:
2005
+ fcurve = node_group.driver_add(data_path)
2006
+ except TypeError:
2007
+ continue
2008
+ if not isinstance(fcurve, FCurve):
2009
+ _logger.error(
2010
+ 'Failed to get fcurve "%s" for node tree "%s"',
2011
+ data_path,
2012
+ node_group.name,
2013
+ )
2014
+ continue
2015
+ if fcurve.array_index != 0:
2016
+ fcurve.array_index = 0
2017
+ driver = fcurve.driver
2018
+ if not isinstance(driver, Driver):
2019
+ _logger.error('Failed to get driver for fcurve "%s"', data_path)
2020
+ continue
2021
+ if driver.type != "SUM":
2022
+ driver.type = "SUM"
2023
+ if not driver.variables:
2024
+ driver.variables.new()
2025
+ while len(driver.variables) > 1:
2026
+ driver.variables.remove(driver.variables[-1])
2027
+ variable = driver.variables[0]
2028
+ if not variable.targets:
2029
+ _logger.error(
2030
+ 'No targets in variable for fcurve "%s" in node_tree "%s"',
2031
+ data_path,
2032
+ node_group.name,
2033
+ )
2034
+ continue
2035
+ target = variable.targets[0]
2036
+ if variable.type != "CONTEXT_PROP":
2037
+ variable.type = "CONTEXT_PROP"
2038
+ if target.context_property != "ACTIVE_SCENE":
2039
+ target.context_property = "ACTIVE_SCENE"
2040
+ if target.data_path != target_data_path:
2041
+ target.data_path = target_data_path
2042
+
2043
+
2044
+ __NodeType = TypeVar("__NodeType", bound=Node)
2045
+
2046
+
2047
+ def search_input_node(
2048
+ node_socket: NodeSocket, node_type: type[__NodeType]
2049
+ ) -> Optional[__NodeType]:
2050
+ searching_node_sockets = [node_socket]
2051
+ while searching_node_sockets:
2052
+ searching_node_socket = searching_node_sockets.pop()
2053
+ for link in searching_node_socket.links:
2054
+ if not link.is_valid or link.is_muted:
2055
+ continue
2056
+
2057
+ from_node = link.from_node
2058
+ if from_node.mute:
2059
+ continue
2060
+
2061
+ if isinstance(from_node, node_type):
2062
+ return from_node
2063
+
2064
+ if not isinstance(from_node, NodeReroute):
2065
+ continue
2066
+
2067
+ searching_node_sockets.extend(from_node.inputs)
2068
+
2069
+ return None
2070
+
2071
+
2072
+ @dataclass(frozen=True)
2073
+ class LegacyAddonMaterial:
2074
+ shader_node_group: ShaderNodeGroup
2075
+ shader_name: str
2076
+
2077
+ @classmethod
2078
+ def try_parse(cls, material: Material) -> Optional["LegacyAddonMaterial"]:
2079
+ if bpy.app.version < (5, 0, 0) and not material.use_nodes:
2080
+ return None
2081
+
2082
+ node_tree = material.node_tree
2083
+ if not node_tree:
2084
+ return None
2085
+
2086
+ for node in node_tree.nodes:
2087
+ if node.mute:
2088
+ continue
2089
+
2090
+ if not isinstance(node, ShaderNodeOutputMaterial):
2091
+ continue
2092
+
2093
+ surface_socket = node.inputs.get("Surface")
2094
+ if not surface_socket:
2095
+ continue
2096
+
2097
+ links = surface_socket.links
2098
+ if not links:
2099
+ continue
2100
+
2101
+ link = links[0]
2102
+ if not link.is_valid or link.is_muted:
2103
+ continue
2104
+
2105
+ group_node = link.from_node
2106
+ if group_node.mute:
2107
+ continue
2108
+
2109
+ if not isinstance(group_node, ShaderNodeGroup):
2110
+ continue
2111
+
2112
+ group_node_tree = group_node.node_tree
2113
+ if not group_node_tree:
2114
+ continue
2115
+
2116
+ shader_name = group_node_tree.get("SHADER")
2117
+ if not isinstance(shader_name, str):
2118
+ continue
2119
+
2120
+ if shader_name not in LEGACY_SHADER_NAMES:
2121
+ continue
2122
+
2123
+ return LegacyAddonMaterial(
2124
+ shader_node_group=group_node,
2125
+ shader_name=shader_name,
2126
+ )
2127
+
2128
+ return None
2129
+
2130
+
2131
+ @dataclass(frozen=True)
2132
+ class MmdMaterial:
2133
+ texture: Optional[Image]
2134
+ sphere_texture: Optional[Image]
2135
+ toon_texture: Optional[Image]
2136
+ diffuse_color: tuple[float, float, float]
2137
+ double_sided: bool
2138
+ alpha: float
2139
+
2140
+ @classmethod
2141
+ def find_mmd_shader_node_group(
2142
+ cls, node_tree: NodeTree
2143
+ ) -> Optional[ShaderNodeGroup]:
2144
+ # Search for a ShaderNodeGroup named "mmd_shader" that has a NodeTree
2145
+ # named "MMDShaderDev".
2146
+ # https://github.com/MMD-Blender/blender_mmd_tools/blob/0845cf64aa4928ff7a5b7b52fff5a9bf8c681338/mmd_tools/core/material.py#L622-L628
2147
+ # https://github.com/MMD-Blender/blender_mmd_tools/blob/0845cf64aa4928ff7a5b7b52fff5a9bf8c681338/mmd_tools/core/material.py#L521-L529
2148
+ for node in node_tree.nodes:
2149
+ if node.mute:
2150
+ continue
2151
+
2152
+ if not isinstance(node, ShaderNodeOutputMaterial):
2153
+ continue
2154
+
2155
+ surface_socket = node.inputs.get("Surface")
2156
+ if not surface_socket:
2157
+ continue
2158
+
2159
+ links = surface_socket.links
2160
+ if not links:
2161
+ continue
2162
+
2163
+ link = links[0]
2164
+ if not link.is_valid or link.is_muted:
2165
+ continue
2166
+
2167
+ group_node = link.from_node
2168
+ if group_node.mute:
2169
+ continue
2170
+
2171
+ if not isinstance(group_node, ShaderNodeGroup):
2172
+ continue
2173
+
2174
+ if group_node.name != "mmd_shader":
2175
+ continue
2176
+
2177
+ group_node_tree = group_node.node_tree
2178
+ if not group_node_tree:
2179
+ continue
2180
+
2181
+ if group_node_tree.name != "MMDShaderDev":
2182
+ continue
2183
+
2184
+ return group_node
2185
+ return None
2186
+
2187
+ @classmethod
2188
+ def try_parse(cls, material: Material) -> Optional["MmdMaterial"]:
2189
+ if bpy.app.version < (5, 0, 0) and not material.use_nodes:
2190
+ return None
2191
+ node_tree = material.node_tree
2192
+ if not node_tree:
2193
+ return None
2194
+ shader_node_group = cls.find_mmd_shader_node_group(node_tree)
2195
+ if not shader_node_group:
2196
+ return None
2197
+
2198
+ # https://github.com/MMD-Blender/blender_mmd_tools/blob/0845cf64aa4928ff7a5b7b52fff5a9bf8c681338/mmd_tools/core/material.py#L220-L221
2199
+ texture_node = node_tree.nodes.get("mmd_base_tex")
2200
+ if isinstance(texture_node, ShaderNodeTexImage):
2201
+ texture_image = texture_node.image
2202
+ else:
2203
+ texture_image = None
2204
+
2205
+ # https://github.com/MMD-Blender/blender_mmd_tools/blob/0845cf64aa4928ff7a5b7b52fff5a9bf8c681338/mmd_tools/core/material.py#L536
2206
+ if isinstance(
2207
+ diffuse_input := shader_node_group.inputs.get("Diffuse Color"),
2208
+ COLOR_SOCKET_CLASSES,
2209
+ ):
2210
+ diffuse_color = (
2211
+ max(0, min(diffuse_input.default_value[0], 1.0)),
2212
+ max(0, min(diffuse_input.default_value[1], 1.0)),
2213
+ max(0, min(diffuse_input.default_value[2], 1.0)),
2214
+ )
2215
+ else:
2216
+ diffuse_color = (1.0, 1.0, 1.0)
2217
+
2218
+ # https://github.com/MMD-Blender/blender_mmd_tools/blob/0845cf64aa4928ff7a5b7b52fff5a9bf8c681338/mmd_tools/core/material.py#L539
2219
+ if isinstance(
2220
+ alpha_input := shader_node_group.inputs.get("Alpha"),
2221
+ SCALAR_SOCKET_CLASSES,
2222
+ ):
2223
+ alpha = convert.float_or(alpha_input.default_value, 1.0, 0.0, 1.0)
2224
+ else:
2225
+ alpha = 1.0
2226
+
2227
+ # https://github.com/MMD-Blender/blender_mmd_tools/blob/0845cf64aa4928ff7a5b7b52fff5a9bf8c681338/mmd_tools/core/material.py#L540
2228
+ if isinstance(
2229
+ double_sided_input := shader_node_group.inputs.get("Double Sided"),
2230
+ SCALAR_SOCKET_CLASSES,
2231
+ ):
2232
+ double_sided = bool(double_sided_input.default_value)
2233
+ else:
2234
+ double_sided = False
2235
+
2236
+ return MmdMaterial(
2237
+ texture=texture_image,
2238
+ sphere_texture=None,
2239
+ toon_texture=None,
2240
+ diffuse_color=diffuse_color,
2241
+ double_sided=double_sided,
2242
+ alpha=alpha,
2243
+ )