1 # SPDX-FileCopyrightText: 2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 """Translate complex shaders to exported POV textures."""
9 # WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10 def write_nodes(pov_mat_name
, ntree
, file):
11 """Translate Blender node trees to pov and write them to file."""
12 # such function local inlined import are official guidelines
13 # of Blender Foundation to lighten addons footprint at startup
15 from .render
import string_strip_hyphen
18 scene
= bpy
.context
.scene
19 for node
in ntree
.nodes
:
20 pov_node_name
= string_strip_hyphen(bpy
.path
.clean_name(node
.name
)) + "_%s" % pov_mat_name
21 if node
.bl_idname
== "PovrayFinishNode" and node
.outputs
["Finish"].is_linked
:
22 file.write("#declare %s = finish {\n" % pov_node_name
)
23 emission
= node
.inputs
["Emission"].default_value
24 if node
.inputs
["Emission"].is_linked
:
26 file.write(" emission %.4g\n" % emission
)
27 for link
in ntree
.links
:
28 if link
.to_node
== node
:
30 if link
.from_node
.bl_idname
== "PovrayDiffuseNode":
35 if link
.from_node
.inputs
["Intensity"].is_linked
:
38 intensity
= link
.from_node
.inputs
["Intensity"].default_value
39 if link
.from_node
.inputs
["Albedo"].is_linked
:
42 if link
.from_node
.inputs
["Albedo"].default_value
:
44 file.write(" diffuse %s %.4g\n" % (albedo
, intensity
))
45 if link
.from_node
.inputs
["Brilliance"].is_linked
:
48 brilliance
= link
.from_node
.inputs
["Brilliance"].default_value
49 file.write(" brilliance %.4g\n" % brilliance
)
50 if link
.from_node
.inputs
["Crand"].is_linked
:
53 crand
= link
.from_node
.inputs
["Crand"].default_value
55 file.write(" crand %.4g\n" % crand
)
57 if link
.from_node
.bl_idname
== "PovraySubsurfaceNode":
58 if scene
.povray
.sslt_enable
:
61 if link
.from_node
.inputs
["Translucency"].is_linked
:
64 r
, g
, b
, a
= link
.from_node
.inputs
["Translucency"].default_value
[:]
65 if link
.from_node
.inputs
["Energy"].is_linked
:
68 energy
= link
.from_node
.inputs
["Energy"].default_value
70 " subsurface { translucency <%.4g,%.4g,%.4g>*%s }\n"
74 if link
.from_node
.bl_idname
in {"PovraySpecularNode", "PovrayPhongNode"}:
80 highlight
= "specular"
81 if link
.from_node
.inputs
["Intensity"].is_linked
:
84 intensity
= link
.from_node
.inputs
["Intensity"].default_value
86 if link
.from_node
.inputs
["Albedo"].is_linked
:
89 if link
.from_node
.inputs
["Albedo"].default_value
:
91 if link
.from_node
.bl_idname
in {"PovrayPhongNode"}:
93 file.write(" %s %s %.4g\n" % (highlight
, albedo
, intensity
))
95 if link
.from_node
.bl_idname
in {"PovraySpecularNode"}:
96 if link
.from_node
.inputs
["Roughness"].is_linked
:
99 roughness
= link
.from_node
.inputs
["Roughness"].default_value
100 file.write(" roughness %.6g\n" % roughness
)
102 if link
.from_node
.bl_idname
in {"PovrayPhongNode"}:
103 if link
.from_node
.inputs
["Size"].is_linked
:
106 phong_size
= link
.from_node
.inputs
["Size"].default_value
107 file.write(" phong_size %s\n" % phong_size
)
109 if link
.from_node
.inputs
["Metallic"].is_linked
:
112 metallic
= link
.from_node
.inputs
["Metallic"].default_value
113 file.write(" metallic %.4g\n" % metallic
)
115 if link
.from_node
.bl_idname
in {"PovrayMirrorNode"}:
116 file.write(" reflection {\n")
123 if link
.from_node
.inputs
["Color"].is_linked
:
126 color
= link
.from_node
.inputs
["Color"].default_value
[:]
127 file.write(" <%.4g,%.4g,%.4g>\n" % (color
[0], color
[1], color
[2]))
129 if link
.from_node
.inputs
["Exponent"].is_linked
:
132 exponent
= link
.from_node
.inputs
["Exponent"].default_value
133 file.write(" exponent %.4g\n" % exponent
)
135 if link
.from_node
.inputs
["Falloff"].is_linked
:
138 falloff
= link
.from_node
.inputs
["Falloff"].default_value
139 file.write(" falloff %.4g\n" % falloff
)
141 if link
.from_node
.inputs
["Metallic"].is_linked
:
144 metallic
= link
.from_node
.inputs
["Metallic"].default_value
145 file.write(" metallic %.4g" % metallic
)
147 if link
.from_node
.inputs
["Fresnel"].is_linked
:
150 if link
.from_node
.inputs
["Fresnel"].default_value
:
153 if link
.from_node
.inputs
["Conserve energy"].is_linked
:
156 if link
.from_node
.inputs
["Conserve energy"].default_value
:
157 conserve
= "conserve_energy"
159 file.write(" %s}\n %s\n" % (fresnel
, conserve
))
161 if link
.from_node
.bl_idname
== "PovrayAmbientNode":
163 if link
.from_node
.inputs
["Ambient"].is_linked
:
166 ambient
= link
.from_node
.inputs
["Ambient"].default_value
[:]
167 file.write(" ambient <%.4g,%.4g,%.4g>\n" % ambient
)
169 if link
.from_node
.bl_idname
in {"PovrayIridescenceNode"}:
170 file.write(" irid {\n")
174 if link
.from_node
.inputs
["Amount"].is_linked
:
177 amount
= link
.from_node
.inputs
["Amount"].default_value
178 file.write(" %.4g\n" % amount
)
180 if link
.from_node
.inputs
["Thickness"].is_linked
:
183 exponent
= link
.from_node
.inputs
["Thickness"].default_value
184 file.write(" thickness %.4g\n" % thickness
)
186 if link
.from_node
.inputs
["Turbulence"].is_linked
:
189 falloff
= link
.from_node
.inputs
["Turbulence"].default_value
190 file.write(" turbulence %.4g}\n" % turbulence
)
194 for node
in ntree
.nodes
:
195 pov_node_name
= string_strip_hyphen(bpy
.path
.clean_name(node
.name
)) + "_%s" % pov_mat_name
196 if node
.bl_idname
== "PovrayTransformNode" and node
.outputs
["Transform"].is_linked
:
197 tx
= node
.inputs
["Translate x"].default_value
198 ty
= node
.inputs
["Translate y"].default_value
199 tz
= node
.inputs
["Translate z"].default_value
200 rx
= node
.inputs
["Rotate x"].default_value
201 ry
= node
.inputs
["Rotate y"].default_value
202 rz
= node
.inputs
["Rotate z"].default_value
203 sx
= node
.inputs
["Scale x"].default_value
204 sy
= node
.inputs
["Scale y"].default_value
205 sz
= node
.inputs
["Scale z"].default_value
207 "#declare %s = transform {\n"
208 " translate<%.4g,%.4g,%.4g>\n"
209 " rotate<%.4g,%.4g,%.4g>\n"
210 " scale<%.4g,%.4g,%.4g>}\n" % (pov_node_name
, tx
, ty
, tz
, rx
, ry
, rz
, sx
, sy
, sz
)
213 for node
in ntree
.nodes
:
214 pov_node_name
= string_strip_hyphen(bpy
.path
.clean_name(node
.name
)) + "_%s" % pov_mat_name
215 if node
.bl_idname
== "PovrayColorImageNode" and node
.outputs
["Pigment"].is_linked
:
216 declare_nodes
.append(node
.name
)
218 file.write("#declare %s = pigment { color rgb 0.8}\n" % pov_node_name
)
220 im
= bpy
.data
.images
[node
.image
]
221 if im
.filepath
and path
.exists(bpy
.path
.abspath(im
.filepath
)): # (os.path)
223 for link
in ntree
.links
:
225 link
.from_node
.bl_idname
== "PovrayTransformNode"
226 and link
.to_node
== node
229 string_strip_hyphen(bpy
.path
.clean_name(link
.from_node
.name
))
230 + "_%s" % pov_mat_name
232 transform
= "transform {%s}" % pov_trans_name
234 if node
.map_type
== "uv_mapping":
236 filepath
= bpy
.path
.abspath(im
.filepath
)
237 file.write("#declare %s = pigment {%s image_map {\n" % (pov_node_name
, uv
))
239 if node
.premultiplied
:
245 ' "%s"\n gamma %.6g\n premultiplied %s\n'
246 % (filepath
, node
.inputs
["Gamma"].default_value
, premul
)
248 file.write(" %s\n" % once
)
249 if node
.map_type
!= "uv_mapping":
250 file.write(" map_type %s\n" % node
.map_type
)
252 " interpolate %s\n filter all %.4g\n transmit all %.4g\n"
255 node
.inputs
["Filter"].default_value
,
256 node
.inputs
["Transmit"].default_value
,
260 file.write(" %s\n" % transform
)
263 for node
in ntree
.nodes
:
264 pov_node_name
= string_strip_hyphen(bpy
.path
.clean_name(node
.name
)) + "_%s" % pov_mat_name
265 if node
.bl_idname
== "PovrayImagePatternNode" and node
.outputs
["Pattern"].is_linked
:
266 declare_nodes
.append(node
.name
)
268 im
= bpy
.data
.images
[node
.image
]
269 if im
.filepath
and path
.exists(bpy
.path
.abspath(im
.filepath
)):
271 for link
in ntree
.links
:
273 link
.from_node
.bl_idname
== "PovrayTransformNode"
274 and link
.to_node
== node
277 string_strip_hyphen(bpy
.path
.clean_name(link
.from_node
.name
))
278 + "_%s" % pov_mat_name
280 transform
= "transform {%s}" % pov_trans_name
282 if node
.map_type
== "uv_mapping":
284 filepath
= bpy
.path
.abspath(im
.filepath
)
285 file.write("#macro %s() %s image_pattern {\n" % (pov_node_name
, uv
))
287 if node
.premultiplied
:
293 ' "%s"\n gamma %.6g\n premultiplied %s\n'
294 % (filepath
, node
.inputs
["Gamma"].default_value
, premul
)
296 file.write(" %s\n" % once
)
297 if node
.map_type
!= "uv_mapping":
298 file.write(" map_type %s\n" % node
.map_type
)
299 file.write(" interpolate %s\n" % node
.interpolate
)
301 file.write(" %s\n" % transform
)
304 for node
in ntree
.nodes
:
305 pov_node_name
= string_strip_hyphen(bpy
.path
.clean_name(node
.name
)) + "_%s" % pov_mat_name
306 if node
.bl_idname
== "PovrayBumpMapNode" and node
.outputs
["Normal"].is_linked
:
308 im
= bpy
.data
.images
[node
.image
]
309 if im
.filepath
and path
.exists(bpy
.path
.abspath(im
.filepath
)):
311 for link
in ntree
.links
:
313 link
.from_node
.bl_idname
== "PovrayTransformNode"
314 and link
.to_node
== node
317 string_strip_hyphen(bpy
.path
.clean_name(link
.from_node
.name
))
318 + "_%s" % pov_mat_name
320 transform
= "transform {%s}" % pov_trans_name
322 if node
.map_type
== "uv_mapping":
324 filepath
= bpy
.path
.abspath(im
.filepath
)
325 file.write("#declare %s = normal {%s bump_map {\n" % (pov_node_name
, uv
))
329 file.write(' "%s"\n' % filepath
)
330 file.write(" %s\n" % once
)
331 if node
.map_type
!= "uv_mapping":
332 file.write(" map_type %s\n" % node
.map_type
)
333 bump_size
= node
.inputs
["Normal"].default_value
334 if node
.inputs
["Normal"].is_linked
:
337 " interpolate %s\n bump_size %.4g\n" % (node
.interpolate
, bump_size
)
340 file.write(" %s\n" % transform
)
342 declare_nodes
.append(node
.name
)
344 for node
in ntree
.nodes
:
345 pov_node_name
= string_strip_hyphen(bpy
.path
.clean_name(node
.name
)) + "_%s" % pov_mat_name
346 if node
.bl_idname
== "PovrayPigmentNode" and node
.outputs
["Pigment"].is_linked
:
347 declare_nodes
.append(node
.name
)
348 r
, g
, b
= node
.inputs
["Color"].default_value
[:]
349 f
= node
.inputs
["Filter"].default_value
350 t
= node
.inputs
["Transmit"].default_value
351 if node
.inputs
["Color"].is_linked
:
354 "#declare %s = pigment{color srgbft <%.4g,%.4g,%.4g,%.4g,%.4g>}\n"
355 % (pov_node_name
, r
, g
, b
, f
, t
)
358 for node
in ntree
.nodes
:
359 pov_node_name
= string_strip_hyphen(bpy
.path
.clean_name(node
.name
)) + "_%s" % pov_mat_name
360 if node
.bl_idname
== "PovrayTextureNode" and node
.outputs
["Texture"].is_linked
:
361 declare_nodes
.append(node
.name
)
362 r
, g
, b
= node
.inputs
["Pigment"].default_value
[:]
363 pov_col_name
= "color rgb <%.4g,%.4g,%.4g>" % (r
, g
, b
)
364 if node
.inputs
["Pigment"].is_linked
:
365 for link
in ntree
.links
:
366 if link
.to_node
== node
and link
.to_socket
.name
== "Pigment":
368 string_strip_hyphen(bpy
.path
.clean_name(link
.from_node
.name
))
369 + "_%s" % pov_mat_name
371 file.write("#declare %s = texture{\n pigment{%s}\n" % (pov_node_name
, pov_col_name
))
372 if node
.inputs
["Normal"].is_linked
:
373 for link
in ntree
.links
:
376 and link
.to_socket
.name
== "Normal"
377 and link
.from_node
.name
in declare_nodes
380 string_strip_hyphen(bpy
.path
.clean_name(link
.from_node
.name
))
381 + "_%s" % pov_mat_name
383 file.write(" normal{%s}\n" % pov_nor_name
)
384 if node
.inputs
["Finish"].is_linked
:
385 for link
in ntree
.links
:
386 if link
.to_node
== node
and link
.to_socket
.name
== "Finish":
388 string_strip_hyphen(bpy
.path
.clean_name(link
.from_node
.name
))
389 + "_%s" % pov_mat_name
391 file.write(" finish{%s}\n" % pov_fin_name
)
393 declare_nodes
.append(node
.name
)
395 for i
in range(0, len(ntree
.nodes
)):
396 for node
in ntree
.nodes
:
397 if node
.bl_idname
in {"ShaderNodeGroup", "ShaderTextureMapNode"}:
398 for output
in node
.outputs
:
400 output
.name
== "Texture"
402 and (node
.name
not in declare_nodes
)
405 for link
in ntree
.links
:
406 if link
.to_node
== node
and link
.to_socket
.name
not in {
413 if link
.from_node
.name
not in declare_nodes
:
417 string_strip_hyphen(bpy
.path
.clean_name(node
.name
))
418 + "_%s" % pov_mat_name
422 for link
in ntree
.links
:
425 and link
.from_node
.bl_idname
== "PovrayMappingNode"
426 and link
.from_node
.warp_type
!= "NONE"
428 w_type
= link
.from_node
.warp_type
429 if w_type
== "uv_mapping":
433 if w_type
== "toroidal":
436 % link
.from_node
.warp_tor_major_radius
438 orient
= link
.from_node
.warp_orientation
439 exp
= link
.from_node
.warp_dist_exp
440 warp
= "warp{%s orientation %s dist_exp %.4g %s}" % (
446 if link
.from_node
.warp_type
== "planar":
447 warp
= "warp{%s %s %.4g}" % (w_type
, orient
, exp
)
448 if link
.from_node
.warp_type
== "cubic":
449 warp
= "warp{%s}" % w_type
450 file.write("#declare %s = texture {%s\n" % (pov_node_name
, uv
))
451 pattern
= node
.inputs
[0].default_value
453 if node
.inputs
[0].is_linked
:
454 for link
in ntree
.links
:
457 and link
.from_node
.bl_idname
== "ShaderPatternNode"
459 # ------------ advanced ------------------------- #
461 pattern
= lfn
.pattern
462 if pattern
== "agate":
463 advanced
= "agate_turb %.4g" % lfn
.agate_turb
464 if pattern
== "crackle":
465 advanced
= "form <%.4g,%.4g,%.4g>" % (
470 advanced
+= " metric %.4g" % lfn
.crackle_metric
471 if lfn
.crackle_solid
:
473 if pattern
in {"spiral1", "spiral2"}:
474 advanced
= "%.4g" % lfn
.spiral_arms
475 if pattern
in {"tiling"}:
476 advanced
= "%.4g" % lfn
.tiling_number
477 if pattern
in {"gradient"}:
478 advanced
= "%s" % lfn
.gradient_orient
481 and link
.from_node
.bl_idname
== "PovrayImagePatternNode"
485 bpy
.path
.clean_name(link
.from_node
.name
)
487 + "_%s" % pov_mat_name
489 pattern
= "%s()" % pov_macro_name
490 file.write(" %s %s %s\n" % (pattern
, advanced
, warp
))
493 for link
in ntree
.links
:
496 and link
.from_node
.bl_idname
== "PovrayMultiplyNode"
498 if link
.from_node
.amount_x
> 1:
499 repeat
+= "warp{repeat %.4g * x}" % link
.from_node
.amount_x
500 if link
.from_node
.amount_y
> 1:
501 repeat
+= " warp{repeat %.4g * y}" % link
.from_node
.amount_y
502 if link
.from_node
.amount_z
> 1:
503 repeat
+= " warp{repeat %.4g * z}" % link
.from_node
.amount_z
506 for link
in ntree
.links
:
509 and link
.from_node
.bl_idname
== "PovrayTransformNode"
513 bpy
.path
.clean_name(link
.from_node
.name
)
515 + "_%s" % pov_mat_name
517 transform
= "transform {%s}" % pov_trans_name
527 for link
in ntree
.links
:
530 and link
.from_node
.bl_idname
== "PovrayModifierNode"
533 if link
.from_node
.inputs
["Turb X"].is_linked
:
536 x
= link
.from_node
.inputs
["Turb X"].default_value
538 if link
.from_node
.inputs
["Turb Y"].is_linked
:
541 y
= link
.from_node
.inputs
["Turb Y"].default_value
543 if link
.from_node
.inputs
["Turb Z"].is_linked
:
546 z
= link
.from_node
.inputs
["Turb Z"].default_value
548 if link
.from_node
.inputs
["Octaves"].is_linked
:
551 d
= link
.from_node
.inputs
["Octaves"].default_value
553 if link
.from_node
.inputs
["Lambda"].is_linked
:
556 e
= link
.from_node
.inputs
["Lambda"].default_value
558 if link
.from_node
.inputs
["Omega"].is_linked
:
561 f
= link
.from_node
.inputs
["Omega"].default_value
563 if link
.from_node
.inputs
["Frequency"].is_linked
:
566 g
= link
.from_node
.inputs
["Frequency"].default_value
568 if link
.from_node
.inputs
["Phase"].is_linked
:
571 h
= link
.from_node
.inputs
["Phase"].default_value
573 turb
= "turbulence <%.4g,%.4g,%.4g>" % (x
, y
, z
)
574 octv
= "octaves %s" % d
575 lmbd
= "lambda %.4g" % e
576 omg
= "omega %.4g" % f
577 freq
= "frequency %.4g" % g
578 pha
= "phase %.4g" % h
588 file.write(" texture_map {\n")
589 if node
.inputs
["Color ramp"].is_linked
:
590 for link
in ntree
.links
:
593 and link
.from_node
.bl_idname
== "ShaderNodeValToRGB"
595 els
= link
.from_node
.color_ramp
.elements
599 pov_in_mat_name
= string_strip_hyphen(
600 bpy
.path
.clean_name(link
.from_node
.name
)
601 ) + "_%s_%s" % (n
, pov_mat_name
)
603 for ilink
in ntree
.links
:
605 ilink
.to_node
== node
606 and ilink
.to_socket
.name
== str(n
)
615 + "_%s" % pov_mat_name
618 r
, g
, b
, a
= el
.color
[:]
620 " #declare %s = texture{"
622 "color srgbt <%.4g,%.4g,%.4g,%.4g>}};\n"
623 % (pov_in_mat_name
, r
, g
, b
, 1 - a
)
626 " [%s %s]\n" % (el
.position
, pov_in_mat_name
)
629 els
= [[0, 0, 0, 0], [1, 1, 1, 1]]
630 for t
in range(0, 2):
631 pov_in_mat_name
= string_strip_hyphen(
632 bpy
.path
.clean_name(link
.from_node
.name
)
633 ) + "_%s_%s" % (t
, pov_mat_name
)
635 for ilink
in ntree
.links
:
636 if ilink
.to_node
== node
and ilink
.to_socket
.name
== str(t
):
640 bpy
.path
.clean_name(ilink
.from_node
.name
)
642 + "_%s" % pov_mat_name
645 r
, g
, b
= els
[t
][1], els
[t
][2], els
[t
][3]
654 " #declare %s = texture{pigment{color rgb <%.4g,%.4g,%.4g>}};\n"
655 % (pov_in_mat_name
, r
, g
, b
)
659 " texture{pigment{color rgb <%.4g,%.4g,%.4g>}}\n"
669 file.write(" [%s %s]\n" % (els
[t
][0], pov_in_mat_name
))
672 file.write(" texture{%s}\n" % pov_in_mat_name
)
681 if pattern
== "brick":
683 "brick_size <%.4g, %.4g, %.4g> mortar %.4g \n"
691 file.write(" %s %s" % (repeat
, transform
))
694 " %s %s %s %s %s %s" % (turb
, octv
, lmbd
, omg
, freq
, pha
)
697 declare_nodes
.append(node
.name
)
699 for link
in ntree
.links
:
700 if link
.to_node
.bl_idname
== "PovrayOutputNode" and link
.from_node
.name
in declare_nodes
:
701 pov_mat_node_name
= (
702 string_strip_hyphen(bpy
.path
.clean_name(link
.from_node
.name
)) + "_%s" % pov_mat_name
704 file.write("#declare %s = %s\n" % (pov_mat_name
, pov_mat_node_name
))