33
33
import xml .dom .minidom as xml
34
34
import functools
35
35
36
- from PyQt5 .QtCore import QSize , Qt , QEvent , QObject , QThread , pyqtSlot , pyqtSignal , QMetaObject , Q_ARG , QTimer
37
- from PyQt5 .QtGui import *
38
- from PyQt5 .QtWidgets import *
36
+ from PyQt5 .QtCore import (
37
+ Qt , QObject , pyqtSlot , pyqtSignal , QMetaObject , Q_ARG , QThread , QTimer , QSize ,
38
+ )
39
+ from PyQt5 .QtWidgets import (
40
+ QApplication , QListView , QMessageBox , QColorDialog ,
41
+ QComboBox , QDoubleSpinBox , QLabel , QPushButton , QLineEdit , QPlainTextEdit ,
42
+ )
43
+ from PyQt5 .QtGui import QColor , QImage , QPixmap
39
44
40
45
from classes import info
41
46
from classes .logger import log
44
49
from classes .app import get_app
45
50
from windows .models .blender_model import BlenderModel
46
51
47
- import json
48
-
49
52
50
53
class BlenderListView (QListView ):
51
- """ A TreeView QWidget used on the animated title window """
54
+ """ A ListView QWidget used on the animated title window """
52
55
53
56
def currentChanged (self , selected , deselected ):
54
57
# Get selected item
@@ -74,7 +77,7 @@ def currentChanged(self, selected, deselected):
74
77
self .generateUniqueFolder ()
75
78
76
79
# Loop through params
77
- for param in animation .get ("params" ,[]):
80
+ for param in animation .get ("params" , []):
78
81
log .info ('Using parameter %s: %s' % (param ["name" ], param ["title" ]))
79
82
80
83
# Is Hidden Param?
@@ -295,7 +298,6 @@ def init_slider_values(self):
295
298
def btnRefresh_clicked (self , checked ):
296
299
297
300
# Render current frame
298
- preview_frame_number = self .win .sliderPreview .value ()
299
301
self .preview_timer .start ()
300
302
301
303
@pyqtSlot ()
@@ -360,62 +362,40 @@ def get_animation_details(self):
360
362
361
363
# Get list of params
362
364
animation = {"title" : animation_title , "path" : xml_path , "service" : service , "params" : []}
363
- xml_params = xmldoc .getElementsByTagName ("param" )
364
365
365
366
# Loop through params
366
- for param in xml_params :
367
- param_item = {}
367
+ for param in xmldoc .getElementsByTagName ("param" ):
368
+ # Set up item dict, "default" key is required
369
+ param_item = {"default" : "" }
368
370
369
371
# Get details of param
370
- if param .attributes ["title" ]:
371
- param_item ["title" ] = param .attributes ["title" ].value
372
-
373
- if param .attributes ["description" ]:
374
- param_item ["description" ] = param .attributes ["description" ].value
375
-
376
- if param .attributes ["name" ]:
377
- param_item ["name" ] = param .attributes ["name" ].value
378
-
379
- if param .attributes ["type" ]:
380
- param_item ["type" ] = param .attributes ["type" ].value
381
-
382
- if param .getElementsByTagName ("min" ):
383
- param_item ["min" ] = param .getElementsByTagName ("min" )[0 ].childNodes [0 ].data
384
-
385
- if param .getElementsByTagName ("max" ):
386
- param_item ["max" ] = param .getElementsByTagName ("max" )[0 ].childNodes [0 ].data
372
+ for att in ["title" , "description" , "name" , "type" ]:
373
+ if param .attributes [att ]:
374
+ param_item [att ] = param .attributes [att ].value
387
375
388
- if param .getElementsByTagName ("step" ):
389
- param_item ["step" ] = param .getElementsByTagName ("step" )[0 ].childNodes [0 ].data
376
+ for tag in ["min" , "max" , "step" , "digits" , "default" ]:
377
+ for p in param .getElementsByTagName (tag ):
378
+ if p .childNodes :
379
+ param_item [tag ] = p .firstChild .data
390
380
391
- if param .getElementsByTagName ("digits" ):
392
- param_item ["digits" ] = param .getElementsByTagName ("digits" )[0 ].childNodes [0 ].data
393
-
394
- if param .getElementsByTagName ("default" ):
395
- if param .getElementsByTagName ("default" )[0 ].childNodes :
396
- param_item ["default" ] = param .getElementsByTagName ("default" )[0 ].childNodes [0 ].data
397
- else :
398
- param_item ["default" ] = ""
399
-
400
- param_item ["values" ] = {}
401
- values = param .getElementsByTagName ("value" )
402
- for value in values :
403
- # Get list of values
404
- name = ""
405
- num = ""
406
-
407
- if value .attributes ["name" ]:
408
- name = value .attributes ["name" ].value
409
-
410
- if value .attributes ["num" ]:
411
- num = value .attributes ["num" ].value
412
-
413
- # add to parameter
414
- param_item ["values" ][name ] = num
381
+ try :
382
+ # Build values dict from list of (name, num) tuples
383
+ param_item ["values" ] = dict ([
384
+ (p .attributes ["name" ].value , p .attributes ["num" ].value )
385
+ for p in param .getElementsByTagName ("value" ) if (
386
+ "name" in p .attributes and "num" in p .attributes
387
+ )
388
+ ])
389
+ except (TypeError , AttributeError ) as ex :
390
+ log .warn ("XML parser: {}" .format (ex ))
391
+ pass
415
392
416
393
# Append param object to list
417
394
animation ["params" ].append (param_item )
418
395
396
+ # Free up XML document memory
397
+ xmldoc .unlink ()
398
+
419
399
# Return animation dictionary
420
400
return animation
421
401
@@ -588,7 +568,7 @@ def Render(self, frame=None):
588
568
589
569
def __init__ (self , * args ):
590
570
# Invoke parent init
591
- QTreeView .__init__ (self , * args )
571
+ super () .__init__ (* args )
592
572
593
573
# Get a reference to the window object
594
574
self .app = get_app ()
@@ -633,7 +613,6 @@ def __init__(self, *args):
633
613
# Refresh view
634
614
self .refresh_view ()
635
615
636
-
637
616
# Background Worker Thread (for Blender process)
638
617
self .background = QThread (self )
639
618
self .worker = Worker () # no parent!
@@ -695,20 +674,21 @@ def Render(self, blend_file_path, target_script, preview_mode=False):
695
674
# Init regex expression used to determine blender's render progress
696
675
s = settings .get_settings ()
697
676
677
+ _ = get_app ()._tr
678
+
698
679
# get the blender executable path
699
680
self .blender_exec_path = s .get ("blender_command" )
700
- self .blender_frame_expression = re .compile (r"Fra:([0-9,]*).*Mem:(.*?) .*Sce:" )
701
- self .blender_saved_expression = re .compile (r"Saved: '(.*.png)(.*)'" )
702
- self .blender_version = re .compile (r"Blender (.*?) " )
703
- self .blend_file_path = blend_file_path
704
- self .target_script = target_script
705
681
self .preview_mode = preview_mode
706
682
self .frame_detected = False
683
+ self .last_frame = 0
707
684
self .version = None
708
685
self .command_output = ""
709
686
self .process = None
710
- self .is_running = True
711
- _ = get_app ()._tr
687
+ self .is_running = False
688
+
689
+ blender_frame_re = re .compile (r"Fra:([0-9,]*)" )
690
+ blender_saved_re = re .compile (r"Saved: '(.*\.png)" )
691
+ blender_version_re = re .compile (r"Blender (.*?) " )
712
692
713
693
startupinfo = None
714
694
if sys .platform == 'win32' :
@@ -718,79 +698,99 @@ def Render(self, blend_file_path, target_script, preview_mode=False):
718
698
try :
719
699
# Shell the blender command to create the image sequence
720
700
command_get_version = [self .blender_exec_path , '-v' ]
721
- command_render = [self .blender_exec_path , '-b' , self . blend_file_path , '-P' , self . target_script ]
701
+ command_render = [self .blender_exec_path , '-b' , blend_file_path , '-P' , target_script ]
722
702
723
703
# Check the version of Blender
724
704
import shlex
725
- log .info ("Checking Blender version, command: {}" .format (" " .join ([shlex .quote (x ) for x in command_get_version ])))
705
+ log .info ("Checking Blender version, command: {}" .format (
706
+ " " .join ([shlex .quote (x ) for x in command_get_version ])))
726
707
727
- self .process = subprocess .Popen (command_get_version , stdout = subprocess .PIPE , stderr = subprocess .PIPE , startupinfo = startupinfo , universal_newlines = True )
708
+ proc = subprocess .Popen (
709
+ command_get_version ,
710
+ stdout = subprocess .PIPE , stderr = subprocess .STDOUT ,
711
+ startupinfo = startupinfo ,
712
+ )
728
713
729
714
# Check the version of Blender
730
- self .version = self .blender_version .findall (self .process .stdout .readline ())
715
+ try :
716
+ # Give Blender up to 10 seconds to respond
717
+ (out , err ) = proc .communicate (timeout = 10 )
718
+ except subprocess .TimeoutExpired :
719
+ self .blender_error_nodata .emit ()
720
+ return
721
+
722
+ ver_string = out .decode ('utf-8' )
723
+ ver_match = blender_version_re .search (ver_string )
724
+
725
+ if not ver_match :
726
+ raise Exception ("No Blender version detected in output" )
731
727
732
- if self .version :
733
- if self .version [0 ] < info .BLENDER_MIN_VERSION :
734
- # change cursor to "default" and stop running blender command
735
- self .is_running = False
728
+ self .version = ver_match .group (1 )
729
+ log .info ("Found Blender version {}" .format (self .version ))
736
730
737
- # Wrong version of Blender.
738
- self .blender_version_error .emit (self .version [0 ])
739
- return
731
+ if self .version < info .BLENDER_MIN_VERSION :
732
+ # Wrong version of Blender.
733
+ self .blender_version_error .emit (self .version )
734
+ return
740
735
741
736
# debug info
742
- log .info ("Running Blender, command: {}" .format (" " .join ([shlex .quote (x ) for x in command_render ])))
737
+ log .info ("Running Blender, command: {}" .format (
738
+ " " .join ([shlex .quote (x ) for x in command_render ])))
743
739
log .info ("Blender output:" )
744
740
745
741
# Run real command to render Blender project
746
- self .process = subprocess .Popen (command_render , stdout = subprocess .PIPE , stderr = subprocess .STDOUT , startupinfo = startupinfo , universal_newlines = True )
747
-
748
- except :
742
+ proc = subprocess .Popen (
743
+ command_render , bufsize = 512 ,
744
+ stdout = subprocess .PIPE , stderr = subprocess .STDOUT ,
745
+ startupinfo = startupinfo ,
746
+ )
747
+ self .process = proc
748
+ self .is_running = True
749
+
750
+ except subprocess .SubprocessError :
749
751
# Error running command. Most likely the blender executable path in
750
752
# the settings is incorrect, or is not a supported Blender version
751
753
self .is_running = False
752
754
self .blender_error_nodata .emit ()
755
+ raise
756
+ except Exception as ex :
757
+ log .error ("{}" .format (ex ))
753
758
return
754
759
755
- while self .is_running and self .process .poll () is None :
756
-
757
- # Look for progress info in the Blender Output
758
- line = self .process .stdout .readline ().strip ()
760
+ while self .is_running and proc .poll () is None :
761
+ for outline in iter (proc .stdout .readline , b'' ):
762
+ line = outline .decode ('utf-8' ).strip ()
759
763
760
- # Skip blank lines
761
- if not line :
762
- continue
764
+ # Skip blank output
765
+ if not line :
766
+ continue
763
767
764
- # append all output into a variable, and log
765
- self .command_output = self .command_output + line + "\n "
766
- log .info (" {}" .format (line ))
768
+ # append all output into a variable, and log
769
+ self .command_output = self .command_output + line + "\n "
770
+ log .info (" {}" .format (line ))
767
771
768
- output_frame = self .blender_frame_expression .findall (line )
772
+ # Look for progress info in the Blender Output
773
+ output_frame = blender_frame_re .search (line )
774
+ output_saved = blender_saved_re .search (line )
769
775
770
- # Does it have a match?
771
- if output_frame :
772
- # Yes, we have a match
773
- self .frame_detected = True
774
- current_frame = output_frame [0 ][0 ]
775
- memory = output_frame [0 ][1 ]
776
+ # Does it have a match?
777
+ if output_frame or output_saved :
778
+ # Yes, we have a match
779
+ self .frame_detected = True
776
780
777
- # Update progress bar
778
- if not self .preview_mode :
779
- # only update progress if in 'render' mode
780
- self .progress .emit (int (current_frame ))
781
+ if output_frame :
782
+ current_frame = int (output_frame .group (1 ))
781
783
782
- # Look for progress info in the Blender Output
783
- output_saved = self .blender_saved_expression .findall (line )
784
+ # Update progress bar
785
+ if current_frame != self .last_frame and not self .preview_mode :
786
+ # update progress on frame change, if in 'render' mode
787
+ self .progress .emit (current_frame )
784
788
785
- # Does it have a match?
786
- if output_saved :
787
- # Yes, we have a match
788
- self .frame_detected = True
789
- image_path = output_saved [0 ][0 ]
790
- time_saved = output_saved [0 ][1 ]
789
+ self .last_frame = current_frame
791
790
792
- # Update preview image
793
- self .image_updated .emit (image_path )
791
+ if output_saved :
792
+ # Update preview image
793
+ self .image_updated .emit (output_saved .group (1 ))
794
794
795
795
log .info ("Blender process exited." )
796
796
0 commit comments