Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unreal: Publishing and Loading for UAssets #4198

Merged
merged 11 commits into from
Jan 9, 2023
61 changes: 61 additions & 0 deletions openpype/hosts/unreal/plugins/create/create_uasset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Create UAsset."""
from pathlib import Path

import unreal

from openpype.hosts.unreal.api import pipeline
from openpype.pipeline import LegacyCreator


class CreateUAsset(LegacyCreator):
"""UAsset."""

name = "UAsset"
label = "UAsset"
family = "uasset"
icon = "cube"

root = "/Game/OpenPype"
suffix = "_INS"

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

def process(self):
ar = unreal.AssetRegistryHelpers.get_asset_registry()

subset = self.data["subset"]
path = f"{self.root}/PublishInstances/"

unreal.EditorAssetLibrary.make_directory(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 len(selection) != 1:
raise RuntimeError("Please select only one object.")

obj = selection[0]

asset = ar.get_asset_by_object_path(obj).get_asset()
sys_path = unreal.SystemLibrary.get_system_path(asset)

if not sys_path:
raise RuntimeError(
f"{Path(obj).name} is not on the disk. Likely it needs to"
"be saved first.")

if Path(sys_path).suffix != ".uasset":
raise RuntimeError(f"{Path(sys_path).name} is not a UAsset.")

unreal.log("selection: {}".format(selection))
container_name = f"{subset}{self.suffix}"
pipeline.create_publish_instance(
instance=container_name, path=path)

data = self.data.copy()
data["members"] = selection

pipeline.imprint(f"{path}/{container_name}", data)
145 changes: 145 additions & 0 deletions openpype/hosts/unreal/plugins/load/load_uasset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
"""Load UAsset."""
from pathlib import Path
import shutil

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 UAssetLoader(plugin.Loader):
"""Load UAsset."""

families = ["uasset"]
label = "Load UAsset"
representations = ["uasset"]
icon = "cube"
color = "orange"

def load(self, context, name, namespace, options):
"""Load and containerise representation into Content Browser.

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.
options (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)

destination_path = asset_dir.replace(
"/Game",
Path(unreal.Paths.project_content_dir()).as_posix(),
1)

shutil.copy(self.fname, f"{destination_path}/{name}.uasset")

# Create Asset Container
unreal_pipeline.create_container(
container=container_name, path=asset_dir)

data = {
"schema": "openpype:container-2.0",
"id": AVALON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)

asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)

for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)

return asset_content

def update(self, container, representation):
ar = unreal.AssetRegistryHelpers.get_asset_registry()

asset_dir = container["namespace"]
name = representation["context"]["subset"]

destination_path = asset_dir.replace(
"/Game",
Path(unreal.Paths.project_content_dir()).as_posix(),
1)

asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=False, include_folder=True
)

for asset in asset_content:
obj = ar.get_asset_by_object_path(asset).get_asset()
if not obj.get_class().get_name() == 'AssetContainer':
unreal.EditorAssetLibrary.delete_asset(asset)

update_filepath = get_representation_path(representation)

shutil.copy(update_filepath, f"{destination_path}/{name}.uasset")

container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})

asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)

for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)

def remove(self, container):
path = container["namespace"]
parent_path = Path(path).parent.as_posix()

unreal.EditorAssetLibrary.delete_directory(path)

asset_content = unreal.EditorAssetLibrary.list_assets(
parent_path, recursive=False
)

if len(asset_content) == 0:
unreal.EditorAssetLibrary.delete_directory(parent_path)
10 changes: 7 additions & 3 deletions openpype/hosts/unreal/plugins/publish/collect_instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ class CollectInstances(pyblish.api.ContextPlugin):
def process(self, context):

ar = unreal.AssetRegistryHelpers.get_asset_registry()
class_name = ["/Script/OpenPype",
"AssetContainer"] if UNREAL_VERSION.major == 5 and \
UNREAL_VERSION.minor > 0 else "OpenPypePublishInstance" # noqa
class_name = [
"/Script/OpenPype",
"OpenPypePublishInstance"
] if (
UNREAL_VERSION.major == 5
and UNREAL_VERSION.minor > 0
) else "OpenPypePublishInstance" # noqa
instance_containers = ar.get_assets_by_class(class_name, True)

for container_data in instance_containers:
Expand Down
42 changes: 42 additions & 0 deletions openpype/hosts/unreal/plugins/publish/extract_uasset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from pathlib import Path
import shutil

import unreal

from openpype.pipeline import publish


class ExtractUAsset(publish.Extractor):
"""Extract a UAsset."""

label = "Extract UAsset"
hosts = ["unreal"]
families = ["uasset"]
optional = True

def process(self, instance):
ar = unreal.AssetRegistryHelpers.get_asset_registry()

self.log.info("Performing extraction..")

staging_dir = self.staging_dir(instance)
filename = "{}.uasset".format(instance.name)

obj = instance[0]

asset = ar.get_asset_by_object_path(obj).get_asset()
sys_path = unreal.SystemLibrary.get_system_path(asset)
filename = Path(sys_path).name

shutil.copy(sys_path, staging_dir)

if "representations" not in instance.data:
instance.data["representations"] = []

representation = {
'name': 'uasset',
'ext': 'uasset',
'files': filename,
"stagingDir": staging_dir,
}
instance.data["representations"].append(representation)
41 changes: 41 additions & 0 deletions openpype/hosts/unreal/plugins/publish/validate_no_dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import unreal

import pyblish.api


class ValidateNoDependencies(pyblish.api.InstancePlugin):
"""Ensure that the uasset has no dependencies

The uasset is checked for dependencies. If there are any, the instance
cannot be published.
"""

order = pyblish.api.ValidatorOrder
label = "Check no dependencies"
families = ["uasset"]
hosts = ["unreal"]
optional = True

def process(self, instance):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
all_dependencies = []

for obj in instance[:]:
asset = ar.get_asset_by_object_path(obj)
dependencies = ar.get_dependencies(
asset.package_name,
unreal.AssetRegistryDependencyOptions(
include_soft_package_references=False,
include_hard_package_references=True,
include_searchable_names=False,
include_soft_management_references=False,
include_hard_management_references=False
))
if dependencies:
for dep in dependencies:
if str(dep).startswith("/Game/"):
all_dependencies.append(str(dep))

if all_dependencies:
raise RuntimeError(
f"Dependencies found: {all_dependencies}")
3 changes: 2 additions & 1 deletion openpype/plugins/publish/integrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
"mvUsdComposition",
"mvUsdOverride",
"simpleUnrealTexture",
"online"
"online",
"uasset"
]

default_template_name = "publish"
Expand Down