From 6adbaf5551ec242e1e0be3c11919b74012d5d500 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 18 Feb 2026 15:25:46 +0100 Subject: [PATCH 01/11] :sparkles: add settings --- .../plugins/create/create_camera.py | 2 +- .../plugins/create/create_layout.py | 2 +- .../plugins/create/create_render.py | 2 +- .../plugins/create/create_staticmeshfbx.py | 2 +- .../plugins/create/create_uasset.py | 2 +- server/creators.py | 19 +++++++++++++++++++ 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/client/ayon_unreal/plugins/create/create_camera.py b/client/ayon_unreal/plugins/create/create_camera.py index c9560cc9..211f8511 100644 --- a/client/ayon_unreal/plugins/create/create_camera.py +++ b/client/ayon_unreal/plugins/create/create_camera.py @@ -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"] diff --git a/client/ayon_unreal/plugins/create/create_layout.py b/client/ayon_unreal/plugins/create/create_layout.py index 421d374c..62fe30d1 100644 --- a/client/ayon_unreal/plugins/create/create_layout.py +++ b/client/ayon_unreal/plugins/create/create_layout.py @@ -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"] diff --git a/client/ayon_unreal/plugins/create/create_render.py b/client/ayon_unreal/plugins/create/create_render.py index 4bbc94c6..66bb11dc 100644 --- a/client/ayon_unreal/plugins/create/create_render.py +++ b/client/ayon_unreal/plugins/create/create_render.py @@ -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"] diff --git a/client/ayon_unreal/plugins/create/create_staticmeshfbx.py b/client/ayon_unreal/plugins/create/create_staticmeshfbx.py index 5d6bdf46..e923a696 100644 --- a/client/ayon_unreal/plugins/create/create_staticmeshfbx.py +++ b/client/ayon_unreal/plugins/create/create_staticmeshfbx.py @@ -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"] diff --git a/client/ayon_unreal/plugins/create/create_uasset.py b/client/ayon_unreal/plugins/create/create_uasset.py index 92689781..dcfa1af6 100644 --- a/client/ayon_unreal/plugins/create/create_uasset.py +++ b/client/ayon_unreal/plugins/create/create_uasset.py @@ -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"] diff --git a/server/creators.py b/server/creators.py index 91e36215..2331d956 100644 --- a/server/creators.py +++ b/server/creators.py @@ -5,6 +5,18 @@ ) +class ProductTypeItemModel(BaseSettingsModel): + """Product type item for creator plugins.""" + _layout = "compact" + product_type: str = SettingsField( + title="Product Type", + decription="Product type name" + ) + label: str = SettingsField( + title="Label", + description="Label to display in UI for the product type", + ) + class BasicCreatorModel(BaseSettingsModel): enabled: bool = SettingsField(title="Enabled") default_variants: list[str] = SettingsField( @@ -12,6 +24,13 @@ class BasicCreatorModel(BaseSettingsModel): description="Default variants used for constructing a product name", title="Default Variants", ) + product_type_items: list[ProductTypeItemModel] = SettingsField( + default_factory=list, + title="Product Type Items", + description=( + "Optional list of product types that this plugin can create." + ) + ) class CreatorsModel(BaseSettingsModel): CreateCamera: BasicCreatorModel = SettingsField( From 3feb257fb756b3162631c6a6e5b9fc71a74def05 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 18 Feb 2026 19:19:11 +0100 Subject: [PATCH 02/11] :sparkles: add support for product base types --- client/ayon_unreal/api/plugin.py | 125 +++++++++++------- .../plugins/load/load_alembic_animation.py | 97 ++++++++------ .../plugins/load/load_animation.py | 94 ++++++++----- .../ayon_unreal/plugins/load/load_camera.py | 90 ++++++++----- .../plugins/load/load_geometrycache_abc.py | 99 ++++++++------ .../plugins/load/load_image_png.py | 81 +++++++----- .../ayon_unreal/plugins/load/load_layout.py | 13 +- .../plugins/load/load_layout_existing.py | 30 +++-- .../plugins/load/load_skeletalmesh_abc.py | 101 ++++++++------ .../plugins/load/load_skeletalmesh_fbx.py | 88 +++++++----- .../plugins/load/load_staticmesh_abc.py | 85 +++++++----- .../plugins/load/load_staticmesh_fbx.py | 89 ++++++++----- .../ayon_unreal/plugins/load/load_uasset.py | 11 +- .../plugins/load/load_yeticache.py | 7 +- .../plugins/publish/collect_render_files.py | 8 +- .../publish/create_farm_render_instances.py | 8 +- .../plugins/publish/extract_layout.py | 2 +- package.py | 6 +- 18 files changed, 630 insertions(+), 404 deletions(-) diff --git a/client/ayon_unreal/api/plugin.py b/client/ayon_unreal/api/plugin.py index a76af311..7a30e467 100644 --- a/client/ayon_unreal/api/plugin.py +++ b/client/ayon_unreal/api/plugin.py @@ -1,14 +1,16 @@ -# -*- 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 from .pipeline import ( create_publish_instance, - imprint, + imprint as _imprint, ls_inst, UNREAL_VERSION ) @@ -30,7 +32,7 @@ ) -class UnrealCreateLogic(): +class UnrealCreateLogic: """Universal class for logic that Unreal creators could inherit from.""" root = "/Game/Ayon/AyonPublishInstances" suffix = "_INS" @@ -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. @@ -55,20 +57,21 @@ 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) + product_base_type = instance.data["product_base_type"] + unreal_cached_legacy_products[product_base_type].append( + instance) - shared_data["unreal_cached_subsets"] = unreal_cached_subsets - shared_data["unreal_cached_legacy_subsets"] = ( - unreal_cached_legacy_subsets + shared_data["unreal_cached_products"] = unreal_cached_products + shared_data["unreal_cached_legacy_products"] = ( + unreal_cached_legacy_products ) return shared_data @@ -76,7 +79,7 @@ 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', '{}')) @@ -104,7 +107,7 @@ def _default_update_instances(self, update_list): key: changes[key].new_value for key in changes.changed_keys } - imprint( + _imprint( instance_node, new_values ) @@ -127,10 +130,11 @@ def create_unreal(self, product_name, instance_data, pre_create_data): instance_data["instance_path"] = f"{self.root}/{instance_name}" instance = CreatedInstance( - self.product_type, - product_name, - instance_data, - self) + product_type=self.product_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) @@ -142,7 +146,7 @@ def create_unreal(self, product_name, instance_data, pre_create_data): obj = ar.get_asset_by_object_path(member).get_asset() assets.add(obj) - imprint(f"{self.root}/{instance_name}", + _imprint(f"{self.root}/{instance_name}", instance.data_to_store()) return instance @@ -171,8 +175,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() @@ -275,13 +279,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" @@ -301,8 +305,7 @@ def _get_fbx_loader(loaders, family): elif family == 'camera': name = "CameraLoader" - if name == "": - + if not name: return None for loader in loaders: @@ -320,7 +323,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: @@ -396,16 +399,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, @@ -423,10 +446,14 @@ 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) @@ -434,22 +461,26 @@ def _load_assets(self, instance_name, repre_id, product_type, repr_format): 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 = { diff --git a/client/ayon_unreal/plugins/load/load_alembic_animation.py b/client/ayon_unreal/plugins/load/load_alembic_animation.py index e53d39b1..93e4a2c2 100644 --- a/client/ayon_unreal/plugins/load/load_alembic_animation.py +++ b/client/ayon_unreal/plugins/load/load_alembic_animation.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Load Alembic Animation.""" +from __future__ import annotations import os from ayon_core.lib import EnumDef @@ -14,7 +15,8 @@ class AnimationAlembicLoader(plugin.Loader): """Load Unreal SkeletalMesh from Alembic""" - product_types = {"animation"} + product_base_types = {"animation"} + product_types = product_base_types label = "Import Alembic Animation" representations = {"abc"} icon = "cube" @@ -140,17 +142,37 @@ def import_and_containerize( def imprint( self, - folder_path, - asset_dir, - container_name, - asset_name, - frameStart, - frameEnd, - representation, - product_type, - project_name, - layout - ): + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + frame_start: int, + frame_end: int, + representation: dict, + project_name: str, + product_base_type: str, + *, + layout: bool + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + frame_start (int): Start frame of the animation. + frame_end (int): End frame of the animation. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + project_name (str): Name of the project to imprint. + layout (bool): Whether the container is created with layout. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -161,12 +183,11 @@ def imprint( "loader": str(self.__class__.__name__), "representation": representation["id"], "parent": representation["versionId"], - "product_type": product_type, - "frameStart": frameStart, - "frameEnd": frameEnd, + "frameStart": frame_start, + "frameEnd": frame_end, # TODO these should be probably removed "asset": folder_path, - "family": product_type, + "family": product_base_type, "project_name": project_name, "layout": layout } @@ -197,7 +218,7 @@ def load(self, context, name, namespace, options): # Create directory for asset and ayon container folder_entity = context["folder"] folder_path = context["folder"]["path"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] suffix = "_CON" path = self.filepath_from_context(context) asset_root, asset_name = unreal_pipeline.format_asset_directory( @@ -236,16 +257,16 @@ def load(self, context, name, namespace, options): # update metadata self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - folder_entity["attrib"]["frameStart"], - folder_entity["attrib"]["frameEnd"], - context["representation"], - product_type, - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + frame_start=folder_entity["attrib"]["frameStart"], + frame_end=folder_entity["attrib"]["frameEnd"], + representation=context["representation"], + product_base_type=product_base_type, + project_name=context["project"]["name"], + layout=should_use_layout ) asset_content = unreal.EditorAssetLibrary.list_assets( @@ -259,7 +280,7 @@ def load(self, context, name, namespace, options): def update(self, container, context): folder_path = context["folder"]["path"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] repre_entity = context["representation"] # Create directory for folder and Ayon container @@ -302,16 +323,16 @@ def update(self, container, context): # update metadata self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - container.get("frameStart", "1"), - container.get("frameEnd", "1"), - repre_entity, - product_type, - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + frame_start=container.get("frameStart", "1"), + frame_end=container.get("frameEnd", "1"), + representation=repre_entity, + product_base_type=product_base_type, + project_name=context["project"]["name"], + layout=should_use_layout ) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True diff --git a/client/ayon_unreal/plugins/load/load_animation.py b/client/ayon_unreal/plugins/load/load_animation.py index d32301ac..2a4bf4bc 100644 --- a/client/ayon_unreal/plugins/load/load_animation.py +++ b/client/ayon_unreal/plugins/load/load_animation.py @@ -20,7 +20,8 @@ class AnimationFBXLoader(plugin.Loader): """Load Unreal SkeletalMesh from FBX.""" - product_types = {"animation"} + product_base_types = {"animation"} + product_types = product_base_types label = "Import FBX Animation" representations = {"fbx"} icon = "cube" @@ -382,17 +383,38 @@ def _import_animation_with_json(self, path, context, hierarchy, return master_level, asset_dir def imprint( - self, - folder_path, - asset_dir, - container_name, - asset_name, - representation, - product_type, - folder_entity, - project_name, - layout - ): + self, + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + representation: dict, + product_base_type: str, + frame_start: int, + frame_end: int, + project_name: str, + *, + layout: bool, + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + frame_start (int): Start frame of the asset. + frame_end (int): End frame of the asset. + project_name (str): Name of the project to imprint. + layout (bool): Whether the container is created with layout. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -403,12 +425,12 @@ def imprint( "representation": representation["id"], "parent": representation["versionId"], "folder_path": folder_path, - "product_type": product_type, + "product_base_type": product_base_type, # TODO these shold be probably removed "asset": folder_path, - "family": product_type, - "frameStart": folder_entity["attrib"]["frameStart"], - "frameEnd": folder_entity["attrib"]["frameEnd"], + "family": product_base_type, + "frameStart": frame_start, + "frameEnd": frame_end, "project_name": project_name, "layout": layout } @@ -442,7 +464,7 @@ def load(self, context, name, namespace, options): folder_path = folder_entity["path"] hierarchy = folder_path.lstrip("/").split("/") folder_name = hierarchy.pop(-1) - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] suffix = "_CON" @@ -472,15 +494,16 @@ def load(self, context, name, namespace, options): container=container_name, path=asset_dir) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - context["representation"], - product_type, - folder_entity, - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + product_base_type=product_base_type, + frame_start=folder_entity["attrib"]["frameStart"], + frame_end=folder_entity["attrib"]["frameEnd"], + project_name=context["project"]["name"], + layout=should_use_layout ) imported_content = EditorAssetLibrary.list_assets( @@ -540,15 +563,16 @@ def update(self, container, context): # update metadata self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - product_type, - folder_entity, - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=repre_entity, + product_base_type=product_type, + frame_start=folder_entity["attrib"]["frameStart"], + frame_end=folder_entity["attrib"]["frameEnd"], + project_name=context["project"]["name"], + layout=should_use_layout ) asset_content = EditorAssetLibrary.list_assets( diff --git a/client/ayon_unreal/plugins/load/load_camera.py b/client/ayon_unreal/plugins/load/load_camera.py index bd62ca8d..7bd5ff4f 100644 --- a/client/ayon_unreal/plugins/load/load_camera.py +++ b/client/ayon_unreal/plugins/load/load_camera.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Load camera from FBX.""" +from __future__ import annotations import unreal from unreal import ( EditorAssetLibrary, @@ -23,7 +24,8 @@ class CameraLoader(plugin.Loader): """Load Unreal StaticMesh from FBX""" - product_types = {"camera"} + product_base_types = {"camera"} + product_types = product_base_types label = "Load Camera" representations = {"fbx"} icon = "cube" @@ -75,17 +77,35 @@ def _import_camera( f"Unreal version {ue_major} not supported") def imprint( - self, - folder_path, - asset_dir, - container_name, - asset_name, - representation, - folder_name, - product_type, - folder_entity, - project_name - ): + self, + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + representation: dict, + product_base_type: str, + frame_start: int, + frame_end: int, + project_name: str, + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + frame_start (int): Start frame of the asset. + frame_end (int): End frame of the asset. + project_name (str): Name of the project to imprint. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -96,12 +116,12 @@ def imprint( "loader": str(self.__class__.__name__), "representation": representation["id"], "parent": representation["versionId"], - "product_type": product_type, + "product_base_type": product_base_type, # TODO these should be probably removed - "asset": folder_name, - "family": product_type, - "frameStart": folder_entity["attrib"]["frameStart"], - "frameEnd": folder_entity["attrib"]["frameEnd"], + "asset": asset_name, + "family": product_base_type, + "frameStart": frame_start, + "frameEnd": frame_end, "project_name": project_name } imprint(f"{asset_dir}/{container_name}", data) @@ -214,15 +234,15 @@ def load(self, context, name, namespace, options): container=container_name, path=asset_dir) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - context["representation"], - folder_name, - context["product"]["productType"], - folder_entity, - context["project"]["name"] + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + product_base_type=context["product"]["productType"], + frame_start=folder_entity["attrib"].get("frameStart"), + frame_end=folder_entity["attrib"].get("frameEnd"), + project_name=context["project"]["name"], ) EditorLevelLibrary.save_all_dirty_levels() @@ -269,15 +289,15 @@ def update(self, container, context): container=container_name, path=asset_dir) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - context["representation"], - folder_entity["name"], - context["product"]["productType"], - folder_entity, - context["project"]["name"] + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + product_base_type=context["product"]["productType"], + frame_start=folder_entity["attrib"].get("frameStart"), + frame_end=folder_entity["attrib"].get("frameEnd"), + project_name=context["project"]["name"], ) EditorLevelLibrary.save_all_dirty_levels() diff --git a/client/ayon_unreal/plugins/load/load_geometrycache_abc.py b/client/ayon_unreal/plugins/load/load_geometrycache_abc.py index d329b769..fe7821ab 100644 --- a/client/ayon_unreal/plugins/load/load_geometrycache_abc.py +++ b/client/ayon_unreal/plugins/load/load_geometrycache_abc.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """Loader for published alembics.""" - +from __future__ import annotations from ayon_core.pipeline import AYON_CONTAINER_ID from ayon_core.lib import EnumDef from ayon_unreal.api import plugin from ayon_unreal.api.pipeline import ( create_container, - imprint, + imprint as _imprint, format_asset_directory, UNREAL_VERSION, get_dir_from_existing_asset @@ -19,7 +19,8 @@ class PointCacheAlembicLoader(plugin.Loader): """Load Point Cache from Alembic""" - product_types = {"model", "pointcache"} + product_base_types = {"model", "pointcache"} + product_types = product_base_types label = "Import Alembic Point Cache" representations = {"abc"} icon = "cube" @@ -157,18 +158,38 @@ def import_and_containerize( return asset_dir def imprint( - self, - folder_path, - asset_dir, - container_name, - asset_name, - representation, - frame_start, - frame_end, - product_type, - project_name, - layout - ): + self, + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + representation: dict, + product_base_type: str, + frame_start: int, + frame_end: int, + project_name: str, + *, + layout: bool, + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + frame_start (int): Start frame of the asset. + frame_end (int): End frame of the asset. + project_name (str): Name of the project to imprint. + layout (bool): Whether the container is created with layout. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -180,15 +201,15 @@ def imprint( "parent": representation["versionId"], "frame_start": frame_start, "frame_end": frame_end, - "product_type": product_type, + "product_base_type": product_base_type, "folder_path": folder_path, # TODO these should be probably removed - "family": product_type, + "family": product_base_type, "asset": folder_path, "project_name": project_name, "layout": layout } - imprint(f"{asset_dir}/{container_name}", data) + _imprint(f"{asset_dir}/{container_name}", data) def load(self, context, name, namespace, options): """Load and containerise representation into Content Browser. @@ -249,16 +270,16 @@ def load(self, context, name, namespace, options): ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - context["representation"], - frame_start, - frame_end, - context["product"]["productType"], - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + frame_start=frame_start, + frame_end=frame_end, + product_base_type=context["product"]["productBaseType"], + project_name=context["project"]["name"], + layout=should_use_layout, ) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True @@ -272,7 +293,7 @@ def load(self, context, name, namespace, options): def update(self, container, context): # Create directory for folder and Ayon container folder_path = context["folder"]["path"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] repre_entity = context["representation"] asset_dir = container["namespace"] suffix = "_CON" @@ -306,16 +327,16 @@ def update(self, container, context): frame_start, frame_end, loaded_options) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - frame_start, - frame_end, - product_type, - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=repre_entity, + frame_start=frame_start, + frame_end=frame_end, + product_base_type=product_base_type, + project_name=context["project"]["name"], + layout=should_use_layout, ) asset_content = unreal.EditorAssetLibrary.list_assets( diff --git a/client/ayon_unreal/plugins/load/load_image_png.py b/client/ayon_unreal/plugins/load/load_image_png.py index c214d7c9..89c50816 100644 --- a/client/ayon_unreal/plugins/load/load_image_png.py +++ b/client/ayon_unreal/plugins/load/load_image_png.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- """Load textures from PNG.""" +from __future__ import annotations + from ayon_core.pipeline import AYON_CONTAINER_ID from ayon_unreal.api import plugin from ayon_unreal.api.pipeline import ( create_container, - imprint, + imprint as _imprint, format_asset_directory ) @@ -14,7 +16,8 @@ class TexturePNGLoader(plugin.Loader): """Load Unreal texture from PNG file.""" - product_types = {"image", "texture", "render"} + product_base_types = {"image", "texture", "render"} + product_types = product_base_types label = "Import image texture 2d" representations = {"*"} extensions = {"png", "jpg", "tiff", "exr"} @@ -85,15 +88,31 @@ def import_and_containerize( return asset_dir def imprint( - self, - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - product_type, - project_name - ): + self, + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + representation: dict, + product_base_type: str, + project_name: str, + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + project_name (str): Name of the project to imprint. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -102,15 +121,15 @@ def imprint( "container_name": container_name, "asset_name": asset_name, "loader": str(self.__class__.__name__), - "representation": repre_entity["id"], - "parent": repre_entity["versionId"], - "product_type": product_type, + "representation": representation["id"], + "parent": representation["versionId"], + "product_type": product_bse_type, # TODO these shold be probably removed "asset": folder_path, - "family": product_type, + "family": product_base_type, "project_name": project_name } - imprint(f"{asset_dir}/{container_name}", data) + _imprint(f"{asset_dir}/{container_name}", data) def load(self, context, name, namespace, options): """Load and containerise representation into Content Browser. @@ -144,13 +163,13 @@ def load(self, context, name, namespace, options): path, asset_dir, container_name ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - context["representation"], - context["product"]["productType"], - context["project"]["name"], + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + product_base_type=context["product"]["productBaseType"], + project_name=context["project"]["name"], ) asset_contents = unreal.EditorAssetLibrary.list_assets( @@ -163,7 +182,7 @@ def load(self, context, name, namespace, options): def update(self, container, context): folder_path = context["folder"]["path"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] repre_entity = context["representation"] path = self.filepath_from_context(context) @@ -182,13 +201,13 @@ def update(self, container, context): ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - product_type, - context["project"]["name"] + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=repre_entity, + product_base_type=product_base_type, + project_name=context["project"]["name"], ) asset_contents = unreal.EditorAssetLibrary.list_assets( diff --git a/client/ayon_unreal/plugins/load/load_layout.py b/client/ayon_unreal/plugins/load/load_layout.py index 4c04f9cf..c27dda0c 100644 --- a/client/ayon_unreal/plugins/load/load_layout.py +++ b/client/ayon_unreal/plugins/load/load_layout.py @@ -180,12 +180,13 @@ def _process(self, lib_path, project_name, asset_dir, sequence, if repre_id not in repr_loaded: repr_loaded.append(repre_id) - product_type = element.get("product_type") - if product_type is None: - product_type = element.get("family") + product_base_type = ( + element.get("product_base_type") + or element.get("product_type") + ) or element.get("family") assets = self._load_assets( - instance_name, repre_id, product_type, repr_format + instance_name, repre_id, product_base_type, repr_format ) container = None @@ -216,12 +217,12 @@ def _process(self, lib_path, project_name, asset_dir, sequence, actors = [] - if product_type in ['model', 'staticMesh']: + if product_base_type in ['model', 'staticMesh']: actors, _ = self._process_family( assets, 'StaticMesh', transform, basis, sequence, rotation, unreal_import=unreal_import ) - elif product_type in ['rig', 'skeletalMesh']: + elif product_base_type in ['rig', 'skeletalMesh']: actors, bindings = self._process_family( assets, 'SkeletalMesh', transform, basis, sequence, rotation, unreal_import=unreal_import diff --git a/client/ayon_unreal/plugins/load/load_layout_existing.py b/client/ayon_unreal/plugins/load/load_layout_existing.py index 129f4080..6731e7d0 100644 --- a/client/ayon_unreal/plugins/load/load_layout_existing.py +++ b/client/ayon_unreal/plugins/load/load_layout_existing.py @@ -59,7 +59,12 @@ def _spawn_actor(self, obj, lasset, sequence): "Skipping to add spawned actor into the sequence." ) - def _load_asset(self, repr_data, instance_name, family, extension): + def _load_asset( + self, + repr_data, + instance_name, + product_base_type, + extension): repre_entity = next((repre_entity for repre_entity in repr_data if repre_entity["name"] == extension), None) if not repre_entity or extension == "ma": @@ -67,10 +72,9 @@ def _load_asset(self, repr_data, instance_name, family, extension): repr_format = repre_entity.get('name') representation = repre_entity.get('id') - assets = self._load_assets( - instance_name, representation, family, repr_format + return self._load_assets( + instance_name, representation, product_base_type, repr_format ) - return assets def _process(self, lib_path, project_name, sequence): ar = unreal.AssetRegistryHelpers.get_asset_registry() @@ -223,31 +227,33 @@ def _process(self, lib_path, project_name, sequence): f" {version_id}") continue - product_type = lasset.get("product_type") - if product_type is None: - product_type = lasset.get("family") + product_base_type = ( + lasset.get("product_base_type") + or lasset.get("product_type") + ) or lasset.get("family") + extension = lasset.get("extension") assets = self._load_asset( repre_entities, - lasset.get('instance_name'), - product_type, + lasset.get("instance_name"), + product_base_type, extension ) con = None for asset in assets: obj = ar.get_asset_by_object_path(asset).get_asset() - if not obj.get_class().get_name() == 'StaticMesh': + if obj.get_class().get_name() != "StaticMesh": continue self._spawn_actor(obj, lasset, sequence) - if obj.get_class().get_name() == 'AyonAssetContainer': + if obj.get_class().get_name() == "AyonAssetContainer": con = obj containers.append(con.get_path_name()) break # Check if an actor was not matched to a representation. # If so, remove it from the scene. for actor in actors: - if not actor.get_class().get_name() == 'StaticMeshActor': + if actor.get_class().get_name() != "StaticMeshActor": continue if actor not in actors_matched: self.log.warning(f"Actor {actor.get_name()} not matched.") diff --git a/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py b/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py index 10b0ab47..68f3bdee 100644 --- a/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py +++ b/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- """Load Skeletal Mesh alembics.""" - +from __future__ import annotations from ayon_core.pipeline import AYON_CONTAINER_ID from ayon_core.lib import EnumDef from ayon_unreal.api import plugin from ayon_unreal.api.pipeline import ( create_container, - imprint, + imprint as _imprint, format_asset_directory, UNREAL_VERSION, get_dir_from_existing_asset @@ -19,7 +19,8 @@ class SkeletalMeshAlembicLoader(plugin.Loader): """Load Unreal SkeletalMesh from Alembic""" - product_types = {"pointcache", "skeletalMesh"} + product_base_types = {"pointcache", "skeletalMesh"} + product_types = product_base_types label = "Import Alembic Skeletal Mesh" representations = {"abc"} icon = "cube" @@ -168,17 +169,37 @@ def import_and_containerize( def imprint( self, - folder_path, - asset_dir, - container_name, - asset_name, - representation, - product_type, - frameStart, - frameEnd, - project_name, - layout - ): + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + representation: dict, + product_base_type: str, + frame_start: int, + frame_end: int, + project_name: str, + *, + layout: bool, + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + frame_start (int): Start frame of the asset. + frame_end (int): End frame of the asset. + project_name (str): Name of the project to imprint. + layout (bool): Whether the container is created with layout. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -189,17 +210,17 @@ def imprint( "loader": str(self.__class__.__name__), "representation": representation["id"], "parent": representation["versionId"], - "product_type": product_type, - "frameStart":frameStart, - "frameEnd": frameEnd, + "product_base_type": product_base_type, + "frameStart":frame_start, + "frameEnd": frame_end, # TODO these should be probably removed "asset": folder_path, - "family": product_type, + "family": product_base_type, "project_name": project_name, "layout": layout } - imprint(f"{asset_dir}/{container_name}", data) + _imprint(f"{asset_dir}/{container_name}", data) def load(self, context, name, namespace, options): """Load and containerise representation into Content Browser. @@ -254,16 +275,16 @@ def load(self, context, name, namespace, options): ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - context["representation"], - context["product"]["productType"], - folder_entity["attrib"]["frameStart"], - folder_entity["attrib"]["frameEnd"], - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + product_base_type=context["product"]["productBaseType"], + frame_start=folder_entity["attrib"]["frameStart"], + frame_end=folder_entity["attrib"]["frameEnd"], + project_name=context["project"]["name"], + layout=should_use_layout, ) asset_content = unreal.EditorAssetLibrary.list_assets( @@ -277,7 +298,7 @@ def load(self, context, name, namespace, options): def update(self, container, context): folder_path = context["folder"]["path"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] repre_entity = context["representation"] # Create directory for folder and Ayon container @@ -312,16 +333,16 @@ def update(self, container, context): ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - product_type, - container.get("frameStart", 1), - container.get("frameEnd", 1), - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=repre_entity, + product_base_type=product_base_type, + frame_start=container.get("frameStart", 1), + frame_end=container.get("frameEnd", 1), + project_name=context["project"]["name"], + layout=should_use_layout ) asset_content = unreal.EditorAssetLibrary.list_assets( diff --git a/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py b/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py index 9b441eaa..fa5ad18c 100644 --- a/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py +++ b/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- """Load Skeletal Meshes form FBX.""" +from __future__ import annotations from ayon_core.pipeline import AYON_CONTAINER_ID from ayon_unreal.api import plugin from ayon_unreal.api.pipeline import ( create_container, - imprint, + _imprint, format_asset_directory, get_dir_from_existing_asset ) @@ -15,7 +16,8 @@ class SkeletalMeshFBXLoader(plugin.Loader): """Load Unreal SkeletalMesh from FBX.""" - product_types = {"rig", "skeletalMesh"} + product_base_types = {"rig", "skeletalMesh"} + product_types = product_base_types label = "Import FBX Skeletal Mesh" representations = {"fbx"} icon = "cube" @@ -93,15 +95,35 @@ def import_and_containerize( def imprint( self, - folder_path, - asset_dir, - container_name, - asset_name, - representation, - product_type, - project_name, - layout - ): + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + representation: dict, + project_name: str, + product_base_type: str, + *, + layout: bool, + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + frame_start (int): Start frame of the asset. + frame_end (int): End frame of the asset. + project_name (str): Name of the project to imprint. + layout (bool): Whether the container is created with layout. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -112,14 +134,14 @@ def imprint( "loader": str(self.__class__.__name__), "representation": representation["id"], "parent": representation["versionId"], - "product_type": product_type, + "product_base_type": product_base_type, # TODO these should be probably removed "asset": folder_path, - "family": product_type, + "family": product_base_type, "project_name": project_name, "layout": layout } - imprint(f"{asset_dir}/{container_name}", data) + _imprint(f"{asset_dir}/{container_name}", data) def load(self, context, name, namespace, options): """Load and containerise representation into Content Browser. @@ -138,7 +160,7 @@ def load(self, context, name, namespace, options): """ # Create directory for asset and Ayon container folder_name = context["folder"]["name"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] suffix = "_CON" path = self.filepath_from_context(context) asset_root, asset_name = format_asset_directory( @@ -164,16 +186,15 @@ def load(self, context, name, namespace, options): ) self.imprint( - folder_name, - asset_dir, - container_name, - asset_name, - context["representation"], - product_type, - context["project"]["name"], - should_use_layout + folder_path=folder_name, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + product_base_type=product_base_type, + project_name=context["project"]["name"], + layout=should_use_layout, ) - asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True ) @@ -185,7 +206,7 @@ def load(self, context, name, namespace, options): def update(self, container, context): folder_path = context["folder"]["path"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] repre_entity = context["representation"] # Create directory for asset and Ayon container @@ -214,15 +235,14 @@ def update(self, container, context): ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - product_type, - context["project"]["name"], - should_use_layout - ) + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=repre_entity, + product_base_type=product_base_type, + project_name=context["project"]["name"], + layout=should_use_layout) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=False diff --git a/client/ayon_unreal/plugins/load/load_staticmesh_abc.py b/client/ayon_unreal/plugins/load/load_staticmesh_abc.py index 4ae3c977..5d7214ad 100644 --- a/client/ayon_unreal/plugins/load/load_staticmesh_abc.py +++ b/client/ayon_unreal/plugins/load/load_staticmesh_abc.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """Loader for Static Mesh alembics.""" - +from __future__ import annotations from ayon_core.pipeline import AYON_CONTAINER_ID from ayon_unreal.api import plugin from ayon_unreal.api.pipeline import ( create_container, - imprint, + imprint as _imprint, format_asset_directory, UNREAL_VERSION, get_dir_from_existing_asset @@ -18,7 +18,8 @@ class StaticMeshAlembicLoader(plugin.Loader): """Load Unreal StaticMesh from Alembic""" - product_types = {"model", "staticMesh"} + product_base_types = {"model", "staticMesh"} + product_types = product_base_types label = "Import Alembic Static Mesh" representations = {"abc"} icon = "cube" @@ -185,15 +186,33 @@ def import_and_containerize( def imprint( self, - folder_path, - asset_dir, - container_name, - asset_name, - representation, - product_type, - project_name, - layout - ): + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + representation: dict, + product_base_type: str, + project_name: str, + *, + layout: bool, + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + project_name (str): Name of the project to imprint. + layout (bool): Whether the container is created with layout. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -204,14 +223,14 @@ def imprint( "loader": str(self.__class__.__name__), "representation": representation["id"], "parent": representation["versionId"], - "product_type": product_type, + "product_base_type": product_base_type, # TODO these should be probably removed "asset": folder_path, - "family": product_type, + "family": product_base_type, "project_name": project_name, "layout": layout } - imprint(f"{asset_dir}/{container_name}", data) + _imprint(f"{asset_dir}/{container_name}", data) def load(self, context, name, namespace, options): """Load and containerise representation into Content Browser. @@ -264,16 +283,16 @@ def load(self, context, name, namespace, options): container_name, loaded_options ) - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - context["representation"], - product_type, - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + product_base_type=product_base_type, + project_name=context["project"]["name"], + layout=should_use_layout ) asset_content = unreal.EditorAssetLibrary.list_assets( @@ -286,7 +305,7 @@ def load(self, context, name, namespace, options): def update(self, container, context): folder_path = context["folder"]["path"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] repre_entity = context["representation"] # Create directory for asset and Ayon container suffix = "_CON" @@ -318,14 +337,14 @@ def update(self, container, context): ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - product_type, - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=repre_entity, + product_base_type=product_base_type, + project_name=context["project"]["name"], + layout=should_use_layout, ) asset_content = unreal.EditorAssetLibrary.list_assets( diff --git a/client/ayon_unreal/plugins/load/load_staticmesh_fbx.py b/client/ayon_unreal/plugins/load/load_staticmesh_fbx.py index 098af80d..70061e5d 100644 --- a/client/ayon_unreal/plugins/load/load_staticmesh_fbx.py +++ b/client/ayon_unreal/plugins/load/load_staticmesh_fbx.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """Load Static meshes form FBX.""" - +from __future__ import annotations from ayon_core.pipeline import AYON_CONTAINER_ID from ayon_unreal.api import plugin from ayon_unreal.api.pipeline import ( create_container, - imprint, + imprint as _imprint, format_asset_directory, get_dir_from_existing_asset ) @@ -16,7 +16,8 @@ class StaticMeshFBXLoader(plugin.Loader): """Load Unreal StaticMesh from FBX.""" - product_types = {"model", "staticMesh"} + product_base_types = {"model", "staticMesh"} + product_types = product_base_types label = "Import FBX Static Mesh" representations = {"fbx"} icon = "cube" @@ -92,16 +93,34 @@ def import_and_containerize( return asset_dir def imprint( - self, - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - product_type, - project_name, - layout - ): + self, + folder_path: str, + asset_dir: str, + container_name: str, + asset_name: str, + representation: dict, + product_base_type: str, + project_name: str, + *, + layout: bool, + ) -> None: + """Imprint AYON_CONTAINER_ID to the container asset. + + Args: + folder_path (str): Path to the folder in Unreal Content Browser. + asset_dir (str): Directory of the asset in Unreal Content Browser. + container_name (str): Name of the container asset. + asset_name (str): Name of the main asset. + representation (dict): Representation data to imprint. + product_base_type (str): Product base type to imprint. + project_name (str): Name of the project to imprint. + layout (bool): Whether the container is created with layout. + + Todo (antirotor): + This per loader imprint is wrong and should be moved to some + common place, custom data usage should be re-evaluated. + + """ data = { "schema": "ayon:container-2.0", "id": AYON_CONTAINER_ID, @@ -110,16 +129,16 @@ def imprint( "container_name": container_name, "asset_name": asset_name, "loader": str(self.__class__.__name__), - "representation": repre_entity["id"], - "parent": repre_entity["versionId"], - "product_type": product_type, + "representation": representation["id"], + "parent": representation["versionId"], + "product_base_type": product_base_type, # TODO these shold be probably removed "asset": folder_path, - "family": product_type, + "family": product_base_type, "project_name": project_name, "layout": layout } - imprint(f"{asset_dir}/{container_name}", data) + _imprint(f"{asset_dir}/{container_name}", data) def load(self, context, name, namespace, options): """Load and containerise representation into Content Browser. @@ -163,14 +182,14 @@ def load(self, context, name, namespace, options): ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - context["representation"], - context["product"]["productType"], - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=context["representation"], + product_base_type=context["product"]["productBaseType"], + project_name=context["project"]["name"], + layout=should_use_layout ) asset_content = unreal.EditorAssetLibrary.list_assets( @@ -184,7 +203,7 @@ def load(self, context, name, namespace, options): def update(self, container, context): folder_path = context["folder"]["path"] - product_type = context["product"]["productType"] + product_base_type = context["product"]["productBaseType"] repre_entity = context["representation"] # Create directory for asset and Ayon container @@ -212,14 +231,14 @@ def update(self, container, context): ) self.imprint( - folder_path, - asset_dir, - container_name, - asset_name, - repre_entity, - product_type, - context["project"]["name"], - should_use_layout + folder_path=folder_path, + asset_dir=asset_dir, + container_name=container_name, + asset_name=asset_name, + representation=repre_entity, + product_base_type=product_base_type, + project_name=context["project"]["name"], + layout=should_use_layout ) asset_content = unreal.EditorAssetLibrary.list_assets( diff --git a/client/ayon_unreal/plugins/load/load_uasset.py b/client/ayon_unreal/plugins/load/load_uasset.py index 3631968b..eebe4ed6 100644 --- a/client/ayon_unreal/plugins/load/load_uasset.py +++ b/client/ayon_unreal/plugins/load/load_uasset.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Load UAsset.""" +from __future__ import annotations from pathlib import Path import os import shutil @@ -33,7 +34,8 @@ def parsing_to_absolute_directory(asset_dir): class UAssetLoader(plugin.Loader): """Load UAsset.""" - product_types = {"uasset"} + product_base_types = {"uasset"} + product_types = product_base_types label = "Load UAsset" representations = {"uasset"} icon = "cube" @@ -105,10 +107,10 @@ def load(self, context, name, namespace, options): "loader": str(self.__class__.__name__), "representation": context["representation"]["id"], "parent": context["representation"]["versionId"], - "product_type": context["product"]["productType"], + "product_base_type": context["product"]["productBaseType"], # TODO these should be probably removed "asset": folder_path, - "family": context["product"]["productType"], + "family": context["product"]["productBaseType"], "project_name": context["project"]["name"] } @@ -177,7 +179,8 @@ def remove(self, container): class UMapLoader(UAssetLoader): """Load Level.""" - product_types = {"uasset"} + product_base_types = {"uasset"} + product_types = product_base_types label = "Load Level" representations = {"umap"} diff --git a/client/ayon_unreal/plugins/load/load_yeticache.py b/client/ayon_unreal/plugins/load/load_yeticache.py index c6710a55..a6e40999 100644 --- a/client/ayon_unreal/plugins/load/load_yeticache.py +++ b/client/ayon_unreal/plugins/load/load_yeticache.py @@ -11,7 +11,8 @@ class YetiLoader(plugin.Loader): """Load Yeti Cache""" - product_types = {"yeticacheUE"} + product_base_types = {"yeticacheUE"} + product_types = product_base_types label = "Import Yeti" representations = {"abc"} icon = "pagelines" @@ -138,10 +139,10 @@ def load(self, context, name, namespace, options): "loader": str(self.__class__.__name__), "representation": context["representation"]["id"], "parent": context["representation"]["versionId"], - "product_type": context["product"]["productType"], + "product_base_type": context["product"]["productBaseType"], # TODO these shold be probably removed "asset": folder_path, - "family": context["product"]["productType"], + "family": context["product"]["productBaseType"], "project_name": context["project"]["name"], "layout": should_use_layout } diff --git a/client/ayon_unreal/plugins/publish/collect_render_files.py b/client/ayon_unreal/plugins/publish/collect_render_files.py index c2cfe35e..27a1727e 100644 --- a/client/ayon_unreal/plugins/publish/collect_render_files.py +++ b/client/ayon_unreal/plugins/publish/collect_render_files.py @@ -57,7 +57,7 @@ def process(self, instance): seq = s.get('sequence') seq_name = seq.get_name() - product_type = "render" + product_base_type = "render" new_product_name = f"{data.get('productName')}_{seq_name}" new_instance = context.create_instance( new_product_name @@ -69,9 +69,9 @@ def process(self, instance): new_data["folderPath"] = instance.data["folderPath"] new_data["setMembers"] = seq_name new_data["productName"] = new_product_name - new_data["productType"] = product_type - new_data["family"] = product_type - new_data["families"] = [product_type, "review"] + new_data["productBaseType"] = product_base_type + new_data["family"] = product_base_type + new_data["families"] = [product_base_type, "review"] new_data["parent"] = data.get("parent") new_data["level"] = data.get("level") new_data["output"] = s['output'] diff --git a/client/ayon_unreal/plugins/publish/create_farm_render_instances.py b/client/ayon_unreal/plugins/publish/create_farm_render_instances.py index 74080f41..e7007275 100644 --- a/client/ayon_unreal/plugins/publish/create_farm_render_instances.py +++ b/client/ayon_unreal/plugins/publish/create_farm_render_instances.py @@ -90,10 +90,10 @@ def preparing_rendering_instance(self, instance): new_data["folderPath"] = instance.data["folderPath"] new_data["setMembers"] = seq_name new_data["productName"] = new_product_name - product_type = "render" - new_data["productType"] = product_type - new_data["family"] = product_type - new_data["families"] = [product_type, "review"] + product_base_type = "render" + new_data["productBaseType"] = product_base_type + new_data["family"] = product_base_type + new_data["families"] = [product_base_type, "review"] new_data["parent"] = data.get("parent") new_data["level"] = data.get("level") new_data["output"] = s['output'] diff --git a/client/ayon_unreal/plugins/publish/extract_layout.py b/client/ayon_unreal/plugins/publish/extract_layout.py index 504e83f9..b1692b37 100644 --- a/client/ayon_unreal/plugins/publish/extract_layout.py +++ b/client/ayon_unreal/plugins/publish/extract_layout.py @@ -76,7 +76,7 @@ def process(self, instance): extension = instance_name.split("_")[-1] asset_name = re.match(f'(.+)_{extension}$', instance_name) json_element["version"] = str(parent_id) - json_element["product_type"] = family + json_element["product_base_type"] = family json_element["instance_name"] = asset_name.group(1) json_element["asset_name"] = instance_name json_element["extension"] = extension diff --git a/package.py b/package.py index ac5baa84..66716ca1 100644 --- a/package.py +++ b/package.py @@ -5,10 +5,10 @@ client_dir = "ayon_unreal" ayon_required_addons = { - "core": ">=1.0.10", + "core": ">=1.8.0", } -ayon_server_version = ">1.3.0" +ayon_server_version = ">1.10.0" ayon_compatible_addons = { "ayon_maya": ">=0.2.13", - "ayon_deadline": ">=0.5.0", + "ayon_deadline": ">=0.7.0", } From c6eb9014aea0429a9d2e1f007b9bf7d5bda043d7 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 18 Feb 2026 19:23:45 +0100 Subject: [PATCH 03/11] :bug: fix typo --- client/ayon_unreal/plugins/load/load_image_png.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_unreal/plugins/load/load_image_png.py b/client/ayon_unreal/plugins/load/load_image_png.py index 89c50816..0d24be6d 100644 --- a/client/ayon_unreal/plugins/load/load_image_png.py +++ b/client/ayon_unreal/plugins/load/load_image_png.py @@ -123,7 +123,7 @@ def imprint( "loader": str(self.__class__.__name__), "representation": representation["id"], "parent": representation["versionId"], - "product_type": product_bse_type, + "product_type": product_base_type, # TODO these shold be probably removed "asset": folder_path, "family": product_base_type, From 6b0f1273a4a0151c40892b464ed595f5d7ceb796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 14 Apr 2026 17:48:03 +0200 Subject: [PATCH 04/11] refactor: apply suggestions fix loaders and imprint renaming, support for legacy instances without product base type --- client/ayon_unreal/api/plugin.py | 29 +++++++++++++++---- .../plugins/load/load_alembic_animation.py | 1 + .../plugins/load/load_animation.py | 2 +- .../ayon_unreal/plugins/load/load_camera.py | 2 +- .../plugins/load/load_image_png.py | 2 +- .../plugins/load/load_skeletalmesh_abc.py | 2 +- server/creators.py | 2 +- 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/client/ayon_unreal/api/plugin.py b/client/ayon_unreal/api/plugin.py index 7a30e467..460f9d9b 100644 --- a/client/ayon_unreal/api/plugin.py +++ b/client/ayon_unreal/api/plugin.py @@ -10,7 +10,7 @@ from .pipeline import ( create_publish_instance, - imprint as _imprint, + imprint, ls_inst, UNREAL_VERSION ) @@ -65,7 +65,19 @@ def get_cached_instances(shared_data): if creator_id: unreal_cached_products[creator_id].append(instance) else: - product_base_type = instance.data["product_base_type"] + product_base_type = instance["product_base_type"] + # 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) @@ -107,7 +119,7 @@ def _default_update_instances(self, update_list): key: changes[key].new_value for key in changes.changed_keys } - _imprint( + imprint( instance_node, new_values ) @@ -129,8 +141,13 @@ 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( - product_type=self.product_type, + product_type=product_type, + product_base_type=self.product_base_type, product_name=product_name, data=instance_data, creator=self, @@ -146,7 +163,7 @@ def create_unreal(self, product_name, instance_data, pre_create_data): obj = ar.get_asset_by_object_path(member).get_asset() assets.add(obj) - _imprint(f"{self.root}/{instance_name}", + imprint(f"{self.root}/{instance_name}", instance.data_to_store()) return instance @@ -446,7 +463,7 @@ def imprint( } if hierarchy_dir is not None: data["master_directory"] = hierarchy_dir - _imprint(f"{asset_dir}/{container_name}", data) + imprint(f"{asset_dir}/{container_name}", data) def _load_assets( self, diff --git a/client/ayon_unreal/plugins/load/load_alembic_animation.py b/client/ayon_unreal/plugins/load/load_alembic_animation.py index 14f1d5ba..8232889a 100644 --- a/client/ayon_unreal/plugins/load/load_alembic_animation.py +++ b/client/ayon_unreal/plugins/load/load_alembic_animation.py @@ -189,6 +189,7 @@ def imprint( # TODO these should be probably removed "asset": folder_path, "family": product_base_type, + "product_base_type": product_base_type, "project_name": project_name, "layout": layout } diff --git a/client/ayon_unreal/plugins/load/load_animation.py b/client/ayon_unreal/plugins/load/load_animation.py index 25c01393..cba3dfe1 100644 --- a/client/ayon_unreal/plugins/load/load_animation.py +++ b/client/ayon_unreal/plugins/load/load_animation.py @@ -569,7 +569,7 @@ def update(self, container, context): container_name=container_name, asset_name=asset_name, representation=repre_entity, - product_base_type=product_type, + product_base_type=context["product"]["productBaseType"], frame_start=folder_entity["attrib"]["frameStart"], frame_end=folder_entity["attrib"]["frameEnd"], project_name=context["project"]["name"], diff --git a/client/ayon_unreal/plugins/load/load_camera.py b/client/ayon_unreal/plugins/load/load_camera.py index b4d5ab71..95f483d2 100644 --- a/client/ayon_unreal/plugins/load/load_camera.py +++ b/client/ayon_unreal/plugins/load/load_camera.py @@ -240,7 +240,7 @@ def load(self, context, name, namespace, options): container_name=container_name, asset_name=asset_name, representation=context["representation"], - product_base_type=context["product"]["productType"], + product_base_type=context["product"]["productBaseType"], frame_start=folder_entity["attrib"].get("frameStart"), frame_end=folder_entity["attrib"].get("frameEnd"), project_name=context["project"]["name"], diff --git a/client/ayon_unreal/plugins/load/load_image_png.py b/client/ayon_unreal/plugins/load/load_image_png.py index 0d24be6d..92502d09 100644 --- a/client/ayon_unreal/plugins/load/load_image_png.py +++ b/client/ayon_unreal/plugins/load/load_image_png.py @@ -123,7 +123,7 @@ def imprint( "loader": str(self.__class__.__name__), "representation": representation["id"], "parent": representation["versionId"], - "product_type": product_base_type, + "product_base_type": product_base_type, # TODO these shold be probably removed "asset": folder_path, "family": product_base_type, diff --git a/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py b/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py index 4d322bb6..a234db46 100644 --- a/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py +++ b/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py @@ -212,7 +212,7 @@ def imprint( "representation": representation["id"], "parent": representation["versionId"], "product_base_type": product_base_type, - "frameStart":frame_start, + "frameStart": frame_start, "frameEnd": frame_end, # TODO these should be probably removed "asset": folder_path, diff --git a/server/creators.py b/server/creators.py index 2331d956..10a34c44 100644 --- a/server/creators.py +++ b/server/creators.py @@ -10,7 +10,7 @@ class ProductTypeItemModel(BaseSettingsModel): _layout = "compact" product_type: str = SettingsField( title="Product Type", - decription="Product type name" + description="Product type name" ) label: str = SettingsField( title="Label", From 8a68ef4c1237227095ebb454d314f60934b97db3 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 2 Jun 2026 12:21:21 +0200 Subject: [PATCH 05/11] fix: incorrect instance type usage and typo fixes --- client/ayon_unreal/api/plugin.py | 5 ++--- client/ayon_unreal/plugins/load/load_camera.py | 2 +- client/ayon_unreal/plugins/load/load_image_png.py | 2 +- client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py | 2 -- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/client/ayon_unreal/api/plugin.py b/client/ayon_unreal/api/plugin.py index 460f9d9b..e1ee93c9 100644 --- a/client/ayon_unreal/api/plugin.py +++ b/client/ayon_unreal/api/plugin.py @@ -65,12 +65,11 @@ def get_cached_instances(shared_data): if creator_id: unreal_cached_products[creator_id].append(instance) else: - product_base_type = instance["product_base_type"] # 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") + instance.get("product_base_type") + or instance.get("product_type") ) if product_base_type is None: unreal.log_warning( diff --git a/client/ayon_unreal/plugins/load/load_camera.py b/client/ayon_unreal/plugins/load/load_camera.py index 95f483d2..848be992 100644 --- a/client/ayon_unreal/plugins/load/load_camera.py +++ b/client/ayon_unreal/plugins/load/load_camera.py @@ -295,7 +295,7 @@ def update(self, container, context): container_name=container_name, asset_name=asset_name, representation=context["representation"], - product_base_type=context["product"]["productType"], + product_base_type=context["product"]["productBaseType"], frame_start=folder_entity["attrib"].get("frameStart"), frame_end=folder_entity["attrib"].get("frameEnd"), project_name=context["project"]["name"], diff --git a/client/ayon_unreal/plugins/load/load_image_png.py b/client/ayon_unreal/plugins/load/load_image_png.py index 92502d09..9221770f 100644 --- a/client/ayon_unreal/plugins/load/load_image_png.py +++ b/client/ayon_unreal/plugins/load/load_image_png.py @@ -124,7 +124,7 @@ def imprint( "representation": representation["id"], "parent": representation["versionId"], "product_base_type": product_base_type, - # TODO these shold be probably removed + # TODO these should be probably removed "asset": folder_path, "family": product_base_type, "project_name": project_name diff --git a/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py b/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py index 0f13ce32..4614cec4 100644 --- a/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py +++ b/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py @@ -115,8 +115,6 @@ def imprint( asset_name (str): Name of the main asset. representation (dict): Representation data to imprint. product_base_type (str): Product base type to imprint. - frame_start (int): Start frame of the asset. - frame_end (int): End frame of the asset. project_name (str): Name of the project to imprint. layout (bool): Whether the container is created with layout. From 469a3cddcdca39e79cc272e953478f12b07fc177 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 2 Jun 2026 15:26:11 +0200 Subject: [PATCH 06/11] =?UTF-8?q?=E2=9A=99=EF=B8=8Fupdate=20ruff=20action?= =?UTF-8?q?=20on=20GH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr_linting.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 3d2431b6..d6c12cf1 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -21,4 +21,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: chartboost/ruff-action@v1 + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v46 + with: + files: | + **.py + - if: steps.changed-files.outputs.all_changed_files != '' + uses: astral-sh/ruff-action@v3 + with: + version-file: "pyproject.toml" + src: ${{ steps.changed-files.outputs.all_changed_files }} From 59fe8102fab080e3ee1dbebd3cb9777571c92d3c Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 2 Jun 2026 15:32:48 +0200 Subject: [PATCH 07/11] :fire: remove unused variable --- client/ayon_unreal/plugins/load/load_animation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_unreal/plugins/load/load_animation.py b/client/ayon_unreal/plugins/load/load_animation.py index cba3dfe1..588f5693 100644 --- a/client/ayon_unreal/plugins/load/load_animation.py +++ b/client/ayon_unreal/plugins/load/load_animation.py @@ -530,7 +530,6 @@ def update(self, container, context): hierarchy = folder_path.lstrip("/").split("/") folder_name = hierarchy.pop(-1) folder_name = context["folder"]["name"] - product_type = context["product"]["productType"] repre_entity = context["representation"] folder_entity = context["folder"] From f13aadd029b8a5e3dcb8d36143a9e62853f197a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 3 Jun 2026 10:59:20 +0200 Subject: [PATCH 08/11] :recycle: apply suggestions --- client/ayon_unreal/api/plugin.py | 2 +- client/ayon_unreal/plugins/load/load_alembic_animation.py | 4 ++-- client/ayon_unreal/plugins/load/load_animation.py | 1 + client/ayon_unreal/plugins/load/load_camera.py | 1 + client/ayon_unreal/plugins/load/load_geometrycache_abc.py | 1 + client/ayon_unreal/plugins/load/load_image_png.py | 1 + client/ayon_unreal/plugins/load/load_layout.py | 7 ++++--- client/ayon_unreal/plugins/load/load_layout_existing.py | 3 ++- client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py | 1 + client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py | 1 + client/ayon_unreal/plugins/load/load_staticmesh_abc.py | 1 + client/ayon_unreal/plugins/load/load_staticmesh_fbx.py | 1 + client/ayon_unreal/plugins/load/load_uasset.py | 1 + client/ayon_unreal/plugins/load/load_yeticache.py | 1 + client/ayon_unreal/plugins/publish/collect_render_files.py | 1 + 15 files changed, 20 insertions(+), 7 deletions(-) diff --git a/client/ayon_unreal/api/plugin.py b/client/ayon_unreal/api/plugin.py index e1ee93c9..58f7e4f4 100644 --- a/client/ayon_unreal/api/plugin.py +++ b/client/ayon_unreal/api/plugin.py @@ -456,7 +456,7 @@ def imprint( "loader": str(self.__class__.__name__), "representation": context["representation"]["id"], "parent": context["representation"]["versionId"], - "family": context["product"]["productType"], + "family": context["product"]["productBaseType"], "loaded_assets": loaded_assets, "project_name": project_name } diff --git a/client/ayon_unreal/plugins/load/load_alembic_animation.py b/client/ayon_unreal/plugins/load/load_alembic_animation.py index 8232889a..f6355380 100644 --- a/client/ayon_unreal/plugins/load/load_alembic_animation.py +++ b/client/ayon_unreal/plugins/load/load_alembic_animation.py @@ -150,8 +150,8 @@ def imprint( frame_start: int, frame_end: int, representation: dict, - project_name: str, product_base_type: str, + project_name: str, *, layout: bool ) -> None: @@ -186,10 +186,10 @@ def imprint( "parent": representation["versionId"], "frameStart": frame_start, "frameEnd": frame_end, + "product_base_type": product_base_type, # TODO these should be probably removed "asset": folder_path, "family": product_base_type, - "product_base_type": product_base_type, "project_name": project_name, "layout": layout } diff --git a/client/ayon_unreal/plugins/load/load_animation.py b/client/ayon_unreal/plugins/load/load_animation.py index 588f5693..aa994629 100644 --- a/client/ayon_unreal/plugins/load/load_animation.py +++ b/client/ayon_unreal/plugins/load/load_animation.py @@ -429,6 +429,7 @@ def imprint( "product_base_type": product_base_type, # TODO these shold be probably removed "asset": folder_path, + "product_type": product_base_type, "family": product_base_type, "frameStart": frame_start, "frameEnd": frame_end, diff --git a/client/ayon_unreal/plugins/load/load_camera.py b/client/ayon_unreal/plugins/load/load_camera.py index 848be992..897fc020 100644 --- a/client/ayon_unreal/plugins/load/load_camera.py +++ b/client/ayon_unreal/plugins/load/load_camera.py @@ -120,6 +120,7 @@ def imprint( "product_base_type": product_base_type, # TODO these should be probably removed "asset": asset_name, + "product_type": product_base_type, "family": product_base_type, "frameStart": frame_start, "frameEnd": frame_end, diff --git a/client/ayon_unreal/plugins/load/load_geometrycache_abc.py b/client/ayon_unreal/plugins/load/load_geometrycache_abc.py index 91a3c394..8bdd1b78 100644 --- a/client/ayon_unreal/plugins/load/load_geometrycache_abc.py +++ b/client/ayon_unreal/plugins/load/load_geometrycache_abc.py @@ -205,6 +205,7 @@ def imprint( "product_base_type": product_base_type, "folder_path": folder_path, # TODO these should be probably removed + "product_type": product_base_type, "family": product_base_type, "asset": folder_path, "project_name": project_name, diff --git a/client/ayon_unreal/plugins/load/load_image_png.py b/client/ayon_unreal/plugins/load/load_image_png.py index 9221770f..93659c5b 100644 --- a/client/ayon_unreal/plugins/load/load_image_png.py +++ b/client/ayon_unreal/plugins/load/load_image_png.py @@ -126,6 +126,7 @@ def imprint( "product_base_type": product_base_type, # TODO these should be probably removed "asset": folder_path, + "product_type": product_base_type, "family": product_base_type, "project_name": project_name } diff --git a/client/ayon_unreal/plugins/load/load_layout.py b/client/ayon_unreal/plugins/load/load_layout.py index c27dda0c..5986b4a1 100644 --- a/client/ayon_unreal/plugins/load/load_layout.py +++ b/client/ayon_unreal/plugins/load/load_layout.py @@ -181,9 +181,10 @@ def _process(self, lib_path, project_name, asset_dir, sequence, repr_loaded.append(repre_id) product_base_type = ( - element.get("product_base_type") - or element.get("product_type") - ) or element.get("family") + element.get("product_base_type") + or element.get("product_type") + or element.get("family") + ) assets = self._load_assets( instance_name, repre_id, product_base_type, repr_format diff --git a/client/ayon_unreal/plugins/load/load_layout_existing.py b/client/ayon_unreal/plugins/load/load_layout_existing.py index 6731e7d0..56dbed7b 100644 --- a/client/ayon_unreal/plugins/load/load_layout_existing.py +++ b/client/ayon_unreal/plugins/load/load_layout_existing.py @@ -230,7 +230,8 @@ def _process(self, lib_path, project_name, sequence): product_base_type = ( lasset.get("product_base_type") or lasset.get("product_type") - ) or lasset.get("family") + or lasset.get("family") + ) extension = lasset.get("extension") assets = self._load_asset( diff --git a/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py b/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py index a234db46..eb335ec5 100644 --- a/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py +++ b/client/ayon_unreal/plugins/load/load_skeletalmesh_abc.py @@ -216,6 +216,7 @@ def imprint( "frameEnd": frame_end, # TODO these should be probably removed "asset": folder_path, + "product_type": product_base_type, "family": product_base_type, "project_name": project_name, "layout": layout diff --git a/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py b/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py index 4614cec4..9972ceb9 100644 --- a/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py +++ b/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py @@ -136,6 +136,7 @@ def imprint( "product_base_type": product_base_type, # TODO these should be probably removed "asset": folder_path, + "product_type": product_base_type, "family": product_base_type, "project_name": project_name, "layout": layout diff --git a/client/ayon_unreal/plugins/load/load_staticmesh_abc.py b/client/ayon_unreal/plugins/load/load_staticmesh_abc.py index 86f72b38..9d6bda3c 100644 --- a/client/ayon_unreal/plugins/load/load_staticmesh_abc.py +++ b/client/ayon_unreal/plugins/load/load_staticmesh_abc.py @@ -227,6 +227,7 @@ def imprint( "product_base_type": product_base_type, # TODO these should be probably removed "asset": folder_path, + "product_type": product_base_type, "family": product_base_type, "project_name": project_name, "layout": layout diff --git a/client/ayon_unreal/plugins/load/load_staticmesh_fbx.py b/client/ayon_unreal/plugins/load/load_staticmesh_fbx.py index 19e44c89..b88ce63d 100644 --- a/client/ayon_unreal/plugins/load/load_staticmesh_fbx.py +++ b/client/ayon_unreal/plugins/load/load_staticmesh_fbx.py @@ -135,6 +135,7 @@ def imprint( "product_base_type": product_base_type, # TODO these shold be probably removed "asset": folder_path, + "product_type": product_base_type, "family": product_base_type, "project_name": project_name, "layout": layout diff --git a/client/ayon_unreal/plugins/load/load_uasset.py b/client/ayon_unreal/plugins/load/load_uasset.py index 80528dde..8d97c47d 100644 --- a/client/ayon_unreal/plugins/load/load_uasset.py +++ b/client/ayon_unreal/plugins/load/load_uasset.py @@ -111,6 +111,7 @@ def load(self, context, name, namespace, options): "product_base_type": context["product"]["productBaseType"], # TODO these should be probably removed "asset": folder_path, + "product_type": context["product"]["productBaseType"], "family": context["product"]["productBaseType"], "project_name": context["project"]["name"] } diff --git a/client/ayon_unreal/plugins/load/load_yeticache.py b/client/ayon_unreal/plugins/load/load_yeticache.py index f764e129..e8b63c00 100644 --- a/client/ayon_unreal/plugins/load/load_yeticache.py +++ b/client/ayon_unreal/plugins/load/load_yeticache.py @@ -143,6 +143,7 @@ def load(self, context, name, namespace, options): "product_base_type": context["product"]["productBaseType"], # TODO these shold be probably removed "asset": folder_path, + "product_type": context["product"]["productType"], "family": context["product"]["productBaseType"], "project_name": context["project"]["name"], "layout": should_use_layout diff --git a/client/ayon_unreal/plugins/publish/collect_render_files.py b/client/ayon_unreal/plugins/publish/collect_render_files.py index 27a1727e..f1d1d4e0 100644 --- a/client/ayon_unreal/plugins/publish/collect_render_files.py +++ b/client/ayon_unreal/plugins/publish/collect_render_files.py @@ -69,6 +69,7 @@ def process(self, instance): new_data["folderPath"] = instance.data["folderPath"] new_data["setMembers"] = seq_name new_data["productName"] = new_product_name + new_data["productType"] = product_base_type new_data["productBaseType"] = product_base_type new_data["family"] = product_base_type new_data["families"] = [product_base_type, "review"] From d52b8c645ad1574a7e781960c115d58b8bbaf972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 3 Jun 2026 11:24:56 +0200 Subject: [PATCH 09/11] :recycle: change to product base type --- client/ayon_unreal/plugins/load/load_yeticache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_unreal/plugins/load/load_yeticache.py b/client/ayon_unreal/plugins/load/load_yeticache.py index e8b63c00..2f757482 100644 --- a/client/ayon_unreal/plugins/load/load_yeticache.py +++ b/client/ayon_unreal/plugins/load/load_yeticache.py @@ -143,7 +143,7 @@ def load(self, context, name, namespace, options): "product_base_type": context["product"]["productBaseType"], # TODO these shold be probably removed "asset": folder_path, - "product_type": context["product"]["productType"], + "product_type": context["product"]["productBaseType"], "family": context["product"]["productBaseType"], "project_name": context["project"]["name"], "layout": should_use_layout From a35337ccdfde874ca363bff5c00d87a0966cf41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:29:10 +0200 Subject: [PATCH 10/11] Update client/ayon_unreal/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_unreal/api/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_unreal/api/plugin.py b/client/ayon_unreal/api/plugin.py index 58f7e4f4..6066e8b8 100644 --- a/client/ayon_unreal/api/plugin.py +++ b/client/ayon_unreal/api/plugin.py @@ -456,6 +456,7 @@ def imprint( "loader": str(self.__class__.__name__), "representation": context["representation"]["id"], "parent": context["representation"]["versionId"], + "product_base_type": context["product"]["productBaseType"], "family": context["product"]["productBaseType"], "loaded_assets": loaded_assets, "project_name": project_name From 4a042fb90340387794952843d4df3df1949db101 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 Jun 2026 14:38:01 +0200 Subject: [PATCH 11/11] :bug: fix wrong import --- client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py b/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py index 9972ceb9..0bce8435 100644 --- a/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py +++ b/client/ayon_unreal/plugins/load/load_skeletalmesh_fbx.py @@ -6,7 +6,7 @@ from ayon_unreal.api import plugin from ayon_unreal.api.pipeline import ( create_container, - _imprint, + imprint as _imprint, format_asset_directory, get_dir_from_existing_asset )