-
Notifications
You must be signed in to change notification settings - Fork 11
Feature/remove plugin requirement #256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Feature/remove plugin requirement #256
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces dual-mode operation for the Unreal addon, allowing it to run either with the Ayon Unreal plugin (original mode) or using native Unreal classes (new mode). This enables vendor scenarios where full project control isn't available. The implementation adds a backend architecture pattern to abstract plugin vs. native functionality, includes new settings for mode selection and exact path usage, and provides blueprint asset copying for versions prior to UE 5.6 where Python API limitations exist.
Key changes:
- Added backend abstraction layer (
AyonPluginBackendandNativeUnrealBackend) to support both modes - Introduced new settings:
use_pluginanduse_exact_pathfor project configuration - Implemented blueprint copying hook for native mode support in Unreal versions below 5.6
Reviewed changes
Copilot reviewed 15 out of 17 changed files in this pull request and generated 27 comments.
Show a summary per file
| File | Description |
|---|---|
| server/settings.py | Adds use_plugin and use_exact_path settings to ProjectSetup with default values |
| client/ayon_unreal/startup/init_unreal.py | New startup script for Unreal that handles Ayon host installation with error handling for missing dependencies |
| client/ayon_unreal/hooks/pre_workfile_preparation.py | Updates hook to support both plugin and native modes, adds exact path configuration, and sets environment variables accordingly |
| client/ayon_unreal/hooks/pre_copy_blueprints.py | New hook to copy blueprint assets to project for native mode in UE versions < 5.6 |
| client/ayon_unreal/blueprints/UE_5.3/AyonPublishInstance.uasset | Binary blueprint asset for publish instances in native mode |
| client/ayon_unreal/blueprints/UE_5.3/AyonAssetContainer.uasset | Binary blueprint asset for containers in native mode |
| client/ayon_unreal/api/tools_ui.py | Code formatting improvements and import reorganization |
| client/ayon_unreal/api/rendering.py | Updates class name check to support both plugin and native blueprint classes |
| client/ayon_unreal/api/plugin.py | Changes from add() to append() for asset list handling |
| client/ayon_unreal/api/pipeline.py | Refactors to use backend abstraction, moves constants to separate file, removes hardcoded implementation details |
| client/ayon_unreal/api/menu.py | New menu system implementation using Unreal's ToolMenus API |
| client/ayon_unreal/api/constants.py | Extracts constants from pipeline.py for better organization |
| client/ayon_unreal/api/backends/native_unreal.py | Implements native Unreal backend using blueprint-based containers and publish instances |
| client/ayon_unreal/api/backends/base.py | Defines abstract backend interface for plugin and native implementations |
| client/ayon_unreal/api/backends/ayon_plugin.py | Implements plugin backend using Ayon plugin factories |
| client/ayon_unreal/api/backends/init.py | Provides backend selection logic based on AYON_PLUGIN_ENABLED environment variable |
| client/ayon_unreal/addon.py | Adds startup path to Python path and menu initialization on host install |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| f"{self.signature} using Ayon plugin from " | ||
| f"{self.launch_context.env.get(env_key)}" | ||
| )) | ||
| if self.launch_context.env.get(env_key): |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition is checked twice in a row on lines 256 and 261. The duplicate check on line 261 appears to be unnecessary since the same condition was just checked on line 256. This could indicate a logic error where a different condition was intended for the second check.
| if self.launch_context.env.get(env_key): |
|
|
||
| project_path = self.launch_context.env.get("AYON_UNREAL_PROJECT_PATH") | ||
| if not project_path: | ||
| raise RuntimeError("AYON_UNREAL_PROJECT_PATH not set.") |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "AYON_UNREAL_PROJECT_PATH not set." should be more descriptive and provide context about when this error occurs and potential solutions. Consider adding information about which hook is raising this error and suggesting that the project path should have been set by the pre_workfile_preparation hook.
| raise RuntimeError("AYON_UNREAL_PROJECT_PATH not set.") | |
| raise RuntimeError( | |
| "AYON_UNREAL_PROJECT_PATH not set in environment during CopyBlueprints pre-launch hook. " | |
| "This usually means the Unreal project path was not set by the pre_workfile_preparation hook. " | |
| "Please ensure that the pre_workfile_preparation hook runs and sets AYON_UNREAL_PROJECT_PATH before launching Unreal." | |
| ) |
| blueprint_files = unreal_blueprint_path.glob('*.uasset') | ||
|
|
||
| container_path.mkdir(exist_ok=True, parents=True) | ||
| for blueprint_file in blueprint_files: | ||
| dest = container_path.joinpath(blueprint_file.name) | ||
| if dest.exists(): | ||
| if not filecmp.cmp(blueprint_file, dest): | ||
| shutil.copy(blueprint_file, dest) | ||
| else: | ||
| continue | ||
| else: | ||
| shutil.copy(blueprint_file, dest) |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The blueprint_files generator from glob is not checked for existence. If the blueprint directory doesn't exist or contains no files, the code will silently succeed without copying any blueprints. Consider adding a check to warn or error if no blueprint files are found, especially since this is a critical step for Native mode to function properly.
| project_path = project_file.parent | ||
| self.log.info(f"New Project File {project_file}") | ||
| if not project_file.is_file(): | ||
| raise RuntimeError("Invalid Project Path") |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "Invalid Project Path" is too generic. It should include the actual path that was attempted and provide more context about what conditions need to be met. Consider including the resolved project_file path in the error message.
| raise RuntimeError("Invalid Project Path") | |
| raise RuntimeError( | |
| f"Invalid Project Path: '{project_file}'. The file does not exist or is not a valid Unreal project file." | |
| ) |
| data_asset_class = unreal.load_class( | ||
| None, "/Game/Ayon/AyonContainerTypes/AyonAssetContainer.AyonAssetContainer_C" | ||
| ) | ||
| print(f"Creating Ayon Container {container}") |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug print statement should be replaced with proper logging. Consider using a logger instance instead of print for better control and consistency with the rest of the codebase.
| project_file = pathlib.Path(project_template.format_strict(template_data)) | ||
| project_path = project_file.parent | ||
| self.log.info(f"New Project File {project_file}") | ||
| if not project_file.is_file(): | ||
| raise RuntimeError("Invalid Project Path") | ||
| else: | ||
| project_file = project_path / unreal_project_filename |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When use_exact_path is True, the code sets project_file and project_path but then continues to check if the project file exists (line 298). If the file doesn't exist, it may enter the project creation logic (lines 298-363), but this logic expects project_path to be set based on workdir/unreal_project_name (line 212), not from the exact path. This could cause the wrong project_path to be set in AYON_UNREAL_PROJECT_PATH at line 365, leading to blueprint copying failures in the CopyBlueprints hook.
| for member in pre_create_data.get("members", []): | ||
| obj = ar.get_asset_by_object_path(member).get_asset() | ||
| assets.add(obj) | ||
| assets.append(obj) |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change from assets.add(obj) to assets.append(obj) suggests that asset_data_external is now a list instead of a set. However, this change could introduce duplicate entries if the same asset is added multiple times, whereas a set would naturally prevent duplicates. Consider whether duplicate prevention logic is needed, or if this behavioral change is intentional.
| assets.append(obj) | |
| if obj not in assets: | |
| assets.append(obj) |
| return instances | ||
|
|
||
| @staticmethod | ||
| def containerise(): |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method requires 0 positional arguments, whereas overridden UnrealBackend.containerise requires at least 4.
| def containerise(): | |
| def containerise(arg1, arg2, arg3, arg4): |
| # Copyright (c) 2024 Ynput s.r.o. | ||
| import unreal | ||
| try: | ||
| import qtpy #noqa F401 |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'qtpy' is not used.
| import unreal | ||
| try: | ||
| import qtpy #noqa F401 | ||
| from qtpy import QtWidgets #noqa F401 |
Copilot
AI
Dec 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'QtWidgets' is not used.
| from qtpy import QtWidgets #noqa F401 |
|
@Kryses Thank you for this contribution. Can you tell specifically why it does not work in 5.6 (as it is working in my side for loading all the ayon-related data correctly)? I mean I do understand user can't create new properties on a blueprint class by using python api, but in terms of what particular aspect, it would affect how the ayon data works? |
client/ayon_unreal/addon.py
Outdated
|
|
||
| def on_host_install(self, host, host_name, project_name): | ||
| self.log.info("Starting Host") | ||
| if host_name == 'unreal': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE: Unreal host integration takes care about calling install, so it can be handled there, instead of handling it on addon level. (UnrealHost.install in client/ayon_unreal/api/pipeline.py)
| UNREAL_VERSION = semver.VersionInfo( | ||
| *os.getenv("AYON_UNREAL_VERSION").split(".") | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| UNREAL_VERSION = semver.VersionInfo( | |
| *os.getenv("AYON_UNREAL_VERSION").split(".") | |
| ) | |
| UNREAL_VERSION = semver.VersionInfo.parse( | |
| os.getenv("AYON_UNREAL_VERSION") | |
| ) |
This file is used only inside unreal, doesn't have unreal dedicated function to get unreal version instead of using AYON_UNREAL_VERSION?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does, but this is primarily used in the pre-launch hooks before we run unreal. The copy might be able to run in the host install but, I'm not entirely sure if unreal will react to adding the uassets after it has been initialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So after doing some testing on this. The AYON_UNREAL_VERSION is set by the application variant in ayon-applications I believe. The defaults are <major>.<minor> in the current version of semver we're using VersionInfo.parse is expecting <major>.<minor>.<patch>. Newer versions of semver have a new argment for optional_minor_and_patch
I'm trying to find away to be less disruptive. If we continue with this version of semver. People might need to update the names of their variants. If we upgrade the version of semver they will need to run the dependencies tool.
What are your thoughts on this?
client/ayon_unreal/api/menu.py
Outdated
|
|
||
|
|
||
| @unreal.uclass() | ||
| class AyonPythonMenuTool(unreal.ToolMenuEntryScript): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be AYON instead of Ayon (if it's new).
client/ayon_unreal/api/pipeline.py
Outdated
| yield cast_map_to_str_dict(data) | ||
|
|
||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| else: | ||
| # Set "AYON_UNREAL_PLUGIN" to current process environment for | ||
| # execution of `create_unreal_project` | ||
| env_key = "AYON_UNREAL_PLUGIN" | ||
| if self.launch_context.env.get(env_key): | ||
| self.log.info(( | ||
| f"{self.signature} using Ayon plugin from " | ||
| f"{self.launch_context.env.get(env_key)}" | ||
| )) | ||
| if self.launch_context.env.get(env_key): | ||
| os.environ[env_key] = self.launch_context.env[env_key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't think we should set this to current process env, it will affect all future launches.
| else: | |
| # Set "AYON_UNREAL_PLUGIN" to current process environment for | |
| # execution of `create_unreal_project` | |
| env_key = "AYON_UNREAL_PLUGIN" | |
| if self.launch_context.env.get(env_key): | |
| self.log.info(( | |
| f"{self.signature} using Ayon plugin from " | |
| f"{self.launch_context.env.get(env_key)}" | |
| )) | |
| if self.launch_context.env.get(env_key): | |
| os.environ[env_key] = self.launch_context.env[env_key] | |
| elif self.launch_context.env.get("AYON_UNREAL_PLUGIN"): | |
| # Set "AYON_UNREAL_PLUGIN" to current process environment for | |
| # execution of `create_unreal_project` | |
| ay_unreal_plugin = self.launch_context.env.get("AYON_UNREAL_PLUGIN") | |
| self.log.info( | |
| f"{self.signature} using AYON plugin from {ay_unreal_plugin}" | |
| ) | |
| os.environ["AYON_UNREAL_PLUGIN"] = ay_unreal_plugin |
It's not necessarily that it won't work in 5.5 or 5.6. I hadn't had a chance to test it in those versions. Because I'm using uassets made from 5.3 I wasn't sure if it would be as simple as copying the uassets into the AyonContainers location. I figured there might be some problems using those assets when going into a newer version. I'd say if it works than it works in this case. |
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
…Entertainment/ayon-unreal into feature/remove-plugin-requirement
| if not env.get("AYON_UNREAL_PLUGIN"): | ||
| env["AYON_UNREAL_PLUGIN"] = unreal_plugin_path | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if not env.get(key): | ||
| env[key] = value | ||
|
|
||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changelog Description
This is an update to allow the unreal add-on to run in two modes. One being the original the requires the installation of the Ayon unreal plug-in to the project. The other storing Ayon data within Unreal native classes. The reason for this use case is in a vendor scenario where the vendor does not have control over the project in its entirety.
Functionally, Native mode works the same as Ayon plug-in, with the exception that in Native mode it will not be able to create a new unreal project. Ayon Unreal use a commandlet provided by the Ayon plug-in in order to create the project.
Some additional note are that prior to Unreal Engine 5.6. You cannot create new properies on a Blueprint class using the python api. For the publish instances to function in Ayon they must have asset_data_internal, add_external_asset and asset_data_external. Because of this the blueprint assets will need to be created for your version of unreal.
Warning
This is not designed to be interchangeable with the Ayon Plugin if
a project is started with the Ayon Plugin it should remain in the plugin mode.
likewise if a project is started in Native mode it should remain in Native mode
Changes
/Game/Ayon/AyonContainerTypes. These are used to generate Containters and Publish instance from the Ayon tools like the Loader and the Publisher.ayon_unreal/api/pipeline.pyhas been moved in to classes that live inayon_unreal/api/backends.Additional review information
Detailed information of the changes made to the product or service, providing an in-depth description of the updates and enhancements. This can include technical information, code examples and anything else needed for the review of the PR.
Added Settings
ayon+settings://unreal/project_setup/use_pluginayon+settings://unreal/project_setup/use_exact_pathTesting notes:
.uprojectfile..uprojectfile. This can either be a full path or a templated path for exampleroot[unreal]/unreal/testproject.uproject