@volter/blender-engine 0.1.148 → 0.1.149

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +4 -11
  3. package/browser/addons/vrm/LICENSE_(OPTION1)_MIT.txt +22 -0
  4. package/browser/addons/vrm/LICENSE_(OPTION2)_GPL-3_0.txt +674 -0
  5. package/browser/addons/vrm/LICENSE_MAIN.txt +1 -0
  6. package/browser/addons/vrm/README.txt +3 -0
  7. package/browser/addons/vrm/__init__.py +41 -0
  8. package/browser/addons/vrm/blender_manifest.toml +16 -0
  9. package/browser/addons/vrm/common/__init__.py +1 -0
  10. package/browser/addons/vrm/common/animation.py +58 -0
  11. package/browser/addons/vrm/common/blender_manifest.py +82 -0
  12. package/browser/addons/vrm/common/char.py +16 -0
  13. package/browser/addons/vrm/common/convert.py +287 -0
  14. package/browser/addons/vrm/common/convert_any.py +48 -0
  15. package/browser/addons/vrm/common/debug.py +40 -0
  16. package/browser/addons/vrm/common/deep.py +162 -0
  17. package/browser/addons/vrm/common/error_dialog.py +274 -0
  18. package/browser/addons/vrm/common/fs.py +71 -0
  19. package/browser/addons/vrm/common/gl.py +20 -0
  20. package/browser/addons/vrm/common/gltf.py +1149 -0
  21. package/browser/addons/vrm/common/human_bone_mapper/__init__.py +1 -0
  22. package/browser/addons/vrm/common/human_bone_mapper/biped_mapping.py +84 -0
  23. package/browser/addons/vrm/common/human_bone_mapper/cats_blender_plugin_fix_model_mapping.py +63 -0
  24. package/browser/addons/vrm/common/human_bone_mapper/human_bone_mapper.py +203 -0
  25. package/browser/addons/vrm/common/human_bone_mapper/microsoft_rocketbox_mapping.py +69 -0
  26. package/browser/addons/vrm/common/human_bone_mapper/mixamo_mapping.py +62 -0
  27. package/browser/addons/vrm/common/human_bone_mapper/mmd_mapping.py +115 -0
  28. package/browser/addons/vrm/common/human_bone_mapper/mpfb_mapping.py +120 -0
  29. package/browser/addons/vrm/common/human_bone_mapper/ready_player_me_mapping.py +65 -0
  30. package/browser/addons/vrm/common/human_bone_mapper/reallusion_mapping.py +94 -0
  31. package/browser/addons/vrm/common/human_bone_mapper/rigify_meta_rig_mapping.py +65 -0
  32. package/browser/addons/vrm/common/human_bone_mapper/structure_based_mapping.py +891 -0
  33. package/browser/addons/vrm/common/human_bone_mapper/unreal_mapping.py +62 -0
  34. package/browser/addons/vrm/common/human_bone_mapper/vrm_addon_mapping.py +112 -0
  35. package/browser/addons/vrm/common/human_bone_mapper/vroid_mapping.py +89 -0
  36. package/browser/addons/vrm/common/legacy_gltf.py +16 -0
  37. package/browser/addons/vrm/common/logger.py +84 -0
  38. package/browser/addons/vrm/common/mtoon0.blend +0 -0
  39. package/browser/addons/vrm/common/mtoon1.blend +0 -0
  40. package/browser/addons/vrm/common/mtoon1_outline.blend +0 -0
  41. package/browser/addons/vrm/common/mtoon_auto_setup.blend +0 -0
  42. package/browser/addons/vrm/common/mtoon_unversioned.py +62 -0
  43. package/browser/addons/vrm/common/ops/__init__.py +10 -0
  44. package/browser/addons/vrm/common/ops/export_scene.py +67 -0
  45. package/browser/addons/vrm/common/ops/icyp.py +53 -0
  46. package/browser/addons/vrm/common/ops/import_scene.py +57 -0
  47. package/browser/addons/vrm/common/ops/vrm.py +2380 -0
  48. package/browser/addons/vrm/common/ops/wm.py +167 -0
  49. package/browser/addons/vrm/common/preferences.py +362 -0
  50. package/browser/addons/vrm/common/progress.py +87 -0
  51. package/browser/addons/vrm/common/rotation.py +89 -0
  52. package/browser/addons/vrm/common/safe_removal.py +46 -0
  53. package/browser/addons/vrm/common/scene_watcher.py +230 -0
  54. package/browser/addons/vrm/common/shader.py +2243 -0
  55. package/browser/addons/vrm/common/shape_key_mapper/__init__.py +1 -0
  56. package/browser/addons/vrm/common/shape_key_mapper/arkit_mapping.py +168 -0
  57. package/browser/addons/vrm/common/shape_key_mapper/mmd_mapping.py +62 -0
  58. package/browser/addons/vrm/common/shape_key_mapper/ready_player_me_mapping.py +52 -0
  59. package/browser/addons/vrm/common/shape_key_mapper/vrchat_mapping.py +17 -0
  60. package/browser/addons/vrm/common/third_party_user_extension.py +214 -0
  61. package/browser/addons/vrm/common/transparent_z_write.blend +0 -0
  62. package/browser/addons/vrm/common/version.py +161 -0
  63. package/browser/addons/vrm/common/vrm0/__init__.py +1 -0
  64. package/browser/addons/vrm/common/vrm0/human_bone.py +662 -0
  65. package/browser/addons/vrm/common/vrm0/material_property.py +346 -0
  66. package/browser/addons/vrm/common/vrm1/__init__.py +1 -0
  67. package/browser/addons/vrm/common/vrm1/expression_preset.py +207 -0
  68. package/browser/addons/vrm/common/vrm1/human_bone.py +822 -0
  69. package/browser/addons/vrm/common/workspace.py +227 -0
  70. package/browser/addons/vrm/common/writable_context.py +77 -0
  71. package/browser/addons/vrm/documentation/en-us/animation/index.md +63 -0
  72. package/browser/addons/vrm/documentation/en-us/create-humanoid-vrm-from-scratch/index.md +131 -0
  73. package/browser/addons/vrm/documentation/en-us/create-simple-vrm-from-scratch/index.md +30 -0
  74. package/browser/addons/vrm/documentation/en-us/development/index.md +136 -0
  75. package/browser/addons/vrm/documentation/en-us/index.md +41 -0
  76. package/browser/addons/vrm/documentation/en-us/installation/index.md +152 -0
  77. package/browser/addons/vrm/documentation/en-us/material-mtoon/index.md +122 -0
  78. package/browser/addons/vrm/documentation/en-us/material-pbr/index.md +94 -0
  79. package/browser/addons/vrm/documentation/en-us/scripting-api/index.md +391 -0
  80. package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrm/index.md +57 -0
  81. package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrma/index.md +17 -0
  82. package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrm/index.md +61 -0
  83. package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrma/index.md +13 -0
  84. package/browser/addons/vrm/documentation/ja-jp/animation/index.md +57 -0
  85. package/browser/addons/vrm/documentation/ja-jp/create-humanoid-vrm-from-scratch/index.md +129 -0
  86. package/browser/addons/vrm/documentation/ja-jp/create-simple-vrm-from-scratch/index.md +29 -0
  87. package/browser/addons/vrm/documentation/ja-jp/development/index.md +137 -0
  88. package/browser/addons/vrm/documentation/ja-jp/index.md +37 -0
  89. package/browser/addons/vrm/documentation/ja-jp/installation/index.md +150 -0
  90. package/browser/addons/vrm/documentation/ja-jp/material-mtoon/index.md +113 -0
  91. package/browser/addons/vrm/documentation/ja-jp/material-pbr/index.md +90 -0
  92. package/browser/addons/vrm/documentation/ja-jp/scripting-api/index.md +389 -0
  93. package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrm/index.md +55 -0
  94. package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrma/index.md +19 -0
  95. package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrm/index.md +56 -0
  96. package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrma/index.md +14 -0
  97. package/browser/addons/vrm/editor/__init__.py +1 -0
  98. package/browser/addons/vrm/editor/extension.py +425 -0
  99. package/browser/addons/vrm/editor/extension_accessor.py +75 -0
  100. package/browser/addons/vrm/editor/handler.py +84 -0
  101. package/browser/addons/vrm/editor/khr_character/__init__.py +1 -0
  102. package/browser/addons/vrm/editor/khr_character/panel.py +69 -0
  103. package/browser/addons/vrm/editor/khr_character/property_group.py +27 -0
  104. package/browser/addons/vrm/editor/khr_xmp_json_ld/__init__.py +1 -0
  105. package/browser/addons/vrm/editor/khr_xmp_json_ld/ops.py +464 -0
  106. package/browser/addons/vrm/editor/khr_xmp_json_ld/panel.py +136 -0
  107. package/browser/addons/vrm/editor/khr_xmp_json_ld/property_group.py +83 -0
  108. package/browser/addons/vrm/editor/khr_xmp_json_ld/ui_list.py +198 -0
  109. package/browser/addons/vrm/editor/make_armature.py +709 -0
  110. package/browser/addons/vrm/editor/menu.py +147 -0
  111. package/browser/addons/vrm/editor/migration.py +297 -0
  112. package/browser/addons/vrm/editor/mtoon1/__init__.py +1 -0
  113. package/browser/addons/vrm/editor/mtoon1/handler.py +20 -0
  114. package/browser/addons/vrm/editor/mtoon1/migration.py +536 -0
  115. package/browser/addons/vrm/editor/mtoon1/ops.py +304 -0
  116. package/browser/addons/vrm/editor/mtoon1/panel.py +442 -0
  117. package/browser/addons/vrm/editor/mtoon1/property_group.py +4977 -0
  118. package/browser/addons/vrm/editor/mtoon1/scene_watcher.py +381 -0
  119. package/browser/addons/vrm/editor/node_constraint1/__init__.py +1 -0
  120. package/browser/addons/vrm/editor/node_constraint1/panel.py +317 -0
  121. package/browser/addons/vrm/editor/node_constraint1/property_group.py +25 -0
  122. package/browser/addons/vrm/editor/ops.py +471 -0
  123. package/browser/addons/vrm/editor/panel.py +315 -0
  124. package/browser/addons/vrm/editor/property_group.py +888 -0
  125. package/browser/addons/vrm/editor/search.py +682 -0
  126. package/browser/addons/vrm/editor/spring_bone1/__init__.py +1 -0
  127. package/browser/addons/vrm/editor/spring_bone1/handler.py +994 -0
  128. package/browser/addons/vrm/editor/spring_bone1/menu.py +246 -0
  129. package/browser/addons/vrm/editor/spring_bone1/migration.py +86 -0
  130. package/browser/addons/vrm/editor/spring_bone1/ops.py +1679 -0
  131. package/browser/addons/vrm/editor/spring_bone1/panel.py +605 -0
  132. package/browser/addons/vrm/editor/spring_bone1/property_group.py +1457 -0
  133. package/browser/addons/vrm/editor/spring_bone1/ui_list.py +408 -0
  134. package/browser/addons/vrm/editor/subscription.py +123 -0
  135. package/browser/addons/vrm/editor/t_pose.py +713 -0
  136. package/browser/addons/vrm/editor/validation.py +1271 -0
  137. package/browser/addons/vrm/editor/vrm0/__init__.py +1 -0
  138. package/browser/addons/vrm/editor/vrm0/gizmo_group.py +63 -0
  139. package/browser/addons/vrm/editor/vrm0/handler.py +23 -0
  140. package/browser/addons/vrm/editor/vrm0/menu.py +145 -0
  141. package/browser/addons/vrm/editor/vrm0/migration.py +761 -0
  142. package/browser/addons/vrm/editor/vrm0/ops.py +2145 -0
  143. package/browser/addons/vrm/editor/vrm0/panel.py +1333 -0
  144. package/browser/addons/vrm/editor/vrm0/property_group.py +1818 -0
  145. package/browser/addons/vrm/editor/vrm0/ui_list.py +688 -0
  146. package/browser/addons/vrm/editor/vrm1/__init__.py +1 -0
  147. package/browser/addons/vrm/editor/vrm1/handler.py +28 -0
  148. package/browser/addons/vrm/editor/vrm1/menu.py +76 -0
  149. package/browser/addons/vrm/editor/vrm1/migration.py +321 -0
  150. package/browser/addons/vrm/editor/vrm1/ops.py +2679 -0
  151. package/browser/addons/vrm/editor/vrm1/panel.py +1243 -0
  152. package/browser/addons/vrm/editor/vrm1/property_group.py +2087 -0
  153. package/browser/addons/vrm/editor/vrm1/scene_watcher.py +90 -0
  154. package/browser/addons/vrm/editor/vrm1/ui_list.py +533 -0
  155. package/browser/addons/vrm/exporter/__init__.py +1 -0
  156. package/browser/addons/vrm/exporter/abstract_base_vrm_exporter.py +628 -0
  157. package/browser/addons/vrm/exporter/export_scene.py +930 -0
  158. package/browser/addons/vrm/exporter/gltf2_export_user_extension.py +45 -0
  159. package/browser/addons/vrm/exporter/khr_character_exporter.py +34 -0
  160. package/browser/addons/vrm/exporter/vrm0_exporter.py +4020 -0
  161. package/browser/addons/vrm/exporter/vrm1_exporter.py +3682 -0
  162. package/browser/addons/vrm/exporter/vrm_animation_exporter.py +1035 -0
  163. package/browser/addons/vrm/external/__init__.py +1 -0
  164. package/browser/addons/vrm/external/auto_rig_pro_support.py +3 -0
  165. package/browser/addons/vrm/external/io_scene_gltf2_support.py +418 -0
  166. package/browser/addons/vrm/external/rigify_support.py +1 -0
  167. package/browser/addons/vrm/importer/__init__.py +1 -0
  168. package/browser/addons/vrm/importer/abstract_base_vrm_importer.py +1548 -0
  169. package/browser/addons/vrm/importer/file_handler.py +34 -0
  170. package/browser/addons/vrm/importer/gltf2_import_user_extension.py +268 -0
  171. package/browser/addons/vrm/importer/import_scene.py +646 -0
  172. package/browser/addons/vrm/importer/license_validation.py +198 -0
  173. package/browser/addons/vrm/importer/vrm0_importer.py +1723 -0
  174. package/browser/addons/vrm/importer/vrm1_importer.py +2014 -0
  175. package/browser/addons/vrm/importer/vrm_animation_importer.py +763 -0
  176. package/browser/addons/vrm/importer/vrm_diff.py +316 -0
  177. package/browser/addons/vrm/locale/__init__.py +1 -0
  178. package/browser/addons/vrm/locale/ja_jp.py +826 -0
  179. package/browser/addons/vrm/locale/ruff.toml +8 -0
  180. package/browser/addons/vrm/locale/translation_dictionary.py +10 -0
  181. package/browser/addons/vrm/locale/zh_hans.py +928 -0
  182. package/browser/addons/vrm/registration.py +670 -0
  183. package/browser/addons/vrm.json +18 -0
  184. package/browser/blender-emscripten-engine.mts +60 -128
  185. package/browser/blender-engine.mts +20 -186
  186. package/browser/blender-wali-engine.mts +35 -81
  187. package/browser/describe-thrown.mts +3 -6
  188. package/browser/document-chunks.mts +4 -16
  189. package/browser/gltf_export.py +3161 -0
  190. package/browser/heap-growth.mts +23 -0
  191. package/browser/index.ts +2 -0
  192. package/browser/protocol.ts +119 -35
  193. package/browser/pull-job.mts +2 -10
  194. package/browser/rna.ts +190 -73
  195. package/browser/runtime.ts +197 -82
  196. package/browser/session-addons.mts +33 -0
  197. package/browser/session-frame.mts +5 -24
  198. package/browser/session.py +1338 -1427
  199. package/browser/three/attach-presenter.ts +11 -39
  200. package/browser/three/blender-base64.ts +1 -36
  201. package/browser/three/blender-corner-normals.ts +1 -23
  202. package/browser/three/blender-display-shaders.json +1 -1
  203. package/browser/three/blender-display-shaders.lut +0 -0
  204. package/browser/three/blender-display-transform.ts +2 -1
  205. package/browser/three/blender-frame-columns.ts +5 -13
  206. package/browser/three/blender-graph-material.ts +18 -3
  207. package/browser/three/blender-node-graph.ts +10 -34
  208. package/browser/three/blender-physical-material.ts +2 -31
  209. package/browser/three/blender-runtime-armature.ts +7 -60
  210. package/browser/three/blender-runtime-frame.ts +9 -23
  211. package/browser/three/blender-runtime-geometry.ts +22 -26
  212. package/browser/three/blender-runtime-lighting.ts +48 -178
  213. package/browser/three/blender-runtime-shadows.ts +187 -1
  214. package/browser/three/blender-runtime-skeleton.ts +53 -1
  215. package/browser/three/blender-runtime-view.ts +505 -283
  216. package/browser/three/blender-runtime-weights.ts +9 -72
  217. package/browser/three/blender-sky.ts +13 -33
  218. package/browser/three/blender-triangulate.ts +7 -35
  219. package/browser/three/blender-viewport-sampling.ts +235 -0
  220. package/browser/three/blender-workbench-material.ts +25 -29
  221. package/browser/three/blender-workbench-outline.ts +199 -0
  222. package/browser/three/blender-world-volume.ts +62 -10
  223. package/browser/three/presenter.ts +10 -26
  224. package/browser/three/release.ts +5 -21
  225. package/browser/three/sky-precompute-worker.ts +1 -18
  226. package/browser/three/sky-worker.ts +5 -23
  227. package/browser/three/world-field-sampler.ts +12 -26
  228. package/browser/volter_three.py +9 -105
  229. package/browser/worker.ts +156 -151
  230. package/package.json +5 -2
  231. package/wasm/BUNDLE.json +17 -17
  232. package/wasm/DEPENDENCY-LICENSES.txt +14 -0
  233. package/wasm/blender_browser.data.br +0 -0
  234. package/wasm/blender_browser.js +1 -1
  235. package/wasm/blender_browser.wasm.br +0 -0
  236. package/browser/frame-transfer.mts +0 -19
  237. package/browser/three/agx-base-srgb.lut +0 -0
  238. package/browser/three/agx-look-medium-high-contrast.lut +0 -0
  239. package/browser/three/agx-look-punchy.lut +0 -0
  240. package/browser/three/blender-agx.ts +0 -235
  241. package/browser/three/blender-display-lut.ts +0 -145
  242. package/browser/three/blender-filmic.ts +0 -49
  243. package/browser/three/blender-standard.ts +0 -68
  244. package/browser/three/blender-world-visibility.ts +0 -278
  245. package/browser/three/filmic-srgb.lut +0 -0
@@ -0,0 +1,4977 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import math
3
+ import re
4
+ import sys
5
+ from collections.abc import Callable, Mapping, Sequence
6
+ from dataclasses import dataclass
7
+ from sys import float_info
8
+ from typing import TYPE_CHECKING, Final, Optional, Protocol, Union
9
+
10
+ import bpy
11
+ from bpy.props import (
12
+ BoolProperty,
13
+ EnumProperty,
14
+ FloatProperty,
15
+ FloatVectorProperty,
16
+ IntProperty,
17
+ IntVectorProperty,
18
+ PointerProperty,
19
+ )
20
+ from bpy.types import (
21
+ Context,
22
+ Driver,
23
+ FCurve,
24
+ Image,
25
+ Material,
26
+ Mesh,
27
+ Node,
28
+ NodeReroute,
29
+ NodesModifier,
30
+ NodeSocketColor,
31
+ NodeSocketFloat,
32
+ NodeTree,
33
+ Object,
34
+ PropertyGroup,
35
+ ShaderNodeBsdfPrincipled,
36
+ ShaderNodeEmission,
37
+ ShaderNodeGroup,
38
+ ShaderNodeMapping,
39
+ ShaderNodeMath,
40
+ ShaderNodeNormalMap,
41
+ ShaderNodeOutputMaterial,
42
+ ShaderNodeTexImage,
43
+ )
44
+ from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
45
+ from mathutils import Vector
46
+
47
+ from ...common import convert, shader
48
+ from ...common.gl import (
49
+ GL_CLAMP_TO_EDGE,
50
+ GL_LINEAR,
51
+ GL_LINEAR_MIPMAP_LINEAR,
52
+ GL_LINEAR_MIPMAP_NEAREST,
53
+ GL_MIRRORED_REPEAT,
54
+ GL_NEAREST,
55
+ GL_NEAREST_MIPMAP_LINEAR,
56
+ GL_NEAREST_MIPMAP_NEAREST,
57
+ GL_REPEAT,
58
+ )
59
+ from ...common.logger import get_logger
60
+ from ...common.preferences import VrmAddonPreferences, get_preferences
61
+ from ...common.shader import LegacyAddonMaterial, MmdMaterial
62
+ from ...common.version import get_addon_version
63
+ from ..extension_accessor import get_material_extension
64
+ from ..property_group import property_group_enum
65
+
66
+ _logger = get_logger(__name__)
67
+
68
+
69
+ MTOON1_OUTPUT_NODE_GROUP_NAME: Final = "Mtoon1Material.Mtoon1Output"
70
+
71
+ PRINCIPLED_BSDF_BASE_COLOR_INPUT_KEY: Final = "Base Color"
72
+ PRINCIPLED_BSDF_ALPHA_INPUT_KEY: Final = "Alpha"
73
+ PRINCIPLED_BSDF_EMISSION_INPUT_KEY: Final = (
74
+ "Emission Color" if bpy.app.version >= (4,) else "Emission"
75
+ )
76
+ PRINCIPLED_BSDF_EMISSION_STRENGTH_INPUT_KEY: Final = "Emission Strength"
77
+ PRINCIPLED_BSDF_NORMAL_INPUT_KEY: Final = "Normal"
78
+ NORMAL_MAP_COLOR_INPUT_KEY: Final = "Color"
79
+ EMISSION_COLOR_INPUT_KEY: Final = "Color"
80
+ EMISSION_STRENGTH_INPUT_KEY: Final = "Strength"
81
+ TEX_IMAGE_COLOR_OUTPUT_KEY: Final = "Color"
82
+ TEX_IMAGE_ALPHA_OUTPUT_KEY: Final = "Alpha"
83
+
84
+ IMAGE_INTERPOLATION_CLOSEST: Final = "Closest"
85
+ IMAGE_INTERPOLATION_LINEAR: Final = "Linear"
86
+ IMAGE_INTERPOLATION_CUBIC: Final = "Cubic"
87
+ IMAGE_INTERPOLATION_SMART: Final = "Smart"
88
+ GL_LINEAR_IMAGE_INTERPOLATIONS: Final = (
89
+ IMAGE_INTERPOLATION_LINEAR,
90
+ IMAGE_INTERPOLATION_CUBIC,
91
+ IMAGE_INTERPOLATION_SMART,
92
+ )
93
+
94
+ ALPHA_CLIP_INPUT_NODE_NAME: Final = "Mtoon1Material.AlphaClip.Input"
95
+ ALPHA_CLIP_INPUT_NODE_SOCKET_NAME: Final = "Value"
96
+ ALPHA_CLIP_OUTPUT_NODE_NAME: Final = "Mtoon1Material.AlphaClip.Output"
97
+ ALPHA_CLIP_OUTPUT_NODE_SOCKET_NAME: Final = "Value"
98
+
99
+
100
+ def _get_gltf_emissive_node(material: Material) -> Optional[ShaderNodeEmission]:
101
+ node_tree = material.node_tree
102
+ if not node_tree:
103
+ return None
104
+ return next(
105
+ (
106
+ node
107
+ for node in node_tree.nodes
108
+ if isinstance(node, ShaderNodeEmission)
109
+ and node.name == "Mtoon1Material.GltfEmissive"
110
+ ),
111
+ None,
112
+ )
113
+
114
+
115
+ class NodeSocketTarget(Protocol):
116
+ def get_in_socket_name(self) -> str: ...
117
+
118
+ def create_node_selector(self, material: Material) -> Callable[[Node], bool]: ...
119
+
120
+
121
+ class PrincipledBsdfNodeSocketTarget(NodeSocketTarget):
122
+ def __init__(self, *, in_socket_name: str) -> None:
123
+ self.in_socket_name = in_socket_name
124
+
125
+ def get_in_socket_name(self) -> str:
126
+ return self.in_socket_name
127
+
128
+ @staticmethod
129
+ def get_node_name(material: Material) -> Optional[str]:
130
+ # node has a short lifetime on the native side and is dangerous, so avoid
131
+ # exposing it outside the function
132
+ node = PrincipledBSDFWrapper(material).node_principled_bsdf
133
+ if node is None:
134
+ return None
135
+ # Use intern to avoid using str directly referenced from node that will be
136
+ # destroyed in the future
137
+ # Previously, using str referenced from destroyed Bone caused problems,
138
+ # so this is a precautionary measure, though it might be overly cautious
139
+ return sys.intern(node.name)
140
+
141
+ def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
142
+ name = self.get_node_name(material)
143
+ if name is None:
144
+ return lambda _: False
145
+ return lambda node: (
146
+ isinstance(node, ShaderNodeBsdfPrincipled) and node.name == name
147
+ )
148
+
149
+
150
+ class StaticNodeSocketTarget(NodeSocketTarget):
151
+ def __init__(
152
+ self, *, in_node_name: str, in_node_type: type[Node], in_socket_name: str
153
+ ) -> None:
154
+ self.in_node_name = in_node_name
155
+ self.in_node_type = in_node_type
156
+ self.in_socket_name = in_socket_name
157
+
158
+ def get_in_socket_name(self) -> str:
159
+ return self.in_socket_name
160
+
161
+ def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
162
+ _ = material
163
+ return lambda node: (
164
+ isinstance(node, self.in_node_type) and node.name == self.in_node_name
165
+ )
166
+
167
+
168
+ class PrincipledBsdfNormalMapNodeSocketTarget(NodeSocketTarget):
169
+ def get_in_socket_name(self) -> str:
170
+ return NORMAL_MAP_COLOR_INPUT_KEY
171
+
172
+ @staticmethod
173
+ def get_node_name(material: Material) -> Optional[str]:
174
+ # node has a short lifetime on the native side and is dangerous, so avoid
175
+ # exposing it outside the function
176
+ node = PrincipledBSDFWrapper(material).node_normalmap
177
+ if node is None:
178
+ return None
179
+ # Use intern to avoid using str directly referenced from node that will be
180
+ # destroyed in the future
181
+ # Previously, using str referenced from destroyed Bone caused problems,
182
+ # so this is a precautionary measure, though it might be overly cautious
183
+ return sys.intern(node.name)
184
+
185
+ def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
186
+ name = self.get_node_name(material)
187
+ if name is None:
188
+ return lambda _: False
189
+ return lambda node: isinstance(node, ShaderNodeNormalMap) and node.name == name
190
+
191
+
192
+ class GltfEmissionNodeSocketTarget(NodeSocketTarget):
193
+ def get_in_socket_name(self) -> str:
194
+ return EMISSION_COLOR_INPUT_KEY
195
+
196
+ def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
197
+ _ = material
198
+ # https://github.com/KhronosGroup/glTF-Blender-IO/pull/740
199
+ return lambda node: isinstance(node, ShaderNodeEmission)
200
+
201
+
202
+ class NodeGroupSocketTarget(NodeSocketTarget):
203
+ def __init__(self, *, node_group_node_tree_name: str, in_socket_name: str) -> None:
204
+ self.node_group_node_tree_name = node_group_node_tree_name
205
+ self.in_socket_name = in_socket_name
206
+
207
+ def get_in_socket_name(self) -> str:
208
+ return self.in_socket_name
209
+
210
+ def select_node(self, node: Node) -> bool:
211
+ if not isinstance(node, ShaderNodeGroup):
212
+ return False
213
+ node_tree = node.node_tree
214
+ if not node_tree:
215
+ return False
216
+ return node_tree.name == self.node_group_node_tree_name
217
+
218
+ def create_node_selector(self, material: Material) -> Callable[[Node], bool]:
219
+ _ = material
220
+ return self.select_node
221
+
222
+
223
+ class MaterialTraceablePropertyGroup(PropertyGroup):
224
+ def find_material(self) -> Material:
225
+ id_data = self.id_data
226
+ if isinstance(id_data, Material):
227
+ return id_data
228
+
229
+ message = (
230
+ f"{type(self)}/{self}.id_data is not a {Material}"
231
+ + f" but {type(id_data)}/{id_data}"
232
+ )
233
+ raise AssertionError(message)
234
+
235
+ def find_outline_property_group(
236
+ self, material: Material
237
+ ) -> Optional["MaterialTraceablePropertyGroup"]:
238
+ if get_material_extension(material).mtoon1.is_outline_material:
239
+ return None
240
+ outline_material = get_material_extension(material).mtoon1.outline_material
241
+ if not outline_material:
242
+ return None
243
+ if material.name == outline_material.name:
244
+ _logger.error(
245
+ "Base material and outline material are same. name=%s",
246
+ material.name,
247
+ )
248
+ return None
249
+ path_from_id = self.path_from_id()
250
+ property_group = outline_material.path_resolve(path_from_id, False)
251
+ if isinstance(property_group, MaterialTraceablePropertyGroup):
252
+ return property_group
253
+ message = f"No matching property group: {type(self)}/{self} {path_from_id}"
254
+ raise AssertionError(message)
255
+
256
+ def get_bool(
257
+ self,
258
+ node_group_name: str,
259
+ group_label: str,
260
+ *,
261
+ default_value: bool,
262
+ ) -> bool:
263
+ value = self.get_value(
264
+ node_group_name, group_label, default_value=int(default_value)
265
+ )
266
+ if isinstance(value, float):
267
+ return math.fabs(value) > 0.000001
268
+ return bool(value)
269
+
270
+ def get_float(
271
+ self,
272
+ node_group_name: str,
273
+ group_label: str,
274
+ *,
275
+ default_value: float,
276
+ ) -> float:
277
+ value = self.get_value(
278
+ node_group_name, group_label, default_value=default_value
279
+ )
280
+ return float(value)
281
+
282
+ def get_int(
283
+ self,
284
+ node_group_name: str,
285
+ group_label: str,
286
+ *,
287
+ default_value: int,
288
+ ) -> int:
289
+ value = self.get_value(
290
+ node_group_name, group_label, default_value=default_value
291
+ )
292
+ if isinstance(value, bool):
293
+ return int(value)
294
+ if isinstance(value, int):
295
+ return value
296
+ return round(value)
297
+
298
+ def get_value(
299
+ self,
300
+ node_group_name: str,
301
+ group_label: str,
302
+ *,
303
+ default_value: float,
304
+ ) -> Union[float, int, bool]:
305
+ material = self.find_material()
306
+ node_tree = material.node_tree
307
+ if not node_tree:
308
+ return default_value
309
+
310
+ node = next(
311
+ (
312
+ node
313
+ for node in node_tree.nodes
314
+ if isinstance(node, ShaderNodeGroup)
315
+ and node.node_tree
316
+ and node.node_tree.name == node_group_name
317
+ ),
318
+ None,
319
+ )
320
+ if not node:
321
+ return default_value
322
+
323
+ socket = node.inputs.get(group_label)
324
+ if isinstance(
325
+ socket,
326
+ (
327
+ *shader.BOOL_SOCKET_CLASSES,
328
+ *shader.FLOAT_SOCKET_CLASSES,
329
+ *shader.INT_SOCKET_CLASSES,
330
+ ),
331
+ ):
332
+ return socket.default_value
333
+
334
+ return default_value
335
+
336
+ def get_rgb(
337
+ self,
338
+ node_group_name: str,
339
+ group_label: str,
340
+ *,
341
+ default_value: tuple[float, float, float],
342
+ ) -> tuple[float, float, float]:
343
+ material = self.find_material()
344
+ node_tree = material.node_tree
345
+ if not node_tree:
346
+ return default_value
347
+
348
+ node = next(
349
+ (
350
+ node
351
+ for node in node_tree.nodes
352
+ if isinstance(node, ShaderNodeGroup)
353
+ and node.node_tree
354
+ and node.node_tree.name == node_group_name
355
+ ),
356
+ None,
357
+ )
358
+ if not node:
359
+ return default_value
360
+
361
+ socket = node.inputs.get(group_label)
362
+ if isinstance(socket, shader.COLOR_SOCKET_CLASSES):
363
+ return (
364
+ socket.default_value[0],
365
+ socket.default_value[1],
366
+ socket.default_value[2],
367
+ )
368
+
369
+ return default_value
370
+
371
+ def set_value(
372
+ self,
373
+ node_group_name: str,
374
+ group_label: str,
375
+ value_obj: object,
376
+ ) -> None:
377
+ if isinstance(value_obj, float):
378
+ value = convert.float_or_none(value_obj)
379
+ if value is None:
380
+ return
381
+ elif isinstance(value_obj, int):
382
+ value = value_obj
383
+ else:
384
+ return
385
+
386
+ material = self.find_material()
387
+ outline = self.find_outline_property_group(material)
388
+ if outline:
389
+ outline.set_value(node_group_name, group_label, value)
390
+
391
+ node_tree = material.node_tree
392
+ if not node_tree:
393
+ return
394
+
395
+ node = next(
396
+ (
397
+ node
398
+ for node in node_tree.nodes
399
+ if isinstance(node, ShaderNodeGroup)
400
+ and node.node_tree
401
+ and node.node_tree.name == node_group_name
402
+ ),
403
+ None,
404
+ )
405
+ if not node:
406
+ _logger.warning('No group node "%s"', node_group_name)
407
+ return
408
+
409
+ socket = node.inputs.get(group_label)
410
+ if isinstance(socket, shader.BOOL_SOCKET_CLASSES):
411
+ socket.default_value = bool(value)
412
+ elif isinstance(socket, shader.FLOAT_SOCKET_CLASSES):
413
+ socket.default_value = float(value)
414
+ elif isinstance(socket, shader.INT_SOCKET_CLASSES):
415
+ socket.default_value = int(value)
416
+ else:
417
+ _logger.warning(
418
+ 'No "%s" in shader node group "%s"', group_label, node_group_name
419
+ )
420
+
421
+ def set_bool(
422
+ self,
423
+ node_group_name: str,
424
+ group_label: str,
425
+ value: object,
426
+ ) -> None:
427
+ self.set_value(node_group_name, group_label, 1 if value else 0)
428
+
429
+ def set_int(
430
+ self,
431
+ node_group_name: str,
432
+ group_label: str,
433
+ value: object,
434
+ ) -> None:
435
+ self.set_value(node_group_name, group_label, value)
436
+
437
+ def set_rgba(
438
+ self,
439
+ node_group_name: str,
440
+ group_label: str,
441
+ value: object,
442
+ default_value: Optional[tuple[float, float, float, float]] = None,
443
+ ) -> None:
444
+ material = self.find_material()
445
+
446
+ outline = self.find_outline_property_group(material)
447
+ if outline:
448
+ outline.set_rgba(node_group_name, group_label, value, default_value)
449
+
450
+ if not default_value:
451
+ default_value = (0.0, 0.0, 0.0, 0.0)
452
+
453
+ node_tree = material.node_tree
454
+ if not node_tree:
455
+ return
456
+
457
+ rgba = shader.rgba_or_none(value) or default_value
458
+
459
+ node = next(
460
+ (
461
+ node
462
+ for node in node_tree.nodes
463
+ if isinstance(node, ShaderNodeGroup)
464
+ and node.node_tree
465
+ and node.node_tree.name == node_group_name
466
+ ),
467
+ None,
468
+ )
469
+ if not node:
470
+ _logger.warning('No group node "%s"', node_group_name)
471
+ return
472
+
473
+ socket = node.inputs.get(group_label)
474
+ if not isinstance(socket, shader.COLOR_SOCKET_CLASSES):
475
+ _logger.warning(
476
+ 'No "%s" in shader node group "%s"', group_label, node_group_name
477
+ )
478
+ return
479
+
480
+ socket.default_value = rgba
481
+
482
+ def set_rgb(
483
+ self,
484
+ node_group_name: str,
485
+ group_label: str,
486
+ value: object,
487
+ default_value: Optional[tuple[float, float, float]] = None,
488
+ ) -> None:
489
+ material = self.find_material()
490
+
491
+ outline = self.find_outline_property_group(material)
492
+ if outline:
493
+ outline.set_rgb(node_group_name, group_label, value, default_value)
494
+
495
+ if not default_value:
496
+ default_value = (0.0, 0.0, 0.0)
497
+
498
+ node_tree = material.node_tree
499
+ if not node_tree:
500
+ return
501
+
502
+ rgb = shader.rgb_or_none(value) or default_value
503
+
504
+ node = next(
505
+ (
506
+ node
507
+ for node in node_tree.nodes
508
+ if isinstance(node, ShaderNodeGroup)
509
+ and node.node_tree
510
+ and node.node_tree.name == node_group_name
511
+ ),
512
+ None,
513
+ )
514
+ if not node:
515
+ _logger.warning('No group node "%s"', node_group_name)
516
+ return
517
+
518
+ socket = node.inputs.get(group_label)
519
+ if not isinstance(socket, shader.COLOR_SOCKET_CLASSES):
520
+ _logger.warning(
521
+ 'No "%s" in shader node group "%s"', group_label, node_group_name
522
+ )
523
+ return
524
+
525
+ socket.default_value = (*rgb, 1.0)
526
+
527
+ def set_vector3(
528
+ self,
529
+ node_group_name: str,
530
+ group_label: str,
531
+ value: object,
532
+ default_value: Optional[tuple[float, float, float]] = None,
533
+ ) -> None:
534
+ material = self.find_material()
535
+
536
+ outline = self.find_outline_property_group(material)
537
+ if outline:
538
+ outline.set_vector3(node_group_name, group_label, value, default_value)
539
+
540
+ if not default_value:
541
+ default_value = (0.0, 0.0, 0.0)
542
+
543
+ node_tree = material.node_tree
544
+ if not node_tree:
545
+ return
546
+
547
+ vector3 = convert.float3_or_none(value) or default_value
548
+
549
+ node = next(
550
+ (
551
+ node
552
+ for node in node_tree.nodes
553
+ if isinstance(node, ShaderNodeGroup)
554
+ and node.node_tree
555
+ and node.node_tree.name == node_group_name
556
+ ),
557
+ None,
558
+ )
559
+ if not node:
560
+ _logger.warning('No group node "%s"', node_group_name)
561
+ return
562
+
563
+ socket = node.inputs.get(group_label)
564
+ if not isinstance(socket, shader.VECTOR_SOCKET_CLASSES):
565
+ _logger.warning(
566
+ 'No "%s" in shader node group "%s"', group_label, node_group_name
567
+ )
568
+ return
569
+
570
+ socket.default_value = vector3
571
+
572
+
573
+ class TextureTraceablePropertyGroup(MaterialTraceablePropertyGroup):
574
+ def get_texture_info_property_group(self) -> "Mtoon1TextureInfoPropertyGroup":
575
+ chain = self.path_from_id().split(".")
576
+ if chain[-1:] == ["sampler"]:
577
+ chain = chain[:-1]
578
+ if chain[-1:] == ["index"]:
579
+ chain = chain[:-1]
580
+ if chain[-1:] == ["khr_texture_transform"]:
581
+ chain = chain[:-1]
582
+ if chain[-1:] == ["extensions"]:
583
+ chain = chain[:-1]
584
+ material = self.find_material()
585
+ property_group = material.path_resolve(".".join(chain), False)
586
+ if not isinstance(property_group, Mtoon1TextureInfoPropertyGroup):
587
+ message = f"{property_group} is not a Mtoon1TextureInfoPropertyGroup"
588
+ raise TypeError(message)
589
+ return property_group
590
+
591
+ def get_texture_node_name(self, extra: str) -> str:
592
+ texture_info = self.get_texture_info_property_group()
593
+ name = type(texture_info.index).__name__
594
+ return re.sub("PropertyGroup$", "", name) + "." + extra
595
+
596
+ def get_image_texture_node_name(self) -> str:
597
+ return self.get_texture_node_name("Image")
598
+
599
+ def get_image_texture_uv_node_name(self) -> str:
600
+ return self.get_texture_node_name("Uv")
601
+
602
+ @classmethod
603
+ def link_nodes(
604
+ cls,
605
+ material: Material,
606
+ out_node_name: str,
607
+ out_node_type: type[Node],
608
+ out_node_socket_name: str,
609
+ node_socket_target: NodeSocketTarget,
610
+ ) -> None:
611
+ node_tree = material.node_tree
612
+ if not node_tree:
613
+ return
614
+
615
+ select_in_node = node_socket_target.create_node_selector(material)
616
+ in_socket_name = node_socket_target.get_in_socket_name()
617
+
618
+ # Check if already connected
619
+ connection_check_node = next(
620
+ (
621
+ link.from_node
622
+ for link in node_tree.links
623
+ if select_in_node(link.to_node)
624
+ and link.to_socket
625
+ and link.to_socket.name == in_socket_name
626
+ ),
627
+ None,
628
+ )
629
+ while connection_check_node:
630
+ if (
631
+ isinstance(connection_check_node, out_node_type)
632
+ and connection_check_node.name == out_node_name
633
+ ):
634
+ return
635
+ if isinstance(connection_check_node, NodeReroute):
636
+ connection_check_inputs = connection_check_node.inputs
637
+ if not connection_check_inputs:
638
+ break
639
+ connection_check_links = connection_check_inputs[0].links
640
+ if not connection_check_links:
641
+ break
642
+ connection_check_link = connection_check_links[0]
643
+ connection_check_node = connection_check_link.from_node
644
+ continue
645
+ break
646
+
647
+ # If connected to unrelated node, disconnect the link
648
+ cls.unlink_nodes(material, node_socket_target)
649
+
650
+ # Search for output node and socket
651
+ out_node = node_tree.nodes.get(out_node_name)
652
+ if not isinstance(out_node, out_node_type):
653
+ _logger.error("No output node: %s", out_node_name)
654
+ return
655
+ out_socket = out_node.outputs.get(out_node_socket_name)
656
+ if not out_socket:
657
+ _logger.error("No output node socket: %s", out_node_socket_name)
658
+ return
659
+ traversing_sockets = [out_socket]
660
+ while traversing_sockets:
661
+ traversing_socket = traversing_sockets.pop()
662
+ for connection_check_link in traversing_socket.links:
663
+ connection_check_node = connection_check_link.to_node
664
+ if not isinstance(connection_check_node, NodeReroute):
665
+ continue
666
+ if connection_check_node.label != in_socket_name:
667
+ traversing_sockets.extend(connection_check_node.outputs)
668
+ continue
669
+ traversing_outputs = connection_check_node.outputs
670
+ if not traversing_outputs:
671
+ continue
672
+ out_socket = traversing_outputs[0]
673
+ out_node = connection_check_node
674
+ break
675
+
676
+ # Search for input node and socket
677
+ in_node = next(
678
+ (n for n in node_tree.nodes if select_in_node(n)),
679
+ None,
680
+ )
681
+ if not in_node:
682
+ _logger.error("No input node")
683
+ return
684
+
685
+ in_socket = in_node.inputs.get(in_socket_name)
686
+ if not in_socket:
687
+ _logger.error("No input socket: %s", in_socket_name)
688
+ return
689
+
690
+ node_tree.links.new(in_socket, out_socket)
691
+
692
+ @classmethod
693
+ def unlink_nodes(
694
+ cls,
695
+ material: Material,
696
+ node_socket_target: NodeSocketTarget,
697
+ ) -> None:
698
+ while True:
699
+ # Refresh in_node/out_node. These nodes may be invalidated.
700
+ node_tree = material.node_tree
701
+ if not node_tree:
702
+ return
703
+
704
+ select_in_node = node_socket_target.create_node_selector(material)
705
+ in_socket_name = node_socket_target.get_in_socket_name()
706
+ disconnecting_link = next(
707
+ (
708
+ link
709
+ for link in node_tree.links
710
+ if select_in_node(link.to_node)
711
+ and link.to_socket
712
+ and link.to_socket.name == in_socket_name
713
+ ),
714
+ None,
715
+ )
716
+ if not disconnecting_link:
717
+ return
718
+
719
+ node_tree.links.remove(disconnecting_link)
720
+
721
+ @classmethod
722
+ def link_or_unlink_nodes(
723
+ cls,
724
+ material: Material,
725
+ out_node_name: str,
726
+ out_node_type: type[Node],
727
+ out_node_socket_name: str,
728
+ node_socket_target: NodeSocketTarget,
729
+ *,
730
+ link: bool,
731
+ ) -> None:
732
+ if link:
733
+ cls.link_nodes(
734
+ material,
735
+ out_node_name,
736
+ out_node_type,
737
+ out_node_socket_name,
738
+ node_socket_target,
739
+ )
740
+ else:
741
+ cls.unlink_nodes(
742
+ material,
743
+ node_socket_target,
744
+ )
745
+
746
+ def update_image(self, image: Optional[Image]) -> None:
747
+ material = self.find_material()
748
+
749
+ outline = self.find_outline_property_group(material)
750
+ if outline and isinstance(outline, TextureTraceablePropertyGroup):
751
+ outline.update_image(image)
752
+
753
+ node_tree = material.node_tree
754
+ if not node_tree:
755
+ return
756
+
757
+ node_name = self.get_image_texture_node_name()
758
+
759
+ node = node_tree.nodes.get(node_name)
760
+ if not isinstance(node, ShaderNodeTexImage):
761
+ _logger.warning('No shader node tex image "%s"', node_name)
762
+ return
763
+
764
+ node.image = image
765
+
766
+ texture_info = self.get_texture_info_property_group()
767
+
768
+ for (
769
+ output_socket_name,
770
+ node_socket_targets,
771
+ ) in texture_info.node_socket_targets.items():
772
+ for node_socket_target in node_socket_targets:
773
+ self.link_or_unlink_nodes(
774
+ material,
775
+ node_name,
776
+ ShaderNodeTexImage,
777
+ output_socket_name,
778
+ node_socket_target,
779
+ link=bool(image),
780
+ )
781
+
782
+ texture_info.setup_drivers(material)
783
+
784
+ def get_connected_node_image(self) -> Optional[Image]:
785
+ material = self.find_material()
786
+ if bpy.app.version < (5, 0, 0) and not material.use_nodes:
787
+ return None
788
+
789
+ node_tree = material.node_tree
790
+ if not node_tree:
791
+ return None
792
+
793
+ texture_info = self.get_texture_info_property_group()
794
+ node_socket_targets = next(
795
+ iter(texture_info.node_socket_targets.values()), None
796
+ )
797
+ if not node_socket_targets:
798
+ return None
799
+
800
+ node_socket_target = next(iter(node_socket_targets), None)
801
+ if not node_socket_target:
802
+ return None
803
+
804
+ node_selector = node_socket_target.create_node_selector(material)
805
+ node = next((node for node in node_tree.nodes if node_selector(node)), None)
806
+ if not node:
807
+ return None
808
+
809
+ traversing_input_socket = None
810
+ for input_socket in node.inputs:
811
+ if input_socket.name == node_socket_target.get_in_socket_name():
812
+ traversing_input_socket = input_socket
813
+ break
814
+
815
+ while True:
816
+ if not traversing_input_socket:
817
+ return None
818
+
819
+ links_to_input_socket = traversing_input_socket.links
820
+ traversing_input_socket = None
821
+ for link_to_input_socket in links_to_input_socket:
822
+ from_node = link_to_input_socket.from_node
823
+ if isinstance(from_node, ShaderNodeTexImage):
824
+ return from_node.image
825
+ if isinstance(from_node, NodeReroute):
826
+ traversing_input_socket = from_node.inputs[0]
827
+ break
828
+
829
+ def get_texture_uv_int(self, name: str, default_value: int) -> int:
830
+ node_name = self.get_image_texture_uv_node_name()
831
+ material = self.find_material()
832
+ if not material.node_tree:
833
+ return default_value
834
+ node_tree = material.node_tree
835
+ node = node_tree.nodes.get(node_name)
836
+ if not isinstance(node, ShaderNodeGroup):
837
+ return default_value
838
+ socket = node.inputs.get(name)
839
+ if not socket:
840
+ _logger.warning('No "%s" in shader node group "%s"', name, node_name)
841
+ return default_value
842
+
843
+ if isinstance(socket, shader.FLOAT_SOCKET_CLASSES):
844
+ return round(socket.default_value)
845
+ if isinstance(socket, shader.INT_SOCKET_CLASSES):
846
+ return socket.default_value
847
+
848
+ return default_value
849
+
850
+ def get_texture_uv_float(self, name: str, default_value: float) -> float:
851
+ node_name = self.get_image_texture_uv_node_name()
852
+ material = self.find_material()
853
+ if not material.node_tree:
854
+ return default_value
855
+ node_tree = material.node_tree
856
+ node = node_tree.nodes.get(node_name)
857
+ if not isinstance(node, ShaderNodeGroup):
858
+ return default_value
859
+ socket = node.inputs.get(name)
860
+ if not socket:
861
+ _logger.warning('No "%s" in shader node group "%s"', name, node_name)
862
+ return default_value
863
+
864
+ if isinstance(socket, shader.FLOAT_SOCKET_CLASSES):
865
+ return socket.default_value
866
+ if isinstance(socket, shader.INT_SOCKET_CLASSES):
867
+ return float(socket.default_value)
868
+
869
+ return default_value
870
+
871
+ def set_texture_uv(self, name: str, value: object) -> None:
872
+ node_name = self.get_image_texture_uv_node_name()
873
+ material = self.find_material()
874
+ if not material.node_tree:
875
+ return
876
+ node_tree = material.node_tree
877
+ node = node_tree.nodes.get(node_name)
878
+ if not isinstance(node, ShaderNodeGroup):
879
+ return
880
+ socket = node.inputs.get(name)
881
+ if not socket:
882
+ _logger.warning('No "%s" in shader node group "%s"', name, node_name)
883
+ return
884
+
885
+ if isinstance(value, (float, int)):
886
+ if isinstance(socket, shader.FLOAT_SOCKET_CLASSES):
887
+ socket.default_value = float(value)
888
+ if isinstance(socket, shader.INT_SOCKET_CLASSES):
889
+ socket.default_value = int(value)
890
+ elif (vector2 := convert.float2_or_none(value)) is not None and isinstance(
891
+ socket, shader.VECTOR_SOCKET_CLASSES
892
+ ):
893
+ socket.default_value = (*vector2, 0.0)
894
+
895
+ outline = self.find_outline_property_group(material)
896
+ if not outline:
897
+ return
898
+ if not isinstance(outline, TextureTraceablePropertyGroup):
899
+ return
900
+ outline.set_texture_uv(name, value)
901
+
902
+
903
+ class Mtoon1KhrTextureTransformPropertyGroup(TextureTraceablePropertyGroup):
904
+ def is_default(self) -> bool:
905
+ offset_x, offset_y = self.offset
906
+ scale_x, scale_y = self.scale
907
+ return (
908
+ abs(offset_x - shader.UV_GROUP_UV_OFFSET_X_DEFAULT) < float_info.epsilon
909
+ and abs(offset_y - shader.UV_GROUP_UV_OFFSET_Y_DEFAULT) < float_info.epsilon
910
+ and abs(scale_x - shader.UV_GROUP_UV_SCALE_X_DEFAULT) < float_info.epsilon
911
+ and abs(scale_y - shader.UV_GROUP_UV_SCALE_Y_DEFAULT) < float_info.epsilon
912
+ )
913
+
914
+ def _call_overridden_get_texture_offset(self) -> tuple[float, float]:
915
+ return self._get_texture_offset()
916
+
917
+ def _get_texture_offset(self) -> tuple[float, float]:
918
+ return (0, 0)
919
+
920
+ def _call_overridden_set_texture_offset(self, value: object) -> None:
921
+ self._set_texture_offset(value)
922
+
923
+ def _set_texture_offset(self, value: object) -> None:
924
+ _ = value
925
+
926
+ def _call_overridden_get_texture_scale(self) -> tuple[float, float]:
927
+ return self._get_texture_scale()
928
+
929
+ def _get_texture_scale(self) -> tuple[float, float]:
930
+ return 1, 1
931
+
932
+ def _call_overridden_set_texture_scale(self, value: object) -> None:
933
+ self._set_texture_scale(value)
934
+
935
+ def _set_texture_scale(self, value: object) -> None:
936
+ _ = value
937
+
938
+ offset: FloatVectorProperty( # type: ignore[valid-type]
939
+ name="Offset",
940
+ size=2,
941
+ default=(
942
+ shader.UV_GROUP_UV_OFFSET_X_DEFAULT,
943
+ shader.UV_GROUP_UV_OFFSET_Y_DEFAULT,
944
+ ),
945
+ get=_call_overridden_get_texture_offset,
946
+ set=_call_overridden_set_texture_offset,
947
+ )
948
+
949
+ scale: FloatVectorProperty( # type: ignore[valid-type]
950
+ name="Scale",
951
+ size=2,
952
+ default=(
953
+ shader.UV_GROUP_UV_SCALE_X_DEFAULT,
954
+ shader.UV_GROUP_UV_SCALE_Y_DEFAULT,
955
+ ),
956
+ get=_call_overridden_get_texture_scale,
957
+ set=_call_overridden_set_texture_scale,
958
+ )
959
+
960
+ if TYPE_CHECKING:
961
+ # This code is auto generated.
962
+ # To regenerate, run the `uv run tools/property_typing.py` command.
963
+ offset: Sequence[float] # type: ignore[no-redef]
964
+ scale: Sequence[float] # type: ignore[no-redef]
965
+
966
+
967
+ class Mtoon1NonBaseColorKhrTextureTransformPropertyGroup(
968
+ Mtoon1KhrTextureTransformPropertyGroup
969
+ ):
970
+ def _get_texture_offset(self) -> tuple[float, float]:
971
+ mtoon1 = get_material_extension(self.find_material()).mtoon1
972
+ if any(
973
+ texture_info.extensions.khr_texture_transform == self
974
+ for texture_info in mtoon1.non_uv_transformable_texture_info()
975
+ ):
976
+ return (0.0, 0.0)
977
+ base_color_texture = mtoon1.pbr_metallic_roughness.base_color_texture
978
+ offset = base_color_texture.extensions.khr_texture_transform.offset
979
+ return (offset[0], offset[1])
980
+
981
+ def _set_texture_offset(self, value: object) -> None:
982
+ _ = value
983
+ # https://github.com/vrm-c/UniVRM/blob/v0.131.1/Packages/VRM10/Runtime/IO/Material/BuiltInRP/Export/Materials/BuiltInVrm10MToonMaterialExporter.cs#L166-L177
984
+ _logger.info(
985
+ "Setting texture offset is not supported for non-base color textures"
986
+ )
987
+
988
+ def _get_texture_scale(self) -> tuple[float, float]:
989
+ mtoon1 = get_material_extension(self.find_material()).mtoon1
990
+ if any(
991
+ texture_info.extensions.khr_texture_transform == self
992
+ for texture_info in mtoon1.non_uv_transformable_texture_info()
993
+ ):
994
+ return (1.0, 1.0)
995
+ base_color_texture = mtoon1.pbr_metallic_roughness.base_color_texture
996
+ scale = base_color_texture.extensions.khr_texture_transform.scale
997
+ return (scale[0], scale[1])
998
+
999
+ def _set_texture_scale(self, value: object) -> None:
1000
+ _ = value
1001
+ # https://github.com/vrm-c/UniVRM/blob/v0.131.1/Packages/VRM10/Runtime/IO/Material/BuiltInRP/Export/Materials/BuiltInVrm10MToonMaterialExporter.cs#L166-L177
1002
+ _logger.info(
1003
+ "Setting texture scale is not supported for non-base color textures"
1004
+ )
1005
+
1006
+
1007
+ class Mtoon1BaseColorKhrTextureTransformPropertyGroup(
1008
+ Mtoon1KhrTextureTransformPropertyGroup
1009
+ ):
1010
+ def _get_texture_offset(self) -> tuple[float, float]:
1011
+ x = self.get_texture_uv_float(
1012
+ shader.UV_GROUP_UV_OFFSET_X_LABEL,
1013
+ default_value=shader.UV_GROUP_UV_OFFSET_X_DEFAULT,
1014
+ )
1015
+ y = self.get_texture_uv_float(
1016
+ shader.UV_GROUP_UV_OFFSET_Y_LABEL,
1017
+ default_value=shader.UV_GROUP_UV_OFFSET_Y_DEFAULT,
1018
+ )
1019
+ return x, y
1020
+
1021
+ def _set_texture_offset(self, value: object) -> None:
1022
+ offset = convert.float2_or_none(value)
1023
+ if offset is None:
1024
+ return
1025
+ material = self.find_material()
1026
+ node_tree = material.node_tree
1027
+ if not node_tree:
1028
+ return
1029
+
1030
+ ext = get_material_extension(material)
1031
+ for texture_info, offset in [
1032
+ *[
1033
+ (texture_info, offset)
1034
+ for texture_info in ext.mtoon1.uv_transformable_texture_info()
1035
+ ],
1036
+ *[
1037
+ (texture_info, (0, 0))
1038
+ for texture_info in ext.mtoon1.non_uv_transformable_texture_info()
1039
+ ],
1040
+ ]:
1041
+ khr_texture_transform = texture_info.extensions.khr_texture_transform
1042
+
1043
+ khr_texture_transform.set_texture_uv(
1044
+ shader.UV_GROUP_UV_OFFSET_X_LABEL, offset[0]
1045
+ )
1046
+ khr_texture_transform.set_texture_uv(
1047
+ shader.UV_GROUP_UV_OFFSET_Y_LABEL, offset[1]
1048
+ )
1049
+
1050
+ node_name = khr_texture_transform.get_image_texture_node_name()
1051
+ node = node_tree.nodes.get(node_name)
1052
+ if not isinstance(node, ShaderNodeTexImage):
1053
+ _logger.warning('No shader node tex image "%s"', node_name)
1054
+ continue
1055
+ node.texture_mapping.translation = Vector((0, 0, 0))
1056
+
1057
+ outline = khr_texture_transform.find_outline_property_group(material)
1058
+ if not outline:
1059
+ continue
1060
+ if not isinstance(outline, Mtoon1KhrTextureTransformPropertyGroup):
1061
+ continue
1062
+ outline.offset = offset
1063
+
1064
+ def _get_texture_scale(self) -> tuple[float, float]:
1065
+ x = self.get_texture_uv_float(
1066
+ shader.UV_GROUP_UV_SCALE_X_LABEL,
1067
+ default_value=shader.UV_GROUP_UV_SCALE_X_DEFAULT,
1068
+ )
1069
+ y = self.get_texture_uv_float(
1070
+ shader.UV_GROUP_UV_SCALE_Y_LABEL,
1071
+ default_value=shader.UV_GROUP_UV_SCALE_Y_DEFAULT,
1072
+ )
1073
+ return x, y
1074
+
1075
+ def _set_texture_scale(self, value: object) -> None:
1076
+ scale = convert.float2_or_none(value)
1077
+ if scale is None:
1078
+ return
1079
+ material = self.find_material()
1080
+ node_tree = material.node_tree
1081
+ if not node_tree:
1082
+ return
1083
+
1084
+ ext = get_material_extension(material)
1085
+ for texture_info, scale in [
1086
+ *[
1087
+ (texture_info, scale)
1088
+ for texture_info in ext.mtoon1.uv_transformable_texture_info()
1089
+ ],
1090
+ *[
1091
+ (texture_info, (1, 1))
1092
+ for texture_info in ext.mtoon1.non_uv_transformable_texture_info()
1093
+ ],
1094
+ ]:
1095
+ khr_texture_transform = texture_info.extensions.khr_texture_transform
1096
+
1097
+ khr_texture_transform.set_texture_uv(
1098
+ shader.UV_GROUP_UV_SCALE_X_LABEL, scale[0]
1099
+ )
1100
+ khr_texture_transform.set_texture_uv(
1101
+ shader.UV_GROUP_UV_SCALE_Y_LABEL, scale[1]
1102
+ )
1103
+
1104
+ node_name = khr_texture_transform.get_image_texture_node_name()
1105
+ node = node_tree.nodes.get(node_name)
1106
+ if not isinstance(node, ShaderNodeTexImage):
1107
+ _logger.warning('No shader node tex image "%s"', node_name)
1108
+ continue
1109
+ node.texture_mapping.scale = Vector((1, 1, 1))
1110
+
1111
+ outline = khr_texture_transform.find_outline_property_group(material)
1112
+ if not outline:
1113
+ continue
1114
+ if not isinstance(outline, Mtoon1KhrTextureTransformPropertyGroup):
1115
+ continue
1116
+ outline.scale = scale
1117
+
1118
+
1119
+ class Mtoon1ShadeMultiplyKhrTextureTransformPropertyGroup(
1120
+ Mtoon1NonBaseColorKhrTextureTransformPropertyGroup
1121
+ ):
1122
+ pass
1123
+
1124
+
1125
+ class Mtoon1NormalKhrTextureTransformPropertyGroup(
1126
+ Mtoon1NonBaseColorKhrTextureTransformPropertyGroup
1127
+ ):
1128
+ pass
1129
+
1130
+
1131
+ class Mtoon1ShadingShiftKhrTextureTransformPropertyGroup(
1132
+ Mtoon1NonBaseColorKhrTextureTransformPropertyGroup
1133
+ ):
1134
+ pass
1135
+
1136
+
1137
+ class Mtoon1EmissiveKhrTextureTransformPropertyGroup(
1138
+ Mtoon1NonBaseColorKhrTextureTransformPropertyGroup
1139
+ ):
1140
+ pass
1141
+
1142
+
1143
+ class Mtoon1RimMultiplyKhrTextureTransformPropertyGroup(
1144
+ Mtoon1NonBaseColorKhrTextureTransformPropertyGroup
1145
+ ):
1146
+ pass
1147
+
1148
+
1149
+ class Mtoon1MatcapKhrTextureTransformPropertyGroup(
1150
+ Mtoon1NonBaseColorKhrTextureTransformPropertyGroup
1151
+ ):
1152
+ pass
1153
+
1154
+
1155
+ class Mtoon1OutlineWidthMultiplyKhrTextureTransformPropertyGroup(
1156
+ Mtoon1NonBaseColorKhrTextureTransformPropertyGroup
1157
+ ):
1158
+ pass
1159
+
1160
+
1161
+ class Mtoon1UvAnimationMaskKhrTextureTransformPropertyGroup(
1162
+ Mtoon1NonBaseColorKhrTextureTransformPropertyGroup
1163
+ ):
1164
+ pass
1165
+
1166
+
1167
+ class Mtoon1TextureInfoExtensionsPropertyGroup(PropertyGroup):
1168
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1169
+ type=Mtoon1KhrTextureTransformPropertyGroup
1170
+ )
1171
+
1172
+ if TYPE_CHECKING:
1173
+ # This code is auto generated.
1174
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1175
+ khr_texture_transform: ( # type: ignore[no-redef]
1176
+ Mtoon1KhrTextureTransformPropertyGroup
1177
+ )
1178
+
1179
+
1180
+ class Mtoon1BaseColorTextureInfoExtensionsPropertyGroup(
1181
+ Mtoon1TextureInfoExtensionsPropertyGroup
1182
+ ):
1183
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1184
+ type=Mtoon1BaseColorKhrTextureTransformPropertyGroup
1185
+ )
1186
+
1187
+ if TYPE_CHECKING:
1188
+ # This code is auto generated.
1189
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1190
+ khr_texture_transform: ( # type: ignore[no-redef]
1191
+ Mtoon1BaseColorKhrTextureTransformPropertyGroup
1192
+ )
1193
+
1194
+
1195
+ class Mtoon1ShadeMultiplyTextureInfoExtensionsPropertyGroup(
1196
+ Mtoon1TextureInfoExtensionsPropertyGroup
1197
+ ):
1198
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1199
+ type=Mtoon1ShadeMultiplyKhrTextureTransformPropertyGroup
1200
+ )
1201
+
1202
+ if TYPE_CHECKING:
1203
+ # This code is auto generated.
1204
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1205
+ khr_texture_transform: ( # type: ignore[no-redef]
1206
+ Mtoon1ShadeMultiplyKhrTextureTransformPropertyGroup
1207
+ )
1208
+
1209
+
1210
+ class Mtoon1NormalTextureInfoExtensionsPropertyGroup(
1211
+ Mtoon1TextureInfoExtensionsPropertyGroup
1212
+ ):
1213
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1214
+ type=Mtoon1NormalKhrTextureTransformPropertyGroup
1215
+ )
1216
+
1217
+ if TYPE_CHECKING:
1218
+ # This code is auto generated.
1219
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1220
+ khr_texture_transform: ( # type: ignore[no-redef]
1221
+ Mtoon1NormalKhrTextureTransformPropertyGroup
1222
+ )
1223
+
1224
+
1225
+ class Mtoon1ShadingShiftTextureInfoExtensionsPropertyGroup(
1226
+ Mtoon1TextureInfoExtensionsPropertyGroup
1227
+ ):
1228
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1229
+ type=Mtoon1ShadingShiftKhrTextureTransformPropertyGroup
1230
+ )
1231
+
1232
+ if TYPE_CHECKING:
1233
+ # This code is auto generated.
1234
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1235
+ khr_texture_transform: ( # type: ignore[no-redef]
1236
+ Mtoon1ShadingShiftKhrTextureTransformPropertyGroup
1237
+ )
1238
+
1239
+
1240
+ class Mtoon1EmissiveTextureInfoExtensionsPropertyGroup(
1241
+ Mtoon1TextureInfoExtensionsPropertyGroup
1242
+ ):
1243
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1244
+ type=Mtoon1EmissiveKhrTextureTransformPropertyGroup
1245
+ )
1246
+
1247
+ if TYPE_CHECKING:
1248
+ # This code is auto generated.
1249
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1250
+ khr_texture_transform: ( # type: ignore[no-redef]
1251
+ Mtoon1EmissiveKhrTextureTransformPropertyGroup
1252
+ )
1253
+
1254
+
1255
+ class Mtoon1RimMultiplyTextureInfoExtensionsPropertyGroup(
1256
+ Mtoon1TextureInfoExtensionsPropertyGroup
1257
+ ):
1258
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1259
+ type=Mtoon1RimMultiplyKhrTextureTransformPropertyGroup
1260
+ )
1261
+
1262
+ if TYPE_CHECKING:
1263
+ # This code is auto generated.
1264
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1265
+ khr_texture_transform: ( # type: ignore[no-redef]
1266
+ Mtoon1RimMultiplyKhrTextureTransformPropertyGroup
1267
+ )
1268
+
1269
+
1270
+ class Mtoon1MatcapTextureInfoExtensionsPropertyGroup(
1271
+ Mtoon1TextureInfoExtensionsPropertyGroup
1272
+ ):
1273
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1274
+ type=Mtoon1MatcapKhrTextureTransformPropertyGroup
1275
+ )
1276
+
1277
+ if TYPE_CHECKING:
1278
+ # This code is auto generated.
1279
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1280
+ khr_texture_transform: ( # type: ignore[no-redef]
1281
+ Mtoon1MatcapKhrTextureTransformPropertyGroup
1282
+ )
1283
+
1284
+
1285
+ class Mtoon1OutlineWidthMultiplyTextureInfoExtensionsPropertyGroup(
1286
+ Mtoon1TextureInfoExtensionsPropertyGroup
1287
+ ):
1288
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1289
+ type=Mtoon1OutlineWidthMultiplyKhrTextureTransformPropertyGroup
1290
+ )
1291
+
1292
+ if TYPE_CHECKING:
1293
+ # This code is auto generated.
1294
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1295
+ khr_texture_transform: ( # type: ignore[no-redef]
1296
+ Mtoon1OutlineWidthMultiplyKhrTextureTransformPropertyGroup
1297
+ )
1298
+
1299
+
1300
+ class Mtoon1UvAnimationMaskTextureInfoExtensionsPropertyGroup(
1301
+ Mtoon1TextureInfoExtensionsPropertyGroup
1302
+ ):
1303
+ khr_texture_transform: PointerProperty( # type: ignore[valid-type]
1304
+ type=Mtoon1UvAnimationMaskKhrTextureTransformPropertyGroup
1305
+ )
1306
+
1307
+ if TYPE_CHECKING:
1308
+ # This code is auto generated.
1309
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1310
+ khr_texture_transform: ( # type: ignore[no-redef]
1311
+ Mtoon1UvAnimationMaskKhrTextureTransformPropertyGroup
1312
+ )
1313
+
1314
+
1315
+ class Mtoon1SamplerPropertyGroup(TextureTraceablePropertyGroup):
1316
+ (
1317
+ mag_filter_enum,
1318
+ (
1319
+ MAG_FILTER_NEAREST,
1320
+ MAG_FILTER_LINEAR,
1321
+ ),
1322
+ ) = property_group_enum(
1323
+ ("NEAREST", "Nearest", "", "NONE", GL_NEAREST),
1324
+ ("LINEAR", "Linear", "", "NONE", GL_LINEAR),
1325
+ )
1326
+ MAG_FILTER_DEFAULT = MAG_FILTER_LINEAR
1327
+
1328
+ def _get_mag_filter(self) -> int:
1329
+ default_value = self.MAG_FILTER_DEFAULT.value
1330
+
1331
+ material = self.find_material()
1332
+ node_name = self.get_image_texture_node_name()
1333
+ node_tree = material.node_tree
1334
+ if node_tree is None:
1335
+ return default_value
1336
+
1337
+ node = node_tree.nodes.get(node_name)
1338
+ if not isinstance(node, ShaderNodeTexImage):
1339
+ return default_value
1340
+
1341
+ if node.interpolation == IMAGE_INTERPOLATION_CLOSEST:
1342
+ return GL_NEAREST
1343
+
1344
+ if node.interpolation in GL_LINEAR_IMAGE_INTERPOLATIONS:
1345
+ return GL_LINEAR
1346
+
1347
+ value = self.get("mag_filter")
1348
+ if isinstance(value, int) and value in self.mag_filter_enum.values():
1349
+ return value
1350
+
1351
+ return default_value
1352
+
1353
+ def _set_mag_filter(self, value: int) -> None:
1354
+ # If input value conflicts with TexImage value, change TexImage value
1355
+ # If input value is GL_NEAREST and TexImage is Closest, delete internal value
1356
+ # If input value is GL_LINEAR and TexImage is Linear/Cubic/Smart,
1357
+ # delete internal value
1358
+
1359
+ if value not in self.mag_filter_enum.values():
1360
+ self.pop("mag_filter", None)
1361
+ return
1362
+
1363
+ material = self.find_material()
1364
+ node_name = self.get_image_texture_node_name()
1365
+ node_tree = material.node_tree
1366
+ if node_tree is not None:
1367
+ node = node_tree.nodes.get(node_name)
1368
+ if isinstance(node, ShaderNodeTexImage):
1369
+ if value == GL_NEAREST:
1370
+ if node.interpolation == IMAGE_INTERPOLATION_CLOSEST:
1371
+ self.pop("mag_filter", None)
1372
+ return
1373
+ node.interpolation = IMAGE_INTERPOLATION_CLOSEST
1374
+ if value == GL_LINEAR:
1375
+ if node.interpolation in GL_LINEAR_IMAGE_INTERPOLATIONS:
1376
+ self.pop("mag_filter", None)
1377
+ return
1378
+ node.interpolation = IMAGE_INTERPOLATION_LINEAR
1379
+
1380
+ self["mag_filter"] = value
1381
+
1382
+ def _get_min_filter(self) -> int:
1383
+ value = self.get("min_filter")
1384
+ if isinstance(value, int) and value in self.min_filter_enum.values():
1385
+ return value
1386
+
1387
+ default_value = self.MIN_FILTER_DEFAULT.value
1388
+
1389
+ material = self.find_material()
1390
+ node_name = self.get_image_texture_node_name()
1391
+ node_tree = material.node_tree
1392
+ if node_tree is None:
1393
+ return default_value
1394
+
1395
+ node = node_tree.nodes.get(node_name)
1396
+ if not isinstance(node, ShaderNodeTexImage):
1397
+ return default_value
1398
+
1399
+ if node.interpolation == IMAGE_INTERPOLATION_CLOSEST:
1400
+ return GL_NEAREST
1401
+
1402
+ if node.interpolation in GL_LINEAR_IMAGE_INTERPOLATIONS:
1403
+ return GL_LINEAR
1404
+
1405
+ return default_value
1406
+
1407
+ def _set_min_filter(self, value: int) -> None:
1408
+ # If input value is GL_NEAREST and TexImage is Closest, delete internal value
1409
+ # If input value is GL_LINEAR and TexImage is Linear/Cubic/Smart,
1410
+ # delete internal value
1411
+
1412
+ if value not in self.min_filter_enum.values():
1413
+ self.pop("min_filter", None)
1414
+ return
1415
+
1416
+ material = self.find_material()
1417
+ node_name = self.get_image_texture_node_name()
1418
+ node_tree = material.node_tree
1419
+ if node_tree is not None:
1420
+ node = node_tree.nodes.get(node_name)
1421
+ if isinstance(node, ShaderNodeTexImage):
1422
+ if (
1423
+ value == GL_NEAREST
1424
+ and node.interpolation == IMAGE_INTERPOLATION_CLOSEST
1425
+ ):
1426
+ self.pop("min_filter", None)
1427
+ return
1428
+ if (
1429
+ value == GL_LINEAR
1430
+ and node.interpolation in GL_LINEAR_IMAGE_INTERPOLATIONS
1431
+ ):
1432
+ self.pop("min_filter", None)
1433
+ return
1434
+
1435
+ self["min_filter"] = value
1436
+
1437
+ (
1438
+ min_filter_enum,
1439
+ (
1440
+ MIN_FILTER_NEAREST,
1441
+ MIN_FILTER_LINEAR,
1442
+ MIN_FILTER_NEAREST_MIPMAP_NEAREST,
1443
+ MIN_FILTER_LINEAR_MIPMAP_NEAREST,
1444
+ MIN_FILTER_NEAREST_MIPMAP_LINEAR,
1445
+ MIN_FILTER_LINEAR_MIPMAP_LINEAR,
1446
+ ),
1447
+ ) = property_group_enum(
1448
+ ("NEAREST", "Nearest", "", "NONE", GL_NEAREST),
1449
+ ("LINEAR", "Linear", "", "NONE", GL_LINEAR),
1450
+ (
1451
+ "NEAREST_MIPMAP_NEAREST",
1452
+ "Nearest Mipmap Nearest",
1453
+ "",
1454
+ "NONE",
1455
+ GL_NEAREST_MIPMAP_NEAREST,
1456
+ ),
1457
+ (
1458
+ "LINEAR_MIPMAP_NEAREST",
1459
+ "Linear Mipmap Nearest",
1460
+ "",
1461
+ "NONE",
1462
+ GL_LINEAR_MIPMAP_NEAREST,
1463
+ ),
1464
+ (
1465
+ "NEAREST_MIPMAP_LINEAR",
1466
+ "Nearest Mipmap Linear",
1467
+ "",
1468
+ "NONE",
1469
+ GL_NEAREST_MIPMAP_LINEAR,
1470
+ ),
1471
+ (
1472
+ "LINEAR_MIPMAP_LINEAR",
1473
+ "Linear Mipmap Linear",
1474
+ "",
1475
+ "NONE",
1476
+ GL_LINEAR_MIPMAP_LINEAR,
1477
+ ),
1478
+ )
1479
+ MIN_FILTER_DEFAULT = MIN_FILTER_LINEAR
1480
+
1481
+ # https://github.com/KhronosGroup/glTF/blob/2a9996a2ea66ab712590eaf62f39f1115996f5a3/specification/2.0/schema/sampler.schema.json#L67-L117
1482
+ (
1483
+ wrap_enum,
1484
+ (
1485
+ WRAP_CLAMP_TO_EDGE,
1486
+ WRAP_MIRRORED_REPEAT,
1487
+ WRAP_REPEAT,
1488
+ ),
1489
+ ) = property_group_enum(
1490
+ ("CLAMP_TO_EDGE", "Clamp to Edge", "", "NONE", GL_CLAMP_TO_EDGE),
1491
+ ("MIRRORED_REPEAT", "Mirrored Repeat", "", "NONE", GL_MIRRORED_REPEAT),
1492
+ ("REPEAT", "Repeat", "", "NONE", GL_REPEAT),
1493
+ )
1494
+
1495
+ WRAP_DEFAULT = WRAP_REPEAT
1496
+
1497
+ def _get_wrap_s(self) -> int:
1498
+ wrap_s = self.get_texture_uv_int(
1499
+ shader.UV_GROUP_WRAP_S_LABEL, shader.UV_GROUP_WRAP_S_DEFAULT
1500
+ )
1501
+ if wrap_s in self.wrap_enum.values():
1502
+ return wrap_s
1503
+ return shader.UV_GROUP_WRAP_S_DEFAULT
1504
+
1505
+ def _set_wrap_s(self, value: object) -> None:
1506
+ self.set_texture_uv(shader.UV_GROUP_WRAP_S_LABEL, value)
1507
+
1508
+ def _get_wrap_t(self) -> int:
1509
+ wrap_t = self.get_texture_uv_int(
1510
+ shader.UV_GROUP_WRAP_T_LABEL, shader.UV_GROUP_WRAP_T_DEFAULT
1511
+ )
1512
+ if wrap_t in self.wrap_enum.values():
1513
+ return wrap_t
1514
+ return shader.UV_GROUP_WRAP_T_DEFAULT
1515
+
1516
+ def _set_wrap_t(self, value: object) -> None:
1517
+ self.set_texture_uv(shader.UV_GROUP_WRAP_T_LABEL, value)
1518
+
1519
+ mag_filter: EnumProperty( # type: ignore[valid-type]
1520
+ items=mag_filter_enum.items(),
1521
+ get=_get_mag_filter,
1522
+ set=_set_mag_filter,
1523
+ name="Mag Filter",
1524
+ )
1525
+
1526
+ min_filter: EnumProperty( # type: ignore[valid-type]
1527
+ items=min_filter_enum.items(),
1528
+ get=_get_min_filter,
1529
+ set=_set_min_filter,
1530
+ name="Min Filter",
1531
+ )
1532
+
1533
+ wrap_s: EnumProperty( # type: ignore[valid-type]
1534
+ items=wrap_enum.items(),
1535
+ name="Wrap S",
1536
+ default=WRAP_DEFAULT.value,
1537
+ get=_get_wrap_s,
1538
+ set=_set_wrap_s,
1539
+ )
1540
+
1541
+ wrap_t: EnumProperty( # type: ignore[valid-type]
1542
+ items=wrap_enum.items(),
1543
+ name="Wrap T",
1544
+ default=WRAP_DEFAULT.value,
1545
+ get=_get_wrap_t,
1546
+ set=_set_wrap_t,
1547
+ )
1548
+
1549
+ if TYPE_CHECKING:
1550
+ # This code is auto generated.
1551
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1552
+ mag_filter: str # type: ignore[no-redef]
1553
+ min_filter: str # type: ignore[no-redef]
1554
+ wrap_s: str # type: ignore[no-redef]
1555
+ wrap_t: str # type: ignore[no-redef]
1556
+
1557
+
1558
+ class Mtoon1BaseColorSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1559
+ pass
1560
+
1561
+
1562
+ class Mtoon1ShadeMultiplySamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1563
+ pass
1564
+
1565
+
1566
+ class Mtoon1NormalSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1567
+ pass
1568
+
1569
+
1570
+ class Mtoon1ShadingShiftSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1571
+ pass
1572
+
1573
+
1574
+ class Mtoon1EmissiveSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1575
+ pass
1576
+
1577
+
1578
+ class Mtoon1RimMultiplySamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1579
+ pass
1580
+
1581
+
1582
+ class Mtoon1MatcapSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1583
+ pass
1584
+
1585
+
1586
+ class Mtoon1OutlineWidthMultiplySamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1587
+ pass
1588
+
1589
+
1590
+ class Mtoon1UvAnimationMaskSamplerPropertyGroup(Mtoon1SamplerPropertyGroup):
1591
+ pass
1592
+
1593
+
1594
+ class Mtoon1TexturePropertyGroup(TextureTraceablePropertyGroup):
1595
+ label = ""
1596
+ panel_label = label
1597
+ colorspace = "sRGB"
1598
+
1599
+ def _update_source(self, context: Context) -> None:
1600
+ _ = context
1601
+ self.update_image(self.source)
1602
+
1603
+ source: PointerProperty( # type: ignore[valid-type]
1604
+ type=Image,
1605
+ update=_update_source,
1606
+ )
1607
+
1608
+ def _update_source_for_desynced_node_tree(self, context: Context) -> None:
1609
+ """Update PointerProperty() passed to prop() when not synced with NodeTree.
1610
+
1611
+ When not synced with NodeTree, want to display the correct Image name on
1612
+ the Placeholder side of prop().
1613
+ Therefore, it operates as follows:
1614
+ - Always display Placeholder, so the value always returns None
1615
+ - When a value is input from outside, reset the value to None and
1616
+ transfer to self.source
1617
+ """
1618
+ original_syncing_source_name: Optional[str] = None
1619
+ if self.source_for_desynced_node_tree:
1620
+ original_syncing_source_name = self.source_for_desynced_node_tree.name
1621
+
1622
+ if self.source_for_desynced_node_tree is not None:
1623
+ self.source_for_desynced_node_tree = None # trigger recursive assignment
1624
+
1625
+ if original_syncing_source_name is not None:
1626
+ image = context.blend_data.images.get(original_syncing_source_name)
1627
+ if image:
1628
+ self.source = image
1629
+ else:
1630
+ self.source = None
1631
+
1632
+ source_for_desynced_node_tree: PointerProperty( # type: ignore[valid-type]
1633
+ type=Image,
1634
+ update=_update_source_for_desynced_node_tree,
1635
+ )
1636
+
1637
+ sampler: PointerProperty( # type: ignore[valid-type]
1638
+ type=Mtoon1SamplerPropertyGroup
1639
+ )
1640
+
1641
+ if TYPE_CHECKING:
1642
+ # This code is auto generated.
1643
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1644
+ source: Optional[Image] # type: ignore[no-redef]
1645
+ source_for_desynced_node_tree: Optional[Image] # type: ignore[no-redef]
1646
+ sampler: Mtoon1SamplerPropertyGroup # type: ignore[no-redef]
1647
+
1648
+
1649
+ class Mtoon1BaseColorTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1650
+ label = "Lit Color, Alpha"
1651
+ panel_label = label
1652
+ colorspace = "sRGB"
1653
+
1654
+ sampler: PointerProperty( # type: ignore[valid-type]
1655
+ type=Mtoon1BaseColorSamplerPropertyGroup
1656
+ )
1657
+
1658
+ def update_image(self, image: Optional[Image]) -> None:
1659
+ super().update_image(image)
1660
+ material = self.find_material()
1661
+ mtoon1 = get_material_extension(material).mtoon1
1662
+ alpha_mode_value = mtoon1.alpha_mode_enum.identifier_to_value(
1663
+ mtoon1.alpha_mode, mtoon1.ALPHA_MODE_OPAQUE.value
1664
+ )
1665
+ mtoon1.update_alpha_nodes(
1666
+ material,
1667
+ alpha_mode_value,
1668
+ )
1669
+
1670
+ if TYPE_CHECKING:
1671
+ # This code is auto generated.
1672
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1673
+ sampler: Mtoon1BaseColorSamplerPropertyGroup # type: ignore[no-redef]
1674
+
1675
+
1676
+ class Mtoon1ShadeMultiplyTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1677
+ label = "Shade Color"
1678
+ panel_label = label
1679
+ colorspace = "sRGB"
1680
+
1681
+ sampler: PointerProperty( # type: ignore[valid-type]
1682
+ type=Mtoon1ShadeMultiplySamplerPropertyGroup
1683
+ )
1684
+
1685
+ if TYPE_CHECKING:
1686
+ # This code is auto generated.
1687
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1688
+ sampler: Mtoon1ShadeMultiplySamplerPropertyGroup # type: ignore[no-redef]
1689
+
1690
+
1691
+ class Mtoon1NormalTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1692
+ label = "Normal Map"
1693
+ panel_label = label
1694
+ colorspace = "Non-Color"
1695
+
1696
+ sampler: PointerProperty( # type: ignore[valid-type]
1697
+ type=Mtoon1NormalSamplerPropertyGroup
1698
+ )
1699
+
1700
+ if TYPE_CHECKING:
1701
+ # This code is auto generated.
1702
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1703
+ sampler: Mtoon1NormalSamplerPropertyGroup # type: ignore[no-redef]
1704
+
1705
+
1706
+ class Mtoon1ShadingShiftTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1707
+ label = "Additive Shading Shift"
1708
+ panel_label = label
1709
+ colorspace = "Non-Color"
1710
+
1711
+ sampler: PointerProperty( # type: ignore[valid-type]
1712
+ type=Mtoon1ShadingShiftSamplerPropertyGroup
1713
+ )
1714
+
1715
+ if TYPE_CHECKING:
1716
+ # This code is auto generated.
1717
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1718
+ sampler: Mtoon1ShadingShiftSamplerPropertyGroup # type: ignore[no-redef]
1719
+
1720
+
1721
+ class Mtoon1EmissiveTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1722
+ label = "Emission"
1723
+ panel_label = label
1724
+ colorspace = "sRGB"
1725
+
1726
+ sampler: PointerProperty( # type: ignore[valid-type]
1727
+ type=Mtoon1EmissiveSamplerPropertyGroup
1728
+ )
1729
+
1730
+ if TYPE_CHECKING:
1731
+ # This code is auto generated.
1732
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1733
+ sampler: Mtoon1EmissiveSamplerPropertyGroup # type: ignore[no-redef]
1734
+
1735
+
1736
+ class Mtoon1RimMultiplyTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1737
+ label = "Rim Color"
1738
+ panel_label = label
1739
+ colorspace = "sRGB"
1740
+
1741
+ sampler: PointerProperty( # type: ignore[valid-type]
1742
+ type=Mtoon1RimMultiplySamplerPropertyGroup
1743
+ )
1744
+
1745
+ if TYPE_CHECKING:
1746
+ # This code is auto generated.
1747
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1748
+ sampler: Mtoon1RimMultiplySamplerPropertyGroup # type: ignore[no-redef]
1749
+
1750
+
1751
+ class Mtoon1MatcapTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1752
+ label = "Matcap Rim"
1753
+ panel_label = label
1754
+ colorspace = "sRGB"
1755
+
1756
+ sampler: PointerProperty( # type: ignore[valid-type]
1757
+ type=Mtoon1MatcapSamplerPropertyGroup
1758
+ )
1759
+
1760
+ if TYPE_CHECKING:
1761
+ # This code is auto generated.
1762
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1763
+ sampler: Mtoon1MatcapSamplerPropertyGroup # type: ignore[no-redef]
1764
+
1765
+
1766
+ class Mtoon1OutlineWidthMultiplyTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1767
+ label = "Outline Width"
1768
+ panel_label = label
1769
+ colorspace = "Non-Color"
1770
+
1771
+ def _update_source(self, context: Context) -> None:
1772
+ mtoon1 = get_material_extension(self.find_material()).mtoon1
1773
+ mtoon1.extensions.vrmc_materials_mtoon.update_outline_geometry()
1774
+ super()._update_source(context)
1775
+
1776
+ sampler: PointerProperty( # type: ignore[valid-type]
1777
+ type=Mtoon1OutlineWidthMultiplySamplerPropertyGroup
1778
+ )
1779
+
1780
+ if TYPE_CHECKING:
1781
+ # This code is auto generated.
1782
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1783
+ sampler: ( # type: ignore[no-redef]
1784
+ Mtoon1OutlineWidthMultiplySamplerPropertyGroup
1785
+ )
1786
+
1787
+
1788
+ class Mtoon1UvAnimationMaskTexturePropertyGroup(Mtoon1TexturePropertyGroup):
1789
+ label = "UV Animation Mask"
1790
+ panel_label = "Mask"
1791
+ colorspace = "Non-Color"
1792
+
1793
+ sampler: PointerProperty( # type: ignore[valid-type]
1794
+ type=Mtoon1UvAnimationMaskSamplerPropertyGroup
1795
+ )
1796
+
1797
+ if TYPE_CHECKING:
1798
+ # This code is auto generated.
1799
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1800
+ sampler: Mtoon1UvAnimationMaskSamplerPropertyGroup # type: ignore[no-redef]
1801
+
1802
+
1803
+ class Mtoon1TextureInfoPropertyGroup(MaterialTraceablePropertyGroup):
1804
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {}
1805
+
1806
+ index: PointerProperty( # type: ignore[valid-type]
1807
+ type=Mtoon1TexturePropertyGroup
1808
+ )
1809
+
1810
+ extensions: PointerProperty( # type: ignore[valid-type]
1811
+ type=Mtoon1TextureInfoExtensionsPropertyGroup
1812
+ )
1813
+
1814
+ @dataclass(frozen=True)
1815
+ class TextureInfoBackup:
1816
+ source: Optional[Image]
1817
+ mag_filter: Optional[str]
1818
+ min_filter: Optional[str]
1819
+ wrap_s: str
1820
+ wrap_t: str
1821
+ offset: tuple[float, float]
1822
+ scale: tuple[float, float]
1823
+
1824
+ def backup(self) -> TextureInfoBackup:
1825
+ return Mtoon1TextureInfoPropertyGroup.TextureInfoBackup(
1826
+ source=self.index.get_connected_node_image(),
1827
+ mag_filter=self.index.sampler.mag_filter,
1828
+ min_filter=self.index.sampler.min_filter,
1829
+ wrap_s=self.index.sampler.wrap_s,
1830
+ wrap_t=self.index.sampler.wrap_t,
1831
+ offset=(
1832
+ self.extensions.khr_texture_transform.offset[0],
1833
+ self.extensions.khr_texture_transform.offset[1],
1834
+ ),
1835
+ scale=(
1836
+ self.extensions.khr_texture_transform.scale[0],
1837
+ self.extensions.khr_texture_transform.scale[1],
1838
+ ),
1839
+ )
1840
+
1841
+ def restore(self, backup: TextureInfoBackup) -> None:
1842
+ self.index.source = backup.source
1843
+
1844
+ if (
1845
+ backup.mag_filter
1846
+ in Mtoon1SamplerPropertyGroup.mag_filter_enum.identifiers()
1847
+ ):
1848
+ self.index.sampler.mag_filter = backup.mag_filter
1849
+ elif backup.mag_filter is not None:
1850
+ _logger.warning("invalid mag filter: %s", backup.mag_filter)
1851
+ self.index.sampler.mag_filter = (
1852
+ Mtoon1SamplerPropertyGroup.MAG_FILTER_DEFAULT.identifier
1853
+ )
1854
+
1855
+ if (
1856
+ backup.min_filter
1857
+ in Mtoon1SamplerPropertyGroup.min_filter_enum.identifiers()
1858
+ ):
1859
+ self.index.sampler.min_filter = backup.min_filter
1860
+ elif backup.min_filter is not None:
1861
+ _logger.warning("invalid min filter: %s", backup.min_filter)
1862
+ self.index.sampler.min_filter = (
1863
+ Mtoon1SamplerPropertyGroup.MIN_FILTER_DEFAULT.identifier
1864
+ )
1865
+
1866
+ if backup.wrap_s in Mtoon1SamplerPropertyGroup.wrap_enum.identifiers():
1867
+ self.index.sampler.wrap_s = backup.wrap_s
1868
+ else:
1869
+ _logger.warning("invalid wrap s: %s", backup.wrap_s)
1870
+ self.index.sampler.wrap_s = (
1871
+ Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
1872
+ )
1873
+
1874
+ if backup.wrap_t in Mtoon1SamplerPropertyGroup.wrap_enum.identifiers():
1875
+ self.index.sampler.wrap_t = backup.wrap_t
1876
+ else:
1877
+ _logger.warning("invalid wrap t: %s", backup.wrap_t)
1878
+ self.index.sampler.wrap_t = (
1879
+ Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
1880
+ )
1881
+
1882
+ self.extensions.khr_texture_transform.offset = backup.offset
1883
+ self.extensions.khr_texture_transform.scale = backup.scale
1884
+
1885
+ show_expanded: BoolProperty() # type: ignore[valid-type]
1886
+
1887
+ def setup_drivers(self, material: Material) -> None:
1888
+ mtoon1 = get_material_extension(material).mtoon1
1889
+ if not mtoon1.enabled:
1890
+ return
1891
+ node_tree = material.node_tree
1892
+ if not node_tree:
1893
+ return
1894
+ animation_data = node_tree.animation_data
1895
+ if not animation_data:
1896
+ animation_data = node_tree.animation_data_create()
1897
+ if not animation_data:
1898
+ _logger.error(
1899
+ 'Failed to create anomation data for node tree "%s"', node_tree.name
1900
+ )
1901
+ return
1902
+ uv_node_name = self.index.get_image_texture_uv_node_name()
1903
+ uv_node = node_tree.nodes.get(uv_node_name)
1904
+ if not uv_node or not isinstance(uv_node, ShaderNodeGroup):
1905
+ _logger.error('Failed to get uv node "%s"', uv_node_name)
1906
+ return
1907
+ image_node_name = self.index.get_image_texture_node_name()
1908
+ image_node = node_tree.nodes.get(image_node_name)
1909
+ image_exists = isinstance(image_node, ShaderNodeTexImage) and isinstance(
1910
+ image_node.image, Image
1911
+ )
1912
+ for size_index, uv_input_label in (
1913
+ (0, shader.UV_GROUP_IMAGE_WIDTH_LABEL),
1914
+ (1, shader.UV_GROUP_IMAGE_HEIGHT_LABEL),
1915
+ ):
1916
+ uv_input_index = next(
1917
+ iter(
1918
+ index
1919
+ for index, socket in enumerate(uv_node.inputs)
1920
+ if socket.name == uv_input_label
1921
+ ),
1922
+ None,
1923
+ )
1924
+ if uv_input_index is None:
1925
+ _logger.error('Failed to get uv input index for "%s"', uv_input_label)
1926
+ continue
1927
+ data_path = (
1928
+ f'nodes["{uv_node_name}"]'
1929
+ + f".inputs[{uv_input_index}]"
1930
+ + ".default_value"
1931
+ )
1932
+ fcurve: Optional[Union[FCurve, list[FCurve]]] = next(
1933
+ iter(
1934
+ fcurve
1935
+ for fcurve in animation_data.drivers
1936
+ if fcurve.data_path == data_path
1937
+ ),
1938
+ None,
1939
+ )
1940
+ if fcurve is None:
1941
+ try:
1942
+ fcurve = node_tree.driver_add(data_path)
1943
+ except TypeError:
1944
+ continue
1945
+ if not isinstance(fcurve, FCurve):
1946
+ _logger.error(
1947
+ 'Failed to get fcurve "%s" for node tree "%s"',
1948
+ data_path,
1949
+ node_tree.name,
1950
+ )
1951
+ continue
1952
+ if fcurve.array_index != 0:
1953
+ fcurve.array_index = 0
1954
+ driver = fcurve.driver
1955
+ if not isinstance(driver, Driver):
1956
+ _logger.error('Failed to get driver for fcurve "%s"', data_path)
1957
+ continue
1958
+ if driver.type != "SUM":
1959
+ driver.type = "SUM"
1960
+ if not driver.variables:
1961
+ driver.variables.new()
1962
+ while len(driver.variables) > 1:
1963
+ driver.variables.remove(driver.variables[-1])
1964
+ variable = driver.variables[0]
1965
+ if variable.type != "SINGLE_PROP":
1966
+ variable.type = "SINGLE_PROP"
1967
+ if not variable.targets:
1968
+ _logger.error(
1969
+ 'No targets in variable for fcurve "%s" in node_tree "%s"',
1970
+ data_path,
1971
+ node_tree.name,
1972
+ )
1973
+ continue
1974
+ target = variable.targets[0]
1975
+ if target.id_type != "MATERIAL":
1976
+ target.id_type = "MATERIAL"
1977
+ if target.id != material:
1978
+ target.id = material
1979
+ target_data_path = (
1980
+ "node_tree"
1981
+ + f'.nodes["{image_node_name}"]'
1982
+ + ".image"
1983
+ + f".size[{size_index}]"
1984
+ )
1985
+ if target.data_path != target_data_path or (
1986
+ # Blender 2.93 sets is_valid to False once image is deleted.
1987
+ # This is resolved by resetting data_path.
1988
+ image_exists and (not fcurve.is_valid or not driver.is_valid)
1989
+ ):
1990
+ target.data_path = target_data_path
1991
+
1992
+ if TYPE_CHECKING:
1993
+ # This code is auto generated.
1994
+ # To regenerate, run the `uv run tools/property_typing.py` command.
1995
+ index: Mtoon1TexturePropertyGroup # type: ignore[no-redef]
1996
+ extensions: Mtoon1TextureInfoExtensionsPropertyGroup # type: ignore[no-redef]
1997
+ show_expanded: bool # type: ignore[no-redef]
1998
+
1999
+
2000
+ # https://github.com/KhronosGroup/glTF/blob/1ab49ec412e638f2e5af0289e9fbb60c7271e457/specification/2.0/schema/textureInfo.schema.json
2001
+ class Mtoon1BaseColorTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
2002
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2003
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2004
+ NodeGroupSocketTarget(
2005
+ node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
2006
+ in_socket_name="Lit Color Texture Color",
2007
+ ),
2008
+ PrincipledBsdfNodeSocketTarget(
2009
+ in_socket_name=PRINCIPLED_BSDF_BASE_COLOR_INPUT_KEY
2010
+ ),
2011
+ ],
2012
+ TEX_IMAGE_ALPHA_OUTPUT_KEY: [
2013
+ NodeGroupSocketTarget(
2014
+ node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
2015
+ in_socket_name="Lit Color Texture Alpha",
2016
+ ),
2017
+ ],
2018
+ }
2019
+
2020
+ index: PointerProperty( # type: ignore[valid-type]
2021
+ type=Mtoon1BaseColorTexturePropertyGroup
2022
+ )
2023
+ extensions: PointerProperty( # type: ignore[valid-type]
2024
+ type=Mtoon1BaseColorTextureInfoExtensionsPropertyGroup
2025
+ )
2026
+
2027
+ if TYPE_CHECKING:
2028
+ # This code is auto generated.
2029
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2030
+ index: Mtoon1BaseColorTexturePropertyGroup # type: ignore[no-redef]
2031
+ extensions: ( # type: ignore[no-redef]
2032
+ Mtoon1BaseColorTextureInfoExtensionsPropertyGroup
2033
+ )
2034
+
2035
+
2036
+ class Mtoon1ShadeMultiplyTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
2037
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2038
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2039
+ NodeGroupSocketTarget(
2040
+ node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
2041
+ in_socket_name="Shade Color Texture",
2042
+ ),
2043
+ ],
2044
+ }
2045
+
2046
+ index: PointerProperty( # type: ignore[valid-type]
2047
+ type=Mtoon1ShadeMultiplyTexturePropertyGroup
2048
+ )
2049
+ extensions: PointerProperty( # type: ignore[valid-type]
2050
+ type=Mtoon1ShadeMultiplyTextureInfoExtensionsPropertyGroup
2051
+ )
2052
+
2053
+ if TYPE_CHECKING:
2054
+ # This code is auto generated.
2055
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2056
+ index: Mtoon1ShadeMultiplyTexturePropertyGroup # type: ignore[no-redef]
2057
+ extensions: ( # type: ignore[no-redef]
2058
+ Mtoon1ShadeMultiplyTextureInfoExtensionsPropertyGroup
2059
+ )
2060
+
2061
+
2062
+ # https://github.com/KhronosGroup/glTF/blob/1ab49ec412e638f2e5af0289e9fbb60c7271e457/specification/2.0/schema/material.normalTextureInfo.schema.json
2063
+ class Mtoon1NormalTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
2064
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2065
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2066
+ NodeGroupSocketTarget(
2067
+ node_group_node_tree_name=shader.NORMAL_GROUP_NAME,
2068
+ in_socket_name="Normal Map Texture",
2069
+ ),
2070
+ PrincipledBsdfNormalMapNodeSocketTarget(),
2071
+ ],
2072
+ }
2073
+
2074
+ index: PointerProperty( # type: ignore[valid-type]
2075
+ type=Mtoon1NormalTexturePropertyGroup
2076
+ )
2077
+
2078
+ def _get_scale(self) -> float:
2079
+ return self.get_float(
2080
+ shader.NORMAL_GROUP_NAME,
2081
+ shader.NORMAL_GROUP_SCALE_LABEL,
2082
+ default_value=shader.NORMAL_GROUP_SCALE_DEFAULT,
2083
+ )
2084
+
2085
+ def _set_scale(self, value: object) -> None:
2086
+ self.set_value(
2087
+ shader.NORMAL_GROUP_NAME,
2088
+ shader.NORMAL_GROUP_SCALE_LABEL,
2089
+ value,
2090
+ )
2091
+ material = self.find_material()
2092
+ principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
2093
+ principled_bsdf.normalmap_strength = self.scale
2094
+
2095
+ mtoon1 = get_material_extension(material).mtoon1
2096
+ if mtoon1.is_outline_material:
2097
+ return
2098
+ outline_material = mtoon1.outline_material
2099
+ if not outline_material:
2100
+ return
2101
+ outline_principled_bsdf = PrincipledBSDFWrapper(
2102
+ outline_material, is_readonly=False
2103
+ )
2104
+ outline_principled_bsdf.normalmap_strength = self.scale
2105
+
2106
+ scale: FloatProperty( # type: ignore[valid-type]
2107
+ name="Scale",
2108
+ default=shader.NORMAL_GROUP_SCALE_DEFAULT,
2109
+ get=_get_scale,
2110
+ set=_set_scale,
2111
+ )
2112
+
2113
+ extensions: PointerProperty( # type: ignore[valid-type]
2114
+ type=Mtoon1NormalTextureInfoExtensionsPropertyGroup
2115
+ )
2116
+
2117
+ if TYPE_CHECKING:
2118
+ # This code is auto generated.
2119
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2120
+ index: Mtoon1NormalTexturePropertyGroup # type: ignore[no-redef]
2121
+ scale: float # type: ignore[no-redef]
2122
+ extensions: ( # type: ignore[no-redef]
2123
+ Mtoon1NormalTextureInfoExtensionsPropertyGroup
2124
+ )
2125
+
2126
+
2127
+ # https://github.com/vrm-c/vrm-specification/blob/c5d1afdc4d59c292cb4fd6d54cad1dc0c4d19c60/specification/VRMC_materials_mtoon-1.0/schema/mtoon.shadingShiftTexture.schema.json
2128
+ class Mtoon1ShadingShiftTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
2129
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2130
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2131
+ NodeGroupSocketTarget(
2132
+ node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
2133
+ in_socket_name="Shading Shift Texture",
2134
+ ),
2135
+ ],
2136
+ }
2137
+
2138
+ index: PointerProperty( # type: ignore[valid-type]
2139
+ type=Mtoon1ShadingShiftTexturePropertyGroup
2140
+ )
2141
+
2142
+ def _get_scale(self) -> float:
2143
+ return self.get_float(
2144
+ shader.OUTPUT_GROUP_NAME,
2145
+ shader.OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_LABEL,
2146
+ default_value=shader.OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_DEFAULT,
2147
+ )
2148
+
2149
+ def _set_scale(self, value: object) -> None:
2150
+ self.set_value(
2151
+ shader.OUTPUT_GROUP_NAME,
2152
+ shader.OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_LABEL,
2153
+ value,
2154
+ )
2155
+
2156
+ scale: FloatProperty( # type: ignore[valid-type]
2157
+ name="Scale",
2158
+ default=shader.OUTPUT_GROUP_SHADING_SHIFT_TEXTURE_SCALE_DEFAULT,
2159
+ set=_set_scale,
2160
+ get=_get_scale,
2161
+ )
2162
+
2163
+ extensions: PointerProperty( # type: ignore[valid-type]
2164
+ type=Mtoon1ShadingShiftTextureInfoExtensionsPropertyGroup
2165
+ )
2166
+
2167
+ if TYPE_CHECKING:
2168
+ # This code is auto generated.
2169
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2170
+ index: Mtoon1ShadingShiftTexturePropertyGroup # type: ignore[no-redef]
2171
+ scale: float # type: ignore[no-redef]
2172
+ extensions: ( # type: ignore[no-redef]
2173
+ Mtoon1ShadingShiftTextureInfoExtensionsPropertyGroup
2174
+ )
2175
+
2176
+
2177
+ # https://github.com/KhronosGroup/glTF/blob/1ab49ec412e638f2e5af0289e9fbb60c7271e457/specification/2.0/schema/textureInfo.schema.json
2178
+ class Mtoon1EmissiveTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
2179
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2180
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2181
+ NodeGroupSocketTarget(
2182
+ node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
2183
+ in_socket_name="Emissive Texture",
2184
+ ),
2185
+ PrincipledBsdfNodeSocketTarget(
2186
+ in_socket_name=PRINCIPLED_BSDF_EMISSION_INPUT_KEY,
2187
+ ),
2188
+ GltfEmissionNodeSocketTarget(),
2189
+ ],
2190
+ }
2191
+
2192
+ index: PointerProperty( # type: ignore[valid-type]
2193
+ type=Mtoon1EmissiveTexturePropertyGroup
2194
+ )
2195
+ extensions: PointerProperty( # type: ignore[valid-type]
2196
+ type=Mtoon1EmissiveTextureInfoExtensionsPropertyGroup
2197
+ )
2198
+
2199
+ if TYPE_CHECKING:
2200
+ # This code is auto generated.
2201
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2202
+ index: Mtoon1EmissiveTexturePropertyGroup # type: ignore[no-redef]
2203
+ extensions: ( # type: ignore[no-redef]
2204
+ Mtoon1EmissiveTextureInfoExtensionsPropertyGroup
2205
+ )
2206
+
2207
+
2208
+ class Mtoon1RimMultiplyTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
2209
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2210
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2211
+ NodeGroupSocketTarget(
2212
+ node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
2213
+ in_socket_name="Rim Color Texture",
2214
+ ),
2215
+ ],
2216
+ }
2217
+
2218
+ index: PointerProperty( # type: ignore[valid-type]
2219
+ type=Mtoon1RimMultiplyTexturePropertyGroup
2220
+ )
2221
+ extensions: PointerProperty( # type: ignore[valid-type]
2222
+ type=Mtoon1RimMultiplyTextureInfoExtensionsPropertyGroup
2223
+ )
2224
+
2225
+ if TYPE_CHECKING:
2226
+ # This code is auto generated.
2227
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2228
+ index: Mtoon1RimMultiplyTexturePropertyGroup # type: ignore[no-redef]
2229
+ extensions: ( # type: ignore[no-redef]
2230
+ Mtoon1RimMultiplyTextureInfoExtensionsPropertyGroup
2231
+ )
2232
+
2233
+
2234
+ class Mtoon1MatcapTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
2235
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2236
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2237
+ NodeGroupSocketTarget(
2238
+ node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
2239
+ in_socket_name="MatCap Texture",
2240
+ ),
2241
+ ],
2242
+ }
2243
+
2244
+ index: PointerProperty( # type: ignore[valid-type]
2245
+ type=Mtoon1MatcapTexturePropertyGroup
2246
+ )
2247
+ extensions: PointerProperty( # type: ignore[valid-type]
2248
+ type=Mtoon1MatcapTextureInfoExtensionsPropertyGroup
2249
+ )
2250
+
2251
+ if TYPE_CHECKING:
2252
+ # This code is auto generated.
2253
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2254
+ index: Mtoon1MatcapTexturePropertyGroup # type: ignore[no-redef]
2255
+ extensions: ( # type: ignore[no-redef]
2256
+ Mtoon1MatcapTextureInfoExtensionsPropertyGroup
2257
+ )
2258
+
2259
+
2260
+ class Mtoon1OutlineWidthMultiplyTextureInfoPropertyGroup(
2261
+ Mtoon1TextureInfoPropertyGroup
2262
+ ):
2263
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2264
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2265
+ NodeGroupSocketTarget(
2266
+ node_group_node_tree_name=shader.OUTPUT_GROUP_NAME,
2267
+ in_socket_name="Outline Width Texture",
2268
+ ),
2269
+ ],
2270
+ }
2271
+
2272
+ index: PointerProperty( # type: ignore[valid-type]
2273
+ type=Mtoon1OutlineWidthMultiplyTexturePropertyGroup
2274
+ )
2275
+ extensions: PointerProperty( # type: ignore[valid-type]
2276
+ type=Mtoon1OutlineWidthMultiplyTextureInfoExtensionsPropertyGroup
2277
+ )
2278
+
2279
+ if TYPE_CHECKING:
2280
+ # This code is auto generated.
2281
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2282
+ index: Mtoon1OutlineWidthMultiplyTexturePropertyGroup # type: ignore[no-redef]
2283
+ extensions: ( # type: ignore[no-redef]
2284
+ Mtoon1OutlineWidthMultiplyTextureInfoExtensionsPropertyGroup
2285
+ )
2286
+
2287
+
2288
+ class Mtoon1UvAnimationMaskTextureInfoPropertyGroup(Mtoon1TextureInfoPropertyGroup):
2289
+ node_socket_targets: Mapping[str, Sequence[NodeSocketTarget]] = {
2290
+ TEX_IMAGE_COLOR_OUTPUT_KEY: [
2291
+ NodeGroupSocketTarget(
2292
+ node_group_node_tree_name=shader.UV_ANIMATION_GROUP_NAME,
2293
+ in_socket_name="Mask Texture",
2294
+ ),
2295
+ ],
2296
+ }
2297
+
2298
+ index: PointerProperty( # type: ignore[valid-type]
2299
+ type=Mtoon1UvAnimationMaskTexturePropertyGroup
2300
+ )
2301
+ extensions: PointerProperty( # type: ignore[valid-type]
2302
+ type=Mtoon1UvAnimationMaskTextureInfoExtensionsPropertyGroup
2303
+ )
2304
+
2305
+ if TYPE_CHECKING:
2306
+ # This code is auto generated.
2307
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2308
+ index: Mtoon1UvAnimationMaskTexturePropertyGroup # type: ignore[no-redef]
2309
+ extensions: ( # type: ignore[no-redef]
2310
+ Mtoon1UvAnimationMaskTextureInfoExtensionsPropertyGroup
2311
+ )
2312
+
2313
+
2314
+ class Mtoon0SamplerPropertyGroup(PropertyGroup):
2315
+ mag_filter: EnumProperty( # type: ignore[valid-type]
2316
+ items=Mtoon1SamplerPropertyGroup.mag_filter_enum.items(),
2317
+ name="Mag Filter",
2318
+ )
2319
+
2320
+ min_filter: EnumProperty( # type: ignore[valid-type]
2321
+ items=Mtoon1SamplerPropertyGroup.min_filter_enum.items(),
2322
+ name="Min Filter",
2323
+ )
2324
+
2325
+ wrap_s: EnumProperty( # type: ignore[valid-type]
2326
+ items=Mtoon1SamplerPropertyGroup.wrap_enum.items(),
2327
+ name="Wrap S",
2328
+ default=Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.value,
2329
+ )
2330
+
2331
+ wrap_t: EnumProperty( # type: ignore[valid-type]
2332
+ items=Mtoon1SamplerPropertyGroup.wrap_enum.items(),
2333
+ name="Wrap T",
2334
+ default=Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.value,
2335
+ )
2336
+
2337
+ if TYPE_CHECKING:
2338
+ # This code is auto generated.
2339
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2340
+ mag_filter: str # type: ignore[no-redef]
2341
+ min_filter: str # type: ignore[no-redef]
2342
+ wrap_s: str # type: ignore[no-redef]
2343
+ wrap_t: str # type: ignore[no-redef]
2344
+
2345
+
2346
+ class Mtoon0TexturePropertyGroup(PropertyGroup):
2347
+ label = ""
2348
+ panel_label = ""
2349
+ colorspace = "sRGB"
2350
+
2351
+ def get_connected_node_image(self) -> Optional[Image]:
2352
+ return self.source if isinstance(self.source, Image) else None
2353
+
2354
+ source: PointerProperty( # type: ignore[valid-type]
2355
+ type=Image,
2356
+ )
2357
+
2358
+ sampler: PointerProperty( # type: ignore[valid-type]
2359
+ type=Mtoon0SamplerPropertyGroup,
2360
+ )
2361
+
2362
+ show_expanded: BoolProperty() # type: ignore[valid-type]
2363
+
2364
+ if TYPE_CHECKING:
2365
+ # This code is auto generated.
2366
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2367
+ source: Optional[Image] # type: ignore[no-redef]
2368
+ sampler: Mtoon0SamplerPropertyGroup # type: ignore[no-redef]
2369
+ show_expanded: bool # type: ignore[no-redef]
2370
+
2371
+
2372
+ class Mtoon0ReceiveShadowTexturePropertyGroup(Mtoon0TexturePropertyGroup):
2373
+ label = "Shadow Receive Multiplier"
2374
+ panel_label = label
2375
+ colorspace = "Non-Color"
2376
+
2377
+
2378
+ class Mtoon0ShadingGradeTexturePropertyGroup(Mtoon0TexturePropertyGroup):
2379
+ label = "Lit & Shade Mixing Multiplier"
2380
+ panel_label = label
2381
+ colorspace = "Non-Color"
2382
+
2383
+
2384
+ # https://github.com/KhronosGroup/glTF/blob/1ab49ec412e638f2e5af0289e9fbb60c7271e457/specification/2.0/schema/material.pbrMetallicRoughness.schema.json#L9-L26
2385
+ class Mtoon1PbrMetallicRoughnessPropertyGroup(MaterialTraceablePropertyGroup):
2386
+ def _get_base_color_factor(self) -> tuple[float, float, float, float]:
2387
+ r, g, b = self.get_rgb(
2388
+ shader.OUTPUT_GROUP_NAME,
2389
+ shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_LABEL,
2390
+ default_value=shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_DEFAULT,
2391
+ )
2392
+ a = self.get_value(
2393
+ shader.OUTPUT_GROUP_NAME,
2394
+ shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_LABEL,
2395
+ default_value=shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_DEFAULT,
2396
+ )
2397
+ return (r, g, b, a)
2398
+
2399
+ def _set_base_color_factor(self, value: object) -> None:
2400
+ color = convert.float4_or_none(value)
2401
+ if color is None:
2402
+ return
2403
+ self.set_rgba(
2404
+ shader.OUTPUT_GROUP_NAME,
2405
+ shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_LABEL,
2406
+ color,
2407
+ )
2408
+ self.set_value(
2409
+ shader.OUTPUT_GROUP_NAME,
2410
+ shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_LABEL,
2411
+ color[3],
2412
+ )
2413
+ material = self.find_material()
2414
+ principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
2415
+ principled_bsdf.base_color = (
2416
+ color[0],
2417
+ color[1],
2418
+ color[2],
2419
+ )
2420
+ mtoon1 = get_material_extension(material).mtoon1
2421
+ alpha_mode_value = mtoon1.alpha_mode_enum.identifier_to_value(
2422
+ mtoon1.alpha_mode, mtoon1.ALPHA_MODE_OPAQUE.value
2423
+ )
2424
+ mtoon1.update_alpha_nodes(material, alpha_mode_value)
2425
+
2426
+ if mtoon1.is_outline_material:
2427
+ return
2428
+ outline_material = mtoon1.outline_material
2429
+ if not outline_material:
2430
+ return
2431
+ outline_mtoon1 = get_material_extension(outline_material).mtoon1
2432
+ outline_mtoon1.update_alpha_nodes(outline_material, alpha_mode_value)
2433
+
2434
+ base_color_factor: FloatVectorProperty( # type: ignore[valid-type]
2435
+ size=4,
2436
+ subtype="COLOR",
2437
+ default=(
2438
+ *shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_COLOR_DEFAULT,
2439
+ shader.OUTPUT_GROUP_BASE_COLOR_FACTOR_ALPHA_DEFAULT,
2440
+ ),
2441
+ min=0,
2442
+ max=1,
2443
+ get=_get_base_color_factor,
2444
+ set=_set_base_color_factor,
2445
+ )
2446
+
2447
+ base_color_texture: PointerProperty( # type: ignore[valid-type]
2448
+ type=Mtoon1BaseColorTextureInfoPropertyGroup
2449
+ )
2450
+
2451
+ if TYPE_CHECKING:
2452
+ # This code is auto generated.
2453
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2454
+ base_color_factor: Sequence[float] # type: ignore[no-redef]
2455
+ base_color_texture: ( # type: ignore[no-redef]
2456
+ Mtoon1BaseColorTextureInfoPropertyGroup
2457
+ )
2458
+
2459
+
2460
+ class Mtoon1VrmcMaterialsMtoonPropertyGroup(MaterialTraceablePropertyGroup):
2461
+ def _get_transparent_with_z_write(self) -> bool:
2462
+ return self.get_bool(
2463
+ shader.OUTPUT_GROUP_NAME,
2464
+ shader.OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_LABEL,
2465
+ default_value=shader.OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_DEFAULT,
2466
+ )
2467
+
2468
+ def _set_transparent_with_z_write(self, value: object) -> None:
2469
+ self.set_bool(
2470
+ shader.OUTPUT_GROUP_NAME,
2471
+ shader.OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_LABEL,
2472
+ value,
2473
+ )
2474
+
2475
+ mtoon1 = get_material_extension(self.find_material()).mtoon1
2476
+ mtoon1.mtoon0_render_queue_and_clamp = mtoon1.mtoon0_render_queue
2477
+
2478
+ transparent_with_z_write: BoolProperty( # type: ignore[valid-type]
2479
+ name="Transparent With ZWrite Mode",
2480
+ default=shader.OUTPUT_GROUP_TRANSPARENT_WITH_Z_WRITE_DEFAULT,
2481
+ get=_get_transparent_with_z_write,
2482
+ set=_set_transparent_with_z_write,
2483
+ )
2484
+
2485
+ def _get_render_queue_offset_number(self) -> int:
2486
+ return self.get_int(
2487
+ shader.OUTPUT_GROUP_NAME,
2488
+ shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_LABEL,
2489
+ default_value=shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_DEFAULT,
2490
+ )
2491
+
2492
+ def _set_render_queue_offset_number(self, value: int) -> None:
2493
+ self.set_int(
2494
+ shader.OUTPUT_GROUP_NAME,
2495
+ shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_LABEL,
2496
+ value,
2497
+ )
2498
+
2499
+ render_queue_offset_number: IntProperty( # type: ignore[valid-type]
2500
+ name="RenderQueue Offset",
2501
+ min=shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_MIN,
2502
+ default=shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_DEFAULT,
2503
+ max=shader.OUTPUT_GROUP_RENDER_QUEUE_OFFSET_NUMBER_MAX,
2504
+ get=_get_render_queue_offset_number,
2505
+ set=_set_render_queue_offset_number,
2506
+ )
2507
+
2508
+ shade_multiply_texture: PointerProperty( # type: ignore[valid-type]
2509
+ type=Mtoon1ShadeMultiplyTextureInfoPropertyGroup
2510
+ )
2511
+
2512
+ def _get_shade_color_factor(self) -> tuple[float, float, float]:
2513
+ return self.get_rgb(
2514
+ shader.OUTPUT_GROUP_NAME,
2515
+ shader.OUTPUT_GROUP_SHADE_COLOR_FACTOR_LABEL,
2516
+ default_value=shader.OUTPUT_GROUP_SHADE_COLOR_FACTOR_DEFAULT,
2517
+ )
2518
+
2519
+ def _set_shade_color_factor(self, value: object) -> None:
2520
+ self.set_rgb(
2521
+ shader.OUTPUT_GROUP_NAME,
2522
+ shader.OUTPUT_GROUP_SHADE_COLOR_FACTOR_LABEL,
2523
+ value,
2524
+ )
2525
+
2526
+ shade_color_factor: FloatVectorProperty( # type: ignore[valid-type]
2527
+ size=3,
2528
+ subtype="COLOR",
2529
+ default=shader.OUTPUT_GROUP_SHADE_COLOR_FACTOR_DEFAULT,
2530
+ min=0.0,
2531
+ max=1.0,
2532
+ get=_get_shade_color_factor,
2533
+ set=_set_shade_color_factor,
2534
+ )
2535
+
2536
+ shading_shift_texture: PointerProperty( # type: ignore[valid-type]
2537
+ type=Mtoon1ShadingShiftTextureInfoPropertyGroup
2538
+ )
2539
+
2540
+ def _get_shading_shift_factor(self) -> float:
2541
+ return self.get_float(
2542
+ shader.OUTPUT_GROUP_NAME,
2543
+ shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_LABEL,
2544
+ default_value=shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_DEFAULT,
2545
+ )
2546
+
2547
+ def _set_shading_shift_factor(self, value: object) -> None:
2548
+ self.set_value(
2549
+ shader.OUTPUT_GROUP_NAME,
2550
+ shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_LABEL,
2551
+ value,
2552
+ )
2553
+
2554
+ shading_shift_factor: FloatProperty( # type: ignore[valid-type]
2555
+ name=shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_LABEL,
2556
+ soft_min=-1.0,
2557
+ default=shader.OUTPUT_GROUP_SHADING_SHIFT_FACTOR_DEFAULT,
2558
+ soft_max=1.0,
2559
+ get=_get_shading_shift_factor,
2560
+ set=_set_shading_shift_factor,
2561
+ )
2562
+
2563
+ def _get_shading_toony_factor(self) -> float:
2564
+ return self.get_float(
2565
+ shader.OUTPUT_GROUP_NAME,
2566
+ shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_LABEL,
2567
+ default_value=shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_DEFAULT,
2568
+ )
2569
+
2570
+ def _set_shading_toony_factor(self, value: object) -> None:
2571
+ self.set_value(
2572
+ shader.OUTPUT_GROUP_NAME,
2573
+ shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_LABEL,
2574
+ value,
2575
+ )
2576
+
2577
+ shading_toony_factor: FloatProperty( # type: ignore[valid-type]
2578
+ name=shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_LABEL,
2579
+ min=0.0,
2580
+ default=shader.OUTPUT_GROUP_SHADING_TOONY_FACTOR_DEFAULT,
2581
+ max=1.0,
2582
+ get=_get_shading_toony_factor,
2583
+ set=_set_shading_toony_factor,
2584
+ )
2585
+
2586
+ def _get_gi_equalization_factor(self) -> float:
2587
+ return self.get_float(
2588
+ shader.OUTPUT_GROUP_NAME,
2589
+ shader.OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_LABEL,
2590
+ default_value=shader.OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_DEFAULT,
2591
+ )
2592
+
2593
+ def _set_gi_equalization_factor(self, value: object) -> None:
2594
+ self.set_value(
2595
+ shader.OUTPUT_GROUP_NAME,
2596
+ shader.OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_LABEL,
2597
+ value,
2598
+ )
2599
+
2600
+ gi_equalization_factor: FloatProperty( # type: ignore[valid-type]
2601
+ name="GI Equalization",
2602
+ min=0.0,
2603
+ default=shader.OUTPUT_GROUP_GI_EQUALIZATION_FACTOR_DEFAULT,
2604
+ max=1.0,
2605
+ get=_get_gi_equalization_factor,
2606
+ set=_set_gi_equalization_factor,
2607
+ )
2608
+
2609
+ def _get_matcap_factor(self) -> tuple[float, float, float]:
2610
+ return self.get_rgb(
2611
+ shader.OUTPUT_GROUP_NAME,
2612
+ shader.OUTPUT_GROUP_MATCAP_FACTOR_LABEL,
2613
+ default_value=shader.OUTPUT_GROUP_MATCAP_FACTOR_DEFAULT,
2614
+ )
2615
+
2616
+ def _set_matcap_factor(self, value: object) -> None:
2617
+ self.set_rgb(
2618
+ shader.OUTPUT_GROUP_NAME,
2619
+ shader.OUTPUT_GROUP_MATCAP_FACTOR_LABEL,
2620
+ value,
2621
+ )
2622
+
2623
+ matcap_factor: FloatVectorProperty( # type: ignore[valid-type]
2624
+ size=3,
2625
+ subtype="COLOR",
2626
+ default=shader.OUTPUT_GROUP_MATCAP_FACTOR_DEFAULT,
2627
+ min=0,
2628
+ max=1,
2629
+ get=_get_matcap_factor,
2630
+ set=_set_matcap_factor,
2631
+ )
2632
+
2633
+ matcap_texture: PointerProperty( # type: ignore[valid-type]
2634
+ type=Mtoon1MatcapTextureInfoPropertyGroup
2635
+ )
2636
+
2637
+ def _get_parametric_rim_color_factor(self) -> tuple[float, float, float]:
2638
+ return self.get_rgb(
2639
+ shader.OUTPUT_GROUP_NAME,
2640
+ shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_LABEL,
2641
+ default_value=shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_DEFAULT,
2642
+ )
2643
+
2644
+ def _set_parametric_rim_color_factor(self, value: object) -> None:
2645
+ self.set_rgb(
2646
+ shader.OUTPUT_GROUP_NAME,
2647
+ shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_LABEL,
2648
+ value,
2649
+ )
2650
+
2651
+ parametric_rim_color_factor: FloatVectorProperty( # type: ignore[valid-type]
2652
+ name=shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_LABEL,
2653
+ size=3,
2654
+ subtype="COLOR",
2655
+ default=shader.OUTPUT_GROUP_PARAMETRIC_RIM_COLOR_FACTOR_DEFAULT,
2656
+ min=0,
2657
+ max=1,
2658
+ get=_get_parametric_rim_color_factor,
2659
+ set=_set_parametric_rim_color_factor,
2660
+ )
2661
+
2662
+ rim_multiply_texture: PointerProperty( # type: ignore[valid-type]
2663
+ type=Mtoon1RimMultiplyTextureInfoPropertyGroup
2664
+ )
2665
+
2666
+ def _get_rim_lighting_mix_factor(self) -> float:
2667
+ return self.get_value(
2668
+ shader.OUTPUT_GROUP_NAME,
2669
+ shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_LABEL,
2670
+ default_value=shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_DEFAULT,
2671
+ )
2672
+
2673
+ def _set_rim_lighting_mix_factor(self, value: object) -> None:
2674
+ self.set_value(
2675
+ shader.OUTPUT_GROUP_NAME,
2676
+ shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_LABEL,
2677
+ value,
2678
+ )
2679
+
2680
+ rim_lighting_mix_factor: FloatProperty( # type: ignore[valid-type]
2681
+ name=shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_LABEL,
2682
+ default=shader.OUTPUT_GROUP_RIM_LIGHTING_MIX_FACTOR_DEFAULT,
2683
+ soft_min=0,
2684
+ soft_max=1,
2685
+ get=_get_rim_lighting_mix_factor,
2686
+ set=_set_rim_lighting_mix_factor,
2687
+ )
2688
+
2689
+ def _get_parametric_rim_fresnel_power_factor(self) -> float:
2690
+ return self.get_float(
2691
+ shader.OUTPUT_GROUP_NAME,
2692
+ shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_LABEL,
2693
+ default_value=shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_DEFAULT,
2694
+ )
2695
+
2696
+ def _set_parametric_rim_fresnel_power_factor(self, value: object) -> None:
2697
+ self.set_value(
2698
+ shader.OUTPUT_GROUP_NAME,
2699
+ shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_LABEL,
2700
+ value,
2701
+ )
2702
+
2703
+ parametric_rim_fresnel_power_factor: FloatProperty( # type: ignore[valid-type]
2704
+ name=shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_LABEL,
2705
+ min=0.0,
2706
+ default=shader.OUTPUT_GROUP_PARAMETRIC_RIM_FRESNEL_POWER_DEFAULT,
2707
+ soft_max=100.0,
2708
+ get=_get_parametric_rim_fresnel_power_factor,
2709
+ set=_set_parametric_rim_fresnel_power_factor,
2710
+ )
2711
+
2712
+ def _get_parametric_rim_lift_factor(self) -> float:
2713
+ return self.get_float(
2714
+ shader.OUTPUT_GROUP_NAME,
2715
+ shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_LABEL,
2716
+ default_value=shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_DEFAULT,
2717
+ )
2718
+
2719
+ def _set_parametric_rim_lift_factor(self, value: object) -> None:
2720
+ self.set_value(
2721
+ shader.OUTPUT_GROUP_NAME,
2722
+ shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_LABEL,
2723
+ value,
2724
+ )
2725
+
2726
+ parametric_rim_lift_factor: FloatProperty( # type: ignore[valid-type]
2727
+ name=shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_LABEL,
2728
+ soft_min=0.0,
2729
+ default=shader.OUTPUT_GROUP_PARAMETRIC_RIM_LIFT_FACTOR_DEFAULT,
2730
+ soft_max=1.0,
2731
+ get=_get_parametric_rim_lift_factor,
2732
+ set=_set_parametric_rim_lift_factor,
2733
+ )
2734
+
2735
+ (
2736
+ outline_width_mode_enum,
2737
+ (
2738
+ OUTLINE_WIDTH_MODE_NONE,
2739
+ OUTLINE_WIDTH_MODE_WORLD_COORDINATES,
2740
+ OUTLINE_WIDTH_MODE_SCREEN_COORDINATES,
2741
+ ),
2742
+ ) = property_group_enum(
2743
+ (
2744
+ "none",
2745
+ "None",
2746
+ "",
2747
+ "NONE",
2748
+ shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_MIN,
2749
+ ),
2750
+ ("worldCoordinates", "World Coordinates", "", "NONE", 1),
2751
+ (
2752
+ "screenCoordinates",
2753
+ "Screen Coordinates",
2754
+ "",
2755
+ "NONE",
2756
+ shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_MAX,
2757
+ ),
2758
+ )
2759
+
2760
+ def _get_outline_width_mode(self) -> int:
2761
+ return self.get_int(
2762
+ shader.OUTPUT_GROUP_NAME,
2763
+ shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_LABEL,
2764
+ default_value=shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_DEFAULT,
2765
+ )
2766
+
2767
+ def _set_outline_width_mode(self, value: object) -> None:
2768
+ self.set_int(
2769
+ shader.OUTPUT_GROUP_NAME,
2770
+ shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_LABEL,
2771
+ value,
2772
+ )
2773
+ self.update_outline_geometry()
2774
+
2775
+ def update_outline_geometry(self) -> None:
2776
+ material = self.find_material()
2777
+ if get_material_extension(material).mtoon1.is_outline_material:
2778
+ return
2779
+ refresh_mtoon1_outline(material_name=material.name, create_modifier=True)
2780
+
2781
+ outline_width_mode: EnumProperty( # type: ignore[valid-type]
2782
+ items=outline_width_mode_enum.items(),
2783
+ name=shader.OUTPUT_GROUP_OUTLINE_WIDTH_MODE_LABEL,
2784
+ get=_get_outline_width_mode,
2785
+ set=_set_outline_width_mode,
2786
+ )
2787
+
2788
+ def _get_outline_width_factor(self) -> float:
2789
+ return self.get_value(
2790
+ shader.OUTPUT_GROUP_NAME,
2791
+ shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_LABEL,
2792
+ default_value=shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_DEFAULT,
2793
+ )
2794
+
2795
+ def _set_outline_width_factor(self, value: object) -> None:
2796
+ self.set_value(
2797
+ shader.OUTPUT_GROUP_NAME,
2798
+ shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_LABEL,
2799
+ value,
2800
+ )
2801
+ self.update_outline_geometry()
2802
+
2803
+ outline_width_factor: FloatProperty( # type: ignore[valid-type]
2804
+ name=shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_LABEL,
2805
+ default=shader.OUTPUT_GROUP_OUTLINE_WIDTH_FACTOR_DEFAULT,
2806
+ min=0.0,
2807
+ soft_max=0.05,
2808
+ get=_get_outline_width_factor,
2809
+ set=_set_outline_width_factor,
2810
+ )
2811
+
2812
+ outline_width_multiply_texture: PointerProperty( # type: ignore[valid-type]
2813
+ type=Mtoon1OutlineWidthMultiplyTextureInfoPropertyGroup
2814
+ )
2815
+
2816
+ def _get_outline_color_factor(self) -> tuple[float, float, float]:
2817
+ return self.get_rgb(
2818
+ shader.OUTPUT_GROUP_NAME,
2819
+ shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_LABEL,
2820
+ default_value=shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_DEFAULT,
2821
+ )
2822
+
2823
+ def _set_outline_color_factor(self, value: object) -> None:
2824
+ self.set_rgb(
2825
+ shader.OUTPUT_GROUP_NAME,
2826
+ shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_LABEL,
2827
+ value,
2828
+ shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_DEFAULT,
2829
+ )
2830
+ self.update_outline_geometry()
2831
+
2832
+ material = self.find_material()
2833
+ mtoon1 = get_material_extension(material).mtoon1
2834
+ if mtoon1.is_outline_material:
2835
+ return
2836
+ outline_material = mtoon1.outline_material
2837
+ if not outline_material:
2838
+ return
2839
+ outline_principled_bsdf = PrincipledBSDFWrapper(
2840
+ outline_material, is_readonly=False
2841
+ )
2842
+
2843
+ outline_diffuse_alpha = outline_material.diffuse_color[3]
2844
+ outline_principled_bsdf.base_color = (
2845
+ self.outline_color_factor[0],
2846
+ self.outline_color_factor[1],
2847
+ self.outline_color_factor[2],
2848
+ )
2849
+ outline_material.diffuse_color[3] = outline_diffuse_alpha
2850
+
2851
+ outline_color_factor: FloatVectorProperty( # type: ignore[valid-type]
2852
+ name=shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_LABEL,
2853
+ size=3,
2854
+ subtype="COLOR",
2855
+ default=shader.OUTPUT_GROUP_OUTLINE_COLOR_FACTOR_DEFAULT,
2856
+ min=0,
2857
+ max=1,
2858
+ get=_get_outline_color_factor,
2859
+ set=_set_outline_color_factor,
2860
+ )
2861
+
2862
+ def _get_outline_lighting_mix_factor(self) -> float:
2863
+ return self.get_value(
2864
+ shader.OUTPUT_GROUP_NAME,
2865
+ shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_LABEL,
2866
+ default_value=shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_DEFAULT,
2867
+ )
2868
+
2869
+ def _set_outline_lighting_mix_factor(self, value: object) -> None:
2870
+ self.set_value(
2871
+ shader.OUTPUT_GROUP_NAME,
2872
+ shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_LABEL,
2873
+ value,
2874
+ )
2875
+ self.update_outline_geometry()
2876
+
2877
+ outline_lighting_mix_factor: FloatProperty( # type: ignore[valid-type]
2878
+ name=shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_LABEL,
2879
+ min=0.0,
2880
+ default=shader.OUTPUT_GROUP_OUTLINE_LIGHTING_MIX_FACTOR_DEFAULT,
2881
+ max=1.0,
2882
+ get=_get_outline_lighting_mix_factor,
2883
+ set=_set_outline_lighting_mix_factor,
2884
+ )
2885
+
2886
+ def _update_enable_outline_preview(self, context: Context) -> None:
2887
+ material_name = self.find_material().name
2888
+ refresh_mtoon1_outline(
2889
+ context, material_name=material_name, create_modifier=True
2890
+ )
2891
+ for material in context.blend_data.materials:
2892
+ if material.name == material_name:
2893
+ continue
2894
+ ext = get_material_extension(material).mtoon1
2895
+ if not ext.enabled:
2896
+ continue
2897
+ mtoon = ext.extensions.vrmc_materials_mtoon
2898
+ if mtoon.enable_outline_preview != self.enable_outline_preview:
2899
+ mtoon.enable_outline_preview = self.enable_outline_preview
2900
+
2901
+ enable_outline_preview: BoolProperty( # type: ignore[valid-type]
2902
+ default=True,
2903
+ update=_update_enable_outline_preview,
2904
+ )
2905
+
2906
+ uv_animation_mask_texture: PointerProperty( # type: ignore[valid-type]
2907
+ type=Mtoon1UvAnimationMaskTextureInfoPropertyGroup
2908
+ )
2909
+
2910
+ def _get_uv_animation_scroll_x_speed_factor(self) -> float:
2911
+ return self.get_value(
2912
+ shader.UV_ANIMATION_GROUP_NAME,
2913
+ shader.UV_ANIMATION_GROUP_TRANSLATE_X_LABEL,
2914
+ default_value=shader.UV_ANIMATION_GROUP_TRANSLATE_X_DEFAULT,
2915
+ )
2916
+
2917
+ def _set_uv_animation_scroll_x_speed_factor(self, value: object) -> None:
2918
+ self.set_value(
2919
+ shader.UV_ANIMATION_GROUP_NAME,
2920
+ shader.UV_ANIMATION_GROUP_TRANSLATE_X_LABEL,
2921
+ value,
2922
+ )
2923
+
2924
+ uv_animation_scroll_x_speed_factor: FloatProperty( # type: ignore[valid-type]
2925
+ name=shader.UV_ANIMATION_GROUP_TRANSLATE_X_LABEL,
2926
+ default=shader.UV_ANIMATION_GROUP_TRANSLATE_X_DEFAULT,
2927
+ get=_get_uv_animation_scroll_x_speed_factor,
2928
+ set=_set_uv_animation_scroll_x_speed_factor,
2929
+ )
2930
+
2931
+ def _get_uv_animation_scroll_y_speed_factor(self) -> float:
2932
+ return self.get_value(
2933
+ shader.UV_ANIMATION_GROUP_NAME,
2934
+ shader.UV_ANIMATION_GROUP_TRANSLATE_Y_LABEL,
2935
+ default_value=shader.UV_ANIMATION_GROUP_TRANSLATE_Y_DEFAULT,
2936
+ )
2937
+
2938
+ def _set_uv_animation_scroll_y_speed_factor(self, value: object) -> None:
2939
+ self.set_value(
2940
+ shader.UV_ANIMATION_GROUP_NAME,
2941
+ shader.UV_ANIMATION_GROUP_TRANSLATE_Y_LABEL,
2942
+ value,
2943
+ )
2944
+
2945
+ uv_animation_scroll_y_speed_factor: FloatProperty( # type: ignore[valid-type]
2946
+ name=shader.UV_ANIMATION_GROUP_TRANSLATE_Y_LABEL,
2947
+ default=shader.UV_ANIMATION_GROUP_TRANSLATE_Y_DEFAULT,
2948
+ get=_get_uv_animation_scroll_y_speed_factor,
2949
+ set=_set_uv_animation_scroll_y_speed_factor,
2950
+ )
2951
+
2952
+ def _get_uv_animation_rotation_speed_factor(self) -> float:
2953
+ return self.get_value(
2954
+ shader.UV_ANIMATION_GROUP_NAME,
2955
+ shader.UV_ANIMATION_GROUP_ROTATION_LABEL,
2956
+ default_value=shader.UV_ANIMATION_GROUP_ROTATION_DEFAULT,
2957
+ )
2958
+
2959
+ def _set_uv_animation_rotation_speed_factor(self, value: object) -> None:
2960
+ self.set_value(
2961
+ shader.UV_ANIMATION_GROUP_NAME,
2962
+ shader.UV_ANIMATION_GROUP_ROTATION_LABEL,
2963
+ value,
2964
+ )
2965
+
2966
+ uv_animation_rotation_speed_factor: FloatProperty( # type: ignore[valid-type]
2967
+ name=shader.UV_ANIMATION_GROUP_ROTATION_LABEL,
2968
+ get=_get_uv_animation_rotation_speed_factor,
2969
+ set=_set_uv_animation_rotation_speed_factor,
2970
+ )
2971
+
2972
+ if TYPE_CHECKING:
2973
+ # This code is auto generated.
2974
+ # To regenerate, run the `uv run tools/property_typing.py` command.
2975
+ transparent_with_z_write: bool # type: ignore[no-redef]
2976
+ render_queue_offset_number: int # type: ignore[no-redef]
2977
+ shade_multiply_texture: ( # type: ignore[no-redef]
2978
+ Mtoon1ShadeMultiplyTextureInfoPropertyGroup
2979
+ )
2980
+ shade_color_factor: Sequence[float] # type: ignore[no-redef]
2981
+ shading_shift_texture: ( # type: ignore[no-redef]
2982
+ Mtoon1ShadingShiftTextureInfoPropertyGroup
2983
+ )
2984
+ shading_shift_factor: float # type: ignore[no-redef]
2985
+ shading_toony_factor: float # type: ignore[no-redef]
2986
+ gi_equalization_factor: float # type: ignore[no-redef]
2987
+ matcap_factor: Sequence[float] # type: ignore[no-redef]
2988
+ matcap_texture: Mtoon1MatcapTextureInfoPropertyGroup # type: ignore[no-redef]
2989
+ parametric_rim_color_factor: Sequence[float] # type: ignore[no-redef]
2990
+ rim_multiply_texture: ( # type: ignore[no-redef]
2991
+ Mtoon1RimMultiplyTextureInfoPropertyGroup
2992
+ )
2993
+ rim_lighting_mix_factor: float # type: ignore[no-redef]
2994
+ parametric_rim_fresnel_power_factor: float # type: ignore[no-redef]
2995
+ parametric_rim_lift_factor: float # type: ignore[no-redef]
2996
+ outline_width_mode: str # type: ignore[no-redef]
2997
+ outline_width_factor: float # type: ignore[no-redef]
2998
+ outline_width_multiply_texture: ( # type: ignore[no-redef]
2999
+ Mtoon1OutlineWidthMultiplyTextureInfoPropertyGroup
3000
+ )
3001
+ outline_color_factor: Sequence[float] # type: ignore[no-redef]
3002
+ outline_lighting_mix_factor: float # type: ignore[no-redef]
3003
+ enable_outline_preview: bool # type: ignore[no-redef]
3004
+ uv_animation_mask_texture: ( # type: ignore[no-redef]
3005
+ Mtoon1UvAnimationMaskTextureInfoPropertyGroup
3006
+ )
3007
+ uv_animation_scroll_x_speed_factor: float # type: ignore[no-redef]
3008
+ uv_animation_scroll_y_speed_factor: float # type: ignore[no-redef]
3009
+ uv_animation_rotation_speed_factor: float # type: ignore[no-redef]
3010
+
3011
+
3012
+ # https://github.com/KhronosGroup/glTF/blob/d997b7dc7e426bc791f5613475f5b4490da0b099/extensions/2.0/Khronos/KHR_materials_emissive_strength/schema/glTF.KHR_materials_emissive_strength.schema.json
3013
+ class Mtoon1KhrMaterialsEmissiveStrengthPropertyGroup(MaterialTraceablePropertyGroup):
3014
+ def _get_emissive_strength(self) -> float:
3015
+ return self.get_float(
3016
+ shader.OUTPUT_GROUP_NAME,
3017
+ shader.OUTPUT_GROUP_EMISSIVE_STRENGTH_LABEL,
3018
+ default_value=shader.OUTPUT_GROUP_EMISSIVE_STRENGTH_DEFAULT,
3019
+ )
3020
+
3021
+ def _set_emissive_strength(self, value: object) -> None:
3022
+ self.set_value(
3023
+ shader.OUTPUT_GROUP_NAME,
3024
+ shader.OUTPUT_GROUP_EMISSIVE_STRENGTH_LABEL,
3025
+ value,
3026
+ )
3027
+
3028
+ material = self.find_material()
3029
+ principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
3030
+ principled_bsdf.emission_strength = self.emissive_strength
3031
+
3032
+ emissive_node = _get_gltf_emissive_node(material)
3033
+ if emissive_node is not None:
3034
+ socket = emissive_node.inputs.get(EMISSION_STRENGTH_INPUT_KEY)
3035
+ if isinstance(socket, NodeSocketFloat):
3036
+ socket.default_value = self.emissive_strength
3037
+
3038
+ mtoon1 = get_material_extension(material).mtoon1
3039
+ if mtoon1.is_outline_material:
3040
+ return
3041
+ outline_material = mtoon1.outline_material
3042
+ if not outline_material:
3043
+ return
3044
+ outline_principled_bsdf = PrincipledBSDFWrapper(
3045
+ outline_material, is_readonly=False
3046
+ )
3047
+ outline_principled_bsdf.emission_strength = self.emissive_strength
3048
+
3049
+ outline_emissive_node = _get_gltf_emissive_node(outline_material)
3050
+ if outline_emissive_node is not None:
3051
+ outline_socket = outline_emissive_node.inputs.get(
3052
+ EMISSION_STRENGTH_INPUT_KEY
3053
+ )
3054
+ if isinstance(outline_socket, NodeSocketFloat):
3055
+ outline_socket.default_value = self.emissive_strength
3056
+
3057
+ emissive_strength: FloatProperty( # type: ignore[valid-type]
3058
+ name="Strength",
3059
+ min=0.0,
3060
+ default=shader.OUTPUT_GROUP_EMISSIVE_STRENGTH_DEFAULT,
3061
+ get=_get_emissive_strength,
3062
+ set=_set_emissive_strength,
3063
+ )
3064
+
3065
+ if TYPE_CHECKING:
3066
+ # This code is auto generated.
3067
+ # To regenerate, run the `uv run tools/property_typing.py` command.
3068
+ emissive_strength: float # type: ignore[no-redef]
3069
+
3070
+
3071
+ class Mtoon1MaterialExtensionsPropertyGroup(PropertyGroup):
3072
+ vrmc_materials_mtoon: PointerProperty( # type: ignore[valid-type]
3073
+ type=Mtoon1VrmcMaterialsMtoonPropertyGroup
3074
+ )
3075
+ khr_materials_emissive_strength: PointerProperty( # type: ignore[valid-type]
3076
+ type=Mtoon1KhrMaterialsEmissiveStrengthPropertyGroup
3077
+ )
3078
+
3079
+ if TYPE_CHECKING:
3080
+ # This code is auto generated.
3081
+ # To regenerate, run the `uv run tools/property_typing.py` command.
3082
+ vrmc_materials_mtoon: ( # type: ignore[no-redef]
3083
+ Mtoon1VrmcMaterialsMtoonPropertyGroup
3084
+ )
3085
+ khr_materials_emissive_strength: ( # type: ignore[no-redef]
3086
+ Mtoon1KhrMaterialsEmissiveStrengthPropertyGroup
3087
+ )
3088
+
3089
+
3090
+ # https://github.com/vrm-c/vrm-specification/blob/8dc51ec7241be27ee95f159cefc0190a0e41967b/specification/VRMC_materials_mtoon-1.0-beta/schema/VRMC_materials_mtoon.schema.json
3091
+ class Mtoon1MaterialPropertyGroup(MaterialTraceablePropertyGroup):
3092
+ UNMANAGED_ADDON_VERSION = VrmAddonPreferences.UNMANAGED_ADDON_VERSION
3093
+
3094
+ addon_version: IntVectorProperty( # type: ignore[valid-type]
3095
+ size=3,
3096
+ default=UNMANAGED_ADDON_VERSION,
3097
+ )
3098
+
3099
+ pbr_metallic_roughness: PointerProperty( # type: ignore[valid-type]
3100
+ type=Mtoon1PbrMetallicRoughnessPropertyGroup
3101
+ )
3102
+
3103
+ (
3104
+ alpha_mode_enum,
3105
+ (
3106
+ ALPHA_MODE_OPAQUE,
3107
+ ALPHA_MODE_MASK,
3108
+ ALPHA_MODE_BLEND,
3109
+ ),
3110
+ ) = property_group_enum(
3111
+ ("OPAQUE", "Opaque", "", "NONE", 0),
3112
+ ("MASK", "Cutout", "", "NONE", 1),
3113
+ ("BLEND", "Transparent", "", "NONE", 2),
3114
+ )
3115
+
3116
+ alpha_mode_blend_method_hashed: BoolProperty() # type: ignore[valid-type]
3117
+
3118
+ def _get_alpha_mode(self) -> int:
3119
+ alpha_mode_value = self.get_int(
3120
+ shader.OUTPUT_GROUP_NAME,
3121
+ shader.OUTPUT_GROUP_ALPHA_MODE_LABEL,
3122
+ default_value=self.ALPHA_MODE_OPAQUE.value,
3123
+ )
3124
+ if alpha_mode_value in self.alpha_mode_enum.values():
3125
+ return alpha_mode_value
3126
+ return self.ALPHA_MODE_OPAQUE.value
3127
+
3128
+ @staticmethod
3129
+ def update_alpha_nodes(material: Material, alpha_mode_value: int) -> None:
3130
+ node_tree = material.node_tree
3131
+ if not node_tree:
3132
+ return
3133
+
3134
+ # Align with glTF nodes
3135
+ # https://docs.blender.org/manual/en/4.2/addons/import_export/scene_gltf2.html#alpha-modes
3136
+ mtoon1 = get_material_extension(material).mtoon1
3137
+ texture = mtoon1.pbr_metallic_roughness.base_color_texture.index
3138
+ tex_image_node_name = texture.get_image_texture_node_name()
3139
+
3140
+ if alpha_mode_value != mtoon1.ALPHA_MODE_MASK.value:
3141
+ TextureTraceablePropertyGroup.unlink_nodes(
3142
+ material,
3143
+ StaticNodeSocketTarget(
3144
+ in_node_name=ALPHA_CLIP_INPUT_NODE_NAME,
3145
+ in_node_type=ShaderNodeMath,
3146
+ in_socket_name=ALPHA_CLIP_INPUT_NODE_SOCKET_NAME,
3147
+ ),
3148
+ )
3149
+ TextureTraceablePropertyGroup.unlink_nodes(
3150
+ material,
3151
+ PrincipledBsdfNodeSocketTarget(
3152
+ in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
3153
+ ),
3154
+ )
3155
+ if alpha_mode_value != mtoon1.ALPHA_MODE_BLEND.value or bpy.app.version < (
3156
+ 4,
3157
+ 2,
3158
+ ):
3159
+ TextureTraceablePropertyGroup.unlink_nodes(
3160
+ material,
3161
+ PrincipledBsdfNodeSocketTarget(
3162
+ in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
3163
+ ),
3164
+ )
3165
+
3166
+ principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
3167
+ if bpy.app.version < (4, 2):
3168
+ principled_bsdf.alpha = mtoon1.pbr_metallic_roughness.base_color_factor[3]
3169
+ tex_image_node = node_tree.nodes.get(tex_image_node_name)
3170
+ if isinstance(tex_image_node, ShaderNodeTexImage):
3171
+ TextureTraceablePropertyGroup.link_or_unlink_nodes(
3172
+ material,
3173
+ tex_image_node_name,
3174
+ ShaderNodeTexImage,
3175
+ TEX_IMAGE_ALPHA_OUTPUT_KEY,
3176
+ PrincipledBsdfNodeSocketTarget(
3177
+ in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
3178
+ ),
3179
+ link=bool(tex_image_node.image),
3180
+ )
3181
+ elif alpha_mode_value == mtoon1.ALPHA_MODE_OPAQUE.value:
3182
+ principled_bsdf.alpha = 1.0
3183
+ elif alpha_mode_value == mtoon1.ALPHA_MODE_MASK.value:
3184
+ tex_image_node = node_tree.nodes.get(tex_image_node_name)
3185
+ image_exists = False
3186
+ if isinstance(tex_image_node, ShaderNodeTexImage):
3187
+ image_exists = bool(tex_image_node.image)
3188
+ TextureTraceablePropertyGroup.link_or_unlink_nodes(
3189
+ material,
3190
+ tex_image_node_name,
3191
+ ShaderNodeTexImage,
3192
+ TEX_IMAGE_ALPHA_OUTPUT_KEY,
3193
+ StaticNodeSocketTarget(
3194
+ in_node_name=ALPHA_CLIP_INPUT_NODE_NAME,
3195
+ in_node_type=ShaderNodeMath,
3196
+ in_socket_name=ALPHA_CLIP_INPUT_NODE_SOCKET_NAME,
3197
+ ),
3198
+ link=image_exists,
3199
+ )
3200
+ TextureTraceablePropertyGroup.link_or_unlink_nodes(
3201
+ material,
3202
+ ALPHA_CLIP_OUTPUT_NODE_NAME,
3203
+ ShaderNodeMath,
3204
+ ALPHA_CLIP_OUTPUT_NODE_SOCKET_NAME,
3205
+ PrincipledBsdfNodeSocketTarget(
3206
+ in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
3207
+ ),
3208
+ link=image_exists,
3209
+ )
3210
+ if not image_exists:
3211
+ alpha = mtoon1.pbr_metallic_roughness.base_color_factor[3]
3212
+ if alpha >= mtoon1.alpha_cutoff:
3213
+ principled_bsdf.alpha = 1.0
3214
+ else:
3215
+ principled_bsdf.alpha = 0.0
3216
+ elif alpha_mode_value == mtoon1.ALPHA_MODE_BLEND.value:
3217
+ principled_bsdf.alpha = mtoon1.pbr_metallic_roughness.base_color_factor[3]
3218
+ tex_image_node = node_tree.nodes.get(tex_image_node_name)
3219
+ if isinstance(tex_image_node, ShaderNodeTexImage):
3220
+ TextureTraceablePropertyGroup.link_or_unlink_nodes(
3221
+ material,
3222
+ tex_image_node_name,
3223
+ ShaderNodeTexImage,
3224
+ TEX_IMAGE_ALPHA_OUTPUT_KEY,
3225
+ PrincipledBsdfNodeSocketTarget(
3226
+ in_socket_name=PRINCIPLED_BSDF_ALPHA_INPUT_KEY
3227
+ ),
3228
+ link=bool(tex_image_node.image),
3229
+ )
3230
+
3231
+ alpha_clip_input_node = node_tree.nodes.get(ALPHA_CLIP_INPUT_NODE_NAME)
3232
+ if (
3233
+ isinstance(alpha_clip_input_node, ShaderNodeMath)
3234
+ and alpha_clip_input_node.operation == "LESS_THAN"
3235
+ ):
3236
+ inputs = alpha_clip_input_node.inputs
3237
+ if len(inputs) >= 2:
3238
+ alpha_input = inputs[0]
3239
+ if isinstance(alpha_input, shader.FLOAT_SOCKET_CLASSES):
3240
+ alpha_input.default_value = (
3241
+ mtoon1.pbr_metallic_roughness.base_color_factor[3]
3242
+ )
3243
+ else:
3244
+ _logger.error(
3245
+ "Unexpected alpha input node input type: %s", type(alpha_input)
3246
+ )
3247
+
3248
+ alpha_cutoff_input = inputs[1]
3249
+ if isinstance(alpha_cutoff_input, shader.FLOAT_SOCKET_CLASSES):
3250
+ alpha_cutoff_input.default_value = mtoon1.alpha_cutoff
3251
+ else:
3252
+ _logger.error(
3253
+ "Unexpected alpha clip input node input type: %s",
3254
+ type(alpha_cutoff_input),
3255
+ )
3256
+ else:
3257
+ _logger.error(
3258
+ "Unexpected alpha clip input node input length: %s", len(inputs)
3259
+ )
3260
+ else:
3261
+ _logger.error(
3262
+ "Unexpected alpha clip input node type: %s",
3263
+ type(alpha_clip_input_node),
3264
+ )
3265
+
3266
+ def _set_alpha_mode(self, value: int) -> None:
3267
+ changed = self._get_alpha_mode() != value
3268
+
3269
+ material = self.find_material()
3270
+
3271
+ shadow_method = None
3272
+ if bpy.app.version < (4, 2):
3273
+ if material.blend_method == "HASHED":
3274
+ self.alpha_mode_blend_method_hashed = True
3275
+ if material.blend_method == "BLEND":
3276
+ self.alpha_mode_blend_method_hashed = False
3277
+
3278
+ if value == self.ALPHA_MODE_OPAQUE.value:
3279
+ material.blend_method = "OPAQUE"
3280
+ shadow_method = "OPAQUE"
3281
+ elif value == self.ALPHA_MODE_MASK.value:
3282
+ material.blend_method = "CLIP"
3283
+ shadow_method = "CLIP"
3284
+ elif value == self.ALPHA_MODE_BLEND.value:
3285
+ material.blend_method = "HASHED"
3286
+ shadow_method = "HASHED"
3287
+ else:
3288
+ _logger.error("Unexpected alpha mode: %s", value)
3289
+ material.blend_method = "OPAQUE"
3290
+ shadow_method = "OPAQUE"
3291
+
3292
+ self.set_value(
3293
+ shader.OUTPUT_GROUP_NAME,
3294
+ shader.OUTPUT_GROUP_ALPHA_MODE_LABEL,
3295
+ value,
3296
+ )
3297
+
3298
+ if changed:
3299
+ self._set_mtoon0_render_queue_and_clamp(self.mtoon0_render_queue)
3300
+
3301
+ self.update_alpha_nodes(material, value)
3302
+
3303
+ if self.is_outline_material:
3304
+ if bpy.app.version < (4, 2):
3305
+ material.shadow_method = "NONE"
3306
+ return
3307
+
3308
+ if bpy.app.version < (4, 3) and shadow_method is not None:
3309
+ material.shadow_method = shadow_method
3310
+
3311
+ outline_material = self.outline_material
3312
+ if not outline_material:
3313
+ return
3314
+ alpha_mode = self.alpha_mode_enum.value_to_identifier(
3315
+ value, self.ALPHA_MODE_OPAQUE.identifier
3316
+ )
3317
+ get_material_extension(outline_material).mtoon1.alpha_mode = alpha_mode
3318
+
3319
+ alpha_mode: EnumProperty( # type: ignore[valid-type]
3320
+ items=alpha_mode_enum.items(),
3321
+ name="Alpha Mode",
3322
+ get=_get_alpha_mode,
3323
+ set=_set_alpha_mode,
3324
+ )
3325
+
3326
+ def _get_double_sided(self) -> bool:
3327
+ return not self.find_material().use_backface_culling
3328
+
3329
+ def _set_double_sided(self, value: object) -> None:
3330
+ material = self.find_material()
3331
+ material.use_backface_culling = not value
3332
+ self.set_bool(
3333
+ shader.OUTPUT_GROUP_NAME, shader.OUTPUT_GROUP_DOUBLE_SIDED_LABEL, value
3334
+ )
3335
+ mtoon1 = get_material_extension(material).mtoon1
3336
+ if mtoon1.is_outline_material:
3337
+ return
3338
+ outline_material = mtoon1.outline_material
3339
+ if not outline_material:
3340
+ return
3341
+ get_material_extension(outline_material).mtoon1.double_sided = False
3342
+
3343
+ double_sided: BoolProperty( # type: ignore[valid-type]
3344
+ name=shader.OUTPUT_GROUP_DOUBLE_SIDED_LABEL,
3345
+ get=_get_double_sided,
3346
+ set=_set_double_sided,
3347
+ )
3348
+
3349
+ def _get_alpha_cutoff(self) -> float:
3350
+ return self.get_float(
3351
+ shader.OUTPUT_GROUP_NAME,
3352
+ shader.OUTPUT_GROUP_ALPHA_CUTOFF_LABEL,
3353
+ default_value=shader.OUTPUT_GROUP_ALPHA_CUTOFF_DEFAULT,
3354
+ )
3355
+
3356
+ def _set_alpha_cutoff(self, value: float) -> None:
3357
+ material = self.find_material()
3358
+ if bpy.app.version < (4, 2):
3359
+ # glTF alphaCutoff: >= is opaque
3360
+ # Blender <4.2 alpha_threshold: > is opaque
3361
+ # Subtract 0.00001 to absorb this difference.
3362
+ material.alpha_threshold = max(0, min(1.0, value - 0.00001))
3363
+ self.set_value(
3364
+ shader.OUTPUT_GROUP_NAME,
3365
+ shader.OUTPUT_GROUP_ALPHA_CUTOFF_LABEL,
3366
+ max(0.0, value),
3367
+ )
3368
+
3369
+ alpha_mode_value = self.alpha_mode_enum.identifier_to_value(
3370
+ self.alpha_mode, self.ALPHA_MODE_OPAQUE.value
3371
+ )
3372
+ self.update_alpha_nodes(material, alpha_mode_value)
3373
+
3374
+ if self.is_outline_material:
3375
+ return
3376
+ outline_material = self.outline_material
3377
+ if not outline_material:
3378
+ return
3379
+ get_material_extension(outline_material).mtoon1.alpha_cutoff = value
3380
+
3381
+ alpha_cutoff: FloatProperty( # type: ignore[valid-type]
3382
+ name="Cutoff",
3383
+ min=0,
3384
+ soft_max=1,
3385
+ get=_get_alpha_cutoff,
3386
+ set=_set_alpha_cutoff,
3387
+ )
3388
+
3389
+ normal_texture: PointerProperty( # type: ignore[valid-type]
3390
+ type=Mtoon1NormalTextureInfoPropertyGroup
3391
+ )
3392
+
3393
+ emissive_texture: PointerProperty( # type: ignore[valid-type]
3394
+ type=Mtoon1EmissiveTextureInfoPropertyGroup
3395
+ )
3396
+
3397
+ def _get_emissive_factor(self) -> tuple[float, float, float]:
3398
+ return self.get_rgb(
3399
+ shader.OUTPUT_GROUP_NAME,
3400
+ shader.OUTPUT_GROUP_EMISSIVE_FACTOR_LABEL,
3401
+ default_value=shader.OUTPUT_GROUP_EMISSIVE_FACTOR_DEFAULT,
3402
+ )
3403
+
3404
+ def _set_emissive_factor(self, value: object) -> None:
3405
+ self.set_rgb(
3406
+ shader.OUTPUT_GROUP_NAME,
3407
+ shader.OUTPUT_GROUP_EMISSIVE_FACTOR_LABEL,
3408
+ value,
3409
+ shader.OUTPUT_GROUP_EMISSIVE_FACTOR_DEFAULT,
3410
+ )
3411
+
3412
+ material = self.find_material()
3413
+ principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
3414
+ principled_bsdf.emission_color = (
3415
+ self.emissive_factor[0],
3416
+ self.emissive_factor[1],
3417
+ self.emissive_factor[2],
3418
+ )
3419
+
3420
+ emissive_node = _get_gltf_emissive_node(material)
3421
+ if emissive_node is not None:
3422
+ socket = emissive_node.inputs.get(EMISSION_COLOR_INPUT_KEY)
3423
+ if isinstance(socket, NodeSocketColor):
3424
+ socket.default_value = (
3425
+ self.emissive_factor[0],
3426
+ self.emissive_factor[1],
3427
+ self.emissive_factor[2],
3428
+ 1,
3429
+ )
3430
+
3431
+ mtoon1 = get_material_extension(material).mtoon1
3432
+ if mtoon1.is_outline_material:
3433
+ return
3434
+ outline_material = mtoon1.outline_material
3435
+ if not outline_material:
3436
+ return
3437
+ outline_principled_bsdf = PrincipledBSDFWrapper(
3438
+ outline_material, is_readonly=False
3439
+ )
3440
+ outline_principled_bsdf.emission_color = (
3441
+ self.emissive_factor[0],
3442
+ self.emissive_factor[1],
3443
+ self.emissive_factor[2],
3444
+ )
3445
+
3446
+ outline_emissive_node = _get_gltf_emissive_node(material)
3447
+ if outline_emissive_node is not None:
3448
+ outline_socket = outline_emissive_node.inputs.get(EMISSION_COLOR_INPUT_KEY)
3449
+ if isinstance(outline_socket, NodeSocketColor):
3450
+ outline_socket.default_value = (
3451
+ self.emissive_factor[0],
3452
+ self.emissive_factor[1],
3453
+ self.emissive_factor[2],
3454
+ 1,
3455
+ )
3456
+
3457
+ emissive_factor: FloatVectorProperty( # type: ignore[valid-type]
3458
+ size=3,
3459
+ subtype="COLOR",
3460
+ default=shader.OUTPUT_GROUP_EMISSIVE_FACTOR_DEFAULT,
3461
+ min=0,
3462
+ max=1,
3463
+ get=_get_emissive_factor,
3464
+ set=_set_emissive_factor,
3465
+ )
3466
+
3467
+ extensions: PointerProperty( # type: ignore[valid-type]
3468
+ type=Mtoon1MaterialExtensionsPropertyGroup
3469
+ )
3470
+
3471
+ def _get_enabled(self) -> bool:
3472
+ if self.is_outline_material:
3473
+ return False
3474
+
3475
+ material = self.find_material()
3476
+ if bpy.app.version < (5, 0, 0) and not material.use_nodes:
3477
+ return False
3478
+
3479
+ node_tree = material.node_tree
3480
+ if not node_tree:
3481
+ return False
3482
+
3483
+ group_node = node_tree.nodes.get("Mtoon1Material.Mtoon1Output")
3484
+ if not isinstance(group_node, ShaderNodeGroup):
3485
+ return False
3486
+
3487
+ group_node_tree = group_node.node_tree
3488
+ if not group_node_tree:
3489
+ return False
3490
+
3491
+ if group_node_tree.name != shader.OUTPUT_GROUP_NAME:
3492
+ return False
3493
+
3494
+ return bool(self.get("enabled"))
3495
+
3496
+ def _set_enabled(self, value: object) -> None:
3497
+ material = self.find_material()
3498
+
3499
+ if not value:
3500
+ if self.get("enabled") and (
3501
+ bpy.app.version >= (5, 0, 0) or material.use_nodes
3502
+ ):
3503
+ convert_mtoon1_to_bsdf_principled(material)
3504
+ self["enabled"] = False
3505
+ return
3506
+
3507
+ if bpy.app.version < (5, 0, 0) and not material.use_nodes:
3508
+ material.use_nodes = True
3509
+ if self.enabled:
3510
+ return
3511
+
3512
+ convert_material_to_mtoon1(material)
3513
+ self["enabled"] = True
3514
+ self.setup_drivers()
3515
+
3516
+ enabled: BoolProperty( # type: ignore[valid-type]
3517
+ name="Enable VRM MToon Material",
3518
+ get=_get_enabled,
3519
+ set=_set_enabled,
3520
+ )
3521
+
3522
+ export_shape_key_normals: BoolProperty( # type: ignore[valid-type]
3523
+ name="Export Shape Key Normals",
3524
+ )
3525
+
3526
+ def _update_is_outline_material(self, _context: Context) -> None:
3527
+ self.set_bool(
3528
+ shader.OUTPUT_GROUP_NAME,
3529
+ shader.OUTPUT_GROUP_IS_OUTLINE_LABEL,
3530
+ self.is_outline_material,
3531
+ )
3532
+ self.set_bool(
3533
+ shader.NORMAL_GROUP_NAME,
3534
+ shader.NORMAL_GROUP_IS_OUTLINE_LABEL,
3535
+ self.is_outline_material,
3536
+ )
3537
+ self.set_bool(
3538
+ shader.OUTPUT_GROUP_NAME,
3539
+ shader.OUTPUT_GROUP_DOUBLE_SIDED_LABEL,
3540
+ False if self.is_outline_material else self.double_sided,
3541
+ )
3542
+
3543
+ is_outline_material: BoolProperty( # type: ignore[valid-type]
3544
+ update=_update_is_outline_material,
3545
+ )
3546
+
3547
+ outline_material: PointerProperty( # type: ignore[valid-type]
3548
+ type=Material,
3549
+ )
3550
+
3551
+ def uv_transformable_texture_info(self) -> list[Mtoon1TextureInfoPropertyGroup]:
3552
+ return [
3553
+ self.pbr_metallic_roughness.base_color_texture,
3554
+ self.normal_texture,
3555
+ self.emissive_texture,
3556
+ self.extensions.vrmc_materials_mtoon.shade_multiply_texture,
3557
+ self.extensions.vrmc_materials_mtoon.shading_shift_texture,
3558
+ self.extensions.vrmc_materials_mtoon.rim_multiply_texture,
3559
+ self.extensions.vrmc_materials_mtoon.uv_animation_mask_texture,
3560
+ ]
3561
+
3562
+ def non_uv_transformable_texture_info(self) -> list[Mtoon1TextureInfoPropertyGroup]:
3563
+ return [
3564
+ self.extensions.vrmc_materials_mtoon.matcap_texture,
3565
+ self.extensions.vrmc_materials_mtoon.outline_width_multiply_texture,
3566
+ ]
3567
+
3568
+ def all_texture_info(self) -> list[Mtoon1TextureInfoPropertyGroup]:
3569
+ return (
3570
+ self.uv_transformable_texture_info()
3571
+ + self.non_uv_transformable_texture_info()
3572
+ )
3573
+
3574
+ def all_textures(
3575
+ self, *, downgrade_to_mtoon0: bool
3576
+ ) -> list[Union[Mtoon0TexturePropertyGroup, Mtoon1TexturePropertyGroup]]:
3577
+ result: list[Union[Mtoon0TexturePropertyGroup, Mtoon1TexturePropertyGroup]] = []
3578
+ result.extend(
3579
+ [
3580
+ self.pbr_metallic_roughness.base_color_texture.index,
3581
+ self.extensions.vrmc_materials_mtoon.shade_multiply_texture.index,
3582
+ self.normal_texture.index,
3583
+ ]
3584
+ )
3585
+ if downgrade_to_mtoon0:
3586
+ result.extend(
3587
+ [
3588
+ self.mtoon0_receive_shadow_texture,
3589
+ self.mtoon0_shading_grade_texture,
3590
+ ]
3591
+ )
3592
+ result.append(self.emissive_texture.index)
3593
+ if not downgrade_to_mtoon0:
3594
+ result.append(
3595
+ self.extensions.vrmc_materials_mtoon.shading_shift_texture.index
3596
+ )
3597
+ result.extend(
3598
+ [
3599
+ self.extensions.vrmc_materials_mtoon.matcap_texture.index,
3600
+ self.extensions.vrmc_materials_mtoon.rim_multiply_texture.index,
3601
+ self.extensions.vrmc_materials_mtoon.outline_width_multiply_texture.index,
3602
+ self.extensions.vrmc_materials_mtoon.uv_animation_mask_texture.index,
3603
+ ]
3604
+ )
3605
+ return result
3606
+
3607
+ show_expanded_mtoon0: BoolProperty( # type: ignore[valid-type]
3608
+ name="Show MToon 0.0 Options",
3609
+ )
3610
+
3611
+ mtoon0_front_cull_mode: BoolProperty( # type: ignore[valid-type]
3612
+ name="Front Face Culling",
3613
+ )
3614
+
3615
+ mtoon0_outline_scaled_max_distance: FloatProperty( # type: ignore[valid-type]
3616
+ name="Outline Width Scaled Max Distance",
3617
+ min=1,
3618
+ default=1,
3619
+ max=10,
3620
+ )
3621
+
3622
+ mtoon0_light_color_attenuation: FloatProperty( # type: ignore[valid-type]
3623
+ name="LightColor Attenuation",
3624
+ min=0,
3625
+ default=0,
3626
+ max=1,
3627
+ )
3628
+
3629
+ mtoon0_receive_shadow_texture: PointerProperty( # type: ignore[valid-type]
3630
+ type=Mtoon0ReceiveShadowTexturePropertyGroup
3631
+ )
3632
+
3633
+ mtoon0_receive_shadow_rate: FloatProperty( # type: ignore[valid-type]
3634
+ min=0,
3635
+ default=1,
3636
+ max=1,
3637
+ )
3638
+
3639
+ mtoon0_shading_grade_texture: PointerProperty( # type: ignore[valid-type]
3640
+ type=Mtoon0ShadingGradeTexturePropertyGroup
3641
+ )
3642
+
3643
+ mtoon0_shading_grade_rate: FloatProperty( # type: ignore[valid-type]
3644
+ min=0,
3645
+ default=1,
3646
+ max=1,
3647
+ )
3648
+
3649
+ mtoon0_rim_lighting_mix: FloatProperty( # type: ignore[valid-type]
3650
+ name="Rim LightingMix (MToon 0.0)",
3651
+ min=0,
3652
+ default=0,
3653
+ max=1,
3654
+ )
3655
+
3656
+ def _get_mtoon0_render_queue_and_clamp(self) -> int:
3657
+ return int(self.mtoon0_render_queue)
3658
+
3659
+ def _set_mtoon0_render_queue_and_clamp(self, value: int) -> None:
3660
+ # https://github.com/Santarh/MToon/blob/42b03163459ac8e6b7aee08070d0f4f912035069/MToon/Scripts/Utils.cs#L74-L113
3661
+ if self.alpha_mode == self.ALPHA_MODE_OPAQUE.identifier:
3662
+ mtoon0_render_queue = 2000
3663
+ elif self.alpha_mode == self.ALPHA_MODE_MASK.identifier:
3664
+ mtoon0_render_queue = 2450
3665
+ elif not self.extensions.vrmc_materials_mtoon.transparent_with_z_write:
3666
+ mtoon0_render_queue = max(2951, min(3000, value))
3667
+ else:
3668
+ mtoon0_render_queue = max(2501, min(2550, value))
3669
+
3670
+ if self.mtoon0_render_queue != mtoon0_render_queue:
3671
+ self.mtoon0_render_queue = mtoon0_render_queue
3672
+
3673
+ # Set the Render Queue value for MToon0. Performs clamping when assigning values.
3674
+ # UniVRM performs clamping when setting values from UI or when changing
3675
+ # Alpha Mode etc., so use this to match that behavior.
3676
+ mtoon0_render_queue_and_clamp: IntProperty( # type: ignore[valid-type]
3677
+ name="Render Queue",
3678
+ get=_get_mtoon0_render_queue_and_clamp,
3679
+ set=_set_mtoon0_render_queue_and_clamp,
3680
+ )
3681
+
3682
+ # Set the Render Queue value for MToon0. Does not perform clamping when
3683
+ # assigning values.
3684
+ # UniVRM does not perform clamping during VRM0 import or export,
3685
+ # so use this during import or export to match that behavior.
3686
+ mtoon0_render_queue: IntProperty( # type: ignore[valid-type]
3687
+ name="Render Queue",
3688
+ default=2000,
3689
+ )
3690
+
3691
+ def setup_drivers(self) -> None:
3692
+ material = self.find_material()
3693
+ for texture_info in self.all_texture_info():
3694
+ texture_info.setup_drivers(material)
3695
+
3696
+ if TYPE_CHECKING:
3697
+ # This code is auto generated.
3698
+ # To regenerate, run the `uv run tools/property_typing.py` command.
3699
+ addon_version: Sequence[int] # type: ignore[no-redef]
3700
+ pbr_metallic_roughness: ( # type: ignore[no-redef]
3701
+ Mtoon1PbrMetallicRoughnessPropertyGroup
3702
+ )
3703
+ alpha_mode_blend_method_hashed: bool # type: ignore[no-redef]
3704
+ alpha_mode: str # type: ignore[no-redef]
3705
+ double_sided: bool # type: ignore[no-redef]
3706
+ alpha_cutoff: float # type: ignore[no-redef]
3707
+ normal_texture: Mtoon1NormalTextureInfoPropertyGroup # type: ignore[no-redef]
3708
+ emissive_texture: ( # type: ignore[no-redef]
3709
+ Mtoon1EmissiveTextureInfoPropertyGroup
3710
+ )
3711
+ emissive_factor: Sequence[float] # type: ignore[no-redef]
3712
+ extensions: Mtoon1MaterialExtensionsPropertyGroup # type: ignore[no-redef]
3713
+ enabled: bool # type: ignore[no-redef]
3714
+ export_shape_key_normals: bool # type: ignore[no-redef]
3715
+ is_outline_material: bool # type: ignore[no-redef]
3716
+ outline_material: Optional[Material] # type: ignore[no-redef]
3717
+ show_expanded_mtoon0: bool # type: ignore[no-redef]
3718
+ mtoon0_front_cull_mode: bool # type: ignore[no-redef]
3719
+ mtoon0_outline_scaled_max_distance: float # type: ignore[no-redef]
3720
+ mtoon0_light_color_attenuation: float # type: ignore[no-redef]
3721
+ mtoon0_receive_shadow_texture: ( # type: ignore[no-redef]
3722
+ Mtoon0ReceiveShadowTexturePropertyGroup
3723
+ )
3724
+ mtoon0_receive_shadow_rate: float # type: ignore[no-redef]
3725
+ mtoon0_shading_grade_texture: ( # type: ignore[no-redef]
3726
+ Mtoon0ShadingGradeTexturePropertyGroup
3727
+ )
3728
+ mtoon0_shading_grade_rate: float # type: ignore[no-redef]
3729
+ mtoon0_rim_lighting_mix: float # type: ignore[no-redef]
3730
+ mtoon0_render_queue_and_clamp: int # type: ignore[no-redef]
3731
+ mtoon0_render_queue: int # type: ignore[no-redef]
3732
+
3733
+
3734
+ def reset_shader_node_group(
3735
+ context: Context,
3736
+ material: Material,
3737
+ *,
3738
+ reset_material_node_tree: bool,
3739
+ reset_node_groups: bool,
3740
+ ) -> None:
3741
+ gltf = get_material_extension(material).mtoon1
3742
+ mtoon = gltf.extensions.vrmc_materials_mtoon
3743
+
3744
+ base_color_factor = list(gltf.pbr_metallic_roughness.base_color_factor)
3745
+ base_color_texture = gltf.pbr_metallic_roughness.base_color_texture.backup()
3746
+ alpha_mode_blend_method_hashed = gltf.alpha_mode_blend_method_hashed
3747
+ alpha_mode = gltf.alpha_mode
3748
+ double_sided = gltf.double_sided
3749
+ alpha_cutoff = gltf.alpha_cutoff
3750
+ normal_texture = gltf.normal_texture.backup()
3751
+ normal_texture_scale = gltf.normal_texture.scale
3752
+ emissive_texture = gltf.emissive_texture.backup()
3753
+ emissive_factor = list(gltf.emissive_factor)
3754
+ export_shape_key_normals = gltf.export_shape_key_normals
3755
+ emissive_strength = (
3756
+ gltf.extensions.khr_materials_emissive_strength.emissive_strength
3757
+ )
3758
+
3759
+ transparent_with_z_write = mtoon.transparent_with_z_write
3760
+ render_queue_offset_number = mtoon.render_queue_offset_number
3761
+ shade_multiply_texture = mtoon.shade_multiply_texture.backup()
3762
+ shade_color_factor = list(mtoon.shade_color_factor)
3763
+ shading_shift_texture = mtoon.shading_shift_texture.backup()
3764
+ shading_shift_texture_scale = mtoon.shading_shift_texture.scale
3765
+ shading_shift_factor = mtoon.shading_shift_factor
3766
+ shading_toony_factor = mtoon.shading_toony_factor
3767
+ gi_equalization_factor = mtoon.gi_equalization_factor
3768
+ matcap_factor = list(mtoon.matcap_factor)
3769
+ matcap_texture = mtoon.matcap_texture.backup()
3770
+ parametric_rim_color_factor = list(mtoon.parametric_rim_color_factor)
3771
+ rim_multiply_texture = mtoon.rim_multiply_texture.backup()
3772
+ rim_lighting_mix_factor = mtoon.rim_lighting_mix_factor
3773
+ parametric_rim_fresnel_power_factor = mtoon.parametric_rim_fresnel_power_factor
3774
+ parametric_rim_lift_factor = mtoon.parametric_rim_lift_factor
3775
+ enable_outline_preview = mtoon.enable_outline_preview
3776
+ outline_width_mode = mtoon.outline_width_mode
3777
+ outline_width_factor = mtoon.outline_width_factor
3778
+ outline_width_multiply_texture = mtoon.outline_width_multiply_texture.backup()
3779
+ outline_color_factor = list(mtoon.outline_color_factor)
3780
+ outline_lighting_mix_factor = mtoon.outline_lighting_mix_factor
3781
+ uv_animation_mask_texture = mtoon.uv_animation_mask_texture.backup()
3782
+ uv_animation_scroll_x_speed_factor = mtoon.uv_animation_scroll_x_speed_factor
3783
+ uv_animation_scroll_y_speed_factor = mtoon.uv_animation_scroll_y_speed_factor
3784
+ uv_animation_rotation_speed_factor = mtoon.uv_animation_rotation_speed_factor
3785
+
3786
+ if reset_material_node_tree:
3787
+ shader.load_mtoon1_shader(
3788
+ context,
3789
+ material,
3790
+ reset_node_groups=reset_node_groups,
3791
+ )
3792
+ outline_material = gltf.outline_material
3793
+ if outline_material:
3794
+ shader.load_mtoon1_shader(
3795
+ context,
3796
+ outline_material,
3797
+ reset_node_groups=reset_node_groups,
3798
+ )
3799
+
3800
+ gltf.is_outline_material = False
3801
+ outline_material = gltf.outline_material
3802
+ if outline_material:
3803
+ get_material_extension(outline_material).mtoon1.is_outline_material = True
3804
+
3805
+ gltf.pbr_metallic_roughness.base_color_factor = base_color_factor
3806
+ gltf.pbr_metallic_roughness.base_color_texture.restore(base_color_texture)
3807
+ gltf.alpha_mode_blend_method_hashed = alpha_mode_blend_method_hashed
3808
+ gltf.alpha_mode = alpha_mode
3809
+ gltf.double_sided = double_sided
3810
+ gltf.alpha_cutoff = alpha_cutoff
3811
+ gltf.normal_texture.restore(normal_texture)
3812
+ gltf.normal_texture.scale = normal_texture_scale
3813
+ gltf.emissive_texture.restore(emissive_texture)
3814
+ gltf.emissive_factor = emissive_factor
3815
+ gltf.export_shape_key_normals = export_shape_key_normals
3816
+ gltf.extensions.khr_materials_emissive_strength.emissive_strength = (
3817
+ emissive_strength
3818
+ )
3819
+
3820
+ mtoon.transparent_with_z_write = transparent_with_z_write
3821
+ mtoon.render_queue_offset_number = render_queue_offset_number
3822
+ mtoon.shade_multiply_texture.restore(shade_multiply_texture)
3823
+ mtoon.shade_color_factor = shade_color_factor
3824
+ mtoon.shading_shift_texture.restore(shading_shift_texture)
3825
+ mtoon.shading_shift_texture.scale = shading_shift_texture_scale
3826
+ mtoon.shading_shift_factor = shading_shift_factor
3827
+ mtoon.shading_toony_factor = shading_toony_factor
3828
+ mtoon.gi_equalization_factor = gi_equalization_factor
3829
+ mtoon.matcap_factor = matcap_factor
3830
+ mtoon.matcap_texture.restore(matcap_texture)
3831
+ mtoon.parametric_rim_color_factor = parametric_rim_color_factor
3832
+ mtoon.rim_multiply_texture.restore(rim_multiply_texture)
3833
+ mtoon.rim_lighting_mix_factor = rim_lighting_mix_factor
3834
+ mtoon.parametric_rim_fresnel_power_factor = parametric_rim_fresnel_power_factor
3835
+ mtoon.parametric_rim_lift_factor = parametric_rim_lift_factor
3836
+ mtoon.enable_outline_preview = enable_outline_preview
3837
+ mtoon.outline_width_mode = outline_width_mode
3838
+ mtoon.outline_width_factor = outline_width_factor
3839
+ mtoon.outline_width_multiply_texture.restore(outline_width_multiply_texture)
3840
+ mtoon.outline_color_factor = outline_color_factor
3841
+ mtoon.outline_lighting_mix_factor = outline_lighting_mix_factor
3842
+ mtoon.uv_animation_mask_texture.restore(uv_animation_mask_texture)
3843
+ mtoon.uv_animation_scroll_x_speed_factor = uv_animation_scroll_x_speed_factor
3844
+ mtoon.uv_animation_scroll_y_speed_factor = uv_animation_scroll_y_speed_factor
3845
+ mtoon.uv_animation_rotation_speed_factor = uv_animation_rotation_speed_factor
3846
+
3847
+ gltf.setup_drivers()
3848
+ gltf.addon_version = get_addon_version()
3849
+
3850
+
3851
+ @dataclass(frozen=True)
3852
+ class NodesModifierInputKey:
3853
+ material_key: str
3854
+ outline_material_key: str
3855
+ outline_width_mode_key: str
3856
+ outline_width_factor_key: str
3857
+ outline_width_multiply_texture_key: str
3858
+ outline_width_multiply_texture_exists_key: str
3859
+ outline_width_multiply_texture_uv_key: str
3860
+ outline_width_multiply_texture_uv_offset_x_key: str
3861
+ outline_width_multiply_texture_uv_offset_y_key: str
3862
+ outline_width_multiply_texture_uv_scale_x_key: str
3863
+ outline_width_multiply_texture_uv_scale_y_key: str
3864
+ extrude_mesh_individual_key: str
3865
+ object_key: str
3866
+ enabled_key: str
3867
+
3868
+ @property
3869
+ def outline_width_multiply_texture_uv_use_attribute_key(self) -> str:
3870
+ return self.outline_width_multiply_texture_uv_key + "_use_attribute"
3871
+
3872
+ @property
3873
+ def outline_width_multiply_texture_uv_attribute_name_key(self) -> str:
3874
+ return self.outline_width_multiply_texture_uv_key + "_attribute_name"
3875
+
3876
+
3877
+ def _get_nodes_modifier_input_key(
3878
+ node_group: NodeTree,
3879
+ ) -> Optional[NodesModifierInputKey]:
3880
+ if bpy.app.version < (4, 0):
3881
+ keys = [i.identifier for i in node_group.inputs]
3882
+ else:
3883
+ from bpy.types import NodeTreeInterfaceSocket
3884
+
3885
+ keys = [
3886
+ item.identifier
3887
+ for item in node_group.interface.items_tree
3888
+ if item.item_type == "SOCKET"
3889
+ and isinstance(item, NodeTreeInterfaceSocket)
3890
+ and item.in_out == "INPUT"
3891
+ ]
3892
+
3893
+ if len(keys) < 15:
3894
+ return None
3895
+ return NodesModifierInputKey(
3896
+ material_key=keys[1],
3897
+ outline_material_key=keys[2],
3898
+ outline_width_mode_key=keys[3],
3899
+ outline_width_factor_key=keys[4],
3900
+ outline_width_multiply_texture_key=keys[5],
3901
+ outline_width_multiply_texture_exists_key=keys[6],
3902
+ outline_width_multiply_texture_uv_key=keys[7],
3903
+ outline_width_multiply_texture_uv_offset_x_key=keys[8],
3904
+ outline_width_multiply_texture_uv_offset_y_key=keys[9],
3905
+ outline_width_multiply_texture_uv_scale_x_key=keys[10],
3906
+ outline_width_multiply_texture_uv_scale_y_key=keys[11],
3907
+ extrude_mesh_individual_key=keys[12],
3908
+ object_key=keys[13],
3909
+ enabled_key=keys[14],
3910
+ )
3911
+
3912
+
3913
+ class NodesModifierCustomProperties:
3914
+ def __init__(
3915
+ self, modifier: NodesModifier, input_key: NodesModifierInputKey
3916
+ ) -> None:
3917
+ self.modifier = modifier
3918
+ self.input_key = input_key
3919
+
3920
+ def _set(self, key: str, value: object) -> bool:
3921
+ if self.modifier.get(key) == value:
3922
+ return False
3923
+ self.modifier[key] = value
3924
+ return True
3925
+
3926
+ def get_material(self) -> Optional[Material]:
3927
+ material = self.modifier.get(self.input_key.material_key)
3928
+ if material is None:
3929
+ return None
3930
+ if not isinstance(material, Material):
3931
+ _logger.error(
3932
+ "Expected Material for key: %s, modifier: %s, got: %s",
3933
+ self.input_key.material_key,
3934
+ self.modifier.name,
3935
+ type(material),
3936
+ )
3937
+ return None
3938
+ return material
3939
+
3940
+ def set_material(self, value: Material) -> bool:
3941
+ return self._set(self.input_key.material_key, value)
3942
+
3943
+ def set_outline_material(self, value: Material) -> bool:
3944
+ return self._set(self.input_key.outline_material_key, value)
3945
+
3946
+ def set_outline_width_mode(self, value: int) -> bool:
3947
+ return self._set(self.input_key.outline_width_mode_key, value)
3948
+
3949
+ def set_outline_width_factor(self, value: float) -> bool:
3950
+ return self._set(self.input_key.outline_width_factor_key, value)
3951
+
3952
+ def set_outline_width_multiply_texture(self, value: Optional[Image]) -> bool:
3953
+ return self._set(self.input_key.outline_width_multiply_texture_key, value)
3954
+
3955
+ def set_outline_width_multiply_texture_exists(self, *, value: bool) -> bool:
3956
+ return self._set(
3957
+ self.input_key.outline_width_multiply_texture_exists_key, value
3958
+ )
3959
+
3960
+ def set_outline_width_multiply_texture_uv_use_attribute(
3961
+ self, *, value: Union[bool, int]
3962
+ ) -> bool:
3963
+ return self._set(
3964
+ self.input_key.outline_width_multiply_texture_uv_use_attribute_key, value
3965
+ )
3966
+
3967
+ def set_outline_width_multiply_texture_uv_attribute_name(self, value: str) -> bool:
3968
+ return self._set(
3969
+ self.input_key.outline_width_multiply_texture_uv_attribute_name_key, value
3970
+ )
3971
+
3972
+ def set_outline_width_multiply_texture_uv_offset_x(self, value: float) -> bool:
3973
+ return self._set(
3974
+ self.input_key.outline_width_multiply_texture_uv_offset_x_key, value
3975
+ )
3976
+
3977
+ def set_outline_width_multiply_texture_uv_offset_y(self, value: float) -> bool:
3978
+ return self._set(
3979
+ self.input_key.outline_width_multiply_texture_uv_offset_y_key, value
3980
+ )
3981
+
3982
+ def set_outline_width_multiply_texture_uv_scale_x(self, value: float) -> bool:
3983
+ return self._set(
3984
+ self.input_key.outline_width_multiply_texture_uv_scale_x_key, value
3985
+ )
3986
+
3987
+ def set_outline_width_multiply_texture_uv_scale_y(self, value: float) -> bool:
3988
+ return self._set(
3989
+ self.input_key.outline_width_multiply_texture_uv_scale_y_key, value
3990
+ )
3991
+
3992
+ def set_extrude_mesh_individual(self, *, value: bool) -> bool:
3993
+ return self._set(self.input_key.extrude_mesh_individual_key, value)
3994
+
3995
+ def set_object(self, value: Object) -> bool:
3996
+ return self._set(self.input_key.object_key, value)
3997
+
3998
+ def set_enabled(self, *, value: bool) -> bool:
3999
+ return self._set(self.input_key.enabled_key, value)
4000
+
4001
+
4002
+ class NodesModifierProperties:
4003
+ def __init__(
4004
+ self, modifier: NodesModifier, input_key: NodesModifierInputKey
4005
+ ) -> None:
4006
+ self.modifier = modifier
4007
+ self.input_key = input_key
4008
+
4009
+ def _get(self, key: str, attr: str = "value") -> Optional[object]:
4010
+ socket = getattr(getattr(self.modifier.properties, "inputs", None), key, None)
4011
+ if not socket:
4012
+ _logger.error(
4013
+ "Socket not found for key: %s, modifier: %s", key, self.modifier.name
4014
+ )
4015
+ return None
4016
+ return getattr(socket, attr)
4017
+
4018
+ def _set(self, key: str, attr: str, value: object) -> bool:
4019
+ socket = getattr(getattr(self.modifier.properties, "inputs", None), key, None)
4020
+ if not socket:
4021
+ _logger.error(
4022
+ "Socket not found for key: %s, modifier: %s", key, self.modifier.name
4023
+ )
4024
+ return False
4025
+ if getattr(socket, attr) == value:
4026
+ return False
4027
+ setattr(socket, attr, value)
4028
+ return True
4029
+
4030
+ def _set_value(self, key: str, value: object) -> bool:
4031
+ changed = False
4032
+ changed |= self._set(key, "type", "VALUE")
4033
+ changed |= self._set(key, "value", value)
4034
+ return changed
4035
+
4036
+ def get_material(self) -> Optional[Material]:
4037
+ material = self._get(self.input_key.material_key)
4038
+ if material is None:
4039
+ return None
4040
+ if not isinstance(material, Material):
4041
+ _logger.error(
4042
+ "Expected Material for key: %s, modifier: %s, got: %s",
4043
+ self.input_key.material_key,
4044
+ self.modifier.name,
4045
+ type(material),
4046
+ )
4047
+ return None
4048
+ return material
4049
+
4050
+ def set_material(self, value: Material) -> bool:
4051
+ return self._set_value(self.input_key.material_key, value)
4052
+
4053
+ def set_outline_material(self, value: Material) -> bool:
4054
+ return self._set_value(self.input_key.outline_material_key, value)
4055
+
4056
+ def set_outline_width_mode(self, value: int) -> bool:
4057
+ return self._set_value(self.input_key.outline_width_mode_key, value)
4058
+
4059
+ def set_outline_width_factor(self, value: float) -> bool:
4060
+ return self._set_value(self.input_key.outline_width_factor_key, value)
4061
+
4062
+ def set_outline_width_multiply_texture(self, value: Optional[Image]) -> bool:
4063
+ return self._set_value(self.input_key.outline_width_multiply_texture_key, value)
4064
+
4065
+ def set_outline_width_multiply_texture_exists(self, *, value: bool) -> bool:
4066
+ return self._set_value(
4067
+ self.input_key.outline_width_multiply_texture_exists_key, value
4068
+ )
4069
+
4070
+ def set_outline_width_multiply_texture_uv_use_attribute(
4071
+ self, *, value: Union[bool, int]
4072
+ ) -> bool:
4073
+ return self._set(
4074
+ self.input_key.outline_width_multiply_texture_uv_key,
4075
+ "type",
4076
+ "ATTRIBUTE" if value else "VALUE",
4077
+ )
4078
+
4079
+ def set_outline_width_multiply_texture_uv_attribute_name(self, value: str) -> bool:
4080
+ return self._set(
4081
+ self.input_key.outline_width_multiply_texture_uv_key,
4082
+ "attribute_name",
4083
+ value,
4084
+ )
4085
+
4086
+ def set_outline_width_multiply_texture_uv_offset_x(self, value: float) -> bool:
4087
+ return self._set_value(
4088
+ self.input_key.outline_width_multiply_texture_uv_offset_x_key, value
4089
+ )
4090
+
4091
+ def set_outline_width_multiply_texture_uv_offset_y(self, value: float) -> bool:
4092
+ return self._set_value(
4093
+ self.input_key.outline_width_multiply_texture_uv_offset_y_key, value
4094
+ )
4095
+
4096
+ def set_outline_width_multiply_texture_uv_scale_x(self, value: float) -> bool:
4097
+ return self._set_value(
4098
+ self.input_key.outline_width_multiply_texture_uv_scale_x_key, value
4099
+ )
4100
+
4101
+ def set_outline_width_multiply_texture_uv_scale_y(self, value: float) -> bool:
4102
+ return self._set_value(
4103
+ self.input_key.outline_width_multiply_texture_uv_scale_y_key, value
4104
+ )
4105
+
4106
+ def set_extrude_mesh_individual(self, *, value: bool) -> bool:
4107
+ return self._set_value(self.input_key.extrude_mesh_individual_key, value)
4108
+
4109
+ def set_object(self, value: Object) -> bool:
4110
+ return self._set_value(self.input_key.object_key, value)
4111
+
4112
+ def set_enabled(self, *, value: bool) -> bool:
4113
+ return self._set_value(self.input_key.enabled_key, value)
4114
+
4115
+
4116
+ class NodesModifierPropertiesProtocol(Protocol):
4117
+ def get_material(self) -> Optional[Material]: ...
4118
+ def set_material(self, value: Material) -> bool: ...
4119
+ def set_outline_material(self, value: Material) -> bool: ...
4120
+ def set_outline_width_mode(self, value: int) -> bool: ...
4121
+ def set_outline_width_factor(self, value: float) -> bool: ...
4122
+ def set_outline_width_multiply_texture(self, value: Optional[Image]) -> bool: ...
4123
+ def set_outline_width_multiply_texture_exists(self, *, value: bool) -> bool: ...
4124
+ def set_outline_width_multiply_texture_uv_use_attribute(
4125
+ self, *, value: Union[bool, int]
4126
+ ) -> bool: ...
4127
+ def set_outline_width_multiply_texture_uv_attribute_name(
4128
+ self, value: str
4129
+ ) -> bool: ...
4130
+ def set_outline_width_multiply_texture_uv_offset_x(self, value: float) -> bool: ...
4131
+ def set_outline_width_multiply_texture_uv_offset_y(self, value: float) -> bool: ...
4132
+ def set_outline_width_multiply_texture_uv_scale_x(self, value: float) -> bool: ...
4133
+ def set_outline_width_multiply_texture_uv_scale_y(self, value: float) -> bool: ...
4134
+ def set_extrude_mesh_individual(self, *, value: bool) -> bool: ...
4135
+ def set_object(self, value: Object) -> bool: ...
4136
+ def set_enabled(self, *, value: bool) -> bool: ...
4137
+
4138
+
4139
+ def _get_nodes_modifier_input_properties(
4140
+ modifier: NodesModifier,
4141
+ ) -> Optional[NodesModifierPropertiesProtocol]:
4142
+ node_group = modifier.node_group
4143
+ if not node_group:
4144
+ return None
4145
+
4146
+ input_key = _get_nodes_modifier_input_key(node_group)
4147
+ if not input_key:
4148
+ return None
4149
+
4150
+ if bpy.app.version < (5, 2):
4151
+ return NodesModifierCustomProperties(modifier, input_key)
4152
+
4153
+ return NodesModifierProperties(modifier, input_key)
4154
+
4155
+
4156
+ def generate_mtoon1_outline_material_name(material: Material) -> str:
4157
+ return f"MToon Outline ({material.name})"
4158
+
4159
+
4160
+ def _assign_mtoon_unversioned_image(
4161
+ context: Context,
4162
+ texture_info: Mtoon1TextureInfoPropertyGroup,
4163
+ image_name_and_sampler_type: Optional[tuple[str, int, int]],
4164
+ uv_offset: tuple[float, float],
4165
+ uv_scale: tuple[float, float],
4166
+ ) -> None:
4167
+ texture_info.extensions.khr_texture_transform.offset = uv_offset
4168
+ texture_info.extensions.khr_texture_transform.scale = uv_scale
4169
+
4170
+ if image_name_and_sampler_type is None:
4171
+ return
4172
+
4173
+ image_name, wrap_number, filter_number = image_name_and_sampler_type
4174
+ image = context.blend_data.images.get(image_name)
4175
+ if not image:
4176
+ return
4177
+
4178
+ texture_info.index.source = image
4179
+
4180
+ texture_info.index.sampler.mag_filter = (
4181
+ Mtoon1SamplerPropertyGroup.mag_filter_enum.value_to_identifier(
4182
+ filter_number, Mtoon1SamplerPropertyGroup.MAG_FILTER_DEFAULT.identifier
4183
+ )
4184
+ )
4185
+ texture_info.index.sampler.min_filter = (
4186
+ Mtoon1SamplerPropertyGroup.min_filter_enum.value_to_identifier(
4187
+ filter_number, Mtoon1SamplerPropertyGroup.MIN_FILTER_DEFAULT.identifier
4188
+ )
4189
+ )
4190
+
4191
+ wrap = Mtoon1SamplerPropertyGroup.wrap_enum.value_to_identifier(
4192
+ wrap_number, Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.identifier
4193
+ )
4194
+ texture_info.index.sampler.wrap_s = wrap
4195
+ texture_info.index.sampler.wrap_t = wrap
4196
+
4197
+
4198
+ def _convert_mtoon_unversioned_to_mtoon1(
4199
+ context: Context,
4200
+ material: Material,
4201
+ node: ShaderNodeGroup,
4202
+ ) -> None:
4203
+ transparent_with_z_write = False
4204
+ alpha_cutoff: Optional[float] = 0.5
4205
+ if material.blend_method == "OPAQUE":
4206
+ alpha_mode = Mtoon1MaterialPropertyGroup.ALPHA_MODE_OPAQUE.identifier
4207
+ elif material.blend_method == "CLIP":
4208
+ alpha_cutoff = shader.get_float_value(node, "CutoffRate", 0, sys.float_info.max)
4209
+ if alpha_cutoff is None:
4210
+ alpha_cutoff = 0.5
4211
+ alpha_mode = Mtoon1MaterialPropertyGroup.ALPHA_MODE_MASK.identifier
4212
+ else:
4213
+ alpha_mode = Mtoon1MaterialPropertyGroup.ALPHA_MODE_BLEND.identifier
4214
+ transparent_with_z_write_float = shader.get_float_value(
4215
+ node, "TransparentWithZWrite"
4216
+ )
4217
+ transparent_with_z_write = (
4218
+ transparent_with_z_write_float is not None
4219
+ and math.fabs(transparent_with_z_write_float) >= sys.float_info.epsilon
4220
+ )
4221
+
4222
+ base_color_factor = shader.get_rgba_value(node, "DiffuseColor", 0.0, 1.0) or (
4223
+ 0,
4224
+ 0,
4225
+ 0,
4226
+ 1,
4227
+ )
4228
+ base_color_texture = shader.get_image_name_and_sampler_type(node, "MainTexture")
4229
+
4230
+ uv_offset = (0.0, 0.0)
4231
+ uv_scale = (1.0, 1.0)
4232
+
4233
+ main_texture_input_socket = node.inputs.get("MainTexture")
4234
+ if (
4235
+ main_texture_input_socket
4236
+ and (
4237
+ tex_image_node := shader.search_input_node(
4238
+ main_texture_input_socket, ShaderNodeTexImage
4239
+ )
4240
+ )
4241
+ and (vector_input_socket := tex_image_node.inputs.get("Vector"))
4242
+ and (
4243
+ mapping_node := shader.search_input_node(
4244
+ vector_input_socket, ShaderNodeMapping
4245
+ )
4246
+ )
4247
+ ):
4248
+ location_socket = mapping_node.inputs.get("Location")
4249
+ if location_socket and isinstance(
4250
+ location_socket, shader.VECTOR_SOCKET_CLASSES
4251
+ ):
4252
+ uv_offset = (
4253
+ float(location_socket.default_value[0]),
4254
+ float(location_socket.default_value[1]),
4255
+ )
4256
+
4257
+ scale_socket = mapping_node.inputs.get("Scale")
4258
+ if scale_socket and isinstance(scale_socket, shader.VECTOR_SOCKET_CLASSES):
4259
+ uv_scale = (
4260
+ float(scale_socket.default_value[0]),
4261
+ float(scale_socket.default_value[1]),
4262
+ )
4263
+
4264
+ shade_color_factor = shader.get_rgb_value(node, "ShadeColor", 0.0, 1.0) or (
4265
+ 0,
4266
+ 0,
4267
+ 0,
4268
+ )
4269
+ shade_multiply_texture = shader.get_image_name_and_sampler_type(
4270
+ node, "ShadeTexture"
4271
+ )
4272
+ normal_texture = shader.get_image_name_and_sampler_type(node, "NormalmapTexture")
4273
+ if not normal_texture:
4274
+ normal_texture = shader.get_image_name_and_sampler_type(node, "NomalmapTexture")
4275
+ normal_texture_scale = shader.get_float_value(node, "BumpScale")
4276
+
4277
+ shading_shift_0x = shader.get_float_value(node, "ShadeShift")
4278
+ if shading_shift_0x is None:
4279
+ shading_shift_0x = 0.0
4280
+
4281
+ shading_toony_0x = shader.get_float_value(node, "ShadeToony")
4282
+ if shading_toony_0x is None:
4283
+ shading_toony_0x = 0.0
4284
+
4285
+ shading_shift_factor = convert.mtoon_shading_shift_0_to_1(
4286
+ shading_toony_0x, shading_shift_0x
4287
+ )
4288
+ shading_toony_factor = convert.mtoon_shading_toony_0_to_1(
4289
+ shading_toony_0x, shading_shift_0x
4290
+ )
4291
+
4292
+ gi_equalization_0x = shader.get_float_value(node, "IndirectLightIntensity")
4293
+ gi_equalization_factor = None
4294
+ if gi_equalization_0x is not None:
4295
+ gi_equalization_factor = convert.mtoon_intensity_to_gi_equalization(
4296
+ gi_equalization_0x
4297
+ )
4298
+
4299
+ emissive_factor = shader.get_rgb_value(node, "EmissionColor", 0.0, 1.0) or (
4300
+ 0,
4301
+ 0,
4302
+ 0,
4303
+ )
4304
+ emissive_texture = shader.get_image_name_and_sampler_type(node, "Emission_Texture")
4305
+ matcap_texture = shader.get_image_name_and_sampler_type(node, "SphereAddTexture")
4306
+
4307
+ parametric_rim_color_factor = shader.get_rgb_value(node, "RimColor", 0.0, 1.0)
4308
+ parametric_rim_fresnel_power_factor = shader.get_float_value(
4309
+ node, "RimFresnelPower", 0.0, sys.float_info.max
4310
+ )
4311
+ parametric_rim_lift_factor = shader.get_float_value(node, "RimLift")
4312
+
4313
+ rim_multiply_texture = shader.get_image_name_and_sampler_type(node, "RimTexture")
4314
+
4315
+ outline_width_mode_float = shader.get_float_value(node, "OutlineWidthMode")
4316
+ outline_width = shader.get_float_value(node, "OutlineWidth")
4317
+ if outline_width is None:
4318
+ outline_width = 0.0
4319
+ outline_width_multiply_texture = shader.get_image_name_and_sampler_type(
4320
+ node, "OutlineWidthTexture"
4321
+ )
4322
+
4323
+ outline_color_factor = shader.get_rgb_value(node, "OutlineColor", 0.0, 1.0) or (
4324
+ 0,
4325
+ 0,
4326
+ 0,
4327
+ )
4328
+
4329
+ outline_color_mode = shader.get_float_value(node, "OutlineColorMode")
4330
+ if outline_color_mode is not None:
4331
+ outline_color_mode = round(outline_color_mode)
4332
+ else:
4333
+ outline_color_mode = 0
4334
+
4335
+ outline_lighting_mix_factor = 0.0
4336
+ if outline_color_mode == 1:
4337
+ outline_lighting_mix_factor = (
4338
+ shader.get_float_value(node, "OutlineLightingMix") or 1.0
4339
+ )
4340
+
4341
+ uv_animation_mask_texture = shader.get_image_name_and_sampler_type(
4342
+ node, "UV_Animation_Mask_Texture"
4343
+ )
4344
+ uv_animation_rotation_speed_factor = shader.get_float_value(
4345
+ node, "UV_Scroll_Rotation"
4346
+ )
4347
+ uv_animation_scroll_x_speed_factor = shader.get_float_value(node, "UV_Scroll_X")
4348
+ uv_animation_scroll_y_speed_factor = shader.get_float_value(node, "UV_Scroll_Y")
4349
+ if uv_animation_scroll_y_speed_factor is not None:
4350
+ uv_animation_scroll_y_speed_factor *= -1
4351
+
4352
+ shader.load_mtoon1_shader(context, material, reset_node_groups=True)
4353
+
4354
+ gltf = get_material_extension(material).mtoon1
4355
+ mtoon = gltf.extensions.vrmc_materials_mtoon
4356
+
4357
+ gltf.alpha_mode = alpha_mode
4358
+ gltf.alpha_cutoff = alpha_cutoff
4359
+ mtoon.transparent_with_z_write = transparent_with_z_write
4360
+ gltf.pbr_metallic_roughness.base_color_factor = base_color_factor
4361
+ _assign_mtoon_unversioned_image(
4362
+ context,
4363
+ gltf.pbr_metallic_roughness.base_color_texture,
4364
+ base_color_texture,
4365
+ uv_offset,
4366
+ uv_scale,
4367
+ )
4368
+ mtoon.shade_color_factor = shade_color_factor
4369
+ _assign_mtoon_unversioned_image(
4370
+ context,
4371
+ mtoon.shade_multiply_texture,
4372
+ shade_multiply_texture,
4373
+ uv_offset,
4374
+ uv_scale,
4375
+ )
4376
+ _assign_mtoon_unversioned_image(
4377
+ context, gltf.normal_texture, normal_texture, uv_offset, uv_scale
4378
+ )
4379
+ if normal_texture_scale is not None:
4380
+ gltf.normal_texture.scale = normal_texture_scale
4381
+
4382
+ mtoon.shading_shift_factor = shading_shift_factor
4383
+ mtoon.shading_toony_factor = shading_toony_factor
4384
+
4385
+ if gi_equalization_factor is not None:
4386
+ mtoon.gi_equalization_factor = gi_equalization_factor
4387
+
4388
+ gltf.emissive_factor = emissive_factor
4389
+ _assign_mtoon_unversioned_image(
4390
+ context, gltf.emissive_texture, emissive_texture, uv_offset, uv_scale
4391
+ )
4392
+ _assign_mtoon_unversioned_image(
4393
+ context, mtoon.matcap_texture, matcap_texture, (0, 0), (1, 1)
4394
+ )
4395
+
4396
+ if parametric_rim_color_factor is not None:
4397
+ mtoon.parametric_rim_color_factor = parametric_rim_color_factor
4398
+ if parametric_rim_fresnel_power_factor is not None:
4399
+ mtoon.parametric_rim_fresnel_power_factor = parametric_rim_fresnel_power_factor
4400
+ if parametric_rim_lift_factor is not None:
4401
+ mtoon.parametric_rim_lift_factor = parametric_rim_lift_factor
4402
+
4403
+ _assign_mtoon_unversioned_image(
4404
+ context, mtoon.rim_multiply_texture, rim_multiply_texture, uv_offset, uv_scale
4405
+ )
4406
+
4407
+ mtoon.rim_lighting_mix_factor = 1.0
4408
+ mtoon.enable_outline_preview = get_preferences(context).enable_mtoon_outline_preview
4409
+
4410
+ if outline_width_mode_float is not None:
4411
+ outline_width_mode = round(outline_width_mode_float)
4412
+ else:
4413
+ outline_width_mode = 0
4414
+
4415
+ if outline_width_mode == 1:
4416
+ mtoon.outline_width_mode = mtoon.OUTLINE_WIDTH_MODE_WORLD_COORDINATES.identifier
4417
+ mtoon.outline_width_factor = max(0.0, outline_width * 0.01)
4418
+ elif outline_width_mode == 2:
4419
+ mtoon.outline_width_mode = (
4420
+ mtoon.OUTLINE_WIDTH_MODE_SCREEN_COORDINATES.identifier
4421
+ )
4422
+ mtoon.outline_width_factor = max(0.0, outline_width * 0.01 * 0.5)
4423
+ else:
4424
+ mtoon.outline_width_mode = mtoon.OUTLINE_WIDTH_MODE_NONE.identifier
4425
+
4426
+ _assign_mtoon_unversioned_image(
4427
+ context,
4428
+ mtoon.outline_width_multiply_texture,
4429
+ outline_width_multiply_texture,
4430
+ uv_offset,
4431
+ uv_scale,
4432
+ )
4433
+
4434
+ mtoon.outline_color_factor = outline_color_factor
4435
+ mtoon.outline_lighting_mix_factor = 0.0
4436
+ if outline_color_mode == 1:
4437
+ mtoon.outline_lighting_mix_factor = outline_lighting_mix_factor
4438
+
4439
+ _assign_mtoon_unversioned_image(
4440
+ context,
4441
+ mtoon.uv_animation_mask_texture,
4442
+ uv_animation_mask_texture,
4443
+ uv_offset,
4444
+ uv_scale,
4445
+ )
4446
+
4447
+ if uv_animation_rotation_speed_factor is not None:
4448
+ mtoon.uv_animation_rotation_speed_factor = uv_animation_rotation_speed_factor
4449
+ if uv_animation_scroll_x_speed_factor is not None:
4450
+ mtoon.uv_animation_scroll_x_speed_factor = uv_animation_scroll_x_speed_factor
4451
+ if uv_animation_scroll_y_speed_factor is not None:
4452
+ mtoon.uv_animation_scroll_y_speed_factor = uv_animation_scroll_y_speed_factor
4453
+
4454
+
4455
+ def convert_material_to_mtoon1(
4456
+ material: Material, context: Optional[Context] = None
4457
+ ) -> None:
4458
+ resolved_context = context or bpy.context
4459
+
4460
+ if (
4461
+ legacy_addon_material := LegacyAddonMaterial.try_parse(material)
4462
+ ) and legacy_addon_material.shader_name == "MToon_unversioned":
4463
+ _convert_mtoon_unversioned_to_mtoon1(
4464
+ resolved_context, material, legacy_addon_material.shader_node_group
4465
+ )
4466
+ return
4467
+
4468
+ mmd_material = MmdMaterial.try_parse(material)
4469
+ if mmd_material:
4470
+ reset_shader_node_group(
4471
+ resolved_context,
4472
+ material,
4473
+ reset_material_node_tree=True,
4474
+ reset_node_groups=False,
4475
+ )
4476
+
4477
+ gltf = get_material_extension(material).mtoon1
4478
+ mtoon = gltf.extensions.vrmc_materials_mtoon
4479
+ if texture := mmd_material.texture:
4480
+ gltf.pbr_metallic_roughness.base_color_texture.index.source = texture
4481
+ mtoon.shade_multiply_texture.index.source = texture
4482
+ gltf.pbr_metallic_roughness.base_color_factor = (
4483
+ 0,
4484
+ 0,
4485
+ 0,
4486
+ mmd_material.alpha,
4487
+ )
4488
+ else:
4489
+ gltf.pbr_metallic_roughness.base_color_factor = (
4490
+ mmd_material.diffuse_color[0],
4491
+ mmd_material.diffuse_color[1],
4492
+ mmd_material.diffuse_color[2],
4493
+ mmd_material.alpha,
4494
+ )
4495
+ gltf.double_sided = mmd_material.double_sided
4496
+ if mmd_material.alpha < 1.0:
4497
+ gltf.alpha_mode = Mtoon1MaterialPropertyGroup.ALPHA_MODE_BLEND.identifier
4498
+ return
4499
+
4500
+ principled_bsdf = PrincipledBSDFWrapper(material)
4501
+ if not principled_bsdf.node_principled_bsdf:
4502
+ reset_shader_node_group(
4503
+ resolved_context,
4504
+ material,
4505
+ reset_material_node_tree=True,
4506
+ reset_node_groups=False,
4507
+ )
4508
+ return
4509
+
4510
+ base_color_factor = (
4511
+ principled_bsdf.base_color[0],
4512
+ principled_bsdf.base_color[1],
4513
+ principled_bsdf.base_color[2],
4514
+ principled_bsdf.alpha,
4515
+ )
4516
+ base_color_texture_node = principled_bsdf.base_color_texture
4517
+ base_color_texture_image = (
4518
+ base_color_texture_node.image if base_color_texture_node else None
4519
+ )
4520
+
4521
+ emissive_factor = principled_bsdf.emission_color
4522
+ emission_color_texture_node = principled_bsdf.emission_color_texture
4523
+ emissive_texture_image = (
4524
+ emission_color_texture_node.image if emission_color_texture_node else None
4525
+ )
4526
+ emissive_strength = principled_bsdf.emission_strength
4527
+
4528
+ normalmap_texture_node = principled_bsdf.normalmap_texture
4529
+ if normalmap_texture_node:
4530
+ normal_texture_image = normalmap_texture_node.image
4531
+ normal_texture_scale = principled_bsdf.normalmap_strength
4532
+ else:
4533
+ normal_texture_image = None
4534
+ normal_texture_scale = None
4535
+
4536
+ reset_shader_node_group(
4537
+ resolved_context,
4538
+ material,
4539
+ reset_material_node_tree=True,
4540
+ reset_node_groups=False,
4541
+ )
4542
+
4543
+ gltf = get_material_extension(material).mtoon1
4544
+ gltf.pbr_metallic_roughness.base_color_factor = base_color_factor
4545
+ if base_color_texture_image:
4546
+ gltf.pbr_metallic_roughness.base_color_texture.index.source = (
4547
+ base_color_texture_image
4548
+ )
4549
+
4550
+ gltf.emissive_factor = emissive_factor
4551
+ gltf.extensions.khr_materials_emissive_strength.emissive_strength = (
4552
+ emissive_strength
4553
+ )
4554
+ if emissive_texture_image:
4555
+ gltf.emissive_texture.index.source = emissive_texture_image
4556
+
4557
+ if normal_texture_image:
4558
+ gltf.normal_texture.index.source = normal_texture_image
4559
+ if normal_texture_scale is not None:
4560
+ gltf.normal_texture.scale = normal_texture_scale
4561
+
4562
+
4563
+ def convert_mtoon1_to_bsdf_principled(material: Material) -> None:
4564
+ if bpy.app.version < (5, 0, 0) and not material.use_nodes:
4565
+ material.use_nodes = True
4566
+ shader.clear_node_tree(material.node_tree, clear_inputs_outputs=True)
4567
+ if not material.node_tree:
4568
+ _logger.error("%s's node tree is None", material.name)
4569
+ return
4570
+
4571
+ principled_bsdf = PrincipledBSDFWrapper(material, is_readonly=False)
4572
+ gltf = get_material_extension(material).mtoon1
4573
+
4574
+ principled_bsdf.base_color = gltf.pbr_metallic_roughness.base_color_factor[:3]
4575
+ base_color_texture_image = (
4576
+ gltf.pbr_metallic_roughness.base_color_texture.index.source
4577
+ )
4578
+ if base_color_texture_image:
4579
+ base_color_texture_node = principled_bsdf.base_color_texture
4580
+ if base_color_texture_node:
4581
+ base_color_texture_node.image = base_color_texture_image
4582
+
4583
+ principled_bsdf.alpha = gltf.pbr_metallic_roughness.base_color_factor[3]
4584
+ principled_bsdf.emission_color = list(gltf.emissive_factor)
4585
+ principled_bsdf.emission_strength = (
4586
+ gltf.extensions.khr_materials_emissive_strength.emissive_strength
4587
+ )
4588
+ emissive_texture_image = gltf.emissive_texture.index.source
4589
+ if emissive_texture_image:
4590
+ emission_color_texture_node = principled_bsdf.emission_color_texture
4591
+ if emission_color_texture_node:
4592
+ emission_color_texture_node.image = emissive_texture_image
4593
+
4594
+ normal_texture_image = gltf.normal_texture.index.source
4595
+ if normal_texture_image:
4596
+ normalmap_texture_node = principled_bsdf.normalmap_texture
4597
+ if normalmap_texture_node:
4598
+ normalmap_texture_node.image = normal_texture_image
4599
+ normal_scale = gltf.normal_texture.scale
4600
+ if abs(normal_scale - 1) >= sys.float_info.epsilon:
4601
+ principled_bsdf.normalmap_strength = gltf.normal_texture.scale
4602
+
4603
+
4604
+ def _assign_mtoon1_outline(
4605
+ context: Context,
4606
+ material: Material,
4607
+ obj: Object,
4608
+ *,
4609
+ create_modifier: bool,
4610
+ ) -> None:
4611
+ shader.load_mtoon1_outline_geometry_node_group(context, reset_node_groups=False)
4612
+ node_group = context.blend_data.node_groups.get(shader.OUTLINE_GEOMETRY_GROUP_NAME)
4613
+ if not node_group:
4614
+ return
4615
+ if not _get_nodes_modifier_input_key(node_group):
4616
+ return
4617
+ mtoon = get_material_extension(material).mtoon1.extensions.vrmc_materials_mtoon
4618
+ outline_width_mode_value = mtoon.outline_width_mode_enum.identifier_to_value(
4619
+ mtoon.outline_width_mode,
4620
+ mtoon.OUTLINE_WIDTH_MODE_NONE.value,
4621
+ )
4622
+ no_outline = (
4623
+ not mtoon.enable_outline_preview
4624
+ or outline_width_mode_value == mtoon.OUTLINE_WIDTH_MODE_NONE.value
4625
+ or mtoon.outline_width_factor < sys.float_info.epsilon
4626
+ )
4627
+ cleanup = no_outline
4628
+ modifier = None
4629
+
4630
+ for search_modifier_name in list(obj.modifiers.keys()):
4631
+ search_modifier = obj.modifiers.get(search_modifier_name)
4632
+ if not search_modifier:
4633
+ continue
4634
+ if search_modifier.type != "NODES":
4635
+ continue
4636
+ if not isinstance(search_modifier, NodesModifier):
4637
+ continue
4638
+ if not search_modifier.node_group:
4639
+ continue
4640
+ if search_modifier.node_group.name != shader.OUTLINE_GEOMETRY_GROUP_NAME:
4641
+ continue
4642
+ input_properties = _get_nodes_modifier_input_properties(search_modifier)
4643
+ if input_properties is None:
4644
+ continue
4645
+ search_material = input_properties.get_material()
4646
+ if search_material is None:
4647
+ continue
4648
+ if search_material.name != material.name:
4649
+ continue
4650
+ if cleanup:
4651
+ obj.modifiers.remove(search_modifier)
4652
+ continue
4653
+ modifier = search_modifier
4654
+ cleanup = True
4655
+
4656
+ if no_outline:
4657
+ return
4658
+
4659
+ if not modifier and not create_modifier:
4660
+ mtoon.outline_width_mode = mtoon.OUTLINE_WIDTH_MODE_NONE.identifier
4661
+ return
4662
+
4663
+ outline_material_name = generate_mtoon1_outline_material_name(material)
4664
+ modifier_name = f"MToon Outline ({material.name})"
4665
+
4666
+ outline_material = get_material_extension(material).mtoon1.outline_material
4667
+ reset_outline_material = False
4668
+ if not outline_material:
4669
+ reset_outline_material = True
4670
+ outline_material = context.blend_data.materials.new(name=outline_material_name)
4671
+ if bpy.app.version < (5, 0, 0) and not outline_material.use_nodes:
4672
+ outline_material.use_nodes = True
4673
+ if outline_material.diffuse_color[3] != 0.25:
4674
+ outline_material.diffuse_color[3] = 0.25
4675
+ if outline_material.roughness != 0:
4676
+ outline_material.roughness = 0
4677
+ shader.load_mtoon1_shader(context, outline_material, reset_node_groups=False)
4678
+ get_material_extension(outline_material).mtoon1.is_outline_material = True
4679
+ get_material_extension(material).mtoon1.outline_material = outline_material
4680
+ if outline_material.name != outline_material_name:
4681
+ outline_material.name = outline_material_name
4682
+ if bpy.app.version < (5, 0, 0) and not outline_material.use_nodes:
4683
+ outline_material.use_nodes = True
4684
+ if not get_material_extension(outline_material).mtoon1.is_outline_material:
4685
+ get_material_extension(outline_material).mtoon1.is_outline_material = True
4686
+ if (
4687
+ get_material_extension(outline_material).mtoon1.alpha_cutoff
4688
+ != get_material_extension(material).mtoon1.alpha_cutoff
4689
+ ):
4690
+ get_material_extension(
4691
+ outline_material
4692
+ ).mtoon1.alpha_cutoff = get_material_extension(material).mtoon1.alpha_cutoff
4693
+ if (
4694
+ get_material_extension(outline_material).mtoon1.alpha_mode
4695
+ != get_material_extension(material).mtoon1.alpha_mode
4696
+ ):
4697
+ get_material_extension(
4698
+ outline_material
4699
+ ).mtoon1.alpha_mode = get_material_extension(material).mtoon1.alpha_mode
4700
+ if bpy.app.version < (4, 3) and outline_material.shadow_method != "NONE":
4701
+ outline_material.shadow_method = "NONE"
4702
+ if not outline_material.use_backface_culling:
4703
+ outline_material.use_backface_culling = True
4704
+ if outline_material.show_transparent_back:
4705
+ outline_material.show_transparent_back = False
4706
+
4707
+ if not modifier:
4708
+ new_modifier = obj.modifiers.new(modifier_name, "NODES")
4709
+ if not isinstance(new_modifier, NodesModifier):
4710
+ message = f"{type(new_modifier)} is not a NodesModifier"
4711
+ raise AssertionError(message)
4712
+ modifier = new_modifier
4713
+ modifier.show_expanded = False
4714
+ modifier.show_in_editmode = False
4715
+ if bpy.app.version >= (4, 0):
4716
+ modifier.show_group_selector = False
4717
+ if bpy.app.version >= (4, 2):
4718
+ modifier.use_pin_to_last = True
4719
+ if bpy.app.version >= (5, 0):
4720
+ modifier.show_manage_panel = False
4721
+
4722
+ if modifier.name != modifier_name:
4723
+ modifier.name = modifier_name
4724
+ if modifier.node_group != node_group:
4725
+ modifier.node_group = node_group
4726
+
4727
+ input_properties = _get_nodes_modifier_input_properties(modifier)
4728
+ if input_properties is None:
4729
+ return
4730
+
4731
+ (
4732
+ outline_width_multiply_texture_uv_offset_x,
4733
+ outline_width_multiply_texture_uv_offset_y,
4734
+ ) = mtoon.outline_width_multiply_texture.extensions.khr_texture_transform.offset
4735
+ (
4736
+ outline_width_multiply_texture_uv_scale_x,
4737
+ outline_width_multiply_texture_uv_scale_y,
4738
+ ) = mtoon.outline_width_multiply_texture.extensions.khr_texture_transform.scale
4739
+
4740
+ uv_layer_name = None
4741
+ if isinstance((mesh_data := obj.data), Mesh):
4742
+ uv_layer_name = next(
4743
+ (
4744
+ uv_layer.name
4745
+ for uv_layer in mesh_data.uv_layers
4746
+ if uv_layer and uv_layer.active_render
4747
+ ),
4748
+ None,
4749
+ )
4750
+ if uv_layer_name is None:
4751
+ uv_layer_name = "UVMap"
4752
+
4753
+ has_auto_smooth = tuple(bpy.app.version) < (4, 1)
4754
+ outline_width_multiply_texture_uv_use_attribute = (
4755
+ True if tuple(bpy.app.version) >= (4, 2) else 1
4756
+ )
4757
+
4758
+ modifier_input_changed = False
4759
+ modifier_input_changed |= input_properties.set_material(material)
4760
+ modifier_input_changed |= input_properties.set_outline_material(outline_material)
4761
+ modifier_input_changed |= input_properties.set_outline_width_mode(
4762
+ outline_width_mode_value
4763
+ )
4764
+ modifier_input_changed |= input_properties.set_outline_width_factor(
4765
+ mtoon.outline_width_factor
4766
+ )
4767
+ modifier_input_changed |= input_properties.set_outline_width_multiply_texture(
4768
+ mtoon.outline_width_multiply_texture.index.source
4769
+ )
4770
+ modifier_input_changed |= (
4771
+ input_properties.set_outline_width_multiply_texture_exists(
4772
+ value=bool(mtoon.outline_width_multiply_texture.index.source)
4773
+ )
4774
+ )
4775
+ modifier_input_changed |= (
4776
+ input_properties.set_outline_width_multiply_texture_uv_use_attribute(
4777
+ value=outline_width_multiply_texture_uv_use_attribute
4778
+ )
4779
+ )
4780
+ modifier_input_changed |= (
4781
+ input_properties.set_outline_width_multiply_texture_uv_attribute_name(
4782
+ uv_layer_name
4783
+ )
4784
+ )
4785
+ modifier_input_changed |= (
4786
+ input_properties.set_outline_width_multiply_texture_uv_offset_x(
4787
+ outline_width_multiply_texture_uv_offset_x
4788
+ )
4789
+ )
4790
+ modifier_input_changed |= (
4791
+ input_properties.set_outline_width_multiply_texture_uv_offset_y(
4792
+ outline_width_multiply_texture_uv_offset_y
4793
+ )
4794
+ )
4795
+ modifier_input_changed |= (
4796
+ input_properties.set_outline_width_multiply_texture_uv_scale_x(
4797
+ outline_width_multiply_texture_uv_scale_x
4798
+ )
4799
+ )
4800
+ modifier_input_changed |= (
4801
+ input_properties.set_outline_width_multiply_texture_uv_scale_y(
4802
+ outline_width_multiply_texture_uv_scale_y
4803
+ )
4804
+ )
4805
+ modifier_input_changed |= input_properties.set_extrude_mesh_individual(
4806
+ value=obj.type == "MESH"
4807
+ and isinstance(mesh_data := obj.data, Mesh)
4808
+ and not (has_auto_smooth and mesh_data.use_auto_smooth)
4809
+ and not any(polygon.use_smooth for polygon in mesh_data.polygons)
4810
+ )
4811
+ modifier_input_changed |= input_properties.set_object(obj)
4812
+ modifier_input_changed |= input_properties.set_enabled(
4813
+ value=obj.mode
4814
+ not in {"VERTEX_PAINT", "TEXTURE_PAINT", "WEIGHT_PAINT", "SCULPT"}
4815
+ )
4816
+
4817
+ if modifier_input_changed:
4818
+ modifier.show_viewport = not modifier.show_viewport
4819
+ modifier.show_viewport = not modifier.show_viewport
4820
+
4821
+ if reset_outline_material:
4822
+ reset_shader_node_group(
4823
+ context,
4824
+ material,
4825
+ reset_material_node_tree=False,
4826
+ reset_node_groups=False,
4827
+ )
4828
+
4829
+
4830
+ def refresh_mtoon1_outline_object(context: Context, obj: Object) -> None:
4831
+ if bpy.app.version < (3, 3):
4832
+ return
4833
+ for material_slot in obj.material_slots:
4834
+ material_ref = material_slot.material
4835
+ if not material_ref:
4836
+ continue
4837
+
4838
+ material = context.blend_data.materials.get(material_ref.name)
4839
+ if not material:
4840
+ continue
4841
+
4842
+ mtoon1 = get_material_extension(material).mtoon1
4843
+ if not mtoon1.enabled or mtoon1.is_outline_material:
4844
+ continue
4845
+
4846
+ _assign_mtoon1_outline(context, material, obj, create_modifier=False)
4847
+
4848
+
4849
+ def refresh_mtoon1_outline(
4850
+ context: Optional[Context] = None,
4851
+ material_name: Optional[str] = None,
4852
+ *,
4853
+ create_modifier: bool,
4854
+ ) -> None:
4855
+ resolved_context = context or bpy.context
4856
+ if bpy.app.version < (3, 3):
4857
+ return
4858
+ for obj in resolved_context.blend_data.objects:
4859
+ if obj.type != "MESH":
4860
+ continue
4861
+ outline_material_names: list[str] = []
4862
+ for material_slot in obj.material_slots:
4863
+ material_ref = material_slot.material
4864
+ if not material_ref:
4865
+ continue
4866
+
4867
+ if material_name is not None and material_name != material_ref.name:
4868
+ continue
4869
+
4870
+ material = resolved_context.blend_data.materials.get(material_ref.name)
4871
+ if not material:
4872
+ continue
4873
+
4874
+ mtoon1 = get_material_extension(material).mtoon1
4875
+ if not mtoon1.enabled or mtoon1.is_outline_material:
4876
+ continue
4877
+
4878
+ _assign_mtoon1_outline(
4879
+ resolved_context,
4880
+ material,
4881
+ obj,
4882
+ create_modifier=create_modifier,
4883
+ )
4884
+ outline_material_names.append(material.name)
4885
+ if material_name is not None:
4886
+ continue
4887
+
4888
+ for search_modifier_name in list(obj.modifiers.keys()):
4889
+ search_modifier = obj.modifiers.get(search_modifier_name)
4890
+ if not search_modifier:
4891
+ continue
4892
+ if search_modifier.type != "NODES":
4893
+ continue
4894
+ if not isinstance(search_modifier, NodesModifier):
4895
+ continue
4896
+ node_group = search_modifier.node_group
4897
+ if not node_group:
4898
+ continue
4899
+ if node_group.name != shader.OUTLINE_GEOMETRY_GROUP_NAME:
4900
+ continue
4901
+ input_properties = _get_nodes_modifier_input_properties(search_modifier)
4902
+ if input_properties is None:
4903
+ continue
4904
+ search_material = input_properties.get_material()
4905
+ if (
4906
+ search_material is not None
4907
+ and search_material.name in outline_material_names
4908
+ ):
4909
+ continue
4910
+ obj.modifiers.remove(search_modifier)
4911
+
4912
+
4913
+ def setup_drivers(context: Context) -> None:
4914
+ for material in context.blend_data.materials:
4915
+ get_material_extension(material).mtoon1.setup_drivers()
4916
+ shader.setup_frame_count_driver(context)
4917
+
4918
+
4919
+ def link_or_unlink_gltf_material_nodes(
4920
+ context: Context, material: Material, *, link: bool
4921
+ ) -> None:
4922
+ TextureTraceablePropertyGroup.link_or_unlink_nodes(
4923
+ material,
4924
+ "Mtoon1Material.CompatibleShader",
4925
+ ShaderNodeBsdfPrincipled,
4926
+ "BSDF",
4927
+ StaticNodeSocketTarget(
4928
+ in_node_name="Mtoon1Material.CompatibleMaterialOutput",
4929
+ in_node_type=ShaderNodeOutputMaterial,
4930
+ in_socket_name="Surface",
4931
+ ),
4932
+ link=link,
4933
+ )
4934
+
4935
+ output_group = context.blend_data.node_groups.get(shader.OUTPUT_GROUP_NAME)
4936
+ if output_group is None:
4937
+ return
4938
+
4939
+ for from_node_name, to_node_name in (
4940
+ (
4941
+ "EeveeMaterialOutputSwitch",
4942
+ "EeveeMaterialOutput",
4943
+ ),
4944
+ (
4945
+ "CyclesMaterialOutputSwitch",
4946
+ "CyclesMaterialOutput",
4947
+ ),
4948
+ ):
4949
+ from_node = output_group.nodes.get(from_node_name)
4950
+ if not from_node or not isinstance(from_node, NodeReroute):
4951
+ continue
4952
+ from_socket = from_node.outputs[0]
4953
+
4954
+ to_node = output_group.nodes.get(to_node_name)
4955
+ if not to_node or not isinstance(to_node, ShaderNodeOutputMaterial):
4956
+ continue
4957
+ to_socket = to_node.inputs["Surface"]
4958
+
4959
+ existing_link = next(
4960
+ (
4961
+ link
4962
+ for link in output_group.links
4963
+ if link.from_node == from_node
4964
+ and link.from_socket == from_socket
4965
+ and link.to_node == to_node
4966
+ and link.to_socket == to_socket
4967
+ ),
4968
+ None,
4969
+ )
4970
+ if existing_link:
4971
+ if not link:
4972
+ output_group.links.remove(existing_link)
4973
+ elif link:
4974
+ output_group.links.new(
4975
+ from_socket,
4976
+ to_socket,
4977
+ )