Skip to content

Commit

Permalink
Merge pull request #4198 from ynput/feature/unreal-uasset_management
Browse files Browse the repository at this point in the history
  • Loading branch information
antirotor committed Jan 9, 2023
2 parents 2037929 + ea65afd commit 0e168a5
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 4 deletions.
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 @@ -131,7 +131,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
"mvUsdComposition",
"mvUsdOverride",
"simpleUnrealTexture",
"online"
"online",
"uasset"
]

default_template_name = "publish"
Expand Down

0 comments on commit 0e168a5

Please sign in to comment.