3
3
# file included in this repository.
4
4
5
5
import sgtk
6
- import os
7
6
import unreal
7
+ from tank_vendor import six
8
+
9
+ import copy
10
+ import datetime
11
+ import os
8
12
import pprint
9
13
import subprocess
10
14
import sys
11
- import datetime
12
15
import tempfile
13
16
17
+
14
18
HookBaseClass = sgtk .get_hook_baseclass ()
15
19
16
20
@@ -42,7 +46,11 @@ def description(self):
42
46
reference to the movie's current path on disk. A <b>Version</b> entry
43
47
will also be created in Shotgun with the movie file being uploaded
44
48
there. Other users will be able to review the movie in the browser or
45
- in RV."""
49
+ in RV.
50
+ <br>
51
+ If available, the Movie Render Queue will be used for rendering,
52
+ the Level Sequencer will be used otherwise.
53
+ """
46
54
47
55
@property
48
56
def settings (self ):
@@ -75,6 +83,12 @@ def settings(self):
75
83
"description" : "Template path for published work files. Should"
76
84
"correspond to a template defined in "
77
85
"templates.yml." ,
86
+ },
87
+ "Movie Render Queue Presets Path" : {
88
+ "type" : "string" ,
89
+ "default" : None ,
90
+ "description" : "Optional Unreal Path to saved presets "
91
+ "for rendering with the Movie Render Queue"
78
92
}
79
93
}
80
94
@@ -94,6 +108,85 @@ def item_filters(self):
94
108
"""
95
109
return ["unreal.asset.LevelSequence" ]
96
110
111
+ def create_settings_widget (self , parent ):
112
+ """
113
+ Creates a Qt widget, for the supplied parent widget (a container widget
114
+ on the right side of the publish UI).
115
+
116
+ :param parent: The parent to use for the widget being created
117
+ :return: A :class:`QtGui.QFrame` that displays editable widgets for
118
+ modifying the plugin's settings.
119
+ """
120
+ # defer Qt-related imports
121
+ from sgtk .platform .qt import QtGui , QtCore
122
+
123
+ # Create a QFrame with all our widgets
124
+ settings_frame = QtGui .QFrame (parent )
125
+ # Create our widgets, we add them as properties on the QFrame so we can
126
+ # retrieve them easily. Qt uses camelCase so our xxxx_xxxx names can't
127
+ # clash with existing Qt properties.
128
+
129
+ # Show this plugin description
130
+ settings_frame .description_label = QtGui .QLabel (self .description )
131
+ settings_frame .description_label .setWordWrap (True )
132
+ settings_frame .description_label .setOpenExternalLinks (True )
133
+ settings_frame .description_label .setTextFormat (QtCore .Qt .RichText )
134
+
135
+ # Unreal setttings
136
+ settings_frame .unreal_render_presets_label = QtGui .QLabel ("Render with Movie Pipeline Presets:" )
137
+ settings_frame .unreal_render_presets_widget = QtGui .QComboBox ()
138
+ settings_frame .unreal_render_presets_widget .addItem ("No presets" )
139
+ presets_folder = unreal .MovieRenderPipelineProjectSettings ().preset_save_dir
140
+ for preset in unreal .EditorAssetLibrary .list_assets (presets_folder .path ):
141
+ settings_frame .unreal_render_presets_widget .addItem (preset .split ("." )[0 ])
142
+ # Create the layout to use within the QFrame
143
+ settings_layout = QtGui .QVBoxLayout ()
144
+ settings_layout .addWidget (settings_frame .description_label )
145
+ settings_layout .addWidget (settings_frame .unreal_render_presets_label )
146
+ settings_layout .addWidget (settings_frame .unreal_render_presets_widget )
147
+
148
+ settings_layout .addStretch ()
149
+ settings_frame .setLayout (settings_layout )
150
+ return settings_frame
151
+
152
+ def get_ui_settings (self , widget ):
153
+ """
154
+ Method called by the publisher to retrieve setting values from the UI.
155
+
156
+ :returns: A dictionary with setting values.
157
+ """
158
+ self .logger .info ("Getting settings from UI" )
159
+
160
+ # Please note that we don't have to return all settings here, just the
161
+ # settings which are editable in the UI.
162
+ render_presets_path = None
163
+ if widget .unreal_render_presets_widget .currentIndex () > 0 : # First entry is "No Presets"
164
+ render_presets_path = six .ensure_str (widget .unreal_render_presets_widget .currentText ())
165
+ settings = {
166
+ "Movie Render Queue Presets Path" : render_presets_path ,
167
+ }
168
+ return settings
169
+
170
+ def set_ui_settings (self , widget , settings ):
171
+ """
172
+ Method called by the publisher to populate the UI with the setting values.
173
+
174
+ :param widget: A QFrame we created in `create_settings_widget`.
175
+ :param settings: A list of dictionaries.
176
+ :raises NotImplementedError: if editing multiple items.
177
+ """
178
+ self .logger .info ("Setting UI settings" )
179
+ if len (settings ) > 1 :
180
+ # We do not allow editing multiple items
181
+ raise NotImplementedError
182
+ cur_settings = settings [0 ]
183
+ render_presets_path = cur_settings ["Movie Render Queue Presets Path" ]
184
+ preset_index = 0
185
+ if render_presets_path :
186
+ preset_index = widget .unreal_render_presets_widget .findText (render_presets_path )
187
+ self .logger .info ("Index for %s is %s" % (render_presets_path , preset_index ))
188
+ widget .unreal_render_presets_widget .setCurrentIndex (preset_index )
189
+
97
190
def accept (self , settings , item ):
98
191
"""
99
192
Method called by the publisher to determine if an item is of any
@@ -230,10 +323,17 @@ def validate(self, settings, item):
230
323
231
324
# Check if we can use the Movie Render queue available from 4.26
232
325
use_movie_render_queue = False
326
+ render_presets = None
233
327
if "MoviePipelineQueueEngineSubsystem" in dir (unreal ):
234
328
if "MoviePipelineAppleProResOutput" in dir (unreal ):
235
329
use_movie_render_queue = True
236
330
self .logger .info ("Movie Render Queue will be used for rendering." )
331
+ render_presets_path = settings ["Movie Render Queue Presets Path" ].value
332
+ if render_presets_path :
333
+ self .logger .info ("Validating render presets path %s" % render_presets_path )
334
+ render_presets = unreal .EditorAssetLibrary .load_asset (render_presets_path )
335
+ for _ , reason in self ._check_render_settings (render_presets ):
336
+ self .logger .warning (reason )
237
337
else :
238
338
self .logger .info (
239
339
"Apple ProRes Media plugin must be loaded to be able to render with the Movie Render Queue, "
@@ -242,6 +342,7 @@ def validate(self, settings, item):
242
342
else :
243
343
self .logger .info ("Movie Render Queue not available, Level Sequencer will be used for rendering." )
244
344
item .properties ["use_movie_render_queue" ] = use_movie_render_queue
345
+ item .properties ["movie_render_queue_presets" ] = render_presets
245
346
# Set the UE movie extension based on the current platform and rendering engine
246
347
if use_movie_render_queue :
247
348
fields ["ue_mov_ext" ] = "mov" # mov on all platforms
@@ -265,6 +366,25 @@ def validate(self, settings, item):
265
366
266
367
return True
267
368
369
+ def _check_render_settings (self , render_config ):
370
+ """
371
+ Check settings from the given render preset and report which ones are problematic and why.
372
+
373
+ :param render_config: An Unreal Movie Pipeline render config.
374
+ :returns: A potentially empty list of tuples, where each tuple is a setting and a string explaining the problem.
375
+ """
376
+ invalid_settings = []
377
+ # To avoid having multiple outputs, only keep the main render pass and the expected output format.
378
+ for setting in render_config .get_all_settings ():
379
+ # Check for render passes. Since some classes derive from MoviePipelineDeferredPassBase, which is what we want to only keep
380
+ # we can't use isinstance and use type instead.
381
+ if isinstance (setting , unreal .MoviePipelineImagePassBase ) and type (setting ) != unreal .MoviePipelineDeferredPassBase :
382
+ invalid_settings .append ((setting , "Render pass %s would cause multiple outputs" % setting .get_name ()))
383
+ # Check rendering outputs
384
+ elif isinstance (setting , unreal .MoviePipelineOutputBase ) and not isinstance (setting , unreal .MoviePipelineAppleProResOutput ):
385
+ invalid_settings .append ((setting , "Render output %s would cause multiple outputs" % setting .get_name ()))
386
+ return invalid_settings
387
+
268
388
def publish (self , settings , item ):
269
389
"""
270
390
Executes the publish logic for the given item and settings.
@@ -300,8 +420,12 @@ def publish(self, settings, item):
300
420
unreal .log ("movie name: {}" .format (movie_name ))
301
421
# Render the movie
302
422
if item .properties .get ("use_movie_render_queue" ):
303
- self .logger .info ("Rendering %s with the Movie Render Queue." % publish_path )
304
- self ._unreal_render_sequence_with_movie_queue (publish_path , unreal_map_path , unreal_asset_path )
423
+ presets = item .properties ["movie_render_queue_presets" ]
424
+ if presets :
425
+ self .logger .info ("Rendering %s with the Movie Render Queue with %s presets." % (publish_path , presets .get_name ()))
426
+ else :
427
+ self .logger .info ("Rendering %s with the Movie Render Queue." % publish_path )
428
+ self ._unreal_render_sequence_with_movie_queue (publish_path , unreal_map_path , unreal_asset_path , presets )
305
429
else :
306
430
self .logger .info ("Rendering %s with the Level Sequencer." % publish_path )
307
431
self ._unreal_render_sequence_with_sequencer (publish_path , unreal_map_path , unreal_asset_path )
@@ -505,13 +629,14 @@ def _unreal_render_sequence_with_sequencer(self, output_path, unreal_map_path, s
505
629
506
630
return os .path .isfile (output_path ), output_path
507
631
508
- def _unreal_render_sequence_with_movie_queue (self , output_path , unreal_map_path , sequence_path ):
632
+ def _unreal_render_sequence_with_movie_queue (self , output_path , unreal_map_path , sequence_path , presets = None ):
509
633
"""
510
634
Renders a given sequence in a given level with the Movie Render queue.
511
635
512
636
:param str output_path: Full path to the movie to render.
513
637
:param str unreal_map_path: Path of the Unreal map in which to run the sequence.
514
638
:param str sequence_path: Content Browser path of sequence to render.
639
+ :param presets: Optional :class:`unreal.MoviePipelineMasterConfig` instance to use for renderig.
515
640
:returns: True if a movie file was generated, False otherwise
516
641
string representing the path of the generated movie file
517
642
"""
@@ -523,21 +648,27 @@ def _unreal_render_sequence_with_movie_queue(self, output_path, unreal_map_path,
523
648
job = queue .allocate_new_job (unreal .MoviePipelineExecutorJob )
524
649
job .sequence = unreal .SoftObjectPath (sequence_path )
525
650
job .map = unreal .SoftObjectPath (unreal_map_path )
526
- # Set settings
651
+ # Set settings from presets, if any
652
+ if presets :
653
+ job .set_preset_origin (presets )
654
+ # Ensure the settings we need are set.
527
655
config = job .get_configuration ()
528
656
# https://docs.unrealengine.com/4.26/en-US/PythonAPI/class/MoviePipelineOutputSetting.html?highlight=setting#unreal.MoviePipelineOutputSetting
529
657
output_setting = config .find_or_add_setting_by_class (unreal .MoviePipelineOutputSetting )
530
658
output_setting .output_directory = unreal .DirectoryPath (output_folder )
531
659
output_setting .output_resolution = unreal .IntPoint (1280 , 720 )
532
660
output_setting .file_name_format = movie_name
533
661
output_setting .override_existing_output = True # Overwrite existing files
662
+ # Remove problematic settings
663
+ for setting , reason in self ._check_render_settings (config ):
664
+ self .logger .warning ("Disabling %s: %s." % (setting .get_name (), reason ))
665
+ config .remove_setting (setting )
666
+
667
+ # Default rendering
668
+ config .find_or_add_setting_by_class (unreal .MoviePipelineDeferredPassBase )
534
669
# Render to a movie
535
670
config .find_or_add_setting_by_class (unreal .MoviePipelineAppleProResOutput )
536
671
# TODO: check which codec we should use.
537
- # Default rendering
538
- config .find_or_add_setting_by_class (unreal .MoviePipelineDeferredPassBase )
539
- # Additional pass with detailed lighting?
540
- # render_pass = config.find_or_add_setting_by_class(unreal.MoviePipelineDeferredPass_DetailLighting)
541
672
542
673
# We render in a forked process that we can control.
543
674
# It would be possible to render in from the running process using an
@@ -625,6 +756,11 @@ def _unreal_render_sequence_with_movie_queue(self, output_path, unreal_map_path,
625
756
# This need to be a path relative the to the Unreal project "Saved" folder.
626
757
"-MoviePipelineConfig=\" %s\" " % manifest_path ,
627
758
]
759
+ # Make a shallow copy of the current environment and clear some variables
760
+ run_env = copy .copy (os .environ )
761
+ # Prevent SG TK to try to bootstrap in the new process
762
+ if "UE_SHOTGUN_BOOTSTRAP" in run_env :
763
+ del run_env ["UE_SHOTGUN_BOOTSTRAP" ]
628
764
self .logger .info ("Running %s" % cmd_args )
629
- subprocess .call (cmd_args )
765
+ subprocess .call (cmd_args , env = run_env )
630
766
return os .path .isfile (output_path ), output_path
0 commit comments