Cleanup: trailing space
[blender-addons.git] / render_povray / render.py
blob0f7da3c6bb6a64c14baf172f65ca38b88e9c9274
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # <pep8 compliant>
5 """Write the POV file using this file's functions and some from other modules then render it."""
7 import bpy
8 import subprocess
9 import os
10 from sys import platform
11 import time
12 from math import (
13 pi,
14 ) # maybe move to scenography.py and topology_*****_data.py respectively with smoke and matrix
16 import re
17 import tempfile # generate temporary files with random names
18 from bpy.types import Operator
19 from bpy.utils import register_class, unregister_class
21 from . import (
22 scripting,
23 ) # for writing, importing and rendering directly POV Scene Description Language items
24 from . import scenography # for atmosphere, environment, effects, lighting, camera
25 from . import shading # for BI POV shaders emulation
26 from . import object_mesh_topology # for mesh based geometry
27 from . import object_curve_topology # for curves based geometry
29 # from . import object_primitives # for import and export of POV specific primitives
32 from .scenography import image_format, img_map, img_map_transforms, path_image
34 from .shading import write_object_material_interior
35 from .object_primitives import write_object_modifiers
38 def string_strip_hyphen(name):
40 """Remove hyphen characters from a string to avoid POV errors."""
42 return name.replace("-", "")
45 def safety(name, ref_level_bound):
46 """append suffix characters to names of various material declinations.
48 Material declinations are necessary to POV syntax and used in shading.py
49 by the pov_has_no_specular_maps function to create the finish map trick and
50 the suffixes avoid name collisions.
51 Keyword arguments:
52 name -- the initial material name as a string
53 ref_level_bound -- the enum number of the ref_level_bound being written:
54 ref_level_bound=1 is for texture with No specular nor Mirror reflection
55 ref_level_bound=2 is for texture with translation of spec and mir levels
56 for when no map influences them
57 ref_level_bound=3 is for texture with Maximum Spec and Mirror
58 """
59 # All the try except clause below seems useless as each time
60 # prefix rewritten even after and outside of it what was the point?
61 # It may not even be any longer possible to feed no arg from Blender UI
62 # try:
63 # if name: # if int(name) > 0: # could be zero if no argument provided
64 # # and always triggered exception so is this similar ?
65 # prefix = "shader"
66 # except BaseException as e:
67 # print(e.__doc__)
68 # print('An exception occurred: {}'.format(e))
69 # prefix = "" # rewritten below...
70 prefix = "shader_"
71 name = string_strip_hyphen(name)
72 if ref_level_bound == 2:
73 return prefix + name
74 # implicit else-if (no return yet)
75 if ref_level_bound == 1:
76 return prefix + name + "0" # used for 0 of specular map
77 # implicit else-if (no return yet)
78 if ref_level_bound == 3:
79 return prefix + name + "1" # used for 1 of specular map
82 # -------- end safety string name material
85 csg_list = []
88 def is_renderable(ob):
89 """test for objects flagged as hidden or boolean operands not to render"""
90 return not ob.hide_render and ob not in csg_list
93 def renderable_objects():
94 """test for non hidden, non boolean operands objects to render"""
95 return [ob for ob in bpy.data.objects if is_renderable(ob)]
98 def no_renderable_objects():
99 """Boolean operands only. Not to render"""
100 return list(csg_list)
103 tab_level = 0
104 unpacked_images = []
106 user_dir = bpy.utils.resource_path('USER')
107 preview_dir = os.path.join(user_dir, "preview")
109 # Make sure Preview directory exists and is empty
110 smoke_path = os.path.join(preview_dir, "smoke.df3")
114 # below properties not added to __init__ yet to avoid conflicts with material sss scale
115 # unless it would override then should be interfaced also in scene units property tab
117 # if scene.pov.sslt_enable:
118 # file.write(" mm_per_unit %s\n"%scene.pov.mm_per_unit)
119 # file.write(" subsurface {\n")
120 # file.write(" samples %s, %s\n"%(scene.pov.sslt_samples_max,scene.pov.sslt_samples_min))
121 # if scene.pov.sslt_radiosity:
122 # file.write(" radiosity on\n")
123 # file.write("}\n")
128 # def write_object_modifiers(ob, File):
129 # """Translate some object level POV statements from Blender UI
130 # to POV syntax and write to exported file """
132 # # Maybe return that string to be added instead of directly written.
134 # '''XXX WIP
135 # import .object_mesh_topology.write_object_csg_inside_vector
136 # write_object_csg_inside_vector(ob, file)
137 # '''
139 # if ob.pov.hollow:
140 # File.write("\thollow\n")
141 # if ob.pov.double_illuminate:
142 # File.write("\tdouble_illuminate\n")
143 # if ob.pov.sturm:
144 # File.write("\tsturm\n")
145 # if ob.pov.no_shadow:
146 # File.write("\tno_shadow\n")
147 # if ob.pov.no_image:
148 # File.write("\tno_image\n")
149 # if ob.pov.no_reflection:
150 # File.write("\tno_reflection\n")
151 # if ob.pov.no_radiosity:
152 # File.write("\tno_radiosity\n")
153 # if ob.pov.inverse:
154 # File.write("\tinverse\n")
155 # if ob.pov.hierarchy:
156 # File.write("\thierarchy\n")
158 # # XXX, Commented definitions
159 # '''
160 # if scene.pov.photon_enable:
161 # File.write("photons {\n")
162 # if ob.pov.target:
163 # File.write("target %.4g\n"%ob.pov.target_value)
164 # if ob.pov.refraction:
165 # File.write("refraction on\n")
166 # if ob.pov.reflection:
167 # File.write("reflection on\n")
168 # if ob.pov.pass_through:
169 # File.write("pass_through\n")
170 # File.write("}\n")
171 # if ob.pov.object_ior > 1:
172 # File.write("interior {\n")
173 # File.write("ior %.4g\n"%ob.pov.object_ior)
174 # if scene.pov.photon_enable and ob.pov.target and ob.pov.refraction and ob.pov.dispersion:
175 # File.write("ior %.4g\n"%ob.pov.dispersion_value)
176 # File.write("ior %s\n"%ob.pov.dispersion_samples)
177 # if scene.pov.photon_enable == False:
178 # File.write("caustics %.4g\n"%ob.pov.fake_caustics_power)
179 # '''
182 def write_pov(filename, scene=None, info_callback=None):
183 """Main export process from Blender UI to POV syntax and write to exported file """
185 import mathutils
187 with open(filename, "w") as file:
188 # Only for testing
189 if not scene:
190 scene = bpy.data.scenes[0]
192 render = scene.render
193 world = scene.world
194 global_matrix = mathutils.Matrix.Rotation(-pi / 2.0, 4, 'X')
195 comments = scene.pov.comments_enable and not scene.pov.tempfiles_enable
196 linebreaksinlists = scene.pov.list_lf_enable and not scene.pov.tempfiles_enable
197 feature_set = bpy.context.preferences.addons[__package__].preferences.branch_feature_set_povray
198 using_uberpov = feature_set == 'uberpov'
199 pov_binary = PovrayRender._locate_binary()
201 if using_uberpov:
202 print("Unofficial UberPOV feature set chosen in preferences")
203 else:
204 print("Official POV-Ray 3.7 feature set chosen in preferences")
205 if 'uber' in pov_binary:
206 print("The name of the binary suggests you are probably rendering with Uber POV engine")
207 else:
208 print("The name of the binary suggests you are probably rendering with standard POV engine")
210 def set_tab(tabtype, spaces):
211 tab_str = ""
212 if tabtype == 'NONE':
213 tab_str = ""
214 elif tabtype == 'TAB':
215 tab_str = "\t"
216 elif tabtype == 'SPACE':
217 tab_str = spaces * " "
218 return tab_str
220 tab = set_tab(scene.pov.indentation_character, scene.pov.indentation_spaces)
221 if not scene.pov.tempfiles_enable:
223 def tab_write(str_o):
224 """Indent POV syntax from brackets levels and write to exported file """
225 global tab_level
226 brackets = str_o.count("{") - str_o.count("}") + str_o.count("[") - str_o.count("]")
227 if brackets < 0:
228 tab_level = tab_level + brackets
229 if tab_level < 0:
230 print("Indentation Warning: tab_level = %s" % tab_level)
231 tab_level = 0
232 if tab_level >= 1:
233 file.write("%s" % tab * tab_level)
234 file.write(str_o)
235 if brackets > 0:
236 tab_level = tab_level + brackets
238 else:
240 def tab_write(str_o):
241 """write directly to exported file if user checked autonamed temp files (faster)."""
243 file.write(str_o)
245 def unique_name(name, name_seq):
246 """Increment any generated POV name that could get identical to avoid collisions"""
248 if name not in name_seq:
249 name = string_strip_hyphen(name)
250 return name
252 name_orig = name
253 i = 1
254 while name in name_seq:
255 name = "%s_%.3d" % (name_orig, i)
256 i += 1
257 name = string_strip_hyphen(name)
258 return name
260 def write_matrix(matrix):
261 """Translate some transform matrix from Blender UI
262 to POV syntax and write to exported file """
263 tab_write(
264 "matrix <%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f>\n"
266 matrix[0][0],
267 matrix[1][0],
268 matrix[2][0],
269 matrix[0][1],
270 matrix[1][1],
271 matrix[2][1],
272 matrix[0][2],
273 matrix[1][2],
274 matrix[2][2],
275 matrix[0][3],
276 matrix[1][3],
277 matrix[2][3],
281 material_names_dictionary = {}
282 DEF_MAT_NAME = "" # or "Default"?
284 # -----------------------------------------------------------------------------
286 def export_meta(metas):
287 """write all POV blob primitives and Blender Metas to exported file """
288 # TODO - blenders 'motherball' naming is not supported.
290 if comments and len(metas) >= 1:
291 file.write("//--Blob objects--\n\n")
292 # Get groups of metaballs by blender name prefix.
293 meta_group = {}
294 meta_elems = {}
295 for meta_ob in metas:
296 prefix = meta_ob.name.split(".")[0]
297 if prefix not in meta_group:
298 meta_group[prefix] = meta_ob # .data.threshold
299 elems = [
300 (elem, meta_ob)
301 for elem in meta_ob.data.elements
302 if elem.type in {'BALL', 'ELLIPSOID', 'CAPSULE', 'CUBE', 'PLANE'}
304 if prefix in meta_elems:
305 meta_elems[prefix].extend(elems)
306 else:
307 meta_elems[prefix] = elems
309 # empty metaball
310 if len(elems) == 0:
311 tab_write("\n//dummy sphere to represent empty meta location\n")
312 tab_write(
313 "sphere {<%.6g, %.6g, %.6g>,0 pigment{rgbt 1} "
314 "no_image no_reflection no_radiosity "
315 "photons{pass_through collect off} hollow}\n\n"
316 % (meta_ob.location.x, meta_ob.location.y, meta_ob.location.z)
317 ) # meta_ob.name > povdataname)
318 # other metaballs
319 else:
320 for mg, mob in meta_group.items():
321 if len(meta_elems[mg]) != 0:
322 tab_write("blob{threshold %.4g // %s \n" % (mob.data.threshold, mg))
323 for elems in meta_elems[mg]:
324 elem = elems[0]
325 loc = elem.co
326 stiffness = elem.stiffness
327 if elem.use_negative:
328 stiffness = -stiffness
329 if elem.type == 'BALL':
330 tab_write(
331 "sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g "
332 % (loc.x, loc.y, loc.z, elem.radius, stiffness)
334 write_matrix(global_matrix @ elems[1].matrix_world)
335 tab_write("}\n")
336 elif elem.type == 'ELLIPSOID':
337 tab_write(
338 "sphere{ <%.6g, %.6g, %.6g>,%.4g,%.4g "
340 loc.x / elem.size_x,
341 loc.y / elem.size_y,
342 loc.z / elem.size_z,
343 elem.radius,
344 stiffness,
347 tab_write(
348 "scale <%.6g, %.6g, %.6g>"
349 % (elem.size_x, elem.size_y, elem.size_z)
351 write_matrix(global_matrix @ elems[1].matrix_world)
352 tab_write("}\n")
353 elif elem.type == 'CAPSULE':
354 tab_write(
355 "cylinder{ <%.6g, %.6g, %.6g>,<%.6g, %.6g, %.6g>,%.4g,%.4g "
357 (loc.x - elem.size_x),
358 loc.y,
359 loc.z,
360 (loc.x + elem.size_x),
361 loc.y,
362 loc.z,
363 elem.radius,
364 stiffness,
367 # tab_write("scale <%.6g, %.6g, %.6g>" % (elem.size_x, elem.size_y, elem.size_z))
368 write_matrix(global_matrix @ elems[1].matrix_world)
369 tab_write("}\n")
371 elif elem.type == 'CUBE':
372 tab_write(
373 "cylinder { -x*8, +x*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1/4,1,1> scale <%.6g, %.6g, %.6g>\n"
375 elem.radius * 2.0,
376 stiffness / 4.0,
377 loc.x,
378 loc.y,
379 loc.z,
380 elem.size_x,
381 elem.size_y,
382 elem.size_z,
385 write_matrix(global_matrix @ elems[1].matrix_world)
386 tab_write("}\n")
387 tab_write(
388 "cylinder { -y*8, +y*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1/4,1> scale <%.6g, %.6g, %.6g>\n"
390 elem.radius * 2.0,
391 stiffness / 4.0,
392 loc.x,
393 loc.y,
394 loc.z,
395 elem.size_x,
396 elem.size_y,
397 elem.size_z,
400 write_matrix(global_matrix @ elems[1].matrix_world)
401 tab_write("}\n")
402 tab_write(
403 "cylinder { -z*8, +z*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1,1/4> scale <%.6g, %.6g, %.6g>\n"
405 elem.radius * 2.0,
406 stiffness / 4.0,
407 loc.x,
408 loc.y,
409 loc.z,
410 elem.size_x,
411 elem.size_y,
412 elem.size_z,
415 write_matrix(global_matrix @ elems[1].matrix_world)
416 tab_write("}\n")
418 elif elem.type == 'PLANE':
419 tab_write(
420 "cylinder { -x*8, +x*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1/4,1,1> scale <%.6g, %.6g, %.6g>\n"
422 elem.radius * 2.0,
423 stiffness / 4.0,
424 loc.x,
425 loc.y,
426 loc.z,
427 elem.size_x,
428 elem.size_y,
429 elem.size_z,
432 write_matrix(global_matrix @ elems[1].matrix_world)
433 tab_write("}\n")
434 tab_write(
435 "cylinder { -y*8, +y*8,%.4g,%.4g translate<%.6g,%.6g,%.6g> scale <1,1/4,1> scale <%.6g, %.6g, %.6g>\n"
437 elem.radius * 2.0,
438 stiffness / 4.0,
439 loc.x,
440 loc.y,
441 loc.z,
442 elem.size_x,
443 elem.size_y,
444 elem.size_z,
447 write_matrix(global_matrix @ elems[1].matrix_world)
448 tab_write("}\n")
450 try:
451 one_material = elems[1].data.materials[
453 ] # lame! - blender cant do enything else.
454 except BaseException as e:
455 print(e.__doc__)
456 print('An exception occurred: {}'.format(e))
457 one_material = None
458 if one_material:
459 diffuse_color = one_material.diffuse_color
460 trans = 1.0 - one_material.pov.alpha
461 if (
462 one_material.use_transparency
463 and one_material.transparency_method == 'RAYTRACE'
465 pov_filter = one_material.pov_raytrace_transparency.filter * (
466 1.0 - one_material.alpha
468 trans = (1.0 - one_material.pov.alpha) - pov_filter
469 else:
470 pov_filter = 0.0
471 material_finish = material_names_dictionary[one_material.name]
472 tab_write(
473 "pigment {srgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} \n"
475 diffuse_color[0],
476 diffuse_color[1],
477 diffuse_color[2],
478 pov_filter,
479 trans,
482 tab_write("finish{%s} " % safety(material_finish, ref_level_bound=2))
483 else:
484 material_finish = DEF_MAT_NAME
485 trans = 0.0
486 tab_write(
487 "pigment{srgbt<1,1,1,%.3g>} finish{%s} "
488 % (trans, safety(material_finish, ref_level_bound=2))
491 write_object_material_interior(one_material, mob, tab_write)
492 # write_object_material_interior(one_material, elems[1])
493 tab_write("radiosity{importance %3g}\n" % mob.pov.importance_value)
494 tab_write("}\n\n") # End of Metaball block
497 meta = ob.data
499 # important because no elements will break parsing.
500 elements = [elem for elem in meta.elements if elem.type in {'BALL', 'ELLIPSOID'}]
502 if elements:
503 tab_write("blob {\n")
504 tab_write("threshold %.4g\n" % meta.threshold)
505 importance = ob.pov.importance_value
507 try:
508 material = meta.materials[0] # lame! - blender cant do enything else.
509 except:
510 material = None
512 for elem in elements:
513 loc = elem.co
515 stiffness = elem.stiffness
516 if elem.use_negative:
517 stiffness = - stiffness
519 if elem.type == 'BALL':
521 tab_write("sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g }\n" %
522 (loc.x, loc.y, loc.z, elem.radius, stiffness))
524 # After this wecould do something simple like...
525 # "pigment {Blue} }"
526 # except we'll write the color
528 elif elem.type == 'ELLIPSOID':
529 # location is modified by scale
530 tab_write("sphere { <%.6g, %.6g, %.6g>, %.4g, %.4g }\n" %
531 (loc.x / elem.size_x,
532 loc.y / elem.size_y,
533 loc.z / elem.size_z,
534 elem.radius, stiffness))
535 tab_write("scale <%.6g, %.6g, %.6g> \n" %
536 (elem.size_x, elem.size_y, elem.size_z))
538 if material:
539 diffuse_color = material.diffuse_color
540 trans = 1.0 - material.pov.alpha
541 if material.use_transparency and material.transparency_method == 'RAYTRACE':
542 pov_filter = material.pov_raytrace_transparency.filter * (1.0 - material.alpha)
543 trans = (1.0 - material.pov.alpha) - pov_filter
544 else:
545 pov_filter = 0.0
547 material_finish = material_names_dictionary[material.name]
549 tab_write("pigment {srgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} \n" %
550 (diffuse_color[0], diffuse_color[1], diffuse_color[2],
551 pov_filter, trans))
552 tab_write("finish {%s}\n" % safety(material_finish, ref_level_bound=2))
554 else:
555 tab_write("pigment {srgb 1} \n")
556 # Write the finish last.
557 tab_write("finish {%s}\n" % (safety(DEF_MAT_NAME, ref_level_bound=2)))
559 write_object_material_interior(material, elems[1])
561 write_matrix(global_matrix @ ob.matrix_world)
562 # Importance for radiosity sampling added here
563 tab_write("radiosity { \n")
564 # importance > ob.pov.importance_value
565 tab_write("importance %3g \n" % importance)
566 tab_write("}\n")
568 tab_write("}\n") # End of Metaball block
570 if comments and len(metas) >= 1:
571 file.write("\n")
574 def export_global_settings(scene):
575 """write all POV global settings to exported file """
576 # Imperial units warning
577 if scene.unit_settings.system == "IMPERIAL":
578 print("Warning: Imperial units not supported")
580 tab_write("global_settings {\n")
581 tab_write("assumed_gamma 1.0\n")
582 tab_write("max_trace_level %d\n" % scene.pov.max_trace_level)
584 if scene.pov.global_settings_advanced:
585 if not scene.pov.radio_enable:
586 file.write(" adc_bailout %.6f\n" % scene.pov.adc_bailout)
587 file.write(" ambient_light <%.6f,%.6f,%.6f>\n" % scene.pov.ambient_light[:])
588 file.write(" irid_wavelength <%.6f,%.6f,%.6f>\n" % scene.pov.irid_wavelength[:])
589 file.write(" number_of_waves %s\n" % scene.pov.number_of_waves)
590 file.write(" noise_generator %s\n" % scene.pov.noise_generator)
591 if scene.pov.radio_enable:
592 tab_write("radiosity {\n")
593 tab_write("adc_bailout %.4g\n" % scene.pov.radio_adc_bailout)
594 tab_write("brightness %.4g\n" % scene.pov.radio_brightness)
595 tab_write("count %d\n" % scene.pov.radio_count)
596 tab_write("error_bound %.4g\n" % scene.pov.radio_error_bound)
597 tab_write("gray_threshold %.4g\n" % scene.pov.radio_gray_threshold)
598 tab_write("low_error_factor %.4g\n" % scene.pov.radio_low_error_factor)
599 tab_write("maximum_reuse %.4g\n" % scene.pov.radio_maximum_reuse)
600 tab_write("minimum_reuse %.4g\n" % scene.pov.radio_minimum_reuse)
601 tab_write("nearest_count %d\n" % scene.pov.radio_nearest_count)
602 tab_write("pretrace_start %.3g\n" % scene.pov.radio_pretrace_start)
603 tab_write("pretrace_end %.3g\n" % scene.pov.radio_pretrace_end)
604 tab_write("recursion_limit %d\n" % scene.pov.radio_recursion_limit)
605 tab_write("always_sample %d\n" % scene.pov.radio_always_sample)
606 tab_write("normal %d\n" % scene.pov.radio_normal)
607 tab_write("media %d\n" % scene.pov.radio_media)
608 tab_write("subsurface %d\n" % scene.pov.radio_subsurface)
609 tab_write("}\n")
610 once_sss = 1
611 once_ambient = 1
612 once_photons = 1
613 for material in bpy.data.materials:
614 if material.pov_subsurface_scattering.use and once_sss:
615 # In pov, the scale has reversed influence compared to blender. these number
616 # should correct that
617 tab_write(
618 "mm_per_unit %.6f\n" % (material.pov_subsurface_scattering.scale * 1000.0)
620 # 1000 rather than scale * (-100.0) + 15.0))
622 # In POV-Ray, the scale factor for all subsurface shaders needs to be the same
624 # formerly sslt_samples were multiplied by 100 instead of 10
625 sslt_samples = (11 - material.pov_subsurface_scattering.error_threshold) * 10
627 tab_write("subsurface { samples %d, %d }\n" % (sslt_samples, sslt_samples / 10))
628 once_sss = 0
630 if world and once_ambient:
631 tab_write("ambient_light rgb<%.3g, %.3g, %.3g>\n" % world.pov.ambient_color[:])
632 once_ambient = 0
634 if scene.pov.photon_enable:
635 if once_photons and (
636 material.pov.refraction_type == "2" or material.pov.photons_reflection
638 tab_write("photons {\n")
639 tab_write("spacing %.6f\n" % scene.pov.photon_spacing)
640 tab_write("max_trace_level %d\n" % scene.pov.photon_max_trace_level)
641 tab_write("adc_bailout %.3g\n" % scene.pov.photon_adc_bailout)
642 tab_write(
643 "gather %d, %d\n"
644 % (scene.pov.photon_gather_min, scene.pov.photon_gather_max)
646 if scene.pov.photon_map_file_save_load in {'save'}:
647 ph_file_name = 'Photon_map_file.ph'
648 if scene.pov.photon_map_file != '':
649 ph_file_name = scene.pov.photon_map_file + '.ph'
650 ph_file_dir = tempfile.gettempdir()
651 path = bpy.path.abspath(scene.pov.photon_map_dir)
652 if os.path.exists(path):
653 ph_file_dir = path
654 full_file_name = os.path.join(ph_file_dir, ph_file_name)
655 tab_write('save_file "%s"\n' % full_file_name)
656 scene.pov.photon_map_file = full_file_name
657 if scene.pov.photon_map_file_save_load in {'load'}:
658 full_file_name = bpy.path.abspath(scene.pov.photon_map_file)
659 if os.path.exists(full_file_name):
660 tab_write('load_file "%s"\n' % full_file_name)
661 tab_write("}\n")
662 once_photons = 0
664 tab_write("}\n")
666 # sel = renderable_objects() #removed for booleans
667 if comments:
668 file.write(
669 "//----------------------------------------------\n"
670 "//--Exported with POV-Ray exporter for Blender--\n"
671 "//----------------------------------------------\n\n"
673 file.write("#version 3.7;\n") # Switch below as soon as 3.8 beta gets easy linked
674 # file.write("#version 3.8;\n")
675 file.write(
676 "#declare Default_texture = texture{pigment {rgb 0.8} " "finish {brilliance 3.8} }\n\n"
678 if comments:
679 file.write("\n//--Global settings--\n\n")
681 export_global_settings(scene)
683 if comments:
684 file.write("\n//--Custom Code--\n\n")
685 scripting.export_custom_code(file)
687 if comments:
688 file.write("\n//--Patterns Definitions--\n\n")
689 local_pattern_names = []
690 for texture in bpy.data.textures: # ok?
691 if texture.users > 0:
692 current_pat_name = string_strip_hyphen(bpy.path.clean_name(texture.name))
693 # string_strip_hyphen(patternNames[texture.name]) #maybe instead of the above
694 local_pattern_names.append(current_pat_name)
695 # use above list to prevent writing texture instances several times and assign in mats?
696 if (
697 texture.type not in {'NONE', 'IMAGE'} and texture.pov.tex_pattern_type == 'emulator'
698 ) or (texture.type in {'NONE', 'IMAGE'} and texture.pov.tex_pattern_type != 'emulator'):
699 file.write("\n#declare PAT_%s = \n" % current_pat_name)
700 file.write(shading.export_pattern(texture))
701 file.write("\n")
702 if comments:
703 file.write("\n//--Background--\n\n")
705 scenography.export_world(scene.world, scene, global_matrix, tab_write)
707 if comments:
708 file.write("\n//--Cameras--\n\n")
710 scenography.export_camera(scene, global_matrix, render, tab_write)
712 if comments:
713 file.write("\n//--Lamps--\n\n")
715 for ob in bpy.data.objects:
716 if ob.type == 'MESH':
717 for mod in ob.modifiers:
718 if mod.type == 'BOOLEAN' and mod.object not in csg_list:
719 csg_list.append(mod.object)
720 if csg_list:
721 csg = False
722 sel = no_renderable_objects()
723 # export non rendered boolean objects operands
724 object_mesh_topology.export_meshes(
725 preview_dir,
726 file,
727 scene,
728 sel,
729 csg,
730 string_strip_hyphen,
731 safety,
732 write_object_modifiers,
733 material_names_dictionary,
734 write_object_material_interior,
735 scenography.exported_lights_count,
736 unpacked_images,
737 image_format,
738 img_map,
739 img_map_transforms,
740 path_image,
741 smoke_path,
742 global_matrix,
743 write_matrix,
744 using_uberpov,
745 comments,
746 linebreaksinlists,
747 tab,
748 tab_level,
749 tab_write,
750 info_callback,
753 csg = True
754 sel = renderable_objects()
756 scenography.export_lights(
757 [L for L in sel if (L.type == 'LIGHT' and L.pov.object_as != 'RAINBOW')],
758 file,
759 scene,
760 global_matrix,
761 write_matrix,
762 tab_write,
765 if comments:
766 file.write("\n//--Rainbows--\n\n")
767 scenography.export_rainbows(
768 [L for L in sel if (L.type == 'LIGHT' and L.pov.object_as == 'RAINBOW')],
769 file,
770 scene,
771 global_matrix,
772 write_matrix,
773 tab_write,
776 if comments:
777 file.write("\n//--Special Curves--\n\n")
778 for c in sel:
779 if c.is_modified(scene, 'RENDER'):
780 continue # don't export as pov curves objects with modifiers, but as mesh
781 # Implicit else-if (as not skipped by previous "continue")
782 if c.type == 'CURVE' and (c.pov.curveshape in {'lathe', 'sphere_sweep', 'loft', 'birail'}):
783 object_curve_topology.export_curves(file, c, string_strip_hyphen, tab_write)
785 if comments:
786 file.write("\n//--Material Definitions--\n\n")
787 # write a default pigment for objects with no material (comment out to show black)
788 file.write("#default{ pigment{ color srgb 0.8 }}\n")
789 # Convert all materials to strings we can access directly per vertex.
790 # exportMaterials()
791 shading.write_material(
792 using_uberpov,
793 DEF_MAT_NAME,
794 tab_write,
795 safety,
796 comments,
797 unique_name,
798 material_names_dictionary,
799 None,
800 ) # default material
801 for material in bpy.data.materials:
802 if material.users > 0:
803 r, g, b, a = material.diffuse_color[:]
804 pigment_color = "pigment {rgbt <%.4g,%.4g,%.4g,%.4g>}" % (r, g, b, 1 - a)
805 if material.pov.material_use_nodes:
806 # Also make here other pigment_color fallback using BSDF node main color ?
807 ntree = material.node_tree
808 pov_mat_name = string_strip_hyphen(bpy.path.clean_name(material.name))
809 if len(ntree.nodes) == 0:
810 file.write('#declare %s = texture {%s}\n' % (pov_mat_name, pigment_color))
811 else:
812 shading.write_nodes(pov_mat_name, ntree, file)
814 for node in ntree.nodes:
815 if node:
816 if node.bl_idname == "PovrayOutputNode":
817 if node.inputs["Texture"].is_linked:
818 for link in ntree.links:
819 if link.to_node.bl_idname == "PovrayOutputNode":
820 pov_mat_name = (
821 string_strip_hyphen(
822 bpy.path.clean_name(link.from_node.name)
824 + "_%s" % pov_mat_name
826 else:
827 file.write(
828 '#declare %s = texture {%s}\n' % (pov_mat_name, pigment_color)
830 else:
831 shading.write_material(
832 using_uberpov,
833 DEF_MAT_NAME,
834 tab_write,
835 safety,
836 comments,
837 unique_name,
838 material_names_dictionary,
839 material,
841 # attributes are all the variables needed by the other python file...
842 if comments:
843 file.write("\n")
845 export_meta([m for m in sel if m.type == 'META'])
847 if comments:
848 file.write("//--Mesh objects--\n")
850 # tbefore = time.time()
851 object_mesh_topology.export_meshes(
852 preview_dir,
853 file,
854 scene,
855 sel,
856 csg,
857 string_strip_hyphen,
858 safety,
859 write_object_modifiers,
860 material_names_dictionary,
861 write_object_material_interior,
862 scenography.exported_lights_count,
863 unpacked_images,
864 image_format,
865 img_map,
866 img_map_transforms,
867 path_image,
868 smoke_path,
869 global_matrix,
870 write_matrix,
871 using_uberpov,
872 comments,
873 linebreaksinlists,
874 tab,
875 tab_level,
876 tab_write,
877 info_callback,
879 # totime = time.time() - tbefore
880 # print("export_meshes took" + str(totime))
882 # What follow used to happen here:
883 # export_camera()
884 # scenography.export_world(scene.world, scene, global_matrix, tab_write)
885 # export_global_settings(scene)
886 # MR:..and the order was important for implementing pov 3.7 baking
887 # (mesh camera) comment for the record
888 # CR: Baking should be a special case than. If "baking", than we could change the order.
890 if not file.closed:
891 file.close()
893 def write_pov_ini(filename_ini, filename_log, filename_pov, filename_image):
894 """Write ini file."""
895 feature_set = bpy.context.preferences.addons[__package__].preferences.branch_feature_set_povray
896 using_uberpov = feature_set == 'uberpov'
897 # scene = bpy.data.scenes[0]
898 scene = bpy.context.scene
899 render = scene.render
901 x = int(render.resolution_x * render.resolution_percentage * 0.01)
902 y = int(render.resolution_y * render.resolution_percentage * 0.01)
904 with open(filename_ini, "w") as file:
905 file.write("Version=3.7\n")
906 # write povray text stream to temporary file of same name with _log suffix
907 # file.write("All_File='%s'\n" % filename_log)
908 # DEBUG.OUT log if none specified:
909 file.write("All_File=1\n")
911 file.write("Input_File_Name='%s'\n" % filename_pov)
912 file.write("Output_File_Name='%s'\n" % filename_image)
914 file.write("Width=%d\n" % x)
915 file.write("Height=%d\n" % y)
917 # Border render.
918 if render.use_border:
919 file.write("Start_Column=%4g\n" % render.border_min_x)
920 file.write("End_Column=%4g\n" % render.border_max_x)
922 file.write("Start_Row=%4g\n" % (1.0 - render.border_max_y))
923 file.write("End_Row=%4g\n" % (1.0 - render.border_min_y))
925 file.write("Bounding_Method=2\n") # The new automatic BSP is faster in most scenes
927 # Activated (turn this back off when better live exchange is done between the two programs
928 # (see next comment)
929 file.write("Display=1\n")
930 file.write("Pause_When_Done=0\n")
931 # PNG, with POV-Ray 3.7, can show background color with alpha. In the long run using the
932 # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
933 file.write("Output_File_Type=N\n")
934 # file.write("Output_File_Type=T\n") # TGA, best progressive loading
935 file.write("Output_Alpha=1\n")
937 if scene.pov.antialias_enable:
938 # method 2 (recursive) with higher max subdiv forced because no mipmapping in POV-Ray
939 # needs higher sampling.
940 # aa_mapping = {"5": 2, "8": 3, "11": 4, "16": 5}
941 if using_uberpov:
942 method = {"0": 1, "1": 2, "2": 3}
943 else:
944 method = {"0": 1, "1": 2, "2": 2}
945 file.write("Antialias=on\n")
946 file.write("Antialias_Depth=%d\n" % scene.pov.antialias_depth)
947 file.write("Antialias_Threshold=%.3g\n" % scene.pov.antialias_threshold)
948 if using_uberpov and scene.pov.antialias_method == '2':
949 file.write("Sampling_Method=%s\n" % method[scene.pov.antialias_method])
950 file.write("Antialias_Confidence=%.3g\n" % scene.pov.antialias_confidence)
951 else:
952 file.write("Sampling_Method=%s\n" % method[scene.pov.antialias_method])
953 file.write("Antialias_Gamma=%.3g\n" % scene.pov.antialias_gamma)
954 if scene.pov.jitter_enable:
955 file.write("Jitter=on\n")
956 file.write("Jitter_Amount=%3g\n" % scene.pov.jitter_amount)
957 else:
958 file.write("Jitter=off\n") # prevent animation flicker
960 else:
961 file.write("Antialias=off\n")
962 if not file.closed:
963 file.close()
966 class PovrayRender(bpy.types.RenderEngine):
967 """Define the external renderer"""
969 bl_idname = 'POVRAY_RENDER'
970 bl_label = "Persitence Of Vision"
971 bl_use_shading_nodes_custom = False
972 DELAY = 0.5
974 @staticmethod
975 def _locate_binary():
976 """Identify POV engine"""
977 addon_prefs = bpy.context.preferences.addons[__package__].preferences
979 # Use the system preference if its set.
980 pov_binary = addon_prefs.filepath_povray
981 if pov_binary:
982 if os.path.exists(pov_binary):
983 return pov_binary
984 # Implicit else, as here return was still not triggered:
985 print("User Preferences path to povray %r NOT FOUND, checking $PATH" % pov_binary)
987 # Windows Only
988 # assume if there is a 64bit binary that the user has a 64bit capable OS
989 if platform.startswith('win'):
990 import winreg
992 win_reg_key = winreg.OpenKey(
993 winreg.HKEY_CURRENT_USER, "Software\\POV-Ray\\v3.7\\Windows"
995 win_home = winreg.QueryValueEx(win_reg_key, "Home")[0]
997 # First try 64bits UberPOV
998 pov_binary = os.path.join(win_home, "bin", "uberpov64.exe")
999 if os.path.exists(pov_binary):
1000 return pov_binary
1002 # Then try 64bits POV
1003 pov_binary = os.path.join(win_home, "bin", "pvengine64.exe")
1004 if os.path.exists(pov_binary):
1005 return pov_binary
1007 # search the path all os's
1008 pov_binary_default = "povray"
1010 os_path_ls = os.getenv("PATH").split(':') + [""]
1012 for dir_name in os_path_ls:
1013 pov_binary = os.path.join(dir_name, pov_binary_default)
1014 if os.path.exists(pov_binary):
1015 return pov_binary
1016 return ""
1018 def _export(self, depsgraph, pov_path, image_render_path):
1019 """gather all necessary output files paths user defined and auto generated and export there"""
1021 scene = bpy.context.scene
1022 if scene.pov.tempfiles_enable:
1023 self._temp_file_in = tempfile.NamedTemporaryFile(suffix=".pov", delete=False).name
1024 # PNG with POV 3.7, can show the background color with alpha. In the long run using the
1025 # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
1026 self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
1027 # self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".tga", delete=False).name
1028 self._temp_file_ini = tempfile.NamedTemporaryFile(suffix=".ini", delete=False).name
1029 self._temp_file_log = os.path.join(tempfile.gettempdir(), "alltext.out")
1030 else:
1031 self._temp_file_in = pov_path + ".pov"
1032 # PNG with POV 3.7, can show the background color with alpha. In the long run using the
1033 # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
1034 self._temp_file_out = image_render_path + ".png"
1035 # self._temp_file_out = image_render_path + ".tga"
1036 self._temp_file_ini = pov_path + ".ini"
1037 log_path = bpy.path.abspath(scene.pov.scene_path).replace('\\', '/')
1038 self._temp_file_log = os.path.join(log_path, "alltext.out")
1040 self._temp_file_in = "/test.pov"
1041 # PNG with POV 3.7, can show the background color with alpha. In the long run using the
1042 # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
1043 self._temp_file_out = "/test.png"
1044 #self._temp_file_out = "/test.tga"
1045 self._temp_file_ini = "/test.ini"
1047 if scene.pov.text_block == "":
1049 def info_callback(txt):
1050 self.update_stats("", "POV-Ray 3.7: " + txt)
1052 # os.makedirs(user_dir, exist_ok=True) # handled with previews
1053 os.makedirs(preview_dir, exist_ok=True)
1055 write_pov(self._temp_file_in, scene, info_callback)
1056 else:
1057 pass
1059 def _render(self, depsgraph):
1060 """Export necessary files and render image."""
1061 scene = bpy.context.scene
1062 try:
1063 os.remove(self._temp_file_out) # so as not to load the old file
1064 except OSError:
1065 pass
1067 pov_binary = PovrayRender._locate_binary()
1068 if not pov_binary:
1069 print("POV-Ray 3.7: could not execute povray, possibly POV-Ray isn't installed")
1070 return False
1072 write_pov_ini(
1073 self._temp_file_ini, self._temp_file_log, self._temp_file_in, self._temp_file_out
1076 print("***-STARTING-***")
1078 extra_args = []
1080 if scene.pov.command_line_switches != "":
1081 for new_arg in scene.pov.command_line_switches.split(" "):
1082 extra_args.append(new_arg)
1084 self._is_windows = False
1085 if platform.startswith('win'):
1086 self._is_windows = True
1087 if "/EXIT" not in extra_args and not scene.pov.pov_editor:
1088 extra_args.append("/EXIT")
1089 else:
1090 # added -d option to prevent render window popup which leads to segfault on linux
1091 extra_args.append("-d")
1093 # Start Rendering!
1094 try:
1095 self._process = subprocess.Popen(
1096 [pov_binary, self._temp_file_ini] + extra_args,
1097 stdout=subprocess.PIPE,
1098 stderr=subprocess.STDOUT,
1100 except OSError:
1101 # TODO, report api
1102 print("POV-Ray 3.7: could not execute '%s'" % pov_binary)
1103 import traceback
1105 traceback.print_exc()
1106 print("***-DONE-***")
1107 return False
1109 else:
1110 print("Engine ready!...")
1111 print("Command line arguments passed: " + str(extra_args))
1112 return True
1114 # Now that we have a valid process
1116 def _cleanup(self):
1117 """Delete temp files and unpacked ones"""
1118 for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out):
1119 for i in range(5):
1120 try:
1121 os.unlink(f)
1122 break
1123 except OSError:
1124 # Wait a bit before retrying file might be still in use by Blender,
1125 # and Windows does not know how to delete a file in use!
1126 time.sleep(self.DELAY)
1127 for i in unpacked_images:
1128 for j in range(5):
1129 try:
1130 os.unlink(i)
1131 break
1132 except OSError:
1133 # Wait a bit before retrying file might be still in use by Blender,
1134 # and Windows does not know how to delete a file in use!
1135 time.sleep(self.DELAY)
1137 def render(self, depsgraph):
1138 """Export necessary files from text editor and render image."""
1140 scene = bpy.context.scene
1141 r = scene.render
1142 x = int(r.resolution_x * r.resolution_percentage * 0.01)
1143 y = int(r.resolution_y * r.resolution_percentage * 0.01)
1144 print("***INITIALIZING***")
1146 # This makes some tests on the render, returning True if all goes good, and False if
1147 # it was finished one way or the other.
1148 # It also pauses the script (time.sleep())
1149 def _test_wait():
1150 time.sleep(self.DELAY)
1152 # User interrupts the rendering
1153 if self.test_break():
1154 try:
1155 self._process.terminate()
1156 print("***POV INTERRUPTED***")
1157 except OSError:
1158 pass
1159 return False
1160 try:
1161 poll_result = self._process.poll()
1162 except AttributeError:
1163 print("***CHECK POV PATH IN PREFERENCES***")
1164 return False
1165 # POV process is finisehd, one way or the other
1166 if poll_result is not None:
1167 if poll_result < 0:
1168 print("***POV PROCESS FAILED : %s ***" % poll_result)
1169 self.update_stats("", "POV-Ray 3.7: Failed")
1170 return False
1172 return True
1174 if bpy.context.scene.pov.text_block != "":
1175 if scene.pov.tempfiles_enable:
1176 self._temp_file_in = tempfile.NamedTemporaryFile(suffix=".pov", delete=False).name
1177 self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
1178 # self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".tga", delete=False).name
1179 self._temp_file_ini = tempfile.NamedTemporaryFile(suffix=".ini", delete=False).name
1180 self._temp_file_log = os.path.join(tempfile.gettempdir(), "alltext.out")
1181 else:
1182 pov_path = scene.pov.text_block
1183 image_render_path = os.path.splitext(pov_path)[0]
1184 self._temp_file_out = os.path.join(preview_dir, image_render_path)
1185 self._temp_file_in = os.path.join(preview_dir, pov_path)
1186 self._temp_file_ini = os.path.join(
1187 preview_dir, (os.path.splitext(self._temp_file_in)[0] + ".INI")
1189 self._temp_file_log = os.path.join(preview_dir, "alltext.out")
1192 try:
1193 os.remove(self._temp_file_in) # so as not to load the old file
1194 except OSError:
1195 pass
1197 print(scene.pov.text_block)
1198 text = bpy.data.texts[scene.pov.text_block]
1199 with open(self._temp_file_in, "w") as file:
1200 # Why are the newlines needed?
1201 file.write("\n")
1202 file.write(text.as_string())
1203 file.write("\n")
1204 if not file.closed:
1205 file.close()
1207 # has to be called to update the frame on exporting animations
1208 scene.frame_set(scene.frame_current)
1210 pov_binary = PovrayRender._locate_binary()
1212 if not pov_binary:
1213 print("POV-Ray 3.7: could not execute povray, possibly POV-Ray isn't installed")
1214 return False
1216 # start ini UI options export
1217 self.update_stats("", "POV-Ray 3.7: Exporting ini options from Blender")
1219 write_pov_ini(
1220 self._temp_file_ini,
1221 self._temp_file_log,
1222 self._temp_file_in,
1223 self._temp_file_out,
1226 print("***-STARTING-***")
1228 extra_args = []
1230 if scene.pov.command_line_switches != "":
1231 for new_arg in scene.pov.command_line_switches.split(" "):
1232 extra_args.append(new_arg)
1234 if platform.startswith('win'):
1235 if "/EXIT" not in extra_args and not scene.pov.pov_editor:
1236 extra_args.append("/EXIT")
1237 else:
1238 # added -d option to prevent render window popup which leads to segfault on linux
1239 extra_args.append("-d")
1241 # Start Rendering!
1242 try:
1243 if scene.pov.sdl_window_enable and not platform.startswith(
1244 'win'
1245 ): # segfault on linux == False !!!
1246 env = {'POV_DISPLAY_SCALED': 'off'}
1247 env.update(os.environ)
1248 self._process = subprocess.Popen(
1249 [pov_binary, self._temp_file_ini],
1250 stdout=subprocess.PIPE,
1251 stderr=subprocess.STDOUT,
1252 env=env,
1254 else:
1255 self._process = subprocess.Popen(
1256 [pov_binary, self._temp_file_ini] + extra_args,
1257 stdout=subprocess.PIPE,
1258 stderr=subprocess.STDOUT,
1260 except OSError:
1261 # TODO, report api
1262 print("POV-Ray 3.7: could not execute '%s'" % pov_binary)
1263 import traceback
1265 traceback.print_exc()
1266 print("***-DONE-***")
1267 return False
1269 else:
1270 print("Engine ready!...")
1271 print("Command line arguments passed: " + str(extra_args))
1272 # return True
1273 self.update_stats("", "POV-Ray 3.7: Parsing File")
1275 # Indented in main function now so repeated here but still not working
1276 # to bring back render result to its buffer
1278 if os.path.exists(self._temp_file_out):
1279 xmin = int(r.border_min_x * x)
1280 ymin = int(r.border_min_y * y)
1281 xmax = int(r.border_max_x * x)
1282 ymax = int(r.border_max_y * y)
1283 result = self.begin_result(0, 0, x, y)
1284 lay = result.layers[0]
1286 time.sleep(self.DELAY)
1287 try:
1288 lay.load_from_file(self._temp_file_out)
1289 except RuntimeError:
1290 print("***POV ERROR WHILE READING OUTPUT FILE***")
1291 self.end_result(result)
1292 # print(self._temp_file_log) #bring the pov log to blender console with proper path?
1293 with open(
1294 self._temp_file_log
1295 ) as f: # The with keyword automatically closes the file when you are done
1296 print(f.read())
1298 self.update_stats("", "")
1300 if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable:
1301 self._cleanup()
1302 else:
1304 # WIP output format
1305 # if r.image_settings.file_format == 'OPENEXR':
1306 # fformat = 'EXR'
1307 # render.image_settings.color_mode = 'RGBA'
1308 # else:
1309 # fformat = 'TGA'
1310 # r.image_settings.file_format = 'TARGA'
1311 # r.image_settings.color_mode = 'RGBA'
1313 blend_scene_name = bpy.data.filepath.split(os.path.sep)[-1].split(".")[0]
1314 pov_scene_name = ""
1315 pov_path = ""
1316 image_render_path = ""
1318 # has to be called to update the frame on exporting animations
1319 scene.frame_set(scene.frame_current)
1321 if not scene.pov.tempfiles_enable:
1323 # check paths
1324 pov_path = bpy.path.abspath(scene.pov.scene_path).replace('\\', '/')
1325 if pov_path == "":
1326 if bpy.data.is_saved:
1327 pov_path = bpy.path.abspath("//")
1328 else:
1329 pov_path = tempfile.gettempdir()
1330 elif pov_path.endswith("/"):
1331 if pov_path == "/":
1332 pov_path = bpy.path.abspath("//")
1333 else:
1334 pov_path = bpy.path.abspath(scene.pov.scene_path)
1336 if not os.path.exists(pov_path):
1337 try:
1338 os.makedirs(pov_path)
1339 except BaseException as e:
1340 print(e.__doc__)
1341 print('An exception occurred: {}'.format(e))
1342 import traceback
1344 traceback.print_exc()
1346 print("POV-Ray 3.7: Cannot create scenes directory: %r" % pov_path)
1347 self.update_stats(
1348 "", "POV-Ray 3.7: Cannot create scenes directory %r" % pov_path
1350 time.sleep(2.0)
1351 # return
1354 # Bug in POV-Ray RC3
1355 image_render_path = bpy.path.abspath(scene.pov.renderimage_path).replace('\\','/')
1356 if image_render_path == "":
1357 if bpy.data.is_saved:
1358 image_render_path = bpy.path.abspath("//")
1359 else:
1360 image_render_path = tempfile.gettempdir()
1361 #print("Path: " + image_render_path)
1362 elif path.endswith("/"):
1363 if image_render_path == "/":
1364 image_render_path = bpy.path.abspath("//")
1365 else:
1366 image_render_path = bpy.path.abspath(scene.pov.)
1367 if not os.path.exists(path):
1368 print("POV-Ray 3.7: Cannot find render image directory")
1369 self.update_stats("", "POV-Ray 3.7: Cannot find render image directory")
1370 time.sleep(2.0)
1371 return
1374 # check name
1375 if scene.pov.scene_name == "":
1376 if blend_scene_name != "":
1377 pov_scene_name = blend_scene_name
1378 else:
1379 pov_scene_name = "untitled"
1380 else:
1381 pov_scene_name = scene.pov.scene_name
1382 if os.path.isfile(pov_scene_name):
1383 pov_scene_name = os.path.basename(pov_scene_name)
1384 pov_scene_name = pov_scene_name.split('/')[-1].split('\\')[-1]
1385 if not pov_scene_name:
1386 print("POV-Ray 3.7: Invalid scene name")
1387 self.update_stats("", "POV-Ray 3.7: Invalid scene name")
1388 time.sleep(2.0)
1389 # return
1390 pov_scene_name = os.path.splitext(pov_scene_name)[0]
1392 print("Scene name: " + pov_scene_name)
1393 print("Export path: " + pov_path)
1394 pov_path = os.path.join(pov_path, pov_scene_name)
1395 pov_path = os.path.realpath(pov_path)
1397 image_render_path = pov_path
1398 # print("Render Image path: " + image_render_path)
1400 # start export
1401 self.update_stats("", "POV-Ray 3.7: Exporting data from Blender")
1402 self._export(depsgraph, pov_path, image_render_path)
1403 self.update_stats("", "POV-Ray 3.7: Parsing File")
1405 if not self._render(depsgraph):
1406 self.update_stats("", "POV-Ray 3.7: Not found")
1407 # return
1409 # r = scene.render
1410 # compute resolution
1411 # x = int(r.resolution_x * r.resolution_percentage * 0.01)
1412 # y = int(r.resolution_y * r.resolution_percentage * 0.01)
1414 # Wait for the file to be created
1415 # XXX This is no more valid, as 3.7 always creates output file once render is finished!
1416 parsing = re.compile(br"= \[Parsing\.\.\.\] =")
1417 rendering = re.compile(br"= \[Rendering\.\.\.\] =")
1418 percent = re.compile(r"\(([0-9]{1,3})%\)")
1419 # print("***POV WAITING FOR FILE***")
1421 data = b""
1422 last_line = ""
1423 while _test_wait():
1424 # POV in Windows did not output its stdout/stderr, it displayed them in its GUI
1425 # But now writes file
1426 if self._is_windows:
1427 self.update_stats("", "POV-Ray 3.7: Rendering File")
1428 else:
1429 t_data = self._process.stdout.read(10000)
1430 if not t_data:
1431 continue
1433 data += t_data
1434 # XXX This is working for UNIX, not sure whether it might need adjustments for
1435 # other OSs
1436 # First replace is for windows
1437 t_data = str(t_data).replace('\\r\\n', '\\n').replace('\\r', '\r')
1438 lines = t_data.split('\\n')
1439 last_line += lines[0]
1440 lines[0] = last_line
1441 print('\n'.join(lines), end="")
1442 last_line = lines[-1]
1444 if rendering.search(data):
1445 _pov_rendering = True
1446 match = percent.findall(str(data))
1447 if match:
1448 self.update_stats("", "POV-Ray 3.7: Rendering File (%s%%)" % match[-1])
1449 else:
1450 self.update_stats("", "POV-Ray 3.7: Rendering File")
1452 elif parsing.search(data):
1453 self.update_stats("", "POV-Ray 3.7: Parsing File")
1455 if os.path.exists(self._temp_file_out):
1456 # print("***POV FILE OK***")
1457 # self.update_stats("", "POV-Ray 3.7: Rendering")
1459 # prev_size = -1
1461 xmin = int(r.border_min_x * x)
1462 ymin = int(r.border_min_y * y)
1463 xmax = int(r.border_max_x * x)
1464 ymax = int(r.border_max_y * y)
1466 # print("***POV UPDATING IMAGE***")
1467 result = self.begin_result(0, 0, x, y)
1468 # XXX, tests for border render.
1469 # result = self.begin_result(xmin, ymin, xmax - xmin, ymax - ymin)
1470 # result = self.begin_result(0, 0, xmax - xmin, ymax - ymin)
1471 lay = result.layers[0]
1473 # This assumes the file has been fully written We wait a bit, just in case!
1474 time.sleep(self.DELAY)
1475 try:
1476 lay.load_from_file(self._temp_file_out)
1477 # XXX, tests for border render.
1478 # lay.load_from_file(self._temp_file_out, xmin, ymin)
1479 except RuntimeError:
1480 print("***POV ERROR WHILE READING OUTPUT FILE***")
1482 # Not needed right now, might only be useful if we find a way to use temp raw output of
1483 # pov 3.7 (in which case it might go under _test_wait()).
1485 def update_image():
1486 # possible the image wont load early on.
1487 try:
1488 lay.load_from_file(self._temp_file_out)
1489 # XXX, tests for border render.
1490 #lay.load_from_file(self._temp_file_out, xmin, ymin)
1491 #lay.load_from_file(self._temp_file_out, xmin, ymin)
1492 except RuntimeError:
1493 pass
1495 # Update while POV-Ray renders
1496 while True:
1497 # print("***POV RENDER LOOP***")
1499 # test if POV-Ray exists
1500 if self._process.poll() is not None:
1501 print("***POV PROCESS FINISHED***")
1502 update_image()
1503 break
1505 # user exit
1506 if self.test_break():
1507 try:
1508 self._process.terminate()
1509 print("***POV PROCESS INTERRUPTED***")
1510 except OSError:
1511 pass
1513 break
1515 # Would be nice to redirect the output
1516 # stdout_value, stderr_value = self._process.communicate() # locks
1518 # check if the file updated
1519 new_size = os.path.getsize(self._temp_file_out)
1521 if new_size != prev_size:
1522 update_image()
1523 prev_size = new_size
1525 time.sleep(self.DELAY)
1528 self.end_result(result)
1530 else:
1531 print("***POV FILE NOT FOUND***")
1533 print("***POV FILE FINISHED***")
1535 # print(filename_log) #bring the pov log to blender console with proper path?
1536 with open(
1537 self._temp_file_log, encoding='utf-8'
1538 ) as f: # The with keyword automatically closes the file when you are done
1539 msg = f.read()
1540 # if isinstance(msg, str):
1541 # stdmsg = msg
1542 # decoded = False
1543 # else:
1544 # if type(msg) == bytes:
1545 # stdmsg = msg.split('\n')
1546 # stdmsg = msg.encode('utf-8', "replace")
1547 # stdmsg = msg.encode("utf-8", "replace")
1549 # stdmsg = msg.decode(encoding)
1550 # decoded = True
1551 # msg.encode('utf-8').decode('utf-8')
1552 msg.replace("\t", " ")
1553 print(msg)
1554 # Also print to the interactive console used in POV centric workspace
1555 # To do: get a grip on new line encoding
1556 # and make this a function to be used elsewhere
1557 for win in bpy.context.window_manager.windows:
1558 if win.screen is not None:
1559 scr = win.screen
1560 for area in scr.areas:
1561 if area.type == 'CONSOLE':
1562 try:
1563 # context override
1564 ctx = {
1565 'area': area,
1566 'screen': scr,
1567 'window': win
1570 # bpy.ops.console.banner(ctx, text = "Hello world")
1571 bpy.ops.console.clear_line(ctx)
1572 for i in msg.split('\n'):
1573 bpy.ops.console.scrollback_append(
1574 ctx,
1575 text=i,
1576 type='INFO'
1578 # bpy.ops.console.insert(ctx, text=(i + "\n"))
1579 except BaseException as e:
1580 print(e.__doc__)
1581 print('An exception occurred: {}'.format(e))
1582 pass
1584 self.update_stats("", "")
1586 if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable:
1587 self._cleanup()
1589 sound_on = bpy.context.preferences.addons[__package__].preferences.use_sounds
1590 finished_render_message = "\'Et Voilà!\'"
1592 if platform.startswith('win') and sound_on:
1593 # Could not find tts Windows command so playing beeps instead :-)
1594 # "Korobeiniki"(Коробе́йники)
1595 # aka "A-Type" Tetris theme
1596 import winsound
1598 winsound.Beep(494, 250) # B
1599 winsound.Beep(370, 125) # F
1600 winsound.Beep(392, 125) # G
1601 winsound.Beep(440, 250) # A
1602 winsound.Beep(392, 125) # G
1603 winsound.Beep(370, 125) # F#
1604 winsound.Beep(330, 275) # E
1605 winsound.Beep(330, 125) # E
1606 winsound.Beep(392, 125) # G
1607 winsound.Beep(494, 275) # B
1608 winsound.Beep(440, 125) # A
1609 winsound.Beep(392, 125) # G
1610 winsound.Beep(370, 275) # F
1611 winsound.Beep(370, 125) # F
1612 winsound.Beep(392, 125) # G
1613 winsound.Beep(440, 250) # A
1614 winsound.Beep(494, 250) # B
1615 winsound.Beep(392, 250) # G
1616 winsound.Beep(330, 350) # E
1617 time.sleep(0.5)
1618 winsound.Beep(440, 250) # A
1619 winsound.Beep(440, 150) # A
1620 winsound.Beep(523, 125) # D8
1621 winsound.Beep(659, 250) # E8
1622 winsound.Beep(587, 125) # D8
1623 winsound.Beep(523, 125) # C8
1624 winsound.Beep(494, 250) # B
1625 winsound.Beep(494, 125) # B
1626 winsound.Beep(392, 125) # G
1627 winsound.Beep(494, 250) # B
1628 winsound.Beep(440, 150) # A
1629 winsound.Beep(392, 125) # G
1630 winsound.Beep(370, 250) # F#
1631 winsound.Beep(370, 125) # F#
1632 winsound.Beep(392, 125) # G
1633 winsound.Beep(440, 250) # A
1634 winsound.Beep(494, 250) # B
1635 winsound.Beep(392, 250) # G
1636 winsound.Beep(330, 300) # E
1638 # Mac supports natively say command
1639 elif platform == "darwin":
1640 # We don't want the say command to block Python,
1641 # so we add an ampersand after the message
1642 # but if the os TTS package isn't up to date it
1643 # still does thus, the try except clause
1644 try:
1645 os.system("say %s &" % finished_render_message)
1646 except BaseException as e:
1647 print(e.__doc__)
1648 print("your Mac may need an update, try to restart computer")
1649 pass
1650 # While Linux frequently has espeak installed or at least can suggest
1651 # Maybe windows could as well ?
1652 elif platform == "linux":
1653 # We don't want the espeak command to block Python,
1654 # so we add an ampersand after the message
1655 # but if the espeak TTS package isn't installed it
1656 # still does thus, the try except clause
1657 try:
1658 os.system("echo %s | espeak &" % finished_render_message)
1659 except BaseException as e:
1660 print(e.__doc__)
1661 pass
1665 # --------------------------------------------------------------------------------- #
1666 # ----------------------------------- Operators ----------------------------------- #
1667 # --------------------------------------------------------------------------------- #
1668 class RenderPovTexturePreview(Operator):
1669 """Export only files necessary to texture preview and render image"""
1671 bl_idname = "tex.preview_update"
1672 bl_label = "Update preview"
1674 def execute(self, context):
1675 tex = bpy.context.object.active_material.active_texture # context.texture
1676 tex_prev_name = string_strip_hyphen(bpy.path.clean_name(tex.name)) + "_prev"
1678 # Make sure Preview directory exists and is empty
1679 if not os.path.isdir(preview_dir):
1680 os.mkdir(preview_dir)
1682 ini_prev_file = os.path.join(preview_dir, "Preview.ini")
1683 input_prev_file = os.path.join(preview_dir, "Preview.pov")
1684 output_prev_file = os.path.join(preview_dir, tex_prev_name)
1685 # ---------------------------------- ini ---------------------------------- #
1686 with open(ini_prev_file, "w") as file_ini:
1687 file_ini.write('Version=3.8\n')
1688 file_ini.write('Input_File_Name="%s"\n' % input_prev_file)
1689 file_ini.write('Output_File_Name="%s.png"\n' % output_prev_file)
1690 file_ini.write('Library_Path="%s"\n' % preview_dir)
1691 file_ini.write('Width=256\n')
1692 file_ini.write('Height=256\n')
1693 file_ini.write('Pause_When_Done=0\n')
1694 file_ini.write('Output_File_Type=N\n')
1695 file_ini.write('Output_Alpha=1\n')
1696 file_ini.write('Antialias=on\n')
1697 file_ini.write('Sampling_Method=2\n')
1698 file_ini.write('Antialias_Depth=3\n')
1699 file_ini.write('-d\n')
1700 if not file_ini.closed:
1701 file_ini.close()
1702 # ---------------------------------- pov ---------------------------------- #
1703 with open(input_prev_file, "w") as file_pov:
1704 pat_name = "PAT_" + string_strip_hyphen(bpy.path.clean_name(tex.name))
1705 file_pov.write("#declare %s = \n" % pat_name)
1706 file_pov.write(shading.export_pattern(tex))
1708 file_pov.write("#declare Plane =\n")
1709 file_pov.write("mesh {\n")
1710 file_pov.write(
1711 " triangle {<-2.021,-1.744,2.021>,<-2.021,-1.744,-2.021>,<2.021,-1.744,2.021>}\n"
1713 file_pov.write(
1714 " triangle {<-2.021,-1.744,-2.021>,<2.021,-1.744,-2.021>,<2.021,-1.744,2.021>}\n"
1716 file_pov.write(" texture{%s}\n" % pat_name)
1717 file_pov.write("}\n")
1718 file_pov.write("object {Plane}\n")
1719 file_pov.write("light_source {\n")
1720 file_pov.write(" <0,4.38,-1.92e-07>\n")
1721 file_pov.write(" color rgb<4, 4, 4>\n")
1722 file_pov.write(" parallel\n")
1723 file_pov.write(" point_at <0, 0, -1>\n")
1724 file_pov.write("}\n")
1725 file_pov.write("camera {\n")
1726 file_pov.write(" location <0, 0, 0>\n")
1727 file_pov.write(" look_at <0, 0, -1>\n")
1728 file_pov.write(" right <-1.0, 0, 0>\n")
1729 file_pov.write(" up <0, 1, 0>\n")
1730 file_pov.write(" angle 96.805211\n")
1731 file_pov.write(" rotate <-90.000003, -0.000000, 0.000000>\n")
1732 file_pov.write(" translate <0.000000, 0.000000, 0.000000>\n")
1733 file_pov.write("}\n")
1734 if not file_pov.closed:
1735 file_pov.close()
1736 # ------------------------------- end write ------------------------------- #
1738 pov_binary = PovrayRender._locate_binary()
1740 if platform.startswith('win'):
1741 p1 = subprocess.Popen(
1742 ["%s" % pov_binary, "/EXIT", "%s" % ini_prev_file],
1743 stdout=subprocess.PIPE,
1744 stderr=subprocess.STDOUT,
1746 else:
1747 p1 = subprocess.Popen(
1748 ["%s" % pov_binary, "-d", "%s" % ini_prev_file],
1749 stdout=subprocess.PIPE,
1750 stderr=subprocess.STDOUT,
1752 p1.wait()
1754 tex.use_nodes = True
1755 tree = tex.node_tree
1756 links = tree.links
1757 for n in tree.nodes:
1758 tree.nodes.remove(n)
1759 im = tree.nodes.new("TextureNodeImage")
1760 path_prev = "%s.png" % output_prev_file
1761 im.image = bpy.data.images.load(path_prev)
1762 name = path_prev
1763 name = name.split("/")
1764 name = name[len(name) - 1]
1765 im.name = name
1766 im.location = 200, 200
1767 previewer = tree.nodes.new('TextureNodeOutput')
1768 previewer.label = "Preview"
1769 previewer.location = 400, 400
1770 links.new(im.outputs[0], previewer.inputs[0])
1771 # tex.type="IMAGE" # makes clip extend possible
1772 # tex.extension="CLIP"
1773 return {'FINISHED'}
1776 class RunPovTextRender(Operator):
1777 """Export files depending on text editor options and render image."""
1779 bl_idname = "text.run"
1780 bl_label = "Run"
1781 bl_context = "text"
1782 bl_description = "Run a render with this text only"
1784 def execute(self, context):
1785 scene = context.scene
1786 scene.pov.text_block = context.space_data.text.name
1788 bpy.ops.render.render()
1790 # empty text name property again
1791 scene.pov.text_block = ""
1792 return {'FINISHED'}
1795 classes = (
1796 PovrayRender,
1797 RenderPovTexturePreview,
1798 RunPovTextRender,
1802 def register():
1803 for cls in classes:
1804 register_class(cls)
1805 scripting.register()
1808 def unregister():
1809 scripting.unregister()
1810 for cls in reversed(classes):
1811 unregister_class(cls)