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