-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4198 from ynput/feature/unreal-uasset_management
- Loading branch information
Showing
6 changed files
with
298 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
41
openpype/hosts/unreal/plugins/publish/validate_no_dependencies.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters