Merge pull request #2410 from simonebarbieri/feature/unreal-rendering

This commit is contained in:
Milan Kolar 2022-04-28 18:40:17 +02:00 committed by GitHub
commit 16fab3d1b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 864 additions and 135 deletions

0
.gitmodules vendored
View file

View file

@ -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)

View file

@ -7,6 +7,7 @@ from openpype import (
)
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):
@ -20,6 +21,7 @@ class ToolsBtnsWidget(QtWidgets.QWidget):
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
)
@ -30,6 +32,7 @@ class ToolsBtnsWidget(QtWidgets.QWidget):
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)
@ -37,6 +40,7 @@ class ToolsBtnsWidget(QtWidgets.QWidget):
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):
@ -51,6 +55,9 @@ class ToolsBtnsWidget(QtWidgets.QWidget):
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")

View file

@ -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}
]
}

View file

@ -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
@ -28,13 +29,13 @@ class CreateLayout(plugin.Creator):
# 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()))

View file

@ -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)

View file

@ -3,13 +3,17 @@
import os
import json
import unreal
from unreal import EditorAssetLibrary
from unreal import MovieSceneSkeletalAnimationTrack
from unreal import MovieSceneSkeletalAnimationSection
from openpype.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
import unreal # noqa
class AnimationFBXLoader(plugin.Loader):
@ -21,59 +25,13 @@ class AnimationFBXLoader(plugin.Loader):
icon = "cube"
color = "orange"
def load(self, context, name, namespace, options=None):
"""
Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
Returns:
list(str): list of container content
"""
# Create directory for asset and OpenPype container
root = "/Game/OpenPype/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
"{}/{}/{}".format(root, asset, name), suffix="")
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
def _process(self, asset_dir, asset_name, instance_name):
automated = False
actor = None
task = unreal.AssetImportTask()
task.options = unreal.FbxImportUI()
lib_path = self.fname.replace("fbx", "json")
with open(lib_path, "r") as fp:
data = json.load(fp)
instance_name = data.get("instance_name")
if instance_name:
automated = True
# Old method to get the actor
@ -131,6 +89,116 @@ class AnimationFBXLoader(plugin.Loader):
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
asset_content = EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
animation = None
for a in asset_content:
imported_asset_data = EditorAssetLibrary.find_asset_data(a)
imported_asset = unreal.AssetRegistryHelpers.get_asset(
imported_asset_data)
if imported_asset.__class__ == unreal.AnimSequence:
animation = imported_asset
break
if animation:
animation.set_editor_property('enable_root_motion', True)
actor.skeletal_mesh_component.set_editor_property(
'animation_mode', unreal.AnimationMode.ANIMATION_SINGLE_NODE)
actor.skeletal_mesh_component.animation_data.set_editor_property(
'anim_to_play', animation)
return animation
def load(self, context, name, namespace, options=None):
"""
Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
Returns:
list(str): list of container content
"""
# Create directory for asset and avalon container
hierarchy = context.get('asset').get('data').get('parents')
root = "/Game/OpenPype"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/Animations/{asset}/{name}", suffix="")
hierarchy_dir = root
for h in hierarchy:
hierarchy_dir = f"{hierarchy_dir}/{h}"
hierarchy_dir = f"{hierarchy_dir}/{asset}"
container_name += suffix
EditorAssetLibrary.make_directory(asset_dir)
libpath = self.fname.replace("fbx", "json")
with open(libpath, "r") as fp:
data = json.load(fp)
instance_name = data.get("instance_name")
animation = self._process(asset_dir, container_name, instance_name)
asset_content = EditorAssetLibrary.list_assets(
hierarchy_dir, recursive=True, include_folder=False)
# Get the sequence for the layout, excluding the camera one.
sequences = [a for a in asset_content
if (EditorAssetLibrary.find_asset_data(a).get_class() ==
unreal.LevelSequence.static_class() and
"_camera" not in a.split("/")[-1])]
ar = unreal.AssetRegistryHelpers.get_asset_registry()
for s in sequences:
sequence = ar.get_asset_by_object_path(s).get_asset()
possessables = [
p for p in sequence.get_possessables()
if p.get_display_name() == instance_name]
for p in possessables:
tracks = [
t for t in p.get_tracks()
if (t.get_class() ==
MovieSceneSkeletalAnimationTrack.static_class())]
for t in tracks:
sections = [
s for s in t.get_sections()
if (s.get_class() ==
MovieSceneSkeletalAnimationSection.static_class())]
for s in sections:
s.params.set_editor_property('animation', animation)
# Create Asset Container
unreal_pipeline.create_container(
container=container_name, path=asset_dir)
@ -150,29 +218,11 @@ class AnimationFBXLoader(plugin.Loader):
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
imported_content = EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=False)
animation = None
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
imported_asset_data = unreal.EditorAssetLibrary.find_asset_data(a)
imported_asset = unreal.AssetRegistryHelpers.get_asset(
imported_asset_data)
if imported_asset.__class__ == unreal.AnimSequence:
animation = imported_asset
break
if animation:
animation.set_editor_property('enable_root_motion', True)
actor.skeletal_mesh_component.set_editor_property(
'animation_mode', unreal.AnimationMode.ANIMATION_SINGLE_NODE)
actor.skeletal_mesh_component.animation_data.set_editor_property(
'anim_to_play', animation)
return asset_content
for a in imported_content:
EditorAssetLibrary.save_asset(a)
def update(self, container, representation):
name = container["asset_name"]
@ -218,7 +268,7 @@ class AnimationFBXLoader(plugin.Loader):
task.options.anim_sequence_import_data.set_editor_property(
'convert_scene', True)
skeletal_mesh = unreal.EditorAssetLibrary.load_asset(
skeletal_mesh = EditorAssetLibrary.load_asset(
container.get('namespace') + "/" + container.get('asset_name'))
skeleton = skeletal_mesh.get_editor_property('skeleton')
task.options.set_editor_property('skeleton', skeleton)
@ -235,22 +285,22 @@ class AnimationFBXLoader(plugin.Loader):
"parent": str(representation["parent"])
})
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_content = EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
EditorAssetLibrary.save_asset(a)
def remove(self, container):
path = container["namespace"]
parent_path = os.path.dirname(path)
unreal.EditorAssetLibrary.delete_directory(path)
EditorAssetLibrary.delete_directory(path)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_content = EditorAssetLibrary.list_assets(
parent_path, recursive=False, include_folder=True
)
if len(asset_content) == 0:
unreal.EditorAssetLibrary.delete_directory(parent_path)
EditorAssetLibrary.delete_directory(parent_path)

View file

@ -2,13 +2,16 @@
"""Load camera from FBX."""
import os
import unreal
from unreal import EditorAssetLibrary
from unreal import EditorLevelLibrary
from openpype.pipeline import (
AVALON_CONTAINER_ID,
legacy_io,
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
import unreal # noqa
class CameraLoader(plugin.Loader):
@ -20,6 +23,40 @@ class CameraLoader(plugin.Loader):
icon = "cube"
color = "orange"
def _get_data(self, asset_name):
asset_doc = legacy_io.find_one({
"type": "asset",
"name": asset_name
})
return asset_doc.get("data")
def _set_sequence_hierarchy(
self, seq_i, seq_j, min_frame_j, max_frame_j
):
tracks = seq_i.get_master_tracks()
track = None
for t in tracks:
if t.get_class() == unreal.MovieSceneSubTrack.static_class():
track = t
break
if not track:
track = seq_i.add_master_track(unreal.MovieSceneSubTrack)
subscenes = track.get_sections()
subscene = None
for s in subscenes:
if s.get_editor_property('sub_sequence') == seq_j:
subscene = s
break
if not subscene:
subscene = track.add_section()
subscene.set_row_index(len(track.get_sections()))
subscene.set_editor_property('sub_sequence', seq_j)
subscene.set_range(
min_frame_j,
max_frame_j + 1)
def load(self, context, name, namespace, data):
"""
Load and containerise representation into Content Browser.
@ -43,8 +80,14 @@ class CameraLoader(plugin.Loader):
list(str): list of container content
"""
# Create directory for asset and OpenPype container
root = "/Game/OpenPype/Assets"
# Create directory for asset and avalon container
hierarchy = context.get('asset').get('data').get('parents')
root = "/Game/OpenPype"
hierarchy_dir = root
hierarchy_list = []
for h in hierarchy:
hierarchy_dir = f"{hierarchy_dir}/{h}"
hierarchy_list.append(hierarchy_dir)
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
@ -54,10 +97,10 @@ class CameraLoader(plugin.Loader):
tools = unreal.AssetToolsHelpers().get_asset_tools()
# Create a unique name for the camera directory
unique_number = 1
if unreal.EditorAssetLibrary.does_directory_exist(f"{root}/{asset}"):
asset_content = unreal.EditorAssetLibrary.list_assets(
if EditorAssetLibrary.does_directory_exist(f"{hierarchy_dir}/{asset}"):
asset_content = EditorAssetLibrary.list_assets(
f"{root}/{asset}", recursive=False, include_folder=True
)
@ -76,42 +119,122 @@ class CameraLoader(plugin.Loader):
unique_number = f_numbers[-1] + 1
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/{asset}/{name}_{unique_number:02d}", suffix="")
f"{hierarchy_dir}/{asset}/{name}_{unique_number:02d}", suffix="")
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
current_level = EditorLevelLibrary.get_editor_world().get_full_name()
EditorLevelLibrary.save_all_dirty_levels()
sequence = tools.create_asset(
asset_name=asset_name,
ar = unreal.AssetRegistryHelpers.get_asset_registry()
filter = unreal.ARFilter(
class_names=["World"],
package_paths=[f"{hierarchy_dir}/{asset}/"],
recursive_paths=True)
maps = ar.get_assets(filter)
# There should be only one map in the list
EditorLevelLibrary.load_level(maps[0].get_full_name())
# Get all the sequences in the hierarchy. It will create them, if
# they don't exist.
sequences = []
frame_ranges = []
i = 0
for h in hierarchy_list:
root_content = EditorAssetLibrary.list_assets(
h, recursive=False, include_folder=False)
existing_sequences = [
EditorAssetLibrary.find_asset_data(asset)
for asset in root_content
if EditorAssetLibrary.find_asset_data(
asset).get_class().get_name() == 'LevelSequence'
]
if not existing_sequences:
scene = tools.create_asset(
asset_name=hierarchy[i],
package_path=h,
asset_class=unreal.LevelSequence,
factory=unreal.LevelSequenceFactoryNew()
)
asset_data = legacy_io.find_one({
"type": "asset",
"name": h.split('/')[-1]
})
id = asset_data.get('_id')
start_frames = []
end_frames = []
elements = list(
legacy_io.find({"type": "asset", "data.visualParent": id}))
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)
scene.set_display_rate(
unreal.FrameRate(asset_data.get('data').get("fps"), 1.0))
scene.set_playback_start(min_frame)
scene.set_playback_end(max_frame)
sequences.append(scene)
frame_ranges.append((min_frame, max_frame))
else:
for e in existing_sequences:
sequences.append(e.get_asset())
frame_ranges.append((
e.get_asset().get_playback_start(),
e.get_asset().get_playback_end()))
i += 1
EditorAssetLibrary.make_directory(asset_dir)
cam_seq = tools.create_asset(
asset_name=f"{asset}_camera",
package_path=asset_dir,
asset_class=unreal.LevelSequence,
factory=unreal.LevelSequenceFactoryNew()
)
io_asset = legacy_io.Session["AVALON_ASSET"]
asset_doc = legacy_io.find_one({
"type": "asset",
"name": io_asset
})
# Add sequences data to hierarchy
for i in range(0, len(sequences) - 1):
self._set_sequence_hierarchy(
sequences[i], sequences[i + 1],
frame_ranges[i + 1][0], frame_ranges[i + 1][1])
data = asset_doc.get("data")
if data:
sequence.set_display_rate(unreal.FrameRate(data.get("fps"), 1.0))
sequence.set_playback_start(data.get("frameStart"))
sequence.set_playback_end(data.get("frameEnd"))
data = self._get_data(asset)
cam_seq.set_display_rate(
unreal.FrameRate(data.get("fps"), 1.0))
cam_seq.set_playback_start(0)
cam_seq.set_playback_end(data.get('clipOut') - data.get('clipIn') + 1)
self._set_sequence_hierarchy(
sequences[-1], cam_seq,
data.get('clipIn'), data.get('clipOut'))
settings = unreal.MovieSceneUserImportFBXSettings()
settings.set_editor_property('reduce_keys', False)
unreal.SequencerTools.import_fbx(
unreal.EditorLevelLibrary.get_editor_world(),
sequence,
sequence.get_bindings(),
settings,
self.fname
)
if cam_seq:
unreal.SequencerTools.import_fbx(
EditorLevelLibrary.get_editor_world(),
cam_seq,
cam_seq.get_bindings(),
settings,
self.fname
)
# Create Asset Container
unreal_pipeline.create_container(
@ -132,12 +255,15 @@ class CameraLoader(plugin.Loader):
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
asset_content = unreal.EditorAssetLibrary.list_assets(
EditorLevelLibrary.save_all_dirty_levels()
EditorLevelLibrary.load_level(current_level)
asset_content = EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
EditorAssetLibrary.save_asset(a)
return asset_content
@ -147,25 +273,25 @@ class CameraLoader(plugin.Loader):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_content = EditorAssetLibrary.list_assets(
path, recursive=False, include_folder=False
)
asset_name = ""
for a in asset_content:
asset = ar.get_asset_by_object_path(a)
if a.endswith("_CON"):
loaded_asset = unreal.EditorAssetLibrary.load_asset(a)
unreal.EditorAssetLibrary.set_metadata_tag(
loaded_asset = EditorAssetLibrary.load_asset(a)
EditorAssetLibrary.set_metadata_tag(
loaded_asset, "representation", str(representation["_id"])
)
unreal.EditorAssetLibrary.set_metadata_tag(
EditorAssetLibrary.set_metadata_tag(
loaded_asset, "parent", str(representation["parent"])
)
asset_name = unreal.EditorAssetLibrary.get_metadata_tag(
asset_name = EditorAssetLibrary.get_metadata_tag(
loaded_asset, "asset_name"
)
elif asset.asset_class == "LevelSequence":
unreal.EditorAssetLibrary.delete_asset(a)
EditorAssetLibrary.delete_asset(a)
sequence = tools.create_asset(
asset_name=asset_name,
@ -191,7 +317,7 @@ class CameraLoader(plugin.Loader):
settings.set_editor_property('reduce_keys', False)
unreal.SequencerTools.import_fbx(
unreal.EditorLevelLibrary.get_editor_world(),
EditorLevelLibrary.get_editor_world(),
sequence,
sequence.get_bindings(),
settings,
@ -202,11 +328,11 @@ class CameraLoader(plugin.Loader):
path = container["namespace"]
parent_path = os.path.dirname(path)
unreal.EditorAssetLibrary.delete_directory(path)
EditorAssetLibrary.delete_directory(path)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_content = EditorAssetLibrary.list_assets(
parent_path, recursive=False, include_folder=True
)
if len(asset_content) == 0:
unreal.EditorAssetLibrary.delete_directory(parent_path)
EditorAssetLibrary.delete_directory(parent_path)

View file

@ -7,6 +7,7 @@ from pathlib import Path
import unreal
from unreal import EditorAssetLibrary
from unreal import EditorLevelLibrary
from unreal import EditorLevelUtils
from unreal import AssetToolsHelpers
from unreal import FBXImportType
from unreal import MathLibrary as umath
@ -17,6 +18,7 @@ from openpype.pipeline import (
load_container,
get_representation_path,
AVALON_CONTAINER_ID,
legacy_io,
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
@ -31,7 +33,7 @@ class LayoutLoader(plugin.Loader):
label = "Load Layout"
icon = "code-fork"
color = "orange"
ASSET_ROOT = "/Game/OpenPype/Assets"
ASSET_ROOT = "/Game/OpenPype"
def _get_asset_containers(self, path):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
@ -85,11 +87,91 @@ class LayoutLoader(plugin.Loader):
return None
@staticmethod
def _process_family(assets, class_name, transform, inst_name=None):
def _get_data(self, asset_name):
asset_doc = legacy_io.find_one({
"type": "asset",
"name": asset_name
})
return asset_doc.get("data")
def _set_sequence_hierarchy(
self, seq_i, seq_j, max_frame_i, min_frame_j, max_frame_j, map_paths
):
# Get existing sequencer tracks or create them if they don't exist
tracks = seq_i.get_master_tracks()
subscene_track = None
visibility_track = None
for t in tracks:
if t.get_class() == unreal.MovieSceneSubTrack.static_class():
subscene_track = t
if (t.get_class() ==
unreal.MovieSceneLevelVisibilityTrack.static_class()):
visibility_track = t
if not subscene_track:
subscene_track = seq_i.add_master_track(unreal.MovieSceneSubTrack)
if not visibility_track:
visibility_track = seq_i.add_master_track(
unreal.MovieSceneLevelVisibilityTrack)
# Create the sub-scene section
subscenes = subscene_track.get_sections()
subscene = None
for s in subscenes:
if s.get_editor_property('sub_sequence') == seq_j:
subscene = s
break
if not subscene:
subscene = subscene_track.add_section()
subscene.set_row_index(len(subscene_track.get_sections()))
subscene.set_editor_property('sub_sequence', seq_j)
subscene.set_range(
min_frame_j,
max_frame_j + 1)
# Create the visibility section
ar = unreal.AssetRegistryHelpers.get_asset_registry()
maps = []
for m in map_paths:
# Unreal requires to load the level to get the map name
EditorLevelLibrary.save_all_dirty_levels()
EditorLevelLibrary.load_level(m)
maps.append(str(ar.get_asset_by_object_path(m).asset_name))
vis_section = visibility_track.add_section()
index = len(visibility_track.get_sections())
vis_section.set_range(
min_frame_j,
max_frame_j + 1)
vis_section.set_visibility(unreal.LevelVisibility.VISIBLE)
vis_section.set_row_index(index)
vis_section.set_level_names(maps)
if min_frame_j > 1:
hid_section = visibility_track.add_section()
hid_section.set_range(
1,
min_frame_j)
hid_section.set_visibility(unreal.LevelVisibility.HIDDEN)
hid_section.set_row_index(index)
hid_section.set_level_names(maps)
if max_frame_j < max_frame_i:
hid_section = visibility_track.add_section()
hid_section.set_range(
max_frame_j + 1,
max_frame_i + 1)
hid_section.set_visibility(unreal.LevelVisibility.HIDDEN)
hid_section.set_row_index(index)
hid_section.set_level_names(maps)
def _process_family(
self, assets, class_name, transform, sequence, inst_name=None
):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
actors = []
bindings = []
for asset in assets:
obj = ar.get_asset_by_object_path(asset).get_asset()
@ -119,14 +201,23 @@ class LayoutLoader(plugin.Loader):
), False)
actor.set_actor_scale3d(transform.get('scale'))
if class_name == 'SkeletalMesh':
skm_comp = actor.get_editor_property(
'skeletal_mesh_component')
skm_comp.set_bounds_scale(10.0)
actors.append(actor)
return actors
binding = sequence.add_possessable(actor)
bindings.append(binding)
return actors, bindings
@staticmethod
def _import_animation(
asset_dir, path, instance_name, skeleton, actors_dict,
animation_file):
self, asset_dir, path, instance_name, skeleton, actors_dict,
animation_file, bindings_dict, sequence
):
anim_file = Path(animation_file)
anim_file_name = anim_file.with_suffix('')
@ -205,7 +296,20 @@ class LayoutLoader(plugin.Loader):
actor.skeletal_mesh_component.animation_data.set_editor_property(
'anim_to_play', animation)
def _process(self, lib_path, asset_dir, loaded=None):
# Add animation to the sequencer
bindings = bindings_dict.get(instance_name)
for binding in bindings:
binding.add_track(unreal.MovieSceneSkeletalAnimationTrack)
for track in binding.get_tracks():
section = track.add_section()
section.set_range(
sequence.get_playback_start(),
sequence.get_playback_end())
sec_params = section.get_editor_property('params')
sec_params.set_editor_property('animation', animation)
def _process(self, lib_path, asset_dir, sequence, loaded=None):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
with open(lib_path, "r") as fp:
@ -220,6 +324,7 @@ class LayoutLoader(plugin.Loader):
skeleton_dict = {}
actors_dict = {}
bindings_dict = {}
for element in data:
reference = None
@ -277,12 +382,13 @@ class LayoutLoader(plugin.Loader):
actors = []
if family == 'model':
actors = self._process_family(
assets, 'StaticMesh', transform, inst)
actors, _ = self._process_family(
assets, 'StaticMesh', transform, sequence, inst)
elif family == 'rig':
actors = self._process_family(
assets, 'SkeletalMesh', transform, inst)
actors, bindings = self._process_family(
assets, 'SkeletalMesh', transform, sequence, inst)
actors_dict[inst] = actors
bindings_dict[inst] = bindings
if family == 'rig':
# Finds skeleton among the imported assets
@ -302,8 +408,8 @@ class LayoutLoader(plugin.Loader):
if animation_file and skeleton:
self._import_animation(
asset_dir, path, instance_name, skeleton,
actors_dict, animation_file)
asset_dir, path, instance_name, skeleton, actors_dict,
animation_file, bindings_dict, sequence)
@staticmethod
def _remove_family(assets, components, class_name, prop_name):
@ -369,7 +475,13 @@ class LayoutLoader(plugin.Loader):
list(str): list of container content
"""
# Create directory for asset and avalon container
hierarchy = context.get('asset').get('data').get('parents')
root = self.ASSET_ROOT
hierarchy_dir = root
hierarchy_list = []
for h in hierarchy:
hierarchy_dir = f"{hierarchy_dir}/{h}"
hierarchy_list.append(hierarchy_dir)
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
@ -379,13 +491,156 @@ class LayoutLoader(plugin.Loader):
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
"{}/{}/{}".format(root, asset, name), suffix="")
"{}/{}/{}".format(hierarchy_dir, asset, name), suffix="")
container_name += suffix
EditorAssetLibrary.make_directory(asset_dir)
self._process(self.fname, asset_dir)
# Create map for the shot, and create hierarchy of map. If the maps
# already exist, we will use them.
maps = []
for h in hierarchy_list:
a = h.split('/')[-1]
map = f"{h}/{a}_map.{a}_map"
new = False
if not EditorAssetLibrary.does_asset_exist(map):
EditorLevelLibrary.new_level(f"{h}/{a}_map")
new = True
maps.append({"map": map, "new": new})
EditorLevelLibrary.new_level(f"{asset_dir}/{asset}_map")
maps.append(
{"map": f"{asset_dir}/{asset}_map.{asset}_map", "new": True})
for i in range(0, len(maps) - 1):
for j in range(i + 1, len(maps)):
if maps[j].get('new'):
EditorLevelLibrary.load_level(maps[i].get('map'))
EditorLevelUtils.add_level_to_world(
EditorLevelLibrary.get_editor_world(),
maps[j].get('map'),
unreal.LevelStreamingDynamic
)
EditorLevelLibrary.save_all_dirty_levels()
EditorLevelLibrary.load_level(maps[-1].get('map'))
# Get all the sequences in the hierarchy. It will create them, if
# they don't exist.
sequences = []
frame_ranges = []
i = 0
for h in hierarchy_list:
root_content = EditorAssetLibrary.list_assets(
h, recursive=False, include_folder=False)
existing_sequences = [
EditorAssetLibrary.find_asset_data(asset)
for asset in root_content
if EditorAssetLibrary.find_asset_data(
asset).get_class().get_name() == 'LevelSequence'
]
if not existing_sequences:
sequence = tools.create_asset(
asset_name=hierarchy[i],
package_path=h,
asset_class=unreal.LevelSequence,
factory=unreal.LevelSequenceFactoryNew()
)
asset_data = legacy_io.find_one({
"type": "asset",
"name": h.split('/')[-1]
})
id = asset_data.get('_id')
start_frames = []
end_frames = []
elements = list(
legacy_io.find({"type": "asset", "data.visualParent": id}))
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)
sequence.set_display_rate(
unreal.FrameRate(asset_data.get('data').get("fps"), 1.0))
sequence.set_playback_start(min_frame)
sequence.set_playback_end(max_frame)
sequences.append(sequence)
frame_ranges.append((min_frame, max_frame))
tracks = sequence.get_master_tracks()
track = None
for t in tracks:
if (t.get_class() ==
unreal.MovieSceneCameraCutTrack.static_class()):
track = t
break
if not track:
track = sequence.add_master_track(
unreal.MovieSceneCameraCutTrack)
else:
for e in existing_sequences:
sequences.append(e.get_asset())
frame_ranges.append((
e.get_asset().get_playback_start(),
e.get_asset().get_playback_end()))
i += 1
shot = tools.create_asset(
asset_name=asset,
package_path=asset_dir,
asset_class=unreal.LevelSequence,
factory=unreal.LevelSequenceFactoryNew()
)
# sequences and frame_ranges have the same length
for i in range(0, len(sequences) - 1):
maps_to_add = []
for j in range(i + 1, len(maps)):
maps_to_add.append(maps[j].get('map'))
self._set_sequence_hierarchy(
sequences[i], sequences[i + 1],
frame_ranges[i][1],
frame_ranges[i + 1][0], frame_ranges[i + 1][1],
maps_to_add)
data = self._get_data(asset)
shot.set_display_rate(
unreal.FrameRate(data.get("fps"), 1.0))
shot.set_playback_start(0)
shot.set_playback_end(data.get('clipOut') - data.get('clipIn') + 1)
self._set_sequence_hierarchy(
sequences[-1], shot,
frame_ranges[-1][1],
data.get('clipIn'), data.get('clipOut'),
[maps[-1].get('map')])
EditorLevelLibrary.load_level(maps[-1].get('map'))
self._process(self.fname, asset_dir, shot)
for s in sequences:
EditorAssetLibrary.save_asset(s.get_full_name())
EditorLevelLibrary.save_current_level()
# Create Asset Container
unreal_pipeline.create_container(
@ -412,6 +667,8 @@ class LayoutLoader(plugin.Loader):
for a in asset_content:
EditorAssetLibrary.save_asset(a)
EditorLevelLibrary.load_level(maps[0].get('map'))
return asset_content
def update(self, container, representation):

View file

@ -0,0 +1,48 @@
from pathlib import Path
import unreal
import openpype.api
class ExtractRender(openpype.api.Extractor):
"""Extract render."""
label = "Extract Render"
hosts = ["unreal"]
families = ["render"]
optional = True
def process(self, instance):
# Define extract output file path
stagingdir = self.staging_dir(instance)
# Perform extraction
self.log.info("Performing extraction..")
# Get the render output directory
project_dir = unreal.Paths.project_dir()
render_dir = (f"{project_dir}/Saved/MovieRenders/"
f"{instance.data['subset']}")
assert unreal.Paths.directory_exists(render_dir), \
"Render directory does not exist"
render_path = Path(render_dir)
frames = []
for x in render_path.iterdir():
if x.is_file() and x.suffix == '.png':
frames.append(str(x))
if "representations" not in instance.data:
instance.data["representations"] = []
render_representation = {
'name': 'png',
'ext': 'png',
'files': frames,
"stagingDir": stagingdir,
}
instance.data["representations"].append(render_representation)

@ -0,0 +1 @@
Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0

@ -0,0 +1 @@
Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e

@ -0,0 +1 @@
Subproject commit 43f6ea943980b29c02a170942b566ae11f2b7080