Skip to content

Commit 1cccb30

Browse files
authored
6649 presets (#4)
Added some UI to select saved rendering presets. Load them and validate them. Make sure we don't have multiple outputs rendered before submitting the job.
1 parent dc50f65 commit 1cccb30

File tree

1 file changed

+148
-12
lines changed

1 file changed

+148
-12
lines changed

hooks/tk-multi-publish2/basic/publish_movie.py

Lines changed: 148 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
# file included in this repository.
44

55
import sgtk
6-
import os
76
import unreal
7+
from tank_vendor import six
8+
9+
import copy
10+
import datetime
11+
import os
812
import pprint
913
import subprocess
1014
import sys
11-
import datetime
1215
import tempfile
1316

17+
1418
HookBaseClass = sgtk.get_hook_baseclass()
1519

1620

@@ -42,7 +46,11 @@ def description(self):
4246
reference to the movie's current path on disk. A <b>Version</b> entry
4347
will also be created in Shotgun with the movie file being uploaded
4448
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+
"""
4654

4755
@property
4856
def settings(self):
@@ -75,6 +83,12 @@ def settings(self):
7583
"description": "Template path for published work files. Should"
7684
"correspond to a template defined in "
7785
"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"
7892
}
7993
}
8094

@@ -94,6 +108,85 @@ def item_filters(self):
94108
"""
95109
return ["unreal.asset.LevelSequence"]
96110

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+
97190
def accept(self, settings, item):
98191
"""
99192
Method called by the publisher to determine if an item is of any
@@ -230,10 +323,17 @@ def validate(self, settings, item):
230323

231324
# Check if we can use the Movie Render queue available from 4.26
232325
use_movie_render_queue = False
326+
render_presets = None
233327
if "MoviePipelineQueueEngineSubsystem" in dir(unreal):
234328
if "MoviePipelineAppleProResOutput" in dir(unreal):
235329
use_movie_render_queue = True
236330
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)
237337
else:
238338
self.logger.info(
239339
"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):
242342
else:
243343
self.logger.info("Movie Render Queue not available, Level Sequencer will be used for rendering.")
244344
item.properties["use_movie_render_queue"] = use_movie_render_queue
345+
item.properties["movie_render_queue_presets"] = render_presets
245346
# Set the UE movie extension based on the current platform and rendering engine
246347
if use_movie_render_queue:
247348
fields["ue_mov_ext"] = "mov" # mov on all platforms
@@ -265,6 +366,25 @@ def validate(self, settings, item):
265366

266367
return True
267368

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+
268388
def publish(self, settings, item):
269389
"""
270390
Executes the publish logic for the given item and settings.
@@ -300,8 +420,12 @@ def publish(self, settings, item):
300420
unreal.log("movie name: {}".format(movie_name))
301421
# Render the movie
302422
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)
305429
else:
306430
self.logger.info("Rendering %s with the Level Sequencer." % publish_path)
307431
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
505629

506630
return os.path.isfile(output_path), output_path
507631

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):
509633
"""
510634
Renders a given sequence in a given level with the Movie Render queue.
511635
512636
:param str output_path: Full path to the movie to render.
513637
:param str unreal_map_path: Path of the Unreal map in which to run the sequence.
514638
:param str sequence_path: Content Browser path of sequence to render.
639+
:param presets: Optional :class:`unreal.MoviePipelineMasterConfig` instance to use for renderig.
515640
:returns: True if a movie file was generated, False otherwise
516641
string representing the path of the generated movie file
517642
"""
@@ -523,21 +648,27 @@ def _unreal_render_sequence_with_movie_queue(self, output_path, unreal_map_path,
523648
job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob)
524649
job.sequence = unreal.SoftObjectPath(sequence_path)
525650
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.
527655
config = job.get_configuration()
528656
# https://docs.unrealengine.com/4.26/en-US/PythonAPI/class/MoviePipelineOutputSetting.html?highlight=setting#unreal.MoviePipelineOutputSetting
529657
output_setting = config.find_or_add_setting_by_class(unreal.MoviePipelineOutputSetting)
530658
output_setting.output_directory = unreal.DirectoryPath(output_folder)
531659
output_setting.output_resolution = unreal.IntPoint(1280, 720)
532660
output_setting.file_name_format = movie_name
533661
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)
534669
# Render to a movie
535670
config.find_or_add_setting_by_class(unreal.MoviePipelineAppleProResOutput)
536671
# 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)
541672

542673
# We render in a forked process that we can control.
543674
# 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,
625756
# This need to be a path relative the to the Unreal project "Saved" folder.
626757
"-MoviePipelineConfig=\"%s\"" % manifest_path,
627758
]
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"]
628764
self.logger.info("Running %s" % cmd_args)
629-
subprocess.call(cmd_args)
765+
subprocess.call(cmd_args, env=run_env)
630766
return os.path.isfile(output_path), output_path

0 commit comments

Comments
 (0)