Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
138 changes: 93 additions & 45 deletions client/ayon_unreal/api/plugin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
"""Unreal specific plugin implementations for creators and loaders."""
from __future__ import annotations
import ast
import collections
from abc import ABC
from typing import Any, Optional

import unreal
import ayon_api
Expand Down Expand Up @@ -30,7 +32,7 @@
)


class UnrealCreateLogic():
class UnrealCreateLogic:
"""Universal class for logic that Unreal creators could inherit from."""
root = "/Game/Ayon/AyonPublishInstances"
suffix = "_INS"
Expand All @@ -40,13 +42,13 @@ class UnrealCreateLogic():
def get_cached_instances(shared_data):
"""Cache instances for Creators to shared data.

Create `unreal_cached_subsets` key when needed in shared data and
Create `unreal_cached_products` key when needed in shared data and
fill it with all collected instances from the scene under its
respective creator identifiers.

If legacy instances are detected in the scene, create
`unreal_cached_legacy_subsets` there and fill it with
all legacy subsets under product_type as a key.
`unreal_cached_legacy_products` there and fill it with
all legacy products under product_base_type as a key.

Args:
Dict[str, Any]: Shared data.
Expand All @@ -55,28 +57,41 @@ def get_cached_instances(shared_data):
Dict[str, Any]: Shared data dictionary.

"""
if shared_data.get("unreal_cached_subsets") is None:
unreal_cached_subsets = collections.defaultdict(list)
unreal_cached_legacy_subsets = collections.defaultdict(list)
if shared_data.get("unreal_cached_products") is None:
unreal_cached_products = collections.defaultdict(list)
unreal_cached_legacy_products = collections.defaultdict(list)
for instance in ls_inst():
creator_id = instance.get("creator_identifier")
if creator_id:
unreal_cached_subsets[creator_id].append(instance)
unreal_cached_products[creator_id].append(instance)
else:
product_type = instance.get("product_type")
unreal_cached_legacy_subsets[product_type].append(instance)

shared_data["unreal_cached_subsets"] = unreal_cached_subsets
shared_data["unreal_cached_legacy_subsets"] = (
unreal_cached_legacy_subsets
product_base_type = instance["product_base_type"]
Comment thread
antirotor marked this conversation as resolved.
Outdated
# Handle legacy instances that may use "product_type"
# instead of "product_base_type" to avoid KeyError.
product_base_type = (
instance.data.get("product_base_type")
or instance.data.get("product_type")
)
if product_base_type is None:
unreal.log_warning(
f"Legacy instance without product_base_type or "
f"product_type: {instance}"
)
continue
unreal_cached_legacy_products[product_base_type].append(
instance)

shared_data["unreal_cached_products"] = unreal_cached_products
shared_data["unreal_cached_legacy_products"] = (
unreal_cached_legacy_products
)
return shared_data

def _default_collect_instances(self):
# cache instances if missing
self.get_cached_instances(self.collection_shared_data)
for instance in self.collection_shared_data[
"unreal_cached_subsets"].get(self.identifier, []):
"unreal_cached_products"].get(self.identifier, []):
# Unreal saves metadata as string, so we need to convert it back
instance['creator_attributes'] = ast.literal_eval(
instance.get('creator_attributes', '{}'))
Expand Down Expand Up @@ -126,11 +141,17 @@ def create_unreal(self, product_name, instance_data, pre_create_data):
instance_data["product_name"] = product_name
instance_data["instance_path"] = f"{self.root}/{instance_name}"

product_type: str = instance_data.get("product_type")
if not product_type:
product_type = self.product_base_type

instance = CreatedInstance(
self.product_type,
product_name,
instance_data,
self)
product_type=product_type,
product_base_type=self.product_base_type,
product_name=product_name,
data=instance_data,
creator=self,
)
self._add_instance_to_context(instance)

pub_instance.set_editor_property('add_external_assets', True)
Expand Down Expand Up @@ -171,8 +192,8 @@ class UnrealBaseCreator(UnrealCreateLogic, Creator):

settings_category = "unreal"

def create(self, subset_name, instance_data, pre_create_data):
self.create_unreal(subset_name, instance_data, pre_create_data)
def create(self, product_name, instance_data, pre_create_data):
self.create_unreal(product_name, instance_data, pre_create_data)

def collect_instances(self):
return self._default_collect_instances()
Expand Down Expand Up @@ -275,13 +296,13 @@ def get_pre_create_attr_defs(self):

class Loader(LoaderPlugin, ABC):
"""This serves as skeleton for future Ayon specific functionality"""
pass


class LayoutLoader(Loader):
"""Load Layout from a JSON file"""

product_types = {"layout"}
product_base_types = {"layout"}
product_types = product_base_types
representations = {"json"}

label = "Load Layout"
Expand All @@ -301,8 +322,7 @@ def _get_fbx_loader(loaders, family):
elif family == 'camera':
name = "CameraLoader"

if name == "":

if not name:
return None

for loader in loaders:
Expand All @@ -320,7 +340,7 @@ def _get_abc_loader(loaders, family):
name = "StaticMeshAlembicLoader"
elif family in ["animation"]:
name = "AnimationAlembicLoader"
if name == "":
if not name:
return None

for loader in loaders:
Expand Down Expand Up @@ -396,16 +416,36 @@ def _get_repre_entities_by_version_id(self, project_name, data, repre_extension,

def imprint(
self,
context,
folder_path,
folder_name,
loaded_assets,
asset_dir,
asset_name,
container_name,
project_name,
hierarchy_dir=None
):
context: dict[str, Any],
folder_path: str,
folder_name: str,
loaded_assets: list[str],
asset_dir: str,
asset_name: str,
container_name: str,
project_name: str,
hierarchy_dir: Optional[str] = None,
) -> None:
"""Imprint the container with the necessary data.

Args:
context (dict): The context of the loading process.
folder_path (str): The path to the folder where the layout is located.
folder_name (str): The name of the folder
loaded_assets (list): List of loaded assets.
asset_dir (str): The asset directory.
asset_name (str): The asset name.
container_name (str): The name of the container.
project_name (str): The name of the project.
hierarchy_dir (str, optional): The directory of the hierarchy.
Defaults to None.

Note:
This method is re-implemented with different signatures in
many loader plugins. We should consider refactoring it in the
future o avoid code duplication.

"""
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
Expand All @@ -423,33 +463,41 @@ def imprint(
}
if hierarchy_dir is not None:
data["master_directory"] = hierarchy_dir
imprint(
"{}/{}".format(asset_dir, container_name), data)
imprint(f"{asset_dir}/{container_name}", data)

def _load_assets(self, instance_name, repre_id, product_type, repr_format):
def _load_assets(
self,
instance_name,
repre_id,
product_base_type,
repr_format):
all_loaders = discover_loader_plugins()
loaders = loaders_from_representation(
all_loaders, repre_id)

loader = None

if repr_format == 'fbx':
loader = self._get_fbx_loader(loaders, product_type)
loader = self._get_fbx_loader(
loaders, product_base_type)
elif repr_format == 'abc':
loader = self._get_abc_loader(loaders, product_type)
loader = self._get_abc_loader(
loaders, product_base_type)

if not loader:
if repr_format == "ma":
msg = (
f"No valid {product_type} loader found for {repre_id} ({repr_format}), "
f"consider using {product_type} loader (fbx/abc) instead."
f"No valid {product_base_type} loader found "
f"for {repre_id} ({repr_format}), "
f"consider using {product_base_type} loader "
"(fbx/abc) instead."
)
self.log.warning(msg)
else:
self.log.error(
f"No valid loader found for {repre_id} "
f"({repr_format}) "
f"{product_type}")
f"{product_base_type}")
return

import_options = {
Expand Down
2 changes: 1 addition & 1 deletion client/ayon_unreal/plugins/create/create_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class CreateCamera(UnrealAssetCreator):

identifier = "io.ayon.creators.unreal.camera"
label = "Camera"
product_type = "camera"
product_base_type = "camera"
product_type = product_base_type
icon = "fa.camera"
default_variants = ["Main"]

Expand Down
2 changes: 1 addition & 1 deletion client/ayon_unreal/plugins/create/create_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class CreateLayout(UnrealActorCreator):

identifier = "io.ayon.creators.unreal.layout"
label = "Layout"
product_type = "layout"
product_base_type = "layout"
product_type = product_base_type
icon = "cubes"
default_variants = ["Main"]

Expand Down
2 changes: 1 addition & 1 deletion client/ayon_unreal/plugins/create/create_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class CreateRender(UnrealAssetCreator):

identifier = "io.ayon.creators.unreal.render"
label = "Render"
product_type = "render"
product_base_type = "render"
product_type = product_base_type
icon = "eye"
default_variants = ["Main"]

Expand Down
2 changes: 1 addition & 1 deletion client/ayon_unreal/plugins/create/create_staticmeshfbx.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class CreateStaticMeshFBX(UnrealAssetCreator):

identifier = "io.ayon.creators.unreal.staticmeshfbx"
label = "Static Mesh (FBX)"
product_type = "staticMesh"
product_base_type = "staticMesh"
product_type = product_base_type
icon = "cube"
default_variants = ["Main"]
2 changes: 1 addition & 1 deletion client/ayon_unreal/plugins/create/create_uasset.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class CreateUAsset(UnrealAssetCreator):

identifier = "io.ayon.creators.unreal.uasset"
label = "UAsset"
product_type = "uasset"
product_base_type = "uasset"
product_type = product_base_type
icon = "cube"
default_variants = ["Main"]

Expand Down
Loading
Loading