Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Unreal: Rendering implementation #2410

Merged
merged 27 commits into from
Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
10fa0ee
Implemented creator for render
simonebarbieri Dec 15, 2021
a9a8a4f
Merge branch 'develop' into feature/unreal-rendering
simonebarbieri Jan 31, 2022
4ff7cf6
Loading layouts and cameras now create level sequences for hierarchy
simonebarbieri Jan 31, 2022
5efc23c
Added button for starting the rendering of the selected instance
simonebarbieri Jan 31, 2022
67339b4
Implemented extraction of renders
simonebarbieri Jan 31, 2022
2966068
Layout and Cameras create the level and sequence hierarchy structure
simonebarbieri Feb 15, 2022
9e72081
Animation are added to sequence when loaded
simonebarbieri Feb 16, 2022
d11a871
Changed logic to obtain min and max frame of sequences
simonebarbieri Feb 17, 2022
ce4984d
Camera is now saved in the right level
simonebarbieri Feb 17, 2022
fa64a26
Removed debug prints
simonebarbieri Feb 17, 2022
26d63e6
Fix start and end frames of sequences
simonebarbieri Feb 18, 2022
55bcd6b
Set correct start and end frames for existing sequences
simonebarbieri Feb 23, 2022
10c6d7b
Add an empty Camera Cut Track to the sequences in the hierarchy
simonebarbieri Feb 23, 2022
6bb615e
Set correct start and end frame for camera sequences
simonebarbieri Feb 23, 2022
35f5e4a
Render from the master sequence and output keeps the hierarchy
simonebarbieri Mar 2, 2022
5d8bac3
Fixed frame range and frame rate for shot sequences
simonebarbieri Mar 3, 2022
468a5e1
Hound fixes
simonebarbieri Mar 3, 2022
df02c64
More hound fixes
simonebarbieri Mar 3, 2022
651220f
Merge branch 'develop' into feature/unreal-rendering
simonebarbieri Mar 7, 2022
e127e08
Fix paths for loading animations
simonebarbieri Mar 8, 2022
ef726be
Activates MovieRenderPipeline plugin when creating Unreal project
simonebarbieri Mar 8, 2022
fedc09f
Set bound scale for rig actors loaded with layout
simonebarbieri Mar 8, 2022
88a59bc
Fixed class name for Render Publish Instance
simonebarbieri Mar 10, 2022
df07b51
Merge branch 'develop' into feature/unreal-rendering
simonebarbieri Mar 18, 2022
8f8a4ef
Fixed import problems
simonebarbieri Mar 18, 2022
ca369aa
Merge branch 'develop' into feature/unreal-rendering
simonebarbieri Apr 28, 2022
ba537e2
Fix old avalon-core import
simonebarbieri Apr 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed .gitmodules
Empty file.
130 changes: 130 additions & 0 deletions openpype/hosts/unreal/api/rendering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import unreal

from openpype.hosts.unreal.api import pipeline


queue = None
executor = None


def _queue_finish_callback(exec, success):
unreal.log("Render completed. Success: " + str(success))

# Delete our reference so we don't keep it alive.
global executor
global queue
del executor
del queue


def _job_finish_callback(job, success):
# You can make any edits you want to the editor world here, and the world
# will be duplicated when the next render happens. Make sure you undo your
# edits in OnQueueFinishedCallback if you don't want to leak state changes
# into the editor world.
unreal.log("Individual job completed.")


def start_rendering():
"""
Start the rendering process.
"""
print("Starting rendering...")

# Get selected sequences
assets = unreal.EditorUtilityLibrary.get_selected_assets()

# instances = pipeline.ls_inst()
instances = [
a for a in assets
if a.get_class().get_name() == "OpenPypePublishInstance"]

inst_data = []

for i in instances:
data = pipeline.parse_container(i.get_path_name())
if data["family"] == "render":
inst_data.append(data)

# subsystem = unreal.get_editor_subsystem(
# unreal.MoviePipelineQueueSubsystem)
# queue = subsystem.get_queue()
global queue
queue = unreal.MoviePipelineQueue()

ar = unreal.AssetRegistryHelpers.get_asset_registry()

for i in inst_data:
sequence = ar.get_asset_by_object_path(i["sequence"]).get_asset()

sequences = [{
"sequence": sequence,
"output": f"{i['subset']}/{sequence.get_name()}",
"frame_range": (
int(float(i["startFrame"])),
int(float(i["endFrame"])) + 1)
}]
render_list = []

# Get all the sequences to render. If there are subsequences,
# add them and their frame ranges to the render list. We also
# use the names for the output paths.
for s in sequences:
tracks = s.get('sequence').get_master_tracks()
subscene_track = None
for t in tracks:
if t.get_class() == unreal.MovieSceneSubTrack.static_class():
subscene_track = t
if subscene_track is not None and subscene_track.get_sections():
subscenes = subscene_track.get_sections()

for ss in subscenes:
sequences.append({
"sequence": ss.get_sequence(),
"output": (f"{s.get('output')}/"
f"{ss.get_sequence().get_name()}"),
"frame_range": (
ss.get_start_frame(), ss.get_end_frame())
})
else:
# Avoid rendering camera sequences
if "_camera" not in s.get('sequence').get_name():
render_list.append(s)

# Create the rendering jobs and add them to the queue.
for r in render_list:
job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob)
job.sequence = unreal.SoftObjectPath(i["master_sequence"])
job.map = unreal.SoftObjectPath(i["master_level"])
job.author = "OpenPype"

# User data could be used to pass data to the job, that can be
# read in the job's OnJobFinished callback. We could,
# for instance, pass the AvalonPublishInstance's path to the job.
# job.user_data = ""

settings = job.get_configuration().find_or_add_setting_by_class(
unreal.MoviePipelineOutputSetting)
settings.output_resolution = unreal.IntPoint(1920, 1080)
settings.custom_start_frame = r.get("frame_range")[0]
settings.custom_end_frame = r.get("frame_range")[1]
settings.use_custom_playback_range = True
settings.file_name_format = "{sequence_name}.{frame_number}"
settings.output_directory.path += r.get('output')

renderPass = job.get_configuration().find_or_add_setting_by_class(
unreal.MoviePipelineDeferredPassBase)
renderPass.disable_multisample_effects = True

job.get_configuration().find_or_add_setting_by_class(
unreal.MoviePipelineImageSequenceOutput_PNG)

# If there are jobs in the queue, start the rendering process.
if queue.get_jobs():
global executor
executor = unreal.MoviePipelinePIEExecutor()
executor.on_executor_finished_delegate.add_callable_unique(
_queue_finish_callback)
executor.on_individual_job_finished_delegate.add_callable_unique(
_job_finish_callback) # Only available on PIE Executor
executor.execute(queue)
7 changes: 7 additions & 0 deletions openpype/hosts/unreal/api/tools_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
)
from openpype.tools.utils import host_tools
from openpype.tools.utils.lib import qt_app_context
from openpype.hosts.unreal.api import rendering


class ToolsBtnsWidget(QtWidgets.QWidget):
Expand All @@ -20,6 +21,7 @@ def __init__(self, parent=None):
load_btn = QtWidgets.QPushButton("Load...", self)
publish_btn = QtWidgets.QPushButton("Publish...", self)
manage_btn = QtWidgets.QPushButton("Manage...", self)
render_btn = QtWidgets.QPushButton("Render...", self)
experimental_tools_btn = QtWidgets.QPushButton(
"Experimental tools...", self
)
Expand All @@ -30,13 +32,15 @@ def __init__(self, parent=None):
layout.addWidget(load_btn, 0)
layout.addWidget(publish_btn, 0)
layout.addWidget(manage_btn, 0)
layout.addWidget(render_btn, 0)
layout.addWidget(experimental_tools_btn, 0)
layout.addStretch(1)

create_btn.clicked.connect(self._on_create)
load_btn.clicked.connect(self._on_load)
publish_btn.clicked.connect(self._on_publish)
manage_btn.clicked.connect(self._on_manage)
render_btn.clicked.connect(self._on_render)
experimental_tools_btn.clicked.connect(self._on_experimental)

def _on_create(self):
Expand All @@ -51,6 +55,9 @@ def _on_publish(self):
def _on_manage(self):
self.tool_required.emit("sceneinventory")

def _on_render(self):
rendering.start_rendering()

def _on_experimental(self):
self.tool_required.emit("experimental_tools")

Expand Down
1 change: 1 addition & 0 deletions openpype/hosts/unreal/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ def create_unreal_project(project_name: str,
{"Name": "PythonScriptPlugin", "Enabled": True},
{"Name": "EditorScriptingUtilities", "Enabled": True},
{"Name": "SequencerScripting", "Enabled": True},
{"Name": "MovieRenderPipeline", "Enabled": True},
{"Name": "OpenPype", "Enabled": True}
]
}
Expand Down
7 changes: 4 additions & 3 deletions openpype/hosts/unreal/plugins/create/create_layout.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from unreal import EditorLevelLibrary as ell
from unreal import EditorLevelLibrary

from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api.pipeline import instantiate

Expand Down Expand Up @@ -28,13 +29,13 @@ def process(self):
# sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
# selection = [a.get_path_name() for a in sel_objects]

data["level"] = ell.get_editor_world().get_path_name()
data["level"] = EditorLevelLibrary.get_editor_world().get_path_name()

data["members"] = []

if (self.options or {}).get("useSelection"):
# Set as members the selected actors
for actor in ell.get_selected_level_actors():
for actor in EditorLevelLibrary.get_selected_level_actors():
data["members"].append("{}.{}".format(
actor.get_outer().get_name(), actor.get_name()))

Expand Down
106 changes: 106 additions & 0 deletions openpype/hosts/unreal/plugins/create/create_render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import unreal

from openpype.pipeline import legacy_io
from openpype.hosts.unreal.api import pipeline
from openpype.hosts.unreal.api.plugin import Creator


class CreateRender(Creator):
"""Create instance for sequence for rendering"""

name = "unrealRender"
label = "Unreal - Render"
family = "render"
icon = "cube"
asset_types = ["LevelSequence"]

root = "/Game/AvalonInstances"
suffix = "_INS"

def __init__(self, *args, **kwargs):
super(CreateRender, self).__init__(*args, **kwargs)

def process(self):
name = self.data["subset"]

ar = unreal.AssetRegistryHelpers.get_asset_registry()

# Get the master sequence and the master level.
# There should be only one sequence and one level in the directory.
filter = unreal.ARFilter(
class_names=["LevelSequence"],
package_paths=[f"/Game/OpenPype/{self.data['asset']}"],
recursive_paths=False)
sequences = ar.get_assets(filter)
ms = sequences[0].object_path
filter = unreal.ARFilter(
class_names=["World"],
package_paths=[f"/Game/OpenPype/{self.data['asset']}"],
recursive_paths=False)
levels = ar.get_assets(filter)
ml = levels[0].object_path

selection = []
if (self.options or {}).get("useSelection"):
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
selection = [
a.get_path_name() for a in sel_objects
if a.get_class().get_name() in self.asset_types]

unreal.log("selection: {}".format(selection))
# instantiate(self.root, name, self.data, selection, self.suffix)
# container_name = "{}{}".format(name, self.suffix)

# if we specify assets, create new folder and move them there. If not,
# just create empty folder
# new_name = pipeline.create_folder(self.root, name)
path = "{}/{}".format(self.root, name)
unreal.EditorAssetLibrary.make_directory(path)

ar = unreal.AssetRegistryHelpers.get_asset_registry()

for a in selection:
d = self.data.copy()
d["members"] = [a]
d["sequence"] = a
d["master_sequence"] = ms
d["master_level"] = ml
asset = ar.get_asset_by_object_path(a).get_asset()
asset_name = asset.get_name()

# Get frame range. We need to go through the hierarchy and check
# the frame range for the children.
asset_data = legacy_io.find_one({
"type": "asset",
"name": asset_name
})
id = asset_data.get('_id')

elements = list(
legacy_io.find({"type": "asset", "data.visualParent": id}))

if elements:
start_frames = []
end_frames = []
for e in elements:
start_frames.append(e.get('data').get('clipIn'))
end_frames.append(e.get('data').get('clipOut'))

elements.extend(legacy_io.find({
"type": "asset",
"data.visualParent": e.get('_id')
}))

min_frame = min(start_frames)
max_frame = max(end_frames)
else:
min_frame = asset_data.get('data').get('clipIn')
max_frame = asset_data.get('data').get('clipOut')

d["startFrame"] = min_frame
d["endFrame"] = max_frame

container_name = f"{asset_name}{self.suffix}"
pipeline.create_publish_instance(
instance=container_name, path=path)
pipeline.imprint("{}/{}".format(path, container_name), d)
Loading