node_shader_utils.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software Foundation,
  15. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18. # <pep8 compliant>
  19. from mathutils import Color, Vector
  20. __all__ = (
  21. "PrincipledBSDFWrapper",
  22. )
  23. def _set_check(func):
  24. from functools import wraps
  25. @wraps(func)
  26. def wrapper(self, *args, **kwargs):
  27. if self.is_readonly:
  28. assert(not "Trying to set value to read-only shader!")
  29. return
  30. return func(self, *args, **kwargs)
  31. return wrapper
  32. def rgb_to_rgba(rgb):
  33. return list(rgb) + [1.0]
  34. def rgba_to_rgb(rgba):
  35. return Color((rgba[0], rgba[1], rgba[2]))
  36. class ShaderWrapper():
  37. """
  38. Base class with minimal common ground for all types of shader interfaces we may want/need to implement.
  39. """
  40. # The two mandatory nodes any children class should support.
  41. NODES_LIST = (
  42. "node_out",
  43. "_node_texcoords",
  44. )
  45. __slots__ = (
  46. "is_readonly",
  47. "material",
  48. "_textures",
  49. "_grid_locations",
  50. *NODES_LIST,
  51. )
  52. _col_size = 300
  53. _row_size = 300
  54. def _grid_to_location(self, x, y, dst_node=None, ref_node=None):
  55. if ref_node is not None: # x and y are relative to this node location.
  56. nx = round(ref_node.location.x / self._col_size)
  57. ny = round(ref_node.location.y / self._row_size)
  58. x += nx
  59. y += ny
  60. loc = None
  61. while True:
  62. loc = (x * self._col_size, y * self._row_size)
  63. if loc not in self._grid_locations:
  64. break
  65. loc = (x * self._col_size, (y - 1) * self._row_size)
  66. if loc not in self._grid_locations:
  67. break
  68. loc = (x * self._col_size, (y - 2) * self._row_size)
  69. if loc not in self._grid_locations:
  70. break
  71. x -= 1
  72. self._grid_locations.add(loc)
  73. if dst_node is not None:
  74. dst_node.location = loc
  75. dst_node.width = min(dst_node.width, self._col_size - 20)
  76. return loc
  77. def __init__(self, material, is_readonly=True, use_nodes=True):
  78. self.is_readonly = is_readonly
  79. self.material = material
  80. if not is_readonly:
  81. self.use_nodes = use_nodes
  82. self.update()
  83. def update(self): # Should be re-implemented by children classes...
  84. for node in self.NODES_LIST:
  85. setattr(self, node, None)
  86. self._textures = {}
  87. self._grid_locations = set()
  88. def use_nodes_get(self):
  89. return self.material.use_nodes
  90. @_set_check
  91. def use_nodes_set(self, val):
  92. self.material.use_nodes = val
  93. self.update()
  94. use_nodes = property(use_nodes_get, use_nodes_set)
  95. def node_texcoords_get(self):
  96. if not self.use_nodes:
  97. return None
  98. if self._node_texcoords is ...:
  99. # Running only once, trying to find a valid texcoords node.
  100. for n in self.material.node_tree.nodes:
  101. if n.bl_idname == 'ShaderNodeTexCoord':
  102. self._node_texcoords = n
  103. self._grid_to_location(0, 0, ref_node=n)
  104. break
  105. if self._node_texcoords is ...:
  106. self._node_texcoords = None
  107. if self._node_texcoords is None and not self.is_readonly:
  108. tree = self.material.node_tree
  109. nodes = tree.nodes
  110. # links = tree.links
  111. node_texcoords = nodes.new(type='ShaderNodeTexCoord')
  112. node_texcoords.label = "Texture Coords"
  113. self._grid_to_location(-5, 1, dst_node=node_texcoords)
  114. self._node_texcoords = node_texcoords
  115. return self._node_texcoords
  116. node_texcoords = property(node_texcoords_get)
  117. class PrincipledBSDFWrapper(ShaderWrapper):
  118. """
  119. Hard coded shader setup, based in Principled BSDF.
  120. Should cover most common cases on import, and gives a basic nodal shaders support for export.
  121. Supports basic: diffuse/spec/reflect/transparency/normal, with texturing.
  122. """
  123. NODES_LIST = (
  124. "node_out",
  125. "node_principled_bsdf",
  126. "_node_normalmap",
  127. "_node_texcoords",
  128. )
  129. __slots__ = (
  130. "is_readonly",
  131. "material",
  132. *NODES_LIST,
  133. )
  134. NODES_LIST = ShaderWrapper.NODES_LIST + NODES_LIST
  135. def __init__(self, material, is_readonly=True, use_nodes=True):
  136. super(PrincipledBSDFWrapper, self).__init__(material, is_readonly, use_nodes)
  137. def update(self):
  138. super(PrincipledBSDFWrapper, self).update()
  139. if not self.use_nodes:
  140. return
  141. tree = self.material.node_tree
  142. nodes = tree.nodes
  143. links = tree.links
  144. # --------------------------------------------------------------------
  145. # Main output and shader.
  146. node_out = None
  147. node_principled = None
  148. for n in nodes:
  149. if n.bl_idname == 'ShaderNodeOutputMaterial' and n.inputs[0].is_linked:
  150. node_out = n
  151. node_principled = n.inputs[0].links[0].from_node
  152. elif n.bl_idname == 'ShaderNodeBsdfPrincipled' and n.outputs[0].is_linked:
  153. node_principled = n
  154. for lnk in n.outputs[0].links:
  155. node_out = lnk.to_node
  156. if node_out.bl_idname == 'ShaderNodeOutputMaterial':
  157. break
  158. if (
  159. node_out is not None and node_principled is not None and
  160. node_out.bl_idname == 'ShaderNodeOutputMaterial' and
  161. node_principled.bl_idname == 'ShaderNodeBsdfPrincipled'
  162. ):
  163. break
  164. node_out = node_principled = None # Could not find a valid pair, let's try again
  165. if node_out is not None:
  166. self._grid_to_location(0, 0, ref_node=node_out)
  167. elif not self.is_readonly:
  168. node_out = nodes.new(type='ShaderNodeOutputMaterial')
  169. node_out.label = "Material Out"
  170. node_out.target = 'ALL'
  171. self._grid_to_location(1, 1, dst_node=node_out)
  172. self.node_out = node_out
  173. if node_principled is not None:
  174. self._grid_to_location(0, 0, ref_node=node_principled)
  175. elif not self.is_readonly:
  176. node_principled = nodes.new(type='ShaderNodeBsdfPrincipled')
  177. node_principled.label = "Principled BSDF"
  178. self._grid_to_location(0, 1, dst_node=node_principled)
  179. # Link
  180. links.new(node_principled.outputs["BSDF"], self.node_out.inputs["Surface"])
  181. self.node_principled_bsdf = node_principled
  182. # --------------------------------------------------------------------
  183. # Normal Map, lazy initialization...
  184. self._node_normalmap = ...
  185. # --------------------------------------------------------------------
  186. # Tex Coords, lazy initialization...
  187. self._node_texcoords = ...
  188. def node_normalmap_get(self):
  189. if not self.use_nodes or self.node_principled_bsdf is None:
  190. return None
  191. node_principled = self.node_principled_bsdf
  192. if self._node_normalmap is ...:
  193. # Running only once, trying to find a valid normalmap node.
  194. if node_principled.inputs["Normal"].is_linked:
  195. node_normalmap = node_principled.inputs["Normal"].links[0].from_node
  196. if node_normalmap.bl_idname == 'ShaderNodeNormalMap':
  197. self._node_normalmap = node_normalmap
  198. self._grid_to_location(0, 0, ref_node=node_normalmap)
  199. if self._node_normalmap is ...:
  200. self._node_normalmap = None
  201. if self._node_normalmap is None and not self.is_readonly:
  202. tree = self.material.node_tree
  203. nodes = tree.nodes
  204. links = tree.links
  205. node_normalmap = nodes.new(type='ShaderNodeNormalMap')
  206. node_normalmap.label = "Normal/Map"
  207. self._grid_to_location(-1, -2, dst_node=node_normalmap, ref_node=node_principled)
  208. # Link
  209. links.new(node_normalmap.outputs["Normal"], node_principled.inputs["Normal"])
  210. self._node_normalmap = node_normalmap
  211. return self._node_normalmap
  212. node_normalmap = property(node_normalmap_get)
  213. # --------------------------------------------------------------------
  214. # Base Color.
  215. def base_color_get(self):
  216. if not self.use_nodes or self.node_principled_bsdf is None:
  217. return self.material.diffuse_color
  218. return rgba_to_rgb(self.node_principled_bsdf.inputs["Base Color"].default_value)
  219. @_set_check
  220. def base_color_set(self, color):
  221. color = rgb_to_rgba(color)
  222. self.material.diffuse_color = color
  223. if self.use_nodes and self.node_principled_bsdf is not None:
  224. self.node_principled_bsdf.inputs["Base Color"].default_value = color
  225. base_color = property(base_color_get, base_color_set)
  226. def base_color_texture_get(self):
  227. if not self.use_nodes or self.node_principled_bsdf is None:
  228. return None
  229. return ShaderImageTextureWrapper(
  230. self, self.node_principled_bsdf,
  231. self.node_principled_bsdf.inputs["Base Color"],
  232. grid_row_diff=1,
  233. )
  234. base_color_texture = property(base_color_texture_get)
  235. # --------------------------------------------------------------------
  236. # Specular.
  237. def specular_get(self):
  238. if not self.use_nodes or self.node_principled_bsdf is None:
  239. return self.material.specular_intensity
  240. return self.node_principled_bsdf.inputs["Specular"].default_value
  241. @_set_check
  242. def specular_set(self, value):
  243. self.material.specular_intensity = value
  244. if self.use_nodes and self.node_principled_bsdf is not None:
  245. self.node_principled_bsdf.inputs["Specular"].default_value = value
  246. specular = property(specular_get, specular_set)
  247. def specular_tint_get(self):
  248. if not self.use_nodes or self.node_principled_bsdf is None:
  249. return 0.0
  250. return self.node_principled_bsdf.inputs["Specular Tint"].default_value
  251. @_set_check
  252. def specular_tint_set(self, value):
  253. if self.use_nodes and self.node_principled_bsdf is not None:
  254. self.node_principled_bsdf.inputs["Specular Tint"].default_value = value
  255. specular_tint = property(specular_tint_get, specular_tint_set)
  256. # Will only be used as gray-scale one...
  257. def specular_texture_get(self):
  258. if not self.use_nodes or self.node_principled_bsdf is None:
  259. print("NO NODES!")
  260. return None
  261. return ShaderImageTextureWrapper(
  262. self, self.node_principled_bsdf,
  263. self.node_principled_bsdf.inputs["Specular"],
  264. grid_row_diff=0,
  265. )
  266. specular_texture = property(specular_texture_get)
  267. # --------------------------------------------------------------------
  268. # Roughness (also sort of inverse of specular hardness...).
  269. def roughness_get(self):
  270. if not self.use_nodes or self.node_principled_bsdf is None:
  271. return self.material.roughness
  272. return self.node_principled_bsdf.inputs["Roughness"].default_value
  273. @_set_check
  274. def roughness_set(self, value):
  275. self.material.roughness = value
  276. if self.use_nodes and self.node_principled_bsdf is not None:
  277. self.node_principled_bsdf.inputs["Roughness"].default_value = value
  278. roughness = property(roughness_get, roughness_set)
  279. # Will only be used as gray-scale one...
  280. def roughness_texture_get(self):
  281. if not self.use_nodes or self.node_principled_bsdf is None:
  282. return None
  283. return ShaderImageTextureWrapper(
  284. self, self.node_principled_bsdf,
  285. self.node_principled_bsdf.inputs["Roughness"],
  286. grid_row_diff=0,
  287. )
  288. roughness_texture = property(roughness_texture_get)
  289. # --------------------------------------------------------------------
  290. # Metallic (a.k.a reflection, mirror).
  291. def metallic_get(self):
  292. if not self.use_nodes or self.node_principled_bsdf is None:
  293. return self.material.metallic
  294. return self.node_principled_bsdf.inputs["Metallic"].default_value
  295. @_set_check
  296. def metallic_set(self, value):
  297. self.material.metallic = value
  298. if self.use_nodes and self.node_principled_bsdf is not None:
  299. self.node_principled_bsdf.inputs["Metallic"].default_value = value
  300. metallic = property(metallic_get, metallic_set)
  301. # Will only be used as gray-scale one...
  302. def metallic_texture_get(self):
  303. if not self.use_nodes or self.node_principled_bsdf is None:
  304. return None
  305. return ShaderImageTextureWrapper(
  306. self, self.node_principled_bsdf,
  307. self.node_principled_bsdf.inputs["Metallic"],
  308. grid_row_diff=0,
  309. )
  310. metallic_texture = property(metallic_texture_get)
  311. # --------------------------------------------------------------------
  312. # Transparency settings.
  313. def ior_get(self):
  314. if not self.use_nodes or self.node_principled_bsdf is None:
  315. return 1.0
  316. return self.node_principled_bsdf.inputs["IOR"].default_value
  317. @_set_check
  318. def ior_set(self, value):
  319. if self.use_nodes and self.node_principled_bsdf is not None:
  320. self.node_principled_bsdf.inputs["IOR"].default_value = value
  321. ior = property(ior_get, ior_set)
  322. # Will only be used as gray-scale one...
  323. def ior_texture_get(self):
  324. if not self.use_nodes or self.node_principled_bsdf is None:
  325. return None
  326. return ShaderImageTextureWrapper(
  327. self, self.node_principled_bsdf,
  328. self.node_principled_bsdf.inputs["IOR"],
  329. grid_row_diff=-1,
  330. )
  331. ior_texture = property(ior_texture_get)
  332. def transmission_get(self):
  333. if not self.use_nodes or self.node_principled_bsdf is None:
  334. return 0.0
  335. return self.node_principled_bsdf.inputs["Transmission"].default_value
  336. @_set_check
  337. def transmission_set(self, value):
  338. if self.use_nodes and self.node_principled_bsdf is not None:
  339. self.node_principled_bsdf.inputs["Transmission"].default_value = value
  340. transmission = property(transmission_get, transmission_set)
  341. # Will only be used as gray-scale one...
  342. def transmission_texture_get(self):
  343. if not self.use_nodes or self.node_principled_bsdf is None:
  344. return None
  345. return ShaderImageTextureWrapper(
  346. self, self.node_principled_bsdf,
  347. self.node_principled_bsdf.inputs["Transmission"],
  348. grid_row_diff=-1,
  349. )
  350. transmission_texture = property(transmission_texture_get)
  351. def alpha_get(self):
  352. if not self.use_nodes or self.node_principled_bsdf is None:
  353. return 1.0
  354. return self.node_principled_bsdf.inputs["Alpha"].default_value
  355. @_set_check
  356. def alpha_set(self, value):
  357. if self.use_nodes and self.node_principled_bsdf is not None:
  358. self.node_principled_bsdf.inputs["Alpha"].default_value = value
  359. alpha = property(alpha_get, alpha_set)
  360. # Will only be used as gray-scale one...
  361. def alpha_texture_get(self):
  362. if not self.use_nodes or self.node_principled_bsdf is None:
  363. return None
  364. return ShaderImageTextureWrapper(
  365. self, self.node_principled_bsdf,
  366. self.node_principled_bsdf.inputs["Alpha"],
  367. grid_row_diff=-1,
  368. )
  369. alpha_texture = property(alpha_texture_get)
  370. # --------------------------------------------------------------------
  371. # Normal map.
  372. def normalmap_strength_get(self):
  373. if not self.use_nodes or self.node_normalmap is None:
  374. return 0.0
  375. return self.node_normalmap.inputs["Strength"].default_value
  376. @_set_check
  377. def normalmap_strength_set(self, value):
  378. if self.use_nodes and self.node_normalmap is not None:
  379. self.node_normalmap.inputs["Strength"].default_value = value
  380. normalmap_strength = property(normalmap_strength_get, normalmap_strength_set)
  381. def normalmap_texture_get(self):
  382. if not self.use_nodes or self.node_normalmap is None:
  383. return None
  384. return ShaderImageTextureWrapper(
  385. self, self.node_normalmap,
  386. self.node_normalmap.inputs["Color"],
  387. grid_row_diff=-2,
  388. colorspace_is_data=True,
  389. )
  390. normalmap_texture = property(normalmap_texture_get)
  391. class ShaderImageTextureWrapper():
  392. """
  393. Generic 'image texture'-like wrapper, handling image node, some mapping (texture coordinates transformations),
  394. and texture coordinates source.
  395. """
  396. # Note: this class assumes we are using nodes, otherwise it should never be used...
  397. NODES_LIST = (
  398. "node_dst",
  399. "socket_dst",
  400. "_node_image",
  401. "_node_mapping",
  402. )
  403. __slots__ = (
  404. "owner_shader",
  405. "is_readonly",
  406. "grid_row_diff",
  407. "use_alpha",
  408. "colorspace_is_data",
  409. *NODES_LIST,
  410. )
  411. def __new__(cls, owner_shader: ShaderWrapper, node_dst, socket_dst, *_args, **_kwargs):
  412. instance = owner_shader._textures.get((node_dst, socket_dst), None)
  413. if instance is not None:
  414. return instance
  415. instance = super(ShaderImageTextureWrapper, cls).__new__(cls)
  416. owner_shader._textures[(node_dst, socket_dst)] = instance
  417. return instance
  418. def __init__(self, owner_shader: ShaderWrapper, node_dst, socket_dst, grid_row_diff=0,
  419. use_alpha=False, colorspace_is_data=...):
  420. self.owner_shader = owner_shader
  421. self.is_readonly = owner_shader.is_readonly
  422. self.node_dst = node_dst
  423. self.socket_dst = socket_dst
  424. self.grid_row_diff = grid_row_diff
  425. self.use_alpha = use_alpha
  426. self.colorspace_is_data = colorspace_is_data
  427. self._node_image = ...
  428. self._node_mapping = ...
  429. # tree = node_dst.id_data
  430. # nodes = tree.nodes
  431. # links = tree.links
  432. if socket_dst.is_linked:
  433. from_node = socket_dst.links[0].from_node
  434. if from_node.bl_idname == 'ShaderNodeTexImage':
  435. self._node_image = from_node
  436. if self.node_image is not None:
  437. socket_dst = self.node_image.inputs["Vector"]
  438. if socket_dst.is_linked:
  439. from_node = socket_dst.links[0].from_node
  440. if from_node.bl_idname == 'ShaderNodeMapping':
  441. self._node_mapping = from_node
  442. def copy_from(self, tex):
  443. # Avoid generating any node in source texture.
  444. is_readonly_back = tex.is_readonly
  445. tex.is_readonly = True
  446. if tex.node_image is not None:
  447. self.image = tex.image
  448. self.projection = tex.projection
  449. self.texcoords = tex.texcoords
  450. self.copy_mapping_from(tex)
  451. tex.is_readonly = is_readonly_back
  452. def copy_mapping_from(self, tex):
  453. # Avoid generating any node in source texture.
  454. is_readonly_back = tex.is_readonly
  455. tex.is_readonly = True
  456. if tex.node_mapping is None: # Used to actually remove mapping node.
  457. if self.has_mapping_node():
  458. # We assume node_image can never be None in that case...
  459. # Find potential existing link into image's Vector input.
  460. socket_dst = socket_src = None
  461. if self.node_mapping.inputs["Vector"].is_linked:
  462. socket_dst = self.node_image.inputs["Vector"]
  463. socket_src = self.node_mapping.inputs["Vector"].links[0].from_socket
  464. tree = self.owner_shader.material.node_tree
  465. tree.nodes.remove(self.node_mapping)
  466. self._node_mapping = None
  467. # If previously existing, re-link texcoords -> image
  468. if socket_src is not None:
  469. tree.links.new(socket_src, socket_dst)
  470. elif self.node_mapping is not None:
  471. self.translation = tex.translation
  472. self.rotation = tex.rotation
  473. self.scale = tex.scale
  474. self.use_min = tex.use_min
  475. self.use_max = tex.use_max
  476. self.min = tex.min
  477. self.max = tex.max
  478. tex.is_readonly = is_readonly_back
  479. # --------------------------------------------------------------------
  480. # Image.
  481. def node_image_get(self):
  482. if self._node_image is ...:
  483. # Running only once, trying to find a valid image node.
  484. if self.socket_dst.is_linked:
  485. node_image = self.socket_dst.links[0].from_node
  486. if node_image.bl_idname == 'ShaderNodeTexImage':
  487. self._node_image = node_image
  488. self.owner_shader._grid_to_location(0, 0, ref_node=node_image)
  489. if self._node_image is ...:
  490. self._node_image = None
  491. if self._node_image is None and not self.is_readonly:
  492. tree = self.owner_shader.material.node_tree
  493. node_image = tree.nodes.new(type='ShaderNodeTexImage')
  494. self.owner_shader._grid_to_location(-1, 0 + self.grid_row_diff, dst_node=node_image, ref_node=self.node_dst)
  495. tree.links.new(node_image.outputs["Alpha" if self.use_alpha else "Color"], self.socket_dst)
  496. self._node_image = node_image
  497. return self._node_image
  498. node_image = property(node_image_get)
  499. def image_get(self):
  500. return self.node_image.image if self.node_image is not None else None
  501. @_set_check
  502. def image_set(self, image):
  503. if self.colorspace_is_data is not ...:
  504. image.colorspace_settings.is_data = self.colorspace_is_data
  505. self.node_image.image = image
  506. image = property(image_get, image_set)
  507. def projection_get(self):
  508. return self.node_image.projection if self.node_image is not None else 'FLAT'
  509. @_set_check
  510. def projection_set(self, projection):
  511. self.node_image.projection = projection
  512. projection = property(projection_get, projection_set)
  513. def texcoords_get(self):
  514. if self.node_image is not None:
  515. socket = (self.node_mapping if self.has_mapping_node() else self.node_image).inputs["Vector"]
  516. if socket.is_linked:
  517. return socket.links[0].from_socket.name
  518. return 'UV'
  519. @_set_check
  520. def texcoords_set(self, texcoords):
  521. # Image texture node already defaults to UVs, no extra node needed.
  522. # ONLY in case we do not have any texcoords mapping!!!
  523. if texcoords == 'UV' and not self.has_mapping_node():
  524. return
  525. tree = self.node_image.id_data
  526. links = tree.links
  527. node_dst = self.node_mapping if self.has_mapping_node() else self.node_image
  528. socket_src = self.owner_shader.node_texcoords.outputs[texcoords]
  529. links.new(socket_src, node_dst.inputs["Vector"])
  530. texcoords = property(texcoords_get, texcoords_set)
  531. def extension_get(self):
  532. return self.node_image.extension if self.node_image is not None else 'REPEAT'
  533. @_set_check
  534. def extension_set(self, extension):
  535. self.node_image.extension = extension
  536. extension = property(extension_get, extension_set)
  537. # --------------------------------------------------------------------
  538. # Mapping.
  539. def has_mapping_node(self):
  540. return self._node_mapping not in {None, ...}
  541. def node_mapping_get(self):
  542. if self._node_mapping is ...:
  543. # Running only once, trying to find a valid mapping node.
  544. if self.node_image is None:
  545. return None
  546. if self.node_image.inputs["Vector"].is_linked:
  547. node_mapping = self.node_image.inputs["Vector"].links[0].from_node
  548. if node_mapping.bl_idname == 'ShaderNodeMapping':
  549. self._node_mapping = node_mapping
  550. self.owner_shader._grid_to_location(0, 0 + self.grid_row_diff, ref_node=node_mapping)
  551. if self._node_mapping is ...:
  552. self._node_mapping = None
  553. if self._node_mapping is None and not self.is_readonly:
  554. # Find potential existing link into image's Vector input.
  555. socket_dst = self.node_image.inputs["Vector"]
  556. # If not already existing, we need to create texcoords -> mapping link (from UV).
  557. socket_src = (socket_dst.links[0].from_socket if socket_dst.is_linked
  558. else self.owner_shader.node_texcoords.outputs['UV'])
  559. tree = self.owner_shader.material.node_tree
  560. node_mapping = tree.nodes.new(type='ShaderNodeMapping')
  561. node_mapping.vector_type = 'TEXTURE'
  562. self.owner_shader._grid_to_location(-1, 0, dst_node=node_mapping, ref_node=self.node_image)
  563. # Link mapping -> image node.
  564. tree.links.new(node_mapping.outputs["Vector"], socket_dst)
  565. # Link texcoords -> mapping.
  566. tree.links.new(socket_src, node_mapping.inputs["Vector"])
  567. self._node_mapping = node_mapping
  568. return self._node_mapping
  569. node_mapping = property(node_mapping_get)
  570. def translation_get(self):
  571. return self.node_mapping.translation if self.node_mapping is not None else Vector((0.0, 0.0, 0.0))
  572. @_set_check
  573. def translation_set(self, translation):
  574. self.node_mapping.translation = translation
  575. translation = property(translation_get, translation_set)
  576. def rotation_get(self):
  577. return self.node_mapping.rotation if self.node_mapping is not None else Vector((0.0, 0.0, 0.0))
  578. @_set_check
  579. def rotation_set(self, rotation):
  580. self.node_mapping.rotation = rotation
  581. rotation = property(rotation_get, rotation_set)
  582. def scale_get(self):
  583. return self.node_mapping.scale if self.node_mapping is not None else Vector((1.0, 1.0, 1.0))
  584. @_set_check
  585. def scale_set(self, scale):
  586. self.node_mapping.scale = scale
  587. scale = property(scale_get, scale_set)
  588. def use_min_get(self):
  589. return self.node_mapping.use_min if self.node_mapping is not None else False
  590. @_set_check
  591. def use_min_set(self, use_min):
  592. self.node_mapping.use_min = use_min
  593. use_min = property(use_min_get, use_min_set)
  594. def use_max_get(self):
  595. return self.node_mapping.use_max if self.node_mapping is not None else False
  596. @_set_check
  597. def use_max_set(self, use_max):
  598. self.node_mapping.use_max = use_max
  599. use_max = property(use_max_get, use_max_set)
  600. def min_get(self):
  601. return self.node_mapping.min if self.node_mapping is not None else Vector((0.0, 0.0, 0.0))
  602. @_set_check
  603. def min_set(self, min):
  604. self.node_mapping.min = min
  605. min = property(min_get, min_set)
  606. def max_get(self):
  607. return self.node_mapping.max if self.node_mapping is not None else Vector((0.0, 0.0, 0.0))
  608. @_set_check
  609. def max_set(self, max):
  610. self.node_mapping.max = max
  611. max = property(max_get, max_set)