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