Merge branch 'blender-v3.6-release'
[blender-addons.git] / render_povray / render_core.py
blob2bf9ced06e9ccb00aa60d136c0e8df2f4e13cd5c
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 """Define the POV render engine from generic Blender RenderEngine class."""
4 import bpy
6 import builtins as __builtin__
7 import subprocess
8 import os
9 from sys import platform
10 import time
11 import re
12 import tempfile
13 from bpy.utils import register_class, unregister_class
14 from . import render
16 def console_get(context):
17 #context = bpy.context
18 for win in context.window_manager.windows:
19 if win.screen is not None:
20 scr = win.screen
21 for area in scr.areas:
22 if area.type == 'CONSOLE':
23 for space in area.spaces:
24 if space.type == 'CONSOLE':
25 return area, space, win, scr
26 return None, None, None, None
28 def console_write(txt):
29 area, space, window, screen = console_get()
30 if space is None:
31 return
32 context = bpy.context.copy()
33 context.update(dict(
34 area=area,
35 space_data=space,
36 region=area.regions[-1],
37 window=window,
38 screen=screen,
40 with bpy.context.temp_override(**context):
41 for line in txt.split("\n"):
42 bpy.ops.console.scrollback_append(text=line, type='INFO')
43 """
44 class RENDER_OT_test(bpy.types.Operator):
45 bl_idname = 'pov.oha_test'
46 bl_label = 'Test'
47 bl_options = {'REGISTER', 'UNDO'}
49 txt: bpy.props.StringProperty(
50 name='text',
51 default='what?'
53 def execute(self, context):
54 try:
55 console_write(self.txt)
56 return {'FINISHED'}
57 except:
58 self.report({'INFO'}, 'Printing report to Info window.')
59 return {'CANCELLED'}
61 def console_print(*args, **kwargs):
62 context = bpy.context
63 #screens = (win.screen for win in context.window_manager.windows if win.screen is not None)
64 for win in context.window_manager.windows:
65 if win.screen is not None:
66 scr = win.screen
67 for a in scr.areas:
68 if a.type == 'CONSOLE':
69 try:
70 c = {}
71 c['area'] = a
72 c['space_data'] = a.spaces.active
73 c['region'] = a.regions[-1]
74 c['window'] = win
75 c['screen'] = scr
76 s = " ".join([str(arg) for arg in args])
77 for line in s.split("\n"):
78 bpy.ops.console.scrollback_append(c, text=line, type='INFO')
80 except BaseException as e:
81 print(e.__doc__)
82 print('An exception occurred: {}'.format(e))
83 pass
86 def print(*args, **kwargs):
87 console_print(*args, **kwargs) # to Python Console
88 __builtin__.print(*args, **kwargs) # to System Console
89 """
91 user_dir = bpy.utils.resource_path('USER')
92 preview_dir = os.path.join(user_dir, "preview")
93 # Make sure Preview directory exists and is empty
94 smoke_path = os.path.join(preview_dir, "smoke.df3")
96 class PovRender(bpy.types.RenderEngine):
97 """Define the external renderer"""
99 bl_idname = 'POVRAY_RENDER'
100 bl_label = "Persitence Of Vision"
101 bl_use_eevee_viewport = True
102 bl_use_shading_nodes_custom = False
103 DELAY = 0.5
105 @staticmethod
106 def _locate_binary():
107 """Identify POV engine"""
108 addon_prefs = bpy.context.preferences.addons[__package__].preferences
110 # Use the system preference if its set.
111 if pov_binary:= addon_prefs.filepath_povray:
112 if os.path.exists(pov_binary):
113 return pov_binary
114 # Implicit else, as here return was still not triggered:
115 print("User Preferences path to povray %r NOT FOUND, checking $PATH" % pov_binary)
117 # Windows Only
118 # assume if there is a 64bit binary that the user has a 64bit capable OS
119 if platform.startswith('win'):
120 import winreg
122 win_reg_key = winreg.OpenKey(
123 winreg.HKEY_CURRENT_USER, "Software\\POV-Ray\\v3.7\\Windows"
125 win_home = winreg.QueryValueEx(win_reg_key, "Home")[0]
127 # First try 64bits UberPOV
128 pov_binary = os.path.join(win_home, "bin", "uberpov64.exe")
129 if os.path.exists(pov_binary):
130 return pov_binary
132 # Then try 64bits POV
133 pov_binary = os.path.join(win_home, "bin", "pvengine64.exe")
134 if os.path.exists(pov_binary):
135 return pov_binary
137 # search the path all os's
138 pov_binary_default = "povray"
140 os_path_ls = os.getenv("PATH").split(':') + [""]
142 for dir_name in os_path_ls:
143 pov_binary = os.path.join(dir_name, pov_binary_default)
144 if os.path.exists(pov_binary):
145 return pov_binary
146 return ""
148 def _export(self, depsgraph, pov_path, image_render_path):
149 """gather all necessary output files paths user defined and auto generated and export there"""
151 scene = bpy.context.scene
152 if scene.pov.tempfiles_enable:
153 self._temp_file_in = tempfile.NamedTemporaryFile(suffix=".pov", delete=False).name
154 # PNG with POV 3.7, can show the background color with alpha. In the long run using the
155 # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
156 self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
157 # self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".tga", delete=False).name
158 self._temp_file_ini = tempfile.NamedTemporaryFile(suffix=".ini", delete=False).name
159 log_path = os.path.join(tempfile.gettempdir(), "alltext.out")
160 else:
161 self._temp_file_in = pov_path + ".pov"
162 # PNG with POV 3.7, can show the background color with alpha. In the long run using the
163 # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
164 self._temp_file_out = image_render_path + ".png"
165 # self._temp_file_out = image_render_path + ".tga"
166 self._temp_file_ini = pov_path + ".ini"
167 scene_path = scene.pov.scene_path
168 abs_log_path = bpy.path.abspath(scene_path)
169 log_path= os.path.join(abs_log_path, "alltext.out")
171 self._temp_file_in = "/test.pov"
172 # PNG with POV 3.7, can show the background color with alpha. In the long run using the
173 # POV-Ray interactive preview like bishop 3D could solve the preview for all formats.
174 self._temp_file_out = "/test.png"
175 #self._temp_file_out = "/test.tga"
176 self._temp_file_ini = "/test.ini"
179 self._temp_file_log = log_path
180 # self._temp_file_log = log_path.replace('\\', '/') # unnecessary relying on os.path
182 if scene.pov.text_block == "":
184 def info_callback(txt):
185 self.update_stats("", "POV-Ray 3.7: " + txt)
187 # os.makedirs(user_dir, exist_ok=True) # handled with previews
188 os.makedirs(preview_dir, exist_ok=True)
190 render.write_pov(self._temp_file_in, scene, info_callback)
191 else:
192 pass
194 def _render(self, depsgraph):
195 """Export necessary files and render image."""
196 scene = bpy.context.scene
197 try:
198 os.remove(self._temp_file_out) # so as not to load the old file
199 except OSError:
200 pass
202 pov_binary = PovRender._locate_binary()
203 if not pov_binary:
204 print("POV-Ray 3.7: could not execute povray, possibly POV-Ray isn't installed")
205 return False
207 render.write_pov_ini(
208 self._temp_file_ini, self._temp_file_log, self._temp_file_in, self._temp_file_out
211 print("***-STARTING-***")
213 extra_args = []
214 # Always add user preferences include path field when specified
215 if (pov_documents := bpy.context.preferences.addons[__package__].preferences.docpath_povray)!="":
216 extra_args.append("+L"+ pov_documents)
217 if scene.pov.command_line_switches != "":
218 extra_args.extend(iter(scene.pov.command_line_switches.split(" ")))
219 self._is_windows = False
220 if platform.startswith('win'):
221 self._is_windows = True
222 if "/EXIT" not in extra_args and not scene.pov.pov_editor:
223 extra_args.append("/EXIT")
224 else:
225 # added -d option to prevent render window popup which leads to segfault on linux
226 extra_args.append("-d")
228 # Start Rendering!
229 try:
230 self._process = subprocess.Popen(
231 [pov_binary, self._temp_file_ini] + extra_args,
232 stdout=subprocess.PIPE,
233 stderr=subprocess.STDOUT,
235 except OSError:
236 # TODO, report api
237 print("POV-Ray 3.7: could not execute '%s'" % pov_binary)
238 import traceback
240 traceback.print_exc()
241 print("***-DONE-***")
242 return False
244 else:
245 print("Engine ready!...")
246 print("Command line arguments passed: " + str(extra_args))
247 return True
249 def _cleanup(self):
250 """Delete temp files and unpacked ones"""
251 for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out):
252 for i in range(5):
253 try:
254 os.unlink(f)
255 break
256 except OSError:
257 # Wait a bit before retrying file might be still in use by Blender,
258 # and Windows does not know how to delete a file in use!
259 time.sleep(self.DELAY)
260 for i in render.unpacked_images:
261 for j in range(5):
262 try:
263 os.unlink(i)
264 break
265 except OSError:
266 # Wait a bit before retrying file might be still in use by Blender,
267 # and Windows does not know how to delete a file in use!
268 time.sleep(self.DELAY)
269 # avoid some crashes if memory leaks from one render to the next?
270 #self.free_blender_memory()
272 def render(self, depsgraph):
273 """Export necessary files from text editor and render image."""
275 scene = bpy.context.scene
276 r = scene.render
277 x = int(r.resolution_x * r.resolution_percentage * 0.01)
278 y = int(r.resolution_y * r.resolution_percentage * 0.01)
279 print("\n***INITIALIZING***")
281 # This makes some tests on the render, returning True if all goes good, and False if
282 # it was finished one way or the other.
283 # It also pauses the script (time.sleep())
284 def _test_wait():
285 time.sleep(self.DELAY)
287 # User interrupts the rendering
288 if self.test_break():
289 try:
290 self._process.terminate()
291 print("***POV INTERRUPTED***")
292 except OSError:
293 pass
294 return False
295 try:
296 poll_result = self._process.poll()
297 except AttributeError:
298 print("***CHECK POV PATH IN PREFERENCES***")
299 return False
300 # POV process is finisehd, one way or the other
301 if poll_result is not None:
302 if poll_result < 0:
303 print("***POV PROCESS FAILED : %s ***" % poll_result)
304 self.update_stats("", "POV-Ray 3.7: Failed")
305 return False
307 return True
309 if bpy.context.scene.pov.text_block != "":
310 if scene.pov.tempfiles_enable:
311 self._temp_file_in = tempfile.NamedTemporaryFile(suffix=".pov", delete=False).name
312 self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
313 # self._temp_file_out = tempfile.NamedTemporaryFile(suffix=".tga", delete=False).name
314 self._temp_file_ini = tempfile.NamedTemporaryFile(suffix=".ini", delete=False).name
315 self._temp_file_log = os.path.join(tempfile.gettempdir(), "alltext.out")
316 else:
317 pov_path = scene.pov.text_block
318 image_render_path = os.path.splitext(pov_path)[0]
319 self._temp_file_out = os.path.join(preview_dir, image_render_path)
320 self._temp_file_in = os.path.join(preview_dir, pov_path)
321 self._temp_file_ini = os.path.join(
322 preview_dir, (os.path.splitext(self._temp_file_in)[0] + ".INI")
324 self._temp_file_log = os.path.join(preview_dir, "alltext.out")
327 try:
328 os.remove(self._temp_file_in) # so as not to load the old file
329 except OSError:
330 pass
332 print(scene.pov.text_block)
333 text = bpy.data.texts[scene.pov.text_block]
334 with open(self._temp_file_in, "w") as file:
335 # Why are the newlines needed?
336 file.write("\n")
337 file.write(text.as_string())
338 file.write("\n")
341 # has to be called to update the frame on exporting animations
342 scene.frame_set(scene.frame_current)
344 pov_binary = PovRender._locate_binary()
346 if not pov_binary:
347 print("Could not execute POV-Ray, which installation possibly isn't standard ?")
348 return False
350 # start ini UI options export
351 self.update_stats("", "POV-Ray 3.7: Exporting ini options from Blender")
353 render.write_pov_ini(
354 self._temp_file_ini,
355 self._temp_file_log,
356 self._temp_file_in,
357 self._temp_file_out,
360 print("***-STARTING-***")
362 extra_args = []
364 if scene.pov.command_line_switches != "":
365 for new_arg in scene.pov.command_line_switches.split(" "):
366 extra_args.append(new_arg)
368 if platform.startswith('win'):
369 if "/EXIT" not in extra_args and not scene.pov.pov_editor:
370 extra_args.append("/EXIT")
371 else:
372 # added -d option to prevent render window popup which leads to segfault on linux
373 extra_args.append("-d")
375 # Start Rendering!
376 try:
377 if scene.pov.sdl_window_enable and not platform.startswith(
378 'win'
379 ): # segfault on linux == False !!!
380 env = {'POV_DISPLAY_SCALED': 'off'}
381 env.update(os.environ)
382 self._process = subprocess.Popen(
383 [pov_binary, self._temp_file_ini],
384 stdout=subprocess.PIPE,
385 stderr=subprocess.STDOUT,
386 env=env,
388 else:
389 self._process = subprocess.Popen(
390 [pov_binary, self._temp_file_ini] + extra_args,
391 stdout=subprocess.PIPE,
392 stderr=subprocess.STDOUT,
394 except OSError:
395 # TODO, report api
396 print("POV-Ray 3.7: could not execute '%s'" % pov_binary)
397 import traceback
399 traceback.print_exc()
400 print("***-DONE-***")
401 return False
403 else:
404 print("Engine ready!...")
405 print("Command line arguments passed: " + str(extra_args))
406 # return True
407 self.update_stats("", "POV-Ray 3.7: Parsing File")
409 # Indented in main function now so repeated here but still not working
410 # to bring back render result to its buffer
412 if os.path.exists(self._temp_file_out):
413 xmin = int(r.border_min_x * x)
414 ymin = int(r.border_min_y * y)
415 xmax = int(r.border_max_x * x)
416 ymax = int(r.border_max_y * y)
417 result = self.begin_result(0, 0, x, y)
418 lay = result.layers[0]
420 time.sleep(self.DELAY)
421 try:
422 lay.load_from_file(self._temp_file_out)
423 except RuntimeError:
424 print("***POV ERROR WHILE READING OUTPUT FILE***")
425 self.end_result(result)
426 # print(self._temp_file_log) #bring the pov log to blender console with proper path?
427 with open(
428 self._temp_file_log
429 ) as f: # The with keyword automatically closes the file when you are done
430 print(f.read()) # console_write(f.read())
432 self.update_stats("", "")
434 if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable:
435 self._cleanup()
436 else:
438 # WIP output format
439 # if r.image_settings.file_format == 'OPENEXR':
440 # fformat = 'EXR'
441 # render.image_settings.color_mode = 'RGBA'
442 # else:
443 # fformat = 'TGA'
444 # r.image_settings.file_format = 'TARGA'
445 # r.image_settings.color_mode = 'RGBA'
447 blend_scene_name = bpy.data.filepath.split(os.path.sep)[-1].split(".")[0]
448 pov_scene_name = ""
449 pov_path = ""
450 image_render_path = ""
452 # has to be called to update the frame on exporting animations
453 scene.frame_set(scene.frame_current)
455 if not scene.pov.tempfiles_enable:
457 # check paths
458 pov_path = bpy.path.abspath(scene.pov.scene_path).replace('\\', '/')
459 if pov_path == "":
460 if bpy.data.is_saved:
461 pov_path = bpy.path.abspath("//")
462 else:
463 pov_path = tempfile.gettempdir()
464 elif pov_path.endswith("/"):
465 if pov_path == "/":
466 pov_path = bpy.path.abspath("//")
467 else:
468 pov_path = bpy.path.abspath(scene.pov.scene_path)
470 if not os.path.exists(pov_path):
471 try:
472 os.makedirs(pov_path)
473 except BaseException as e:
474 print(e.__doc__)
475 print('An exception occurred: {}'.format(e))
476 import traceback
478 traceback.print_exc()
480 print("POV-Ray 3.7: Cannot create scenes directory: %r" % pov_path)
481 self.update_stats(
482 "", "POV-Ray 3.7: Cannot create scenes directory %r" % pov_path
484 time.sleep(2.0)
485 # return
488 # Bug in POV-Ray RC3
489 image_render_path = bpy.path.abspath(scene.pov.renderimage_path).replace('\\','/')
490 if image_render_path == "":
491 if bpy.data.is_saved:
492 image_render_path = bpy.path.abspath("//")
493 else:
494 image_render_path = tempfile.gettempdir()
495 #print("Path: " + image_render_path)
496 elif path.endswith("/"):
497 if image_render_path == "/":
498 image_render_path = bpy.path.abspath("//")
499 else:
500 image_render_path = bpy.path.abspath(scene.pov.)
501 if not os.path.exists(path):
502 print("POV-Ray 3.7: Cannot find render image directory")
503 self.update_stats("", "POV-Ray 3.7: Cannot find render image directory")
504 time.sleep(2.0)
505 return
508 # check name
509 if scene.pov.scene_name == "":
510 if blend_scene_name != "":
511 pov_scene_name = blend_scene_name
512 else:
513 pov_scene_name = "untitled"
514 else:
515 pov_scene_name = scene.pov.scene_name
516 if os.path.isfile(pov_scene_name):
517 pov_scene_name = os.path.basename(pov_scene_name)
518 pov_scene_name = pov_scene_name.split('/')[-1].split('\\')[-1]
519 if not pov_scene_name:
520 print("POV-Ray 3.7: Invalid scene name")
521 self.update_stats("", "POV-Ray 3.7: Invalid scene name")
522 time.sleep(2.0)
523 # return
524 pov_scene_name = os.path.splitext(pov_scene_name)[0]
526 print("Scene name: " + pov_scene_name)
527 print("Export path: " + pov_path)
528 pov_path = os.path.join(pov_path, pov_scene_name)
529 pov_path = os.path.realpath(pov_path)
531 image_render_path = pov_path
532 # print("Render Image path: " + image_render_path)
534 # start export
535 self.update_stats("", "POV-Ray 3.7: Exporting data from Blender")
536 self._export(depsgraph, pov_path, image_render_path)
537 self.update_stats("", "POV-Ray 3.7: Parsing File")
539 if not self._render(depsgraph):
540 self.update_stats("", "POV-Ray 3.7: Not found")
541 # return
543 # r = scene.render
544 # compute resolution
545 # x = int(r.resolution_x * r.resolution_percentage * 0.01)
546 # y = int(r.resolution_y * r.resolution_percentage * 0.01)
548 # Wait for the file to be created
549 # XXX This is no more valid, as 3.7 always creates output file once render is finished!
550 parsing = re.compile(br"= \[Parsing\.\.\.\] =")
551 rendering = re.compile(br"= \[Rendering\.\.\.\] =")
552 percent = re.compile(r"\(([0-9]{1,3})%\)")
553 # print("***POV WAITING FOR FILE***")
555 data = b""
556 last_line = ""
557 while _test_wait():
558 # POV in Windows did not output its stdout/stderr, it displayed them in its GUI
559 # But now writes file
560 if self._is_windows:
561 self.update_stats("", "POV-Ray 3.7: Rendering File")
562 else:
563 t_data = self._process.stdout.read(10000)
564 if not t_data:
565 continue
567 data += t_data
568 # XXX This is working for UNIX, not sure whether it might need adjustments for
569 # other OSs
570 # First replace is for windows
571 t_data = str(t_data).replace('\\r\\n', '\\n').replace('\\r', '\r')
572 lines = t_data.split('\\n')
573 last_line += lines[0]
574 lines[0] = last_line
575 print('\n'.join(lines), end="")
576 last_line = lines[-1]
578 if rendering.search(data):
579 _pov_rendering = True
580 match = percent.findall(str(data))
581 if match:
582 self.update_stats("", "POV-Ray 3.7: Rendering File (%s%%)" % match[-1])
583 else:
584 self.update_stats("", "POV-Ray 3.7: Rendering File")
586 elif parsing.search(data):
587 self.update_stats("", "POV-Ray 3.7: Parsing File")
589 if os.path.exists(self._temp_file_out):
590 # print("***POV FILE OK***")
591 # self.update_stats("", "POV-Ray 3.7: Rendering")
593 # prev_size = -1
595 xmin = int(r.border_min_x * x)
596 ymin = int(r.border_min_y * y)
597 xmax = int(r.border_max_x * x)
598 ymax = int(r.border_max_y * y)
600 # print("***POV UPDATING IMAGE***")
601 result = self.begin_result(0, 0, x, y)
602 # XXX, tests for border render.
603 # result = self.begin_result(xmin, ymin, xmax - xmin, ymax - ymin)
604 # result = self.begin_result(0, 0, xmax - xmin, ymax - ymin)
605 lay = result.layers[0]
607 # This assumes the file has been fully written We wait a bit, just in case!
608 time.sleep(self.DELAY)
609 try:
610 lay.load_from_file(self._temp_file_out)
611 # XXX, tests for border render.
612 # lay.load_from_file(self._temp_file_out, xmin, ymin)
613 except RuntimeError:
614 print("***POV ERROR WHILE READING OUTPUT FILE***")
616 # Not needed right now, might only be useful if we find a way to use temp raw output of
617 # pov 3.7 (in which case it might go under _test_wait()).
619 def update_image():
620 # possible the image wont load early on.
621 try:
622 lay.load_from_file(self._temp_file_out)
623 # XXX, tests for border render.
624 #lay.load_from_file(self._temp_file_out, xmin, ymin)
625 #lay.load_from_file(self._temp_file_out, xmin, ymin)
626 except RuntimeError:
627 pass
629 # Update while POV-Ray renders
630 while True:
631 # print("***POV RENDER LOOP***")
633 # test if POV-Ray exists
634 if self._process.poll() is not None:
635 print("***POV PROCESS FINISHED***")
636 update_image()
637 break
639 # user exit
640 if self.test_break():
641 try:
642 self._process.terminate()
643 print("***POV PROCESS INTERRUPTED***")
644 except OSError:
645 pass
647 break
649 # Would be nice to redirect the output
650 # stdout_value, stderr_value = self._process.communicate() # locks
652 # check if the file updated
653 new_size = os.path.getsize(self._temp_file_out)
655 if new_size != prev_size:
656 update_image()
657 prev_size = new_size
659 time.sleep(self.DELAY)
662 self.end_result(result)
663 else:
664 print("***NO POV OUTPUT IMAGE***")
666 print("***POV INPUT FILE WRITTEN***")
668 # print(filename_log) #bring the pov log to blender console with proper path?
669 try:
670 with open(
671 self._temp_file_log, encoding='utf-8'
672 ) as f: # The with keyword automatically closes the file when you are done
673 msg = f.read()
674 if isinstance(msg, str):
675 stdmsg = msg
676 #decoded = False
677 elif type(msg) == bytes:
678 #stdmsg = msg.split('\n')
679 stdmsg = msg.encode('utf-8', "replace")
680 # stdmsg = msg.encode("utf-8", "replace")
682 # stdmsg = msg.decode(encoding)
683 # decoded = True
684 # msg.encode('utf-8').decode('utf-8')
685 stdmsg.replace("\t", " ")
686 print(stdmsg) # console_write(stdmsg) # todo fix segfault and use
687 except FileNotFoundError:
688 print("No render log to read")
689 self.update_stats("", "")
691 if scene.pov.tempfiles_enable or scene.pov.deletefiles_enable:
692 self._cleanup()
694 sound_on = bpy.context.preferences.addons[__package__].preferences.use_sounds
695 finished_render_message = "\'Et Voilà!\'"
697 if platform.startswith('win') and sound_on:
698 # Could not find tts Windows command so playing beeps instead :-)
699 # "Korobeiniki"(Коробе́йники)
700 # aka "A-Type" Tetris theme
701 import winsound
703 winsound.Beep(494, 250) # B
704 winsound.Beep(370, 125) # F
705 winsound.Beep(392, 125) # G
706 winsound.Beep(440, 250) # A
707 winsound.Beep(392, 125) # G
708 winsound.Beep(370, 125) # F#
709 winsound.Beep(330, 275) # E
710 winsound.Beep(330, 125) # E
711 winsound.Beep(392, 125) # G
712 winsound.Beep(494, 275) # B
713 winsound.Beep(440, 125) # A
714 winsound.Beep(392, 125) # G
715 winsound.Beep(370, 275) # F
716 winsound.Beep(370, 125) # F
717 winsound.Beep(392, 125) # G
718 winsound.Beep(440, 250) # A
719 winsound.Beep(494, 250) # B
720 winsound.Beep(392, 250) # G
721 winsound.Beep(330, 350) # E
722 time.sleep(0.5)
723 winsound.Beep(440, 250) # A
724 winsound.Beep(440, 150) # A
725 winsound.Beep(523, 125) # D8
726 winsound.Beep(659, 250) # E8
727 winsound.Beep(587, 125) # D8
728 winsound.Beep(523, 125) # C8
729 winsound.Beep(494, 250) # B
730 winsound.Beep(494, 125) # B
731 winsound.Beep(392, 125) # G
732 winsound.Beep(494, 250) # B
733 winsound.Beep(440, 150) # A
734 winsound.Beep(392, 125) # G
735 winsound.Beep(370, 250) # F#
736 winsound.Beep(370, 125) # F#
737 winsound.Beep(392, 125) # G
738 winsound.Beep(440, 250) # A
739 winsound.Beep(494, 250) # B
740 winsound.Beep(392, 250) # G
741 winsound.Beep(330, 300) # E
743 # Mac supports natively say command
744 elif platform == "darwin":
745 # We don't want the say command to block Python,
746 # so we add an ampersand after the message
747 # but if the os TTS package isn't up to date it
748 # still does thus, the try except clause
749 try:
750 os.system("say -v Amelie %s &" % finished_render_message)
751 except BaseException as e:
752 print(e.__doc__)
753 print("your Mac may need an update, try to restart computer")
754 pass
755 # While Linux frequently has espeak installed or at least can suggest
756 # Maybe windows could as well ?
757 elif platform == "linux":
758 # We don't want the espeak command to block Python,
759 # so we add an ampersand after the message
760 # but if the espeak TTS package isn't installed it
761 # still does thus, the try except clause
762 try:
763 os.system("echo %s | espeak &" % finished_render_message)
764 except BaseException as e:
765 print(e.__doc__)
766 pass
768 classes = (
769 PovRender,
773 def register():
774 for cls in classes:
775 register_class(cls)
779 def unregister():
780 for cls in reversed(classes):
781 unregister_class(cls)