diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5050d37c7a..7d224aa73f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,9 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.7-nightly.3 + - 3.15.7-nightly.2 + - 3.15.7-nightly.1 - 3.15.6 - 3.15.6-nightly.3 - 3.15.6-nightly.2 @@ -132,9 +135,6 @@ body: - 3.14.1-nightly.4 - 3.14.1-nightly.3 - 3.14.1-nightly.2 - - 3.14.1-nightly.1 - - 3.14.0 - - 3.14.0-nightly.1 validations: required: true - type: dropdown diff --git a/.gitignore b/.gitignore index 18e7cd7bf2..50f52f65a3 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,9 @@ tools/run_eventserver.* tools/dev_* .github_changelog_generator + + +# Addons +######## +/openpype/addons/* +!/openpype/addons/README.md diff --git a/openpype/addons/README.md b/openpype/addons/README.md new file mode 100644 index 0000000000..92b8b8c07c --- /dev/null +++ b/openpype/addons/README.md @@ -0,0 +1,3 @@ +This directory is for storing external addons that needs to be included in the pipeline when distributed. + +The directory is ignored by Git, but included in the zip and installation files. diff --git a/openpype/hosts/fusion/plugins/publish/increment_current_file.py b/openpype/hosts/fusion/plugins/publish/increment_current_file.py index 42891446f7..08a65bf52d 100644 --- a/openpype/hosts/fusion/plugins/publish/increment_current_file.py +++ b/openpype/hosts/fusion/plugins/publish/increment_current_file.py @@ -1,29 +1,39 @@ import pyblish.api +from openpype.pipeline import OptionalPyblishPluginMixin +from openpype.pipeline import KnownPublishError -class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): + +class FusionIncrementCurrentFile( + pyblish.api.ContextPlugin, OptionalPyblishPluginMixin +): """Increment the current file. Saves the current file with an increased version number. """ - label = "Increment current file" + label = "Increment workfile version" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["fusion"] - families = ["workfile"] optional = True def process(self, context): + if not self.is_active(context.data): + return from openpype.lib import version_up from openpype.pipeline.publish import get_errored_plugins_from_context errored_plugins = get_errored_plugins_from_context(context) - if any(plugin.__name__ == "FusionSubmitDeadline" - for plugin in errored_plugins): - raise RuntimeError("Skipping incrementing current file because " - "submission to render farm failed.") + if any( + plugin.__name__ == "FusionSubmitDeadline" + for plugin in errored_plugins + ): + raise KnownPublishError( + "Skipping incrementing current file because " + "submission to render farm failed." + ) comp = context.data.get("currentComp") assert comp, "Must have comp" diff --git a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py index db2c4f0dd9..6908889eb4 100644 --- a/openpype/hosts/fusion/plugins/publish/validate_background_depth.py +++ b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py @@ -1,12 +1,17 @@ import pyblish.api -from openpype.pipeline.publish import RepairAction -from openpype.pipeline import PublishValidationError +from openpype.pipeline import ( + publish, + OptionalPyblishPluginMixin, + PublishValidationError, +) from openpype.hosts.fusion.api.action import SelectInvalidAction -class ValidateBackgroundDepth(pyblish.api.InstancePlugin): +class ValidateBackgroundDepth( + pyblish.api.InstancePlugin, OptionalPyblishPluginMixin +): """Validate if all Background tool are set to float32 bit""" order = pyblish.api.ValidatorOrder @@ -15,11 +20,10 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): families = ["render"] optional = True - actions = [SelectInvalidAction, RepairAction] + actions = [SelectInvalidAction, publish.RepairAction] @classmethod def get_invalid(cls, instance): - context = instance.context comp = context.data.get("currentComp") assert comp, "Must have Comp object" @@ -31,12 +35,16 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): return [i for i in backgrounds if i.GetInput("Depth") != 4.0] def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( "Found {} Backgrounds tools which" " are not set to float32".format(len(invalid)), - title=self.label) + title=self.label, + ) @classmethod def repair(cls, instance): diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 61274e6028..b8b8fefb52 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -81,7 +81,13 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): # TODO: make sure this doesn't trigger when # opening with last workfile. _set_context_settings() - shelves.generate_shelves() + + if not IS_HEADLESS: + import hdefereval # noqa, hdefereval is only available in ui mode + # Defer generation of shelves due to issue on Windows where shelf + # initialization during start up delays Houdini UI by minutes + # making it extremely slow to launch. + hdefereval.executeDeferred(shelves.generate_shelves) if not IS_HEADLESS: import hdefereval # noqa, hdefereval is only available in ui mode diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index ad9a450cad..1310097f29 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -138,7 +138,7 @@ def get_default_render_folder(project_setting=None): ["default_render_image_folder"]) -def set_framerange(start_frame, end_frame): +def set_render_frame_range(start_frame, end_frame): """ Note: Frame range can be specified in different types. Possible values are: @@ -150,10 +150,10 @@ def set_framerange(start_frame, end_frame): Todo: Current type is hard-coded, there should be a custom setting for this. """ - rt.rendTimeType = 4 + rt.rendTimeType = 3 if start_frame is not None and end_frame is not None: - frame_range = "{0}-{1}".format(start_frame, end_frame) - rt.rendPickupFrames = frame_range + rt.rendStart = int(start_frame) + rt.rendEnd = int(end_frame) def get_multipass_setting(project_setting=None): @@ -173,10 +173,16 @@ def set_scene_resolution(width: int, height: int): None """ + # make sure the render dialog is closed + # for the update of resolution + # Changing the Render Setup dialog settingsshould be done + # with the actual Render Setup dialog in a closed state. + if rt.renderSceneDialog.isOpen(): + rt.renderSceneDialog.close() + rt.renderWidth = width rt.renderHeight = height - def reset_scene_resolution(): """Apply the scene resolution from the project definition @@ -243,6 +249,7 @@ def reset_frame_range(fps: bool = True): frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) frange_cmd = f"animationRange = interval {frame_start} {frame_end}" rt.execute(frange_cmd) + set_render_frame_range(frame_start, frame_end) def set_context_setting(): @@ -259,6 +266,7 @@ def set_context_setting(): None """ reset_scene_resolution() + reset_frame_range() def get_max_version(): diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 350eb97661..8224d589ad 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -36,8 +36,9 @@ class RenderProducts(object): container) context = get_current_project_asset() - startFrame = context["data"].get("frameStart") - endFrame = context["data"].get("frameEnd") + 1 + # TODO: change the frame range follows the current render setting + startFrame = int(rt.rendStart) + endFrame = int(rt.rendEnd) + 1 img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa full_render_list = self.beauty_render_product(output_file, diff --git a/openpype/hosts/max/api/lib_rendersettings.py b/openpype/hosts/max/api/lib_rendersettings.py index 4940265a23..91e4a5bf9b 100644 --- a/openpype/hosts/max/api/lib_rendersettings.py +++ b/openpype/hosts/max/api/lib_rendersettings.py @@ -6,7 +6,7 @@ from openpype.pipeline import legacy_io from openpype.pipeline.context_tools import get_current_project_asset from openpype.hosts.max.api.lib import ( - set_framerange, + set_render_frame_range, get_current_renderer, get_default_render_folder ) @@ -68,7 +68,7 @@ class RenderSettings(object): # Set Frame Range frame_start = context["data"].get("frame_start") frame_end = context["data"].get("frame_end") - set_framerange(frame_start, frame_end) + set_render_frame_range(frame_start, frame_end) # get the production render renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] @@ -105,6 +105,9 @@ class RenderSettings(object): rt.rendSaveFile = True + if rt.renderSceneDialog.isOpen(): + rt.renderSceneDialog.close() + def arnold_setup(self): # get Arnold RenderView run in the background # for setting up renderable camera diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index dacc402318..50fe30b299 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -52,6 +52,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): def context_setting(): return lib.set_context_setting() + rt.callbacks.addScript(rt.Name('systemPostNew'), context_setting) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 269fff2e32..68ae5eac72 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -27,6 +27,11 @@ class CreateRender(plugin.MaxCreator): # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) + # make sure the render dialog is closed + # for the update of resolution + # Changing the Render Setup dialog settings should be done + # with the actual Render Setup dialog in a closed state. + # set viewport camera for rendering(mandatory for deadline) RenderSettings().set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index b040467522..00e00a8eb5 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -46,7 +46,6 @@ class CollectRender(pyblish.api.InstancePlugin): self.log.debug(f"Setting {version_int} to context.") context.data["version"] = version_int - # setup the plugin as 3dsmax for the internal renderer data = { "subset": instance.name, @@ -59,8 +58,8 @@ class CollectRender(pyblish.api.InstancePlugin): "source": filepath, "expectedFiles": render_layer_files, "plugin": "3dsmax", - "frameStart": context.data['frameStart'], - "frameEnd": context.data['frameEnd'], + "frameStart": int(rt.rendStart), + "frameEnd": int(rt.rendEnd), "version": version_int, "farm": True } diff --git a/openpype/hosts/max/plugins/publish/validate_frame_range.py b/openpype/hosts/max/plugins/publish/validate_frame_range.py new file mode 100644 index 0000000000..21e847405e --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_frame_range.py @@ -0,0 +1,64 @@ +import pyblish.api + +from pymxs import runtime as rt +from openpype.pipeline import ( + OptionalPyblishPluginMixin +) +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError +) + + +class ValidateFrameRange(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validates the frame ranges. + + This is an optional validator checking if the frame range on instance + matches the frame range specified for the asset. + + It also validates render frame ranges of render layers. + + Repair action will change everything to match the asset frame range. + + This can be turned off by the artist to allow custom ranges. + """ + + label = "Validate Frame Range" + order = ValidateContentsOrder + families = ["maxrender"] + hosts = ["max"] + optional = True + actions = [RepairAction] + + def process(self, instance): + if not self.is_active(instance.data): + self.log.info("Skipping validation...") + return + context = instance.context + + frame_start = int(context.data.get("frameStart")) + frame_end = int(context.data.get("frameEnd")) + + inst_frame_start = int(instance.data.get("frameStart")) + inst_frame_end = int(instance.data.get("frameEnd")) + + errors = [] + if frame_start != inst_frame_start: + errors.append( + f"Start frame ({inst_frame_start}) on instance does not match " # noqa + f"with the start frame ({frame_start}) set on the asset data. ") # noqa + if frame_end != inst_frame_end: + errors.append( + f"End frame ({inst_frame_end}) on instance does not match " + f"with the end frame ({frame_start}) from the asset data. ") + + if errors: + errors.append("You can use repair action to fix it.") + raise PublishValidationError("\n".join(errors)) + + @classmethod + def repair(cls, instance): + rt.rendStart = instance.context.data.get("frameStart") + rt.rendEnd = instance.context.data.get("frameEnd") diff --git a/openpype/hosts/max/plugins/publish/validate_resolution_setting.py b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py new file mode 100644 index 0000000000..5fcb843b20 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_resolution_setting.py @@ -0,0 +1,65 @@ +import pyblish.api +from openpype.pipeline import ( + PublishValidationError, + OptionalPyblishPluginMixin +) +from pymxs import runtime as rt +from openpype.hosts.max.api.lib import reset_scene_resolution + +from openpype.pipeline.context_tools import ( + get_current_project_asset, + get_current_project +) + + +class ValidateResolutionSetting(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validate the resolution setting aligned with DB""" + + order = pyblish.api.ValidatorOrder - 0.01 + families = ["maxrender"] + hosts = ["max"] + label = "Validate Resolution Setting" + optional = True + + def process(self, instance): + if not self.is_active(instance.data): + return + width, height = self.get_db_resolution(instance) + current_width = rt.renderwidth + current_height = rt.renderHeight + if current_width != width and current_height != height: + raise PublishValidationError("Resolution Setting " + "not matching resolution " + "set on asset or shot.") + if current_width != width: + raise PublishValidationError("Width in Resolution Setting " + "not matching resolution set " + "on asset or shot.") + + if current_height != height: + raise PublishValidationError("Height in Resolution Setting " + "not matching resolution set " + "on asset or shot.") + + def get_db_resolution(self, instance): + data = ["data.resolutionWidth", "data.resolutionHeight"] + project_resolution = get_current_project(fields=data) + project_resolution_data = project_resolution["data"] + asset_resolution = get_current_project_asset(fields=data) + asset_resolution_data = asset_resolution["data"] + # Set project resolution + project_width = int( + project_resolution_data.get("resolutionWidth", 1920)) + project_height = int( + project_resolution_data.get("resolutionHeight", 1080)) + width = int( + asset_resolution_data.get("resolutionWidth", project_width)) + height = int( + asset_resolution_data.get("resolutionHeight", project_height)) + + return width, height + + @classmethod + def repair(cls, instance): + reset_scene_resolution() diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index d65e4c74d2..6e6166c2ef 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -43,7 +43,24 @@ class MayaTemplateBuilder(AbstractTemplateBuilder): )) cmds.sets(name=PLACEHOLDER_SET, empty=True) - new_nodes = cmds.file(path, i=True, returnNewNodes=True) + new_nodes = cmds.file( + path, + i=True, + returnNewNodes=True, + preserveReferences=True, + loadReferenceDepth="all", + ) + + # make default cameras non-renderable + default_cameras = [cam for cam in cmds.ls(cameras=True) + if cmds.camera(cam, query=True, startupCamera=True)] + for cam in default_cameras: + if not cmds.attributeQuery("renderable", node=cam, exists=True): + self.log.debug( + "Camera {} has no attribute 'renderable'".format(cam) + ) + continue + cmds.setAttr("{}.renderable".format(cam), 0) cmds.setAttr(PLACEHOLDER_SET + ".hiddenInOutliner", True) diff --git a/openpype/hosts/maya/plugins/publish/validate_shader_name.py b/openpype/hosts/maya/plugins/publish/validate_shader_name.py index b3e51f011d..034db471da 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shader_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_shader_name.py @@ -50,7 +50,8 @@ class ValidateShaderName(pyblish.api.InstancePlugin): asset_name = instance.data.get("asset", None) # Check the number of connected shadingEngines per shape - r = re.compile(cls.regex) + regex_compile = re.compile(cls.regex) + error_message = "object {0} has invalid shader name {1}" for shape in shapes: shading_engines = cmds.listConnections(shape, destination=True, @@ -60,19 +61,18 @@ class ValidateShaderName(pyblish.api.InstancePlugin): ) for shader in shaders: - m = r.match(cls.regex, shader) + m = regex_compile.match(shader) if m is None: invalid.append(shape) - cls.log.error( - "object {0} has invalid shader name {1}".format(shape, - shader) - ) + cls.log.error(error_message.format(shape, shader)) else: - if 'asset' in r.groupindex: + if 'asset' in regex_compile.groupindex: if m.group('asset') != asset_name: invalid.append(shape) - cls.log.error(("object {0} has invalid " - "shader name {1}").format(shape, - shader)) + message = error_message + message += " with missing asset name \"{2}\"" + cls.log.error( + message.format(shape, shader, asset_name) + ) return invalid diff --git a/openpype/hosts/unreal/README.md b/openpype/hosts/unreal/README.md index 0a69b9e0cf..d131105659 100644 --- a/openpype/hosts/unreal/README.md +++ b/openpype/hosts/unreal/README.md @@ -4,6 +4,6 @@ Supported Unreal Engine version is 4.26+ (mainly because of major Python changes ### Project naming Unreal doesn't support project names starting with non-alphabetic character. So names like `123_myProject` are -invalid. If OpenPype detects such name it automatically prepends letter **P** to make it valid name, so `123_myProject` +invalid. If Ayon detects such name it automatically prepends letter **P** to make it valid name, so `123_myProject` will become `P123_myProject`. There is also soft-limit on project name length to be shorter than 20 characters. -Longer names will issue warning in Unreal Editor that there might be possible side effects. \ No newline at end of file +Longer names will issue warning in Unreal Editor that there might be possible side effects. diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index 24e2db975d..9ded333d7d 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -1,5 +1,8 @@ import os -from openpype.modules import OpenPypeModule, IHostAddon +from pathlib import Path + +from openpype.modules import IHostAddon, OpenPypeModule +from .lib import get_compatible_integration UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -13,15 +16,23 @@ class UnrealAddon(OpenPypeModule, IHostAddon): def add_implementation_envs(self, env, app): """Modify environments to contain all required for implementation.""" - # Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation + # Set AYON_UNREAL_PLUGIN required for Unreal implementation - ue_plugin = "UE_5.0" if app.name[:1] == "5" else "UE_4.7" + ue_version = app.name.replace("-", ".") unreal_plugin_path = os.path.join( - UNREAL_ROOT_DIR, "integration", ue_plugin, "OpenPype" + UNREAL_ROOT_DIR, "integration", f"UE_{ue_version}", "Ayon" ) - if not env.get("OPENPYPE_UNREAL_PLUGIN") or \ - env.get("OPENPYPE_UNREAL_PLUGIN") != unreal_plugin_path: - env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path + if not Path(unreal_plugin_path).exists(): + compatible_versions = get_compatible_integration( + ue_version, Path(UNREAL_ROOT_DIR) / "integration" + ) + if compatible_versions: + unreal_plugin_path = compatible_versions[-1] / "Ayon" + unreal_plugin_path = unreal_plugin_path.as_posix() + + if not env.get("AYON_UNREAL_PLUGIN") or \ + env.get("AYON_UNREAL_PLUGIN") != unreal_plugin_path: + env["AYON_UNREAL_PLUGIN"] = unreal_plugin_path # Set default environments if are not set via settings defaults = { diff --git a/openpype/hosts/unreal/api/__init__.py b/openpype/hosts/unreal/api/__init__.py index 2618a7677c..de0fce13d5 100644 --- a/openpype/hosts/unreal/api/__init__.py +++ b/openpype/hosts/unreal/api/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Unreal Editor OpenPype host API.""" +"""Unreal Editor Ayon host API.""" from .plugin import ( UnrealActorCreator, diff --git a/openpype/hosts/unreal/api/helpers.py b/openpype/hosts/unreal/api/helpers.py index 0b6f07f52f..e9ab3fb4c5 100644 --- a/openpype/hosts/unreal/api/helpers.py +++ b/openpype/hosts/unreal/api/helpers.py @@ -2,15 +2,15 @@ import unreal # noqa -class OpenPypeUnrealException(Exception): +class AyonUnrealException(Exception): pass @unreal.uclass() -class OpenPypeHelpers(unreal.OpenPypeLib): - """Class wrapping some useful functions for OpenPype. +class AyonHelpers(unreal.AyonLib): + """Class wrapping some useful functions for Ayon. - This class is extending native BP class in OpenPype Integration Plugin. + This class is extending native BP class in Ayon Integration Plugin. """ @@ -29,13 +29,13 @@ class OpenPypeHelpers(unreal.OpenPypeLib): Example: - OpenPypeHelpers().set_folder_color( + AyonHelpers().set_folder_color( "/Game/Path", unreal.LinearColor(a=1.0, r=1.0, g=0.5, b=0) ) Note: This will take effect only after Editor is restarted. I couldn't - find a way to refresh it. Also this saves the color definition + find a way to refresh it. Also, this saves the color definition into the project config, binding this path with color. So if you delete this path and later re-create, it will set this color again. diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index 1a7c626984..bb45fa8c01 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -14,7 +14,7 @@ from openpype.pipeline import ( register_creator_plugin_path, deregister_loader_plugin_path, deregister_creator_plugin_path, - AVALON_CONTAINER_ID, + AYON_CONTAINER_ID, ) from openpype.tools.utils import host_tools import openpype.hosts.unreal @@ -22,12 +22,13 @@ from openpype.host import HostBase, ILoadHost, IPublishHost import unreal # noqa +# Rename to Ayon once parent module renames logger = logging.getLogger("openpype.hosts.unreal") -OPENPYPE_CONTAINERS = "OpenPypeContainers" -CONTEXT_CONTAINER = "OpenPype/context.json" +AYON_CONTAINERS = "AyonContainers" +CONTEXT_CONTAINER = "Ayon/context.json" UNREAL_VERSION = semver.VersionInfo( - *os.getenv("OPENPYPE_UNREAL_VERSION").split(".") + *os.getenv("AYON_UNREAL_VERSION").split(".") ) HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.unreal.__file__)) @@ -53,14 +54,14 @@ class UnrealHost(HostBase, ILoadHost, IPublishHost): def get_containers(self): return ls() - def show_tools_popup(self): + @staticmethod + def show_tools_popup(): """Show tools popup with actions leading to show other tools.""" - show_tools_popup() - def show_tools_dialog(self): + @staticmethod + def show_tools_dialog(): """Show tools dialog with actions leading to show other tools.""" - show_tools_dialog() def update_context_data(self, data, changes): @@ -72,9 +73,10 @@ class UnrealHost(HostBase, ILoadHost, IPublishHost): with open(op_ctx, "w+") as f: json.dump(data, f) break - except IOError: + except IOError as e: if i == attempts - 1: - raise Exception("Failed to write context data. Aborting.") + raise Exception( + "Failed to write context data. Aborting.") from e unreal.log_warning("Failed to write context data. Retrying...") i += 1 time.sleep(3) @@ -95,19 +97,30 @@ def install(): print("-=" * 40) logo = '''. . - ____________ - / \\ __ \\ - \\ \\ \\/_\\ \\ - \\ \\ _____/ ______ - \\ \\ \\___// \\ \\ - \\ \\____\\ \\ \\_____\\ - \\/_____/ \\/______/ PYPE Club . + · + │ + ·∙/ + ·-∙•∙-· + / \\ /∙· / \\ + ∙ \\ │ / ∙ + \\ \\ · / / + \\\\ ∙ ∙ // + \\\\/ \\// + ___ + │ │ + │ │ + │ │ + │___│ + -· + + ·-─═─-∙ A Y O N ∙-─═─-· + by YNPUT . ''' print(logo) - print("installing OpenPype for Unreal ...") + print("installing Ayon for Unreal ...") print("-=" * 40) - logger.info("installing OpenPype for Unreal") + logger.info("installing Ayon for Unreal") pyblish.api.register_host("unreal") pyblish.api.register_plugin_path(str(PUBLISH_PATH)) register_loader_plugin_path(str(LOAD_PATH)) @@ -117,7 +130,7 @@ def install(): def uninstall(): - """Uninstall Unreal configuration for Avalon.""" + """Uninstall Unreal configuration for Ayon.""" pyblish.api.deregister_plugin_path(str(PUBLISH_PATH)) deregister_loader_plugin_path(str(LOAD_PATH)) deregister_creator_plugin_path(str(CREATE_PATH)) @@ -125,14 +138,14 @@ def uninstall(): def _register_callbacks(): """ - TODO: Implement callbacks if supported by UE4 + TODO: Implement callbacks if supported by UE """ pass def _register_events(): """ - TODO: Implement callbacks if supported by UE4 + TODO: Implement callbacks if supported by UE """ pass @@ -146,32 +159,30 @@ def ls(): """ ar = unreal.AssetRegistryHelpers.get_asset_registry() # UE 5.1 changed how class name is specified - class_name = ["/Script/OpenPype", "AssetContainer"] if UNREAL_VERSION.major == 5 and UNREAL_VERSION.minor > 0 else "AssetContainer" # noqa - openpype_containers = ar.get_assets_by_class(class_name, True) + class_name = ["/Script/Ayon", "AyonAssetContainer"] if UNREAL_VERSION.major == 5 and UNREAL_VERSION.minor > 0 else "AyonAssetContainer" # noqa + ayon_containers = ar.get_assets_by_class(class_name, True) # get_asset_by_class returns AssetData. To get all metadata we need to # load asset. get_tag_values() work only on metadata registered in # Asset Registry Project settings (and there is no way to set it with # python short of editing ini configuration file). - for asset_data in openpype_containers: + for asset_data in ayon_containers: asset = asset_data.get_asset() data = unreal.EditorAssetLibrary.get_metadata_tag_values(asset) data["objectName"] = asset_data.asset_name - data = cast_map_to_str_dict(data) - - yield data + yield cast_map_to_str_dict(data) def ls_inst(): ar = unreal.AssetRegistryHelpers.get_asset_registry() # UE 5.1 changed how class name is specified class_name = [ - "/Script/OpenPype", - "OpenPypePublishInstance" + "/Script/Ayon", + "AyonPublishInstance" ] if ( UNREAL_VERSION.major == 5 and UNREAL_VERSION.minor > 0 - ) else "OpenPypePublishInstance" # noqa + ) else "AyonPublishInstance" # noqa instances = ar.get_assets_by_class(class_name, True) # get_asset_by_class returns AssetData. To get all metadata we need to @@ -182,13 +193,11 @@ def ls_inst(): asset = asset_data.get_asset() data = unreal.EditorAssetLibrary.get_metadata_tag_values(asset) data["objectName"] = asset_data.asset_name - data = cast_map_to_str_dict(data) - - yield data + yield cast_map_to_str_dict(data) def parse_container(container): - """To get data from container, AssetContainer must be loaded. + """To get data from container, AyonAssetContainer must be loaded. Args: container(str): path to container @@ -217,7 +226,7 @@ def containerise(name, namespace, nodes, context, loader=None, suffix="_CON"): Unreal doesn't support *groups* of assets that you can add metadata to. But it does support folders that helps to organize asset. Unfortunately those folders are just that - you cannot add any additional information - to them. OpenPype Integration Plugin is providing way out - Implementing + to them. Ayon Integration Plugin is providing way out - Implementing `AssetContainer` Blueprint class. This class when added to folder can handle metadata on it using standard :func:`unreal.EditorAssetLibrary.set_metadata_tag()` and @@ -226,30 +235,30 @@ def containerise(name, namespace, nodes, context, loader=None, suffix="_CON"): those assets is available as `assets` property. This is list of strings starting with asset type and ending with its path: - `Material /Game/OpenPype/Test/TestMaterial.TestMaterial` + `Material /Game/Ayon/Test/TestMaterial.TestMaterial` """ # 1 - create directory for container root = "/Game" - container_name = "{}{}".format(name, suffix) + container_name = f"{name}{suffix}" new_name = move_assets_to_path(root, container_name, nodes) # 2 - create Asset Container there - path = "{}/{}".format(root, new_name) + path = f"{root}/{new_name}" create_container(container=container_name, path=path) namespace = path data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "name": new_name, "namespace": namespace, "loader": str(loader), "representation": context["representation"]["_id"], } # 3 - imprint data - imprint("{}/{}".format(path, container_name), data) + imprint(f"{path}/{container_name}", data) return path @@ -257,7 +266,7 @@ def instantiate(root, name, data, assets=None, suffix="_INS"): """Bundles *nodes* into *container*. Marking it with metadata as publishable instance. If assets are provided, - they are moved to new path where `OpenPypePublishInstance` class asset is + they are moved to new path where `AyonPublishInstance` class asset is created and imprinted with metadata. This can then be collected for publishing by Pyblish for example. @@ -271,7 +280,7 @@ def instantiate(root, name, data, assets=None, suffix="_INS"): suffix (str): suffix string to append to instance name """ - container_name = "{}{}".format(name, suffix) + container_name = f"{name}{suffix}" # if we specify assets, create new folder and move them there. If not, # just create empty folder @@ -280,10 +289,10 @@ def instantiate(root, name, data, assets=None, suffix="_INS"): else: new_name = create_folder(root, name) - path = "{}/{}".format(root, new_name) + path = f"{root}/{new_name}" create_publish_instance(instance=container_name, path=path) - imprint("{}/{}".format(path, container_name), data) + imprint(f"{path}/{container_name}", data) def imprint(node, data): @@ -299,7 +308,7 @@ def imprint(node, data): loaded_asset, key, str(value) ) - with unreal.ScopedEditorTransaction("OpenPype containerising"): + with unreal.ScopedEditorTransaction("Ayon containerising"): unreal.EditorAssetLibrary.save_asset(node) @@ -366,11 +375,11 @@ def create_folder(root: str, name: str) -> str: eal = unreal.EditorAssetLibrary index = 1 while True: - if eal.does_directory_exist("{}/{}".format(root, name)): - name = "{}{}".format(name, index) + if eal.does_directory_exist(f"{root}/{name}"): + name = f"{name}{index}" index += 1 else: - eal.make_directory("{}/{}".format(root, name)) + eal.make_directory(f"{root}/{name}") break return name @@ -403,9 +412,7 @@ def move_assets_to_path(root: str, name: str, assets: List[str]) -> str: unreal.log(assets) for asset in assets: loaded = eal.load_asset(asset) - eal.rename_asset( - asset, "{}/{}/{}".format(root, name, loaded.get_name()) - ) + eal.rename_asset(asset, f"{root}/{name}/{loaded.get_name()}") return name @@ -432,17 +439,16 @@ def create_container(container: str, path: str) -> unreal.Object: ) """ - factory = unreal.AssetContainerFactory() + factory = unreal.AyonAssetContainerFactory() tools = unreal.AssetToolsHelpers().get_asset_tools() - asset = tools.create_asset(container, path, None, factory) - return asset + return tools.create_asset(container, path, None, factory) def create_publish_instance(instance: str, path: str) -> unreal.Object: - """Helper function to create OpenPype Publish Instance on given path. + """Helper function to create Ayon Publish Instance on given path. - This behaves similarly as :func:`create_openpype_container`. + This behaves similarly as :func:`create_ayon_container`. Args: path (str): Path where to create Publish Instance. @@ -460,10 +466,9 @@ def create_publish_instance(instance: str, path: str) -> unreal.Object: ) """ - factory = unreal.OpenPypePublishInstanceFactory() + factory = unreal.AyonPublishInstanceFactory() tools = unreal.AssetToolsHelpers().get_asset_tools() - asset = tools.create_asset(instance, path, None, factory) - return asset + return tools.create_asset(instance, path, None, factory) def cast_map_to_str_dict(umap) -> dict: @@ -494,11 +499,14 @@ def get_subsequences(sequence: unreal.LevelSequence): """ tracks = sequence.get_master_tracks() - subscene_track = None - for t in tracks: - if t.get_class() == unreal.MovieSceneSubTrack.static_class(): - subscene_track = t - break + subscene_track = next( + ( + t + for t in tracks + if t.get_class() == unreal.MovieSceneSubTrack.static_class() + ), + None, + ) if subscene_track is not None and subscene_track.get_sections(): return subscene_track.get_sections() return [] diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index d60050a696..26ef69af86 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -31,7 +31,7 @@ from openpype.pipeline import ( @six.add_metaclass(ABCMeta) class UnrealBaseCreator(Creator): """Base class for Unreal creator plugins.""" - root = "/Game/OpenPype/PublishInstances" + root = "/Game/Ayon/AyonPublishInstances" suffix = "_INS" @staticmethod @@ -243,5 +243,5 @@ class UnrealActorCreator(UnrealBaseCreator): class Loader(LoaderPlugin, ABC): - """This serves as skeleton for future OpenPype specific functionality""" + """This serves as skeleton for future Ayon specific functionality""" pass diff --git a/openpype/hosts/unreal/api/rendering.py b/openpype/hosts/unreal/api/rendering.py index 3b54ab08a2..efe6fc54ad 100644 --- a/openpype/hosts/unreal/api/rendering.py +++ b/openpype/hosts/unreal/api/rendering.py @@ -51,7 +51,7 @@ def start_rendering(): # instances = pipeline.ls_inst() instances = [ a for a in assets - if a.get_class().get_name() == "OpenPypePublishInstance"] + if a.get_class().get_name() == "AyonPublishInstance"] inst_data = [] @@ -64,8 +64,9 @@ def start_rendering(): project = os.environ.get("AVALON_PROJECT") anatomy = Anatomy(project) root = anatomy.roots['renders'] - except Exception: - raise Exception("Could not find render root in anatomy settings.") + except Exception as e: + raise Exception( + "Could not find render root in anatomy settings.") from e render_dir = f"{root}/{project}" @@ -121,7 +122,7 @@ def start_rendering(): job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob) job.sequence = unreal.SoftObjectPath(i["master_sequence"]) job.map = unreal.SoftObjectPath(i["master_level"]) - job.author = "OpenPype" + job.author = "Ayon" # If we have a saved configuration, copy it to the job. if config: @@ -129,7 +130,7 @@ def start_rendering(): # User data could be used to pass data to the job, that can be # read in the job's OnJobFinished callback. We could, - # for instance, pass the AvalonPublishInstance's path to the job. + # for instance, pass the AyonPublishInstance's path to the job. # job.user_data = "" output_dir = render_setting.get('output') diff --git a/openpype/hosts/unreal/api/tools_ui.py b/openpype/hosts/unreal/api/tools_ui.py index 8531472142..5a4c689918 100644 --- a/openpype/hosts/unreal/api/tools_ui.py +++ b/openpype/hosts/unreal/api/tools_ui.py @@ -64,7 +64,7 @@ class ToolsDialog(QtWidgets.QDialog): def __init__(self, *args, **kwargs): super(ToolsDialog, self).__init__(*args, **kwargs) - self.setWindowTitle("OpenPype tools") + self.setWindowTitle("Ayon tools") icon = QtGui.QIcon(resources.get_openpype_icon_filepath()) self.setWindowIcon(icon) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index efbacc3b16..f01609d314 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -186,15 +186,15 @@ class UnrealPrelaunchHook(PreLaunchHook): project_path.mkdir(parents=True, exist_ok=True) - # Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for + # Set "AYON_UNREAL_PLUGIN" to current process environment for # execution of `create_unreal_project` - if self.launch_context.env.get("OPENPYPE_UNREAL_PLUGIN"): + if self.launch_context.env.get("AYON_UNREAL_PLUGIN"): self.log.info(( - f"{self.signature} using OpenPype plugin from " - f"{self.launch_context.env.get('OPENPYPE_UNREAL_PLUGIN')}" + f"{self.signature} using Ayon plugin from " + f"{self.launch_context.env.get('AYON_UNREAL_PLUGIN')}" )) - env_key = "OPENPYPE_UNREAL_PLUGIN" + env_key = "AYON_UNREAL_PLUGIN" if self.launch_context.env.get(env_key): os.environ[env_key] = self.launch_context.env[env_key] @@ -213,7 +213,7 @@ class UnrealPrelaunchHook(PreLaunchHook): engine_path, project_path) - self.launch_context.env["OPENPYPE_UNREAL_VERSION"] = engine_version + self.launch_context.env["AYON_UNREAL_VERSION"] = engine_version # Append project file to launch arguments self.launch_context.launch_args.append( f"\"{project_file.as_posix()}\"") diff --git a/openpype/hosts/unreal/integration/README.md b/openpype/hosts/unreal/integration/README.md new file mode 100644 index 0000000000..961eea83e6 --- /dev/null +++ b/openpype/hosts/unreal/integration/README.md @@ -0,0 +1,10 @@ +# Building the plugin + +In order to successfully build the plugin, make sure that the path to the UnrealBuildTool.exe is specified correctly. +After the UBT path specify for which platform it will be compiled. in the -Project parameter, specify the path to the +CommandletProject.uproject file. Next the build type has to be specified (DebugGame, Development, Package, etc.) and then the -TargetType (Editor, Runtime, etc.) + +`BuildPlugin_[Ver].bat` runs the building process in the background. If you want to show the progress inside the +command prompt, use the `BuildPlugin_[Ver]_Window.bat` file. + + diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore b/openpype/hosts/unreal/integration/UE_4.27/Ayon/.gitignore similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/.gitignore diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Ayon.uplugin b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Ayon.uplugin new file mode 100644 index 0000000000..0838da5577 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Ayon.uplugin @@ -0,0 +1,23 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Ayon", + "Description": "Ayon Integration", + "Category": "Ayon.Integration", + "CreatedBy": "Ondrej Samohel", + "CreatedByURL": "https://ayon.ynput.io", + "DocsURL": "https://ayon.ynput.io/docs/artist_hosts_unreal", + "MarketplaceURL": "", + "SupportURL": "https://ynput.io/", + "EngineVersion": "4.27", + "CanContainContent": true, + "Installed": true, + "Modules": [ + { + "Name": "Ayon", + "Type": "Editor", + "LoadingPhase": "Default" + } + ] +} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/DefaultAyonSettings.ini b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/DefaultAyonSettings.ini new file mode 100644 index 0000000000..9ad7f55201 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/DefaultAyonSettings.ini @@ -0,0 +1,2 @@ +[/Script/Ayon.AyonSettings] +FolderColor=(R=91,G=197,B=220,A=255) \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/FilterPlugin.ini b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/FilterPlugin.ini similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/FilterPlugin.ini rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Config/FilterPlugin.ini diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Content/Python/init_unreal.py new file mode 100644 index 0000000000..43d6b8b7cf --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Content/Python/init_unreal.py @@ -0,0 +1,30 @@ +import unreal + +ayon_detected = True +try: + from openpype.pipeline import install_host + from openpype.hosts.unreal.api import UnrealHost + + ayon_host = UnrealHost() +except ImportError as exc: + ayon_host = None + ayon_detected = False + unreal.log_error(f"OpenPype: cannot load Ayon [ {exc} ]") + +if ayon_detected: + install_host(ayon_host) + + +@unreal.uclass() +class AyonIntegration(unreal.AyonPythonBridge): + @unreal.ufunction(override=True) + def RunInPython_Popup(self): + unreal.log_warning("Ayon: showing tools popup") + if ayon_detected: + ayon_host.show_tools_popup() + + @unreal.ufunction(override=True) + def RunInPython_Dialog(self): + unreal.log_warning("Ayon: showing tools dialog") + if ayon_detected: + ayon_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/README.md b/openpype/hosts/unreal/integration/UE_4.27/Ayon/README.md new file mode 100644 index 0000000000..77ae8c7e98 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/README.md @@ -0,0 +1,3 @@ +# Ayon Unreal Integration plugin - UE 4.x + +This is plugin for Unreal Editor, creating menu for [Ayon](https://github.com/ynput/OpenPype) tools to run. diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon128.png b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon128.png new file mode 100644 index 0000000000..799d849aa3 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon128.png differ diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon40.png b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon40.png new file mode 100644 index 0000000000..f5bf40ea16 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon40.png differ diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon512.png b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon512.png new file mode 100644 index 0000000000..990d5917e2 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Resources/ayon512.png differ diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Ayon.Build.cs similarity index 82% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Ayon.Build.cs index f77c1383eb..a18fa93d4f 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Ayon.Build.cs @@ -2,35 +2,37 @@ using UnrealBuildTool; -public class OpenPype : ModuleRules +public class Ayon : ModuleRules { - public OpenPype(ReadOnlyTargetRules Target) : base(Target) + public Ayon(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicIncludePaths.AddRange( - new string[] { + new string[] + { // ... add public include paths required here ... } - ); - - + ); + + PrivateIncludePaths.AddRange( - new string[] { + new string[] + { // ... add other private include paths required here ... } - ); - - + ); + + PublicDependencyModuleNames.AddRange( new string[] { "Core", // ... add other public dependencies that you statically link with here ... } - ); - - + ); + + PrivateDependencyModuleNames.AddRange( new string[] { @@ -46,14 +48,14 @@ public class OpenPype : ModuleRules "AssetTools" // ... add private dependencies that you statically link with here ... } - ); - - + ); + + DynamicallyLoadedModuleNames.AddRange( new string[] { // ... add any modules that your module loads dynamically here ... } - ); + ); } -} +} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Ayon.cpp similarity index 59% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Ayon.cpp index 9bf7b341c5..158a32e496 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Ayon.cpp @@ -1,25 +1,26 @@ // Copyright 2023, Ayon, All rights reserved. -#include "OpenPype.h" +#include "Ayon.h" #include "ISettingsContainer.h" #include "ISettingsModule.h" #include "ISettingsSection.h" #include "LevelEditor.h" -#include "OpenPypePythonBridge.h" -#include "OpenPypeSettings.h" -#include "OpenPypeStyle.h" +#include "AyonPythonBridge.h" +#include "AyonSettings.h" +#include "AyonStyle.h" +#include "Modules/ModuleManager.h" -static const FName OpenPypeTabName("OpenPype"); +static const FName AyonTabName("Ayon"); -#define LOCTEXT_NAMESPACE "FOpenPypeModule" +#define LOCTEXT_NAMESPACE "FAyonModule" // This function is triggered when the plugin is staring up -void FOpenPypeModule::StartupModule() +void FAyonModule::StartupModule() { if (!IsRunningCommandlet()) { - FOpenPypeStyle::Initialize(); - FOpenPypeStyle::SetIcon("Logo", "openpype40"); + FAyonStyle::Initialize(); + FAyonStyle::SetIcon("Logo", "ayon40"); // Create the Extender that will add content to the menu FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); @@ -31,13 +32,13 @@ void FOpenPypeModule::StartupModule() "LevelEditor", EExtensionHook::After, NULL, - FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry) + FMenuExtensionDelegate::CreateRaw(this, &FAyonModule::AddMenuEntry) ); ToolbarExtender->AddToolBarExtension( "Settings", EExtensionHook::After, NULL, - FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry)); + FToolBarExtensionDelegate::CreateRaw(this, &FAyonModule::AddToobarEntry)); LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); @@ -47,56 +48,56 @@ void FOpenPypeModule::StartupModule() } } -void FOpenPypeModule::ShutdownModule() +void FAyonModule::ShutdownModule() { - FOpenPypeStyle::Shutdown(); + FAyonStyle::Shutdown(); } -void FOpenPypeModule::AddMenuEntry(FMenuBuilder& MenuBuilder) +void FAyonModule::AddMenuEntry(FMenuBuilder& MenuBuilder) { // Create Section - MenuBuilder.BeginSection("OpenPype", TAttribute(FText::FromString("OpenPype"))); + MenuBuilder.BeginSection("Ayon", TAttribute(FText::FromString("Ayon"))); { // Create a Submenu inside of the Section MenuBuilder.AddMenuEntry( FText::FromString("Tools..."), FText::FromString("Pipeline tools"), - FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo"), - FUIAction(FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup)) + FSlateIcon(FAyonStyle::GetStyleSetName(), "Ayon.Logo"), + FUIAction(FExecuteAction::CreateRaw(this, &FAyonModule::MenuPopup)) ); MenuBuilder.AddMenuEntry( FText::FromString("Tools dialog..."), FText::FromString("Pipeline tools dialog"), - FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo"), - FUIAction(FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuDialog)) + FSlateIcon(FAyonStyle::GetStyleSetName(), "Ayon.Logo"), + FUIAction(FExecuteAction::CreateRaw(this, &FAyonModule::MenuDialog)) ); } MenuBuilder.EndSection(); } -void FOpenPypeModule::AddToobarEntry(FToolBarBuilder& ToolbarBuilder) +void FAyonModule::AddToobarEntry(FToolBarBuilder& ToolbarBuilder) { - ToolbarBuilder.BeginSection(TEXT("OpenPype")); + ToolbarBuilder.BeginSection(TEXT("Ayon")); { ToolbarBuilder.AddToolBarButton( FUIAction( - FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup), + FExecuteAction::CreateRaw(this, &FAyonModule::MenuPopup), NULL, FIsActionChecked() ), NAME_None, - LOCTEXT("OpenPype_label", "OpenPype"), - LOCTEXT("OpenPype_tooltip", "OpenPype Tools"), - FSlateIcon(FOpenPypeStyle::GetStyleSetName(), "OpenPype.Logo") + LOCTEXT("Ayon_label", "Ayon"), + LOCTEXT("Ayon_tooltip", "Ayon Tools"), + FSlateIcon(FAyonStyle::GetStyleSetName(), "Ayon.Logo") ); } ToolbarBuilder.EndSection(); } -void FOpenPypeModule::RegisterSettings() +void FAyonModule::RegisterSettings() { ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked("Settings"); @@ -104,10 +105,10 @@ void FOpenPypeModule::RegisterSettings() // TODO: After the movement of the plugin from the game to editor, it might be necessary to move this! ISettingsContainerPtr SettingsContainer = SettingsModule.GetContainer("Project"); - UOpenPypeSettings* Settings = GetMutableDefault(); + UAyonSettings* Settings = GetMutableDefault(); // Register the settings - ISettingsSectionPtr SettingsSection = SettingsModule.RegisterSettings("Project", "OpenPype", "General", + ISettingsSectionPtr SettingsSection = SettingsModule.RegisterSettings("Project", "Ayon", "General", LOCTEXT("RuntimeGeneralSettingsName", "General"), LOCTEXT("RuntimeGeneralSettingsDescription", @@ -119,13 +120,13 @@ void FOpenPypeModule::RegisterSettings() // validate those or just act to settings changes. if (SettingsSection.IsValid()) { - SettingsSection->OnModified().BindRaw(this, &FOpenPypeModule::HandleSettingsSaved); + SettingsSection->OnModified().BindRaw(this, &FAyonModule::HandleSettingsSaved); } } -bool FOpenPypeModule::HandleSettingsSaved() +bool FAyonModule::HandleSettingsSaved() { - UOpenPypeSettings* Settings = GetMutableDefault(); + UAyonSettings* Settings = GetMutableDefault(); bool ResaveSettings = false; // You can put any validation code in here and resave the settings in case an invalid @@ -140,16 +141,16 @@ bool FOpenPypeModule::HandleSettingsSaved() } -void FOpenPypeModule::MenuPopup() +void FAyonModule::MenuPopup() { - UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get(); + UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); bridge->RunInPython_Popup(); } -void FOpenPypeModule::MenuDialog() +void FAyonModule::MenuDialog() { - UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get(); + UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); bridge->RunInPython_Dialog(); } -IMPLEMENT_MODULE(FOpenPypeModule, OpenPype) +IMPLEMENT_MODULE(FAyonModule, Ayon) diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp new file mode 100644 index 0000000000..e3989eb03c --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp @@ -0,0 +1,114 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "AyonAssetContainer.h" +#include "AssetRegistryModule.h" +#include "Misc/PackageName.h" +#include "Containers/UnrealString.h" + +UAyonAssetContainer::UAyonAssetContainer(const FObjectInitializer& ObjectInitializer) +: UAssetUserData(ObjectInitializer) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + FString path = UAyonAssetContainer::GetPathName(); + UE_LOG(LogTemp, Warning, TEXT("UAyonAssetContainer %s"), *path); + FARFilter Filter; + Filter.PackagePaths.Add(FName(*path)); + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonAssetContainer::OnAssetAdded); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonAssetContainer::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAyonAssetContainer::OnAssetRenamed); +} + +void UAyonAssetContainer::OnAssetAdded(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAyonAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AyonAssetContainer") + { + assets.Add(assetPath); + assetsData.Add(AssetData); + UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); + } + } +} + +void UAyonAssetContainer::OnAssetRemoved(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAyonAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + FString path = UAyonAssetContainer::GetPathName(); + FString lpp = FPackageName::GetLongPackagePath(*path); + + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AyonAssetContainer") + { + // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); + assets.Remove(assetPath); + assetsData.Remove(AssetData); + } + } +} + +void UAyonAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAyonAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClass.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AyonAssetContainer") + { + + assets.Remove(str); + assets.Add(assetPath); + assetsData.Remove(AssetData); + // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); + } + } +} + diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp new file mode 100644 index 0000000000..086fc1036e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp @@ -0,0 +1,20 @@ +#include "AyonAssetContainerFactory.h" +#include "AyonAssetContainer.h" + +UAyonAssetContainerFactory::UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UAyonAssetContainer::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UAyonAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + UAyonAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); + return AssetContainer; +} + +bool UAyonAssetContainerFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonLib.cpp similarity index 83% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonLib.cpp index 34faba1f49..bff99caee3 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonLib.cpp @@ -1,5 +1,5 @@ // Copyright 2023, Ayon, All rights reserved. -#include "OpenPypeLib.h" +#include "AyonLib.h" #include "AssetViewUtils.h" #include "Misc/Paths.h" @@ -13,7 +13,7 @@ * @warning This color will appear only after Editor restart. Is there a better way? */ -bool UOpenPypeLib::SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor, const bool& bForceAdd) +bool UAyonLib::SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor, const bool& bForceAdd) { if (AssetViewUtils::DoesFolderExist(FolderPath)) { @@ -31,11 +31,11 @@ bool UOpenPypeLib::SetFolderColor(const FString& FolderPath, const FLinearColor& } /** - * Returns all properties on given object + * Returns all poperties on given object * @param cls - class * @return TArray of properties */ -TArray UOpenPypeLib::GetAllProperties(UClass* cls) +TArray UAyonLib::GetAllProperties(UClass* cls) { TArray Ret; if (cls != nullptr) diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp new file mode 100644 index 0000000000..d7550e2ed1 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp @@ -0,0 +1,203 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "AyonPublishInstance.h" +#include "AssetRegistryModule.h" +#include "AyonLib.h" +#include "AyonSettings.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Widgets/Notifications/SNotificationList.h" + +//Moves all the invalid pointers to the end to prepare them for the shrinking +#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ + VAR.Shrink(); + +UAyonPublishInstance::UAyonPublishInstance(const FObjectInitializer& ObjectInitializer) + : UPrimaryDataAsset(ObjectInitializer) +{ + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< + FAssetRegistryModule>("AssetRegistry"); + + const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( + "PropertyEditor"); + + FString Left, Right; + GetPathName().Split("/" + GetName(), &Left, &Right); + + FARFilter Filter; + Filter.PackagePaths.Emplace(FName(Left)); + + TArray FoundAssets; + AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); + + for (const FAssetData& AssetData : FoundAssets) + OnAssetCreated(AssetData); + + REMOVE_INVALID_ENTRIES(AssetDataInternal) + REMOVE_INVALID_ENTRIES(AssetDataExternal) + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonPublishInstance::OnAssetCreated); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonPublishInstance::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UAyonPublishInstance::OnAssetUpdated); + +#ifdef WITH_EDITOR + ColorAyonDirs(); +#endif + +} + +void UAyonPublishInstance::OnAssetCreated(const FAssetData& InAssetData) +{ + TArray split; + + UObject* Asset = InAssetData.GetAsset(); + + if (!IsValid(Asset)) + { + UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), + *InAssetData.ObjectPath.ToString()); + return; + } + + const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; + + if (result) + { + if (AssetDataInternal.Emplace(Asset).IsValidId()) + { + UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), + *this->GetName(), *Asset->GetName()); + } + } +} + +void UAyonPublishInstance::OnAssetRemoved(const FAssetData& InAssetData) +{ + if (Cast(InAssetData.GetAsset()) == nullptr) + { + if (AssetDataInternal.Contains(nullptr)) + { + AssetDataInternal.Remove(nullptr); + REMOVE_INVALID_ENTRIES(AssetDataInternal) + } + else + { + AssetDataExternal.Remove(nullptr); + REMOVE_INVALID_ENTRIES(AssetDataExternal) + } + } +} + +void UAyonPublishInstance::OnAssetUpdated(const FAssetData& InAssetData) +{ + REMOVE_INVALID_ENTRIES(AssetDataInternal); + REMOVE_INVALID_ENTRIES(AssetDataExternal); +} + +bool UAyonPublishInstance::IsUnderSameDir(const UObject* InAsset) const +{ + FString ThisLeft, ThisRight; + this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); + + return InAsset->GetPathName().StartsWith(ThisLeft); +} + +#ifdef WITH_EDITOR + +void UAyonPublishInstance::ColorAyonDirs() +{ + FString PathName = this->GetPathName(); + + //Check whether the path contains the defined Ayon folder + if (!PathName.Contains(TEXT("Ayon"))) return; + + //Get the base path for open pype + FString PathLeft, PathRight; + PathName.Split(FString("Ayon"), &PathLeft, &PathRight); + + if (PathLeft.IsEmpty() || PathRight.IsEmpty()) + { + UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base Ayon directory!")) + return; + } + + PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); + + //Get the current settings + const UAyonSettings* Settings = GetMutableDefault(); + + //Color the base folder + UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); + + //Get Sub paths, iterate through them and color them according to the folder color in UAyonSettings + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( + "AssetRegistry"); + + TArray PathList; + + AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); + + if (PathList.Num() > 0) + { + for (const FString& Path : PathList) + { + UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); + } + } +} + +void UAyonPublishInstance::SendNotification(const FString& Text) const +{ + FNotificationInfo Info{FText::FromString(Text)}; + + Info.bFireAndForget = true; + Info.bUseLargeFont = false; + Info.bUseThrobber = false; + Info.bUseSuccessFailIcons = false; + Info.ExpireDuration = 4.f; + Info.FadeOutDuration = 2.f; + + FSlateNotificationManager::Get().AddNotification(Info); + + UE_LOG(LogAssetData, Warning, + TEXT( + "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" + ), *GetName() + ) +} + + +void UAyonPublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && + PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( + UAyonPublishInstance, AssetDataExternal)) + { + // Check for duplicated assets + for (const auto& Asset : AssetDataInternal) + { + if (AssetDataExternal.Contains(Asset)) + { + AssetDataExternal.Remove(Asset); + return SendNotification( + "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); + } + } + + // Check if no UAyonPublishInstance type assets are included + for (const auto& Asset : AssetDataExternal) + { + if (Cast(Asset.Get()) != nullptr) + { + AssetDataExternal.Remove(Asset); + return SendNotification("You are not allowed to add publish instances!"); + } + } + } +} + +#endif diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp new file mode 100644 index 0000000000..f79c428a6d --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp @@ -0,0 +1,23 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#include "AyonPublishInstanceFactory.h" +#include "AyonPublishInstance.h" + +UAyonPublishInstanceFactory::UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UAyonPublishInstance::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UAyonPublishInstanceFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + check(InClass->IsChildOf(UAyonPublishInstance::StaticClass())); + return NewObject(InParent, InClass, InName, Flags); +} + +bool UAyonPublishInstanceFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp new file mode 100644 index 0000000000..0ed4b2f704 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp @@ -0,0 +1,14 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "AyonPythonBridge.h" + +UAyonPythonBridge* UAyonPythonBridge::Get() +{ + TArray AyonPythonBridgeClasses; + GetDerivedClasses(UAyonPythonBridge::StaticClass(), AyonPythonBridgeClasses); + int32 NumClasses = AyonPythonBridgeClasses.Num(); + if (NumClasses > 0) + { + return Cast(AyonPythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); + } + return nullptr; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonSettings.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonSettings.cpp new file mode 100644 index 0000000000..509b7268ba --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonSettings.cpp @@ -0,0 +1,20 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "AyonSettings.h" + +#include "Interfaces/IPluginManager.h" + +/** + * Mainly is used for initializing default values if the DefaultAyonSettings.ini file does not exist in the saved config + */ +UAyonSettings::UAyonSettings(const FObjectInitializer& ObjectInitializer) +{ + + const FString ConfigFilePath = AYON_SETTINGS_FILEPATH; + + // This has to be probably in the future set using the UE Reflection system + FColor Color; + GConfig->GetColor(TEXT("/Script/Ayon.AyonSettings"), TEXT("FolderColor"), Color, ConfigFilePath); + + FolderColor = Color; +} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonStyle.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonStyle.cpp new file mode 100644 index 0000000000..b133225fd5 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/AyonStyle.cpp @@ -0,0 +1,70 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "AyonStyle.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyle.h" +#include "Styling/SlateStyleRegistry.h" + + +TUniquePtr< FSlateStyleSet > FAyonStyle::AyonStyleInstance = nullptr; + +void FAyonStyle::Initialize() +{ + if (!AyonStyleInstance.IsValid()) + { + AyonStyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*AyonStyleInstance); + } +} + +void FAyonStyle::Shutdown() +{ + if (AyonStyleInstance.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*AyonStyleInstance); + AyonStyleInstance.Reset(); + } +} + +FName FAyonStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("AyonStyle")); + return StyleSetName; +} + +FName FAyonStyle::GetContextName() +{ + static FName ContextName(TEXT("Ayon")); + return ContextName; +} + +#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) + +const FVector2D Icon40x40(40.0f, 40.0f); + +TUniquePtr< FSlateStyleSet > FAyonStyle::Create() +{ + TUniquePtr< FSlateStyleSet > Style = MakeUnique(GetStyleSetName()); + Style->SetContentRoot(FPaths::EnginePluginsDir() / TEXT("Marketplace/Ayon/Resources")); + + return Style; +} + +void FAyonStyle::SetIcon(const FString& StyleName, const FString& ResourcePath) +{ + FSlateStyleSet* Style = AyonStyleInstance.Get(); + + FString Name(GetContextName().ToString()); + Name = Name + "." + StyleName; + Style->Set(*Name, new FSlateImageBrush(Style->RootToContentDir(ResourcePath, TEXT(".png")), Icon40x40)); + + + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); +} + +#undef IMAGE_BRUSH + +const ISlateStyle& FAyonStyle::Get() +{ + check(AyonStyleInstance); + return *AyonStyleInstance; +} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp new file mode 100644 index 0000000000..49376e8648 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp @@ -0,0 +1,41 @@ +// Copyright 2023, Ayon, All rights reserved. + + +#include "Commandlets/AyonActionResult.h" +#include "Logging/Ayon_Log.h" + +EAyon_ActionResult::Type& FAyon_ActionResult::GetStatus() +{ + return Status; +} + +FText& FAyon_ActionResult::GetReason() +{ + return Reason; +} + +FAyon_ActionResult::FAyon_ActionResult():Status(EAyon_ActionResult::Type::Ok) +{ + +} + +FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum):Status(InEnum) +{ + TryLog(); +} + +FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) +{ + TryLog(); +}; + +bool FAyon_ActionResult::IsProblem() const +{ + return Status != EAyon_ActionResult::Ok; +} + +void FAyon_ActionResult::TryLog() const +{ + if(IsProblem()) + UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString()); +} diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp new file mode 100644 index 0000000000..0328d3b7e6 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp @@ -0,0 +1,141 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "Commandlets/Implementations/AyonGenerateProjectCommandlet.h" + +#include "Editor.h" +#include "GameProjectUtils.h" +#include "AyonConstants.h" +#include "Commandlets/AyonActionResult.h" +#include "ProjectDescriptor.h" + +int32 UAyonGenerateProjectCommandlet::Main(const FString& CommandLineParams) +{ + //Parses command line parameters & creates structure FProjectInformation + const FAyonGenerateProjectParams ParsedParams = FAyonGenerateProjectParams(CommandLineParams); + ProjectInformation = ParsedParams.GenerateUEProjectInformation(); + + //Creates .uproject & other UE files + EVALUATE_AYON_ACTION_RESULT(TryCreateProject()); + + //Loads created .uproject + EVALUATE_AYON_ACTION_RESULT(TryLoadProjectDescriptor()); + + //Adds needed plugin to .uproject + AttachPluginsToProjectDescriptor(); + + //Saves .uproject + EVALUATE_AYON_ACTION_RESULT(TrySave()); + + //When we are here, there should not be problems in generating Unreal Project for Ayon + return 0; +} + + +FAyonGenerateProjectParams::FAyonGenerateProjectParams(): FAyonGenerateProjectParams("") +{ +} + +FAyonGenerateProjectParams::FAyonGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( + CommandLineParams) +{ + UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); +} + +FProjectInformation FAyonGenerateProjectParams::GenerateUEProjectInformation() const +{ + FProjectInformation ProjectInformation = FProjectInformation(); + ProjectInformation.ProjectFilename = GetProjectFileName(); + + ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); + + return ProjectInformation; +} + +FString FAyonGenerateProjectParams::TryGetToken(const int32 Index) const +{ + return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; +} + +FString FAyonGenerateProjectParams::GetProjectFileName() const +{ + return TryGetToken(0); +} + +bool FAyonGenerateProjectParams::IsSwitchPresent(const FString& Switch) const +{ + return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool + { + return Item.Equals(Switch); + } + ); +} + + +UAyonGenerateProjectCommandlet::UAyonGenerateProjectCommandlet() +{ + LogToConsole = true; +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TryCreateProject() const +{ + FText FailReason; + FText FailLog; + TArray OutCreatedFiles; + + if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) + return FAyon_ActionResult(EAyon_ActionResult::ProjectNotCreated, FailReason); + return FAyon_ActionResult(); +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TryLoadProjectDescriptor() +{ + FText FailReason; + const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); + + return FAyon_ActionResult(bLoaded ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotLoaded, FailReason); +} + +void UAyonGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() +{ + FPluginReferenceDescriptor AyonPluginDescriptor; + AyonPluginDescriptor.bEnabled = true; + AyonPluginDescriptor.Name = AyonConstants::Ayon_PluginName; + ProjectDescriptor.Plugins.Add(AyonPluginDescriptor); + + FPluginReferenceDescriptor PythonPluginDescriptor; + PythonPluginDescriptor.bEnabled = true; + PythonPluginDescriptor.Name = AyonConstants::PythonScript_PluginName; + ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); + + FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; + SequencerScriptingPluginDescriptor.bEnabled = true; + SequencerScriptingPluginDescriptor.Name = AyonConstants::SequencerScripting_PluginName; + ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); + + FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; + MovieRenderPipelinePluginDescriptor.bEnabled = true; + MovieRenderPipelinePluginDescriptor.Name = AyonConstants::MovieRenderPipeline_PluginName; + ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); + + FPluginReferenceDescriptor EditorScriptingPluginDescriptor; + EditorScriptingPluginDescriptor.bEnabled = true; + EditorScriptingPluginDescriptor.Name = AyonConstants::EditorScriptingUtils_PluginName; + ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TrySave() +{ + FText FailReason; + const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); + + return FAyon_ActionResult(bSaved ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotSaved, FailReason); +} + +FAyonGenerateProjectParams UAyonGenerateProjectCommandlet::ParseParameters(const FString& Params) const +{ + FAyonGenerateProjectParams ParamsResult; + + TArray Tokens, Switches; + ParseCommandLine(*Params, Tokens, Switches); + + return ParamsResult; +} diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp similarity index 92% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp index 05638fbd0b..320285591e 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp @@ -1,10 +1,12 @@ // Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. #pragma once #include "OpenPypePublishInstance.h" #include "AssetRegistryModule.h" -#include "OpenPypeLib.h" -#include "OpenPypeSettings.h" +#include "AyonLib.h" +#include "AyonSettings.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" @@ -43,7 +45,7 @@ UOpenPypePublishInstance::UOpenPypePublishInstance(const FObjectInitializer& Obj #ifdef WITH_EDITOR ColorOpenPypeDirs(); #endif - + } void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData) @@ -124,12 +126,12 @@ void UOpenPypePublishInstance::ColorOpenPypeDirs() PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); //Get the current settings - const UOpenPypeSettings* Settings = GetMutableDefault(); + const UAyonSettings* Settings = GetMutableDefault(); //Color the base folder - UOpenPypeLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); + UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); - //Get Sub paths, iterate through them and color them according to the folder color in UOpenPypeSettings + //Get Sub paths, iterate through them and color them according to the folder color in UAyonSettings const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry"); @@ -141,7 +143,7 @@ void UOpenPypePublishInstance::ColorOpenPypeDirs() { for (const FString& Path : PathList) { - UOpenPypeLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); + UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); } } } diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Ayon.h similarity index 84% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Ayon.h index 2454344128..d11af70058 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Ayon.h @@ -2,10 +2,8 @@ #pragma once -#include "Engine.h" - -class FOpenPypeModule : public IModuleInterface +class FAyonModule : public IModuleInterface { public: virtual void StartupModule() override; diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainer.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainer.h new file mode 100644 index 0000000000..cc17b3960a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainer.h @@ -0,0 +1,39 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "Engine/AssetUserData.h" +#include "AssetData.h" +#include "AyonAssetContainer.generated.h" + +/** + * + */ +UCLASS(Blueprintable) +class AYON_API UAyonAssetContainer : public UAssetUserData +{ + GENERATED_BODY() + +public: + + UAyonAssetContainer(const FObjectInitializer& ObjectInitalizer); + // ~UAyonAssetContainer(); + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Assets") + TArray assets; + + // There seems to be no reflection option to expose array of FAssetData + /* + UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) + TArray assetsData; + */ +private: + TArray assetsData; + void OnAssetAdded(const FAssetData& AssetData); + void OnAssetRemoved(const FAssetData& AssetData); + void OnAssetRenamed(const FAssetData& AssetData, const FString& str); +}; + + diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h new file mode 100644 index 0000000000..7c35897911 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h @@ -0,0 +1,21 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "AyonAssetContainerFactory.generated.h" + +/** + * + */ +UCLASS() +class AYON_API UAyonAssetContainerFactory : public UFactory +{ + GENERATED_BODY() + +public: + UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer); + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonConstants.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonConstants.h new file mode 100644 index 0000000000..6a02b5682f --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonConstants.h @@ -0,0 +1,15 @@ +// Copyright 2023, Ayon, All rights reserved. +#pragma once + +#include "CoreMinimal.h" + +namespace AyonConstants +{ + const FString Ayon_PluginName = "Ayon"; + const FString PythonScript_PluginName = "PythonScriptPlugin"; + const FString SequencerScripting_PluginName = "SequencerScripting"; + const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline"; + const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities"; +} + + diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonLib.h similarity index 75% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonLib.h index ef4d1027ea..da83b448fb 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonLib.h @@ -1,12 +1,11 @@ // Copyright 2023, Ayon, All rights reserved. #pragma once -#include "Engine.h" -#include "OpenPypeLib.generated.h" +#include "AyonLib.generated.h" UCLASS(Blueprintable) -class OPENPYPE_API UOpenPypeLib : public UBlueprintFunctionLibrary +class AYON_API UAyonLib : public UBlueprintFunctionLibrary { GENERATED_BODY() diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstance.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstance.h new file mode 100644 index 0000000000..0a0628c3ec --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstance.h @@ -0,0 +1,103 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "AyonPublishInstance.generated.h" + + +UCLASS(Blueprintable) +class AYON_API UAyonPublishInstance : public UPrimaryDataAsset +{ + GENERATED_UCLASS_BODY() + +public: + /** + * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is + * placed in) + * + * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetInternalAssets() const + { + //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. + TSet ResultSet; + + for (const auto& Asset : AssetDataInternal) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + + /** + * Retrieves all the assets which have been added manually by the Publish Instance + * + * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetExternalAssets() const + { + //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. + TSet ResultSet; + + for (const auto& Asset : AssetDataExternal) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + + /** + * Function for returning all the assets in the container combined. + * + * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are + * returning raw pointers. Seems like an issue in UE5 + * + * @attention If the bAddExternalAssets variable is false, external assets won't be included! + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetAllAssets() const + { + const TSet>& IteratedSet = bAddExternalAssets + ? AssetDataInternal.Union(AssetDataExternal) + : AssetDataInternal; + + //Create a new TSet only with raw pointers. + TSet ResultSet; + + for (auto& Asset : IteratedSet) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + +private: + UPROPERTY(VisibleAnywhere, Category="Assets") + TSet> AssetDataInternal; + + /** + * This property allows exposing the array to include other assets from any other directory than what it's currently + * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! + */ + UPROPERTY(EditAnywhere, Category = "Assets") + bool bAddExternalAssets = false; + + UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") + TSet> AssetDataExternal; + + + void OnAssetCreated(const FAssetData& InAssetData); + void OnAssetRemoved(const FAssetData& InAssetData); + void OnAssetUpdated(const FAssetData& InAssetData); + + bool IsUnderSameDir(const UObject* InAsset) const; + +#ifdef WITH_EDITOR + + void ColorAyonDirs(); + + void SendNotification(const FString& Text) const; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + +#endif +}; diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h similarity index 54% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h index 3fdb984411..3cef8e76b2 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h @@ -1,20 +1,22 @@ // Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. #pragma once #include "CoreMinimal.h" #include "Factories/Factory.h" -#include "OpenPypePublishInstanceFactory.generated.h" +#include "AyonPublishInstanceFactory.generated.h" /** * */ UCLASS() -class OPENPYPE_API UOpenPypePublishInstanceFactory : public UFactory +class AYON_API UAyonPublishInstanceFactory : public UFactory { GENERATED_BODY() public: - UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer); + UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer); virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; virtual bool ShouldShowInNewMenu() const override; }; diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPythonBridge.h similarity index 70% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPythonBridge.h index 827f76f56b..3c429fd7d3 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonPythonBridge.h @@ -1,16 +1,15 @@ // Copyright 2023, Ayon, All rights reserved. #pragma once -#include "Engine.h" -#include "OpenPypePythonBridge.generated.h" +#include "AyonPythonBridge.generated.h" UCLASS(Blueprintable) -class UOpenPypePythonBridge : public UObject +class UAyonPythonBridge : public UObject { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category = Python) - static UOpenPypePythonBridge* Get(); + static UAyonPythonBridge* Get(); UFUNCTION(BlueprintImplementableEvent, Category = Python) void RunInPython_Popup() const; diff --git a/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonSettings.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonSettings.h new file mode 100644 index 0000000000..7a93f107c5 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonSettings.h @@ -0,0 +1,31 @@ +// Copyright 2023, Ayon, All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AyonSettings.generated.h" + +#define AYON_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Config") / TEXT("DefaultAyonSettings.ini") + +UCLASS(Config=AyonSettings, DefaultConfig) +class AYON_API UAyonSettings : public UObject +{ + GENERATED_UCLASS_BODY() + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = Settings) + FColor GetFolderFColor() const + { + return FolderColor; + } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = Settings) + FLinearColor GetFolderFLinearColor() const + { + return FLinearColor(FolderColor); + } + +protected: + + UPROPERTY(config, EditAnywhere, Category = Folders) + FColor FolderColor = FColor(25,45,223); +}; diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonStyle.h similarity index 84% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonStyle.h index 0e4af129d0..188e4a510c 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/AyonStyle.h @@ -6,7 +6,7 @@ class FSlateStyleSet; class ISlateStyle; -class FOpenPypeStyle +class FAyonStyle { public: static void Initialize(); @@ -19,5 +19,5 @@ public: private: static TUniquePtr< FSlateStyleSet > Create(); - static TUniquePtr< FSlateStyleSet > OpenPypeStyleInstance; + static TUniquePtr< FSlateStyleSet > AyonStyleInstance; }; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h similarity index 63% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h index 322a23a3e8..4694055164 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h @@ -3,23 +3,23 @@ #pragma once #include "CoreMinimal.h" -#include "OPActionResult.generated.h" +#include "AyonActionResult.generated.h" /** * @brief This macro returns error code when is problem or does nothing when there is no problem. - * @param ActionResult FOP_ActionResult structure + * @param ActionResult FAyon_ActionResult structure */ -#define EVALUATE_OP_ACTION_RESULT(ActionResult) \ +#define EVALUATE_AYON_ACTION_RESULT(ActionResult) \ if(ActionResult.IsProblem()) \ return ActionResult.GetStatus(); /** * @brief This enum values are humanly readable mapping of error codes. * Here should be all error codes to be possible find what went wrong. -* TODO: In the future a web document should exists with the mapped error code & what problem occurred & how to repair it... +* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... */ UENUM() -namespace EOP_ActionResult +namespace EAyon_ActionResult { enum Type { @@ -27,11 +27,11 @@ namespace EOP_ActionResult ProjectNotCreated, ProjectNotLoaded, ProjectNotSaved, - //....Here insert another values + //....Here insert another values //Do not remove! //Usable for looping through enum values - __Last UMETA(Hidden) + __Last UMETA(Hidden) }; } @@ -40,44 +40,44 @@ namespace EOP_ActionResult * @brief This struct holds action result enum and optionally reason of fail */ USTRUCT() -struct FOP_ActionResult +struct FAyon_ActionResult { GENERATED_BODY() public: /** @brief Default constructor usable when there is no problem */ - FOP_ActionResult(); + FAyon_ActionResult(); /** * @brief This constructor initializes variables & attempts to log when is error * @param InEnum Status */ - FOP_ActionResult(const EOP_ActionResult::Type& InEnum); + FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum); /** * @brief This constructor initializes variables & attempts to log when is error * @param InEnum Status * @param InReason Reason of potential fail */ - FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason); + FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason); private: /** @brief Action status */ - EOP_ActionResult::Type Status; + EAyon_ActionResult::Type Status; /** @brief Optional reason of fail */ - FText Reason; + FText Reason; public: /** * @brief Checks if there is problematic state - * @return true when status is not equal to EOP_ActionResult::Ok + * @return true when status is not equal to EAyon_ActionResult::Ok */ bool IsProblem() const; - EOP_ActionResult::Type& GetStatus(); + EAyon_ActionResult::Type& GetStatus(); FText& GetReason(); -private: +private: void TryLog() const; }; diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h similarity index 63% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h index d1129aa070..cabd524b8c 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h @@ -2,10 +2,10 @@ #pragma once #include "GameProjectUtils.h" -#include "Commandlets/OPActionResult.h" +#include "Commandlets/AyonActionResult.h" #include "ProjectDescriptor.h" #include "Commandlets/Commandlet.h" -#include "OPGenerateProjectCommandlet.generated.h" +#include "AyonGenerateProjectCommandlet.generated.h" struct FProjectDescriptor; struct FProjectInformation; @@ -14,7 +14,7 @@ struct FProjectInformation; * @brief Structure which parses command line parameters and generates FProjectInformation */ USTRUCT() -struct FOPGenerateProjectParams +struct FAyonGenerateProjectParams { GENERATED_BODY() @@ -24,8 +24,8 @@ private: TArray Switches; public: - FOPGenerateProjectParams(); - FOPGenerateProjectParams(const FString& CommandLineParams); + FAyonGenerateProjectParams(); + FAyonGenerateProjectParams(const FString& CommandLineParams); FProjectInformation GenerateUEProjectInformation() const; @@ -37,7 +37,7 @@ private: }; UCLASS() -class OPENPYPE_API UOPGenerateProjectCommandlet : public UCommandlet +class AYON_API UAyonGenerateProjectCommandlet : public UCommandlet { GENERATED_BODY() @@ -46,15 +46,15 @@ private: FProjectDescriptor ProjectDescriptor; public: - UOPGenerateProjectCommandlet(); + UAyonGenerateProjectCommandlet(); virtual int32 Main(const FString& CommandLineParams) override; private: - FOPGenerateProjectParams ParseParameters(const FString& Params) const; - FOP_ActionResult TryCreateProject() const; - FOP_ActionResult TryLoadProjectDescriptor(); + FAyonGenerateProjectParams ParseParameters(const FString& Params) const; + FAyon_ActionResult TryCreateProject() const; + FAyon_ActionResult TryLoadProjectDescriptor(); void AttachPluginsToProjectDescriptor(); - FOP_ActionResult TrySave(); + FAyon_ActionResult TrySave(); }; diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h similarity index 95% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h index 3740c5285a..21571afd02 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h @@ -1,4 +1,4 @@ // Copyright 2023, Ayon, All rights reserved. #pragma once -DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All); \ No newline at end of file +DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All); diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h similarity index 94% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h rename to openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h index 8cfcd067c0..4a7a6a3a9f 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h +++ b/openpype/hosts/unreal/integration/UE_4.27/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h @@ -1,12 +1,13 @@ // Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. #pragma once -#include "Engine.h" #include "OpenPypePublishInstance.generated.h" UCLASS(Blueprintable) -class OPENPYPE_API UOpenPypePublishInstance : public UPrimaryDataAsset +class AYON_API UOpenPypePublishInstance : public UPrimaryDataAsset { GENERATED_UCLASS_BODY() @@ -48,7 +49,7 @@ public: /** * Function for returning all the assets in the container combined. - * + * * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are * returning raw pointers. Seems like an issue in UE5 * @@ -94,7 +95,7 @@ private: #ifdef WITH_EDITOR void ColorOpenPypeDirs(); - + void SendNotification(const FString& Text) const; virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; diff --git a/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27.bat b/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27.bat new file mode 100644 index 0000000000..96cdb96f8a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27.bat @@ -0,0 +1 @@ +D:\UE4\UE_4.27\Engine\Build\BatchFiles\RunUAT.bat BuildPlugin -plugin="D:\OpenPype\openpype\hosts\unreal\integration\UE_4.27\Ayon\Ayon.uplugin" -Package="D:\BuiltPlugins\4.27" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27_Window.bat b/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27_Window.bat new file mode 100644 index 0000000000..1343843a82 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.27/BuildPlugin_4-27_Window.bat @@ -0,0 +1 @@ +cmd /k "BuildPlugin_4-27.bat" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/.gitignore similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore rename to openpype/hosts/unreal/integration/UE_4.27/CommandletProject/.gitignore diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject b/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/CommandletProject.uproject similarity index 85% rename from openpype/hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject rename to openpype/hosts/unreal/integration/UE_4.27/CommandletProject/CommandletProject.uproject index 4d75e03bf3..ea7bf21dc4 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject +++ b/openpype/hosts/unreal/integration/UE_4.27/CommandletProject/CommandletProject.uproject @@ -5,7 +5,7 @@ "Description": "", "Plugins": [ { - "Name": "OpenPype", + "Name": "Ayon", "Enabled": true } ] diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/DefaultOpenPypeSettings.ini b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/DefaultOpenPypeSettings.ini deleted file mode 100644 index 8a883cf1db..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/DefaultOpenPypeSettings.ini +++ /dev/null @@ -1,2 +0,0 @@ -[/Script/OpenPype.OpenPypeSettings] -FolderColor=(R=91,G=197,B=220,A=255) \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Content/Python/init_unreal.py deleted file mode 100644 index b85f970699..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Content/Python/init_unreal.py +++ /dev/null @@ -1,30 +0,0 @@ -import unreal - -openpype_detected = True -try: - from openpype.pipeline import install_host - from openpype.hosts.unreal.api import UnrealHost - - openpype_host = UnrealHost() -except ImportError as exc: - openpype_host = None - openpype_detected = False - unreal.log_error("OpenPype: cannot load OpenPype [ {} ]".format(exc)) - -if openpype_detected: - install_host(openpype_host) - - -@unreal.uclass() -class OpenPypeIntegration(unreal.OpenPypePythonBridge): - @unreal.ufunction(override=True) - def RunInPython_Popup(self): - unreal.log_warning("OpenPype: showing tools popup") - if openpype_detected: - openpype_host.show_tools_popup() - - @unreal.ufunction(override=True) - def RunInPython_Dialog(self): - unreal.log_warning("OpenPype: showing tools dialog") - if openpype_detected: - openpype_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin deleted file mode 100644 index b2cbe3cff3..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin +++ /dev/null @@ -1,23 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "OpenPype", - "Description": "OpenPype Integration", - "Category": "OpenPype.Integration", - "CreatedBy": "Ondrej Samohel", - "CreatedByURL": "https://openpype.io", - "DocsURL": "https://openpype.io/docs/artist_hosts_unreal", - "MarketplaceURL": "", - "SupportURL": "https://pype.club/", - "EngineVersion": "4.27", - "CanContainContent": true, - "Installed": true, - "Modules": [ - { - "Name": "OpenPype", - "Type": "Editor", - "LoadingPhase": "Default" - } - ] -} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/README.md b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/README.md deleted file mode 100644 index a08c1ada39..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# OpenPype Unreal Integration plugin - UE 4.x - -This is plugin for Unreal Editor, creating menu for [OpenPype](https://github.com/getavalon) tools to run. - -## How does this work - -Plugin is creating basic menu items in **Window/OpenPype** section of Unreal Editor main menu and a button -on the main toolbar with associated menu. Clicking on those menu items is calling callbacks that are -declared in c++ but needs to be implemented during Unreal Editor -startup in `Plugins/OpenPype/Content/Python/init_unreal.py` - this should be executed by Unreal Editor -automatically. diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype128.png b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype128.png deleted file mode 100644 index abe8a807ef..0000000000 Binary files a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype128.png and /dev/null differ diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype40.png b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype40.png deleted file mode 100644 index f983e7a1f2..0000000000 Binary files a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype40.png and /dev/null differ diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype512.png b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype512.png deleted file mode 100644 index 97c4d4326b..0000000000 Binary files a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype512.png and /dev/null differ diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp deleted file mode 100644 index abb1975027..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "Commandlets/Implementations/OPGenerateProjectCommandlet.h" - -#include "Editor.h" -#include "GameProjectUtils.h" -#include "OPConstants.h" -#include "Commandlets/OPActionResult.h" -#include "ProjectDescriptor.h" - -int32 UOPGenerateProjectCommandlet::Main(const FString& CommandLineParams) -{ - //Parses command line parameters & creates structure FProjectInformation - const FOPGenerateProjectParams ParsedParams = FOPGenerateProjectParams(CommandLineParams); - ProjectInformation = ParsedParams.GenerateUEProjectInformation(); - - //Creates .uproject & other UE files - EVALUATE_OP_ACTION_RESULT(TryCreateProject()); - - //Loads created .uproject - EVALUATE_OP_ACTION_RESULT(TryLoadProjectDescriptor()); - - //Adds needed plugin to .uproject - AttachPluginsToProjectDescriptor(); - - //Saves .uproject - EVALUATE_OP_ACTION_RESULT(TrySave()); - - //When we are here, there should not be problems in generating Unreal Project for OpenPype - return 0; -} - - -FOPGenerateProjectParams::FOPGenerateProjectParams(): FOPGenerateProjectParams("") -{ -} - -FOPGenerateProjectParams::FOPGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( - CommandLineParams) -{ - UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); -} - -FProjectInformation FOPGenerateProjectParams::GenerateUEProjectInformation() const -{ - FProjectInformation ProjectInformation = FProjectInformation(); - ProjectInformation.ProjectFilename = GetProjectFileName(); - - ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); - - return ProjectInformation; -} - -FString FOPGenerateProjectParams::TryGetToken(const int32 Index) const -{ - return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; -} - -FString FOPGenerateProjectParams::GetProjectFileName() const -{ - return TryGetToken(0); -} - -bool FOPGenerateProjectParams::IsSwitchPresent(const FString& Switch) const -{ - return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool - { - return Item.Equals(Switch); - } - ); -} - - -UOPGenerateProjectCommandlet::UOPGenerateProjectCommandlet() -{ - LogToConsole = true; -} - -FOP_ActionResult UOPGenerateProjectCommandlet::TryCreateProject() const -{ - FText FailReason; - FText FailLog; - TArray OutCreatedFiles; - - if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) - return FOP_ActionResult(EOP_ActionResult::ProjectNotCreated, FailReason); - return FOP_ActionResult(); -} - -FOP_ActionResult UOPGenerateProjectCommandlet::TryLoadProjectDescriptor() -{ - FText FailReason; - const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); - - return FOP_ActionResult(bLoaded ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotLoaded, FailReason); -} - -void UOPGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() -{ - FPluginReferenceDescriptor OPPluginDescriptor; - OPPluginDescriptor.bEnabled = true; - OPPluginDescriptor.Name = OPConstants::OP_PluginName; - ProjectDescriptor.Plugins.Add(OPPluginDescriptor); - - FPluginReferenceDescriptor PythonPluginDescriptor; - PythonPluginDescriptor.bEnabled = true; - PythonPluginDescriptor.Name = OPConstants::PythonScript_PluginName; - ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); - - FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; - SequencerScriptingPluginDescriptor.bEnabled = true; - SequencerScriptingPluginDescriptor.Name = OPConstants::SequencerScripting_PluginName; - ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); - - FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; - MovieRenderPipelinePluginDescriptor.bEnabled = true; - MovieRenderPipelinePluginDescriptor.Name = OPConstants::MovieRenderPipeline_PluginName; - ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); - - FPluginReferenceDescriptor EditorScriptingPluginDescriptor; - EditorScriptingPluginDescriptor.bEnabled = true; - EditorScriptingPluginDescriptor.Name = OPConstants::EditorScriptingUtils_PluginName; - ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); -} - -FOP_ActionResult UOPGenerateProjectCommandlet::TrySave() -{ - FText FailReason; - const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); - - return FOP_ActionResult(bSaved ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotSaved, FailReason); -} - -FOPGenerateProjectParams UOPGenerateProjectCommandlet::ParseParameters(const FString& Params) const -{ - FOPGenerateProjectParams ParamsResult; - - TArray Tokens, Switches; - ParseCommandLine(*Params, Tokens, Switches); - - return ParamsResult; -} diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp deleted file mode 100644 index 6e50ef2221..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - - -#include "Commandlets/OPActionResult.h" -#include "Logging/OP_Log.h" - -EOP_ActionResult::Type& FOP_ActionResult::GetStatus() -{ - return Status; -} - -FText& FOP_ActionResult::GetReason() -{ - return Reason; -} - -FOP_ActionResult::FOP_ActionResult():Status(EOP_ActionResult::Type::Ok) -{ - -} - -FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum):Status(InEnum) -{ - TryLog(); -} - -FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) -{ - TryLog(); -}; - -bool FOP_ActionResult::IsProblem() const -{ - return Status != EOP_ActionResult::Ok; -} - -void FOP_ActionResult::TryLog() const -{ - if(IsProblem()) - UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString()); -} diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp deleted file mode 100644 index 29b1068c21..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "Logging/OP_Log.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp deleted file mode 100644 index a32ebe32cb..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "OpenPypePublishInstanceFactory.h" -#include "OpenPypePublishInstance.h" - -UOpenPypePublishInstanceFactory::UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UOpenPypePublishInstance::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UOpenPypePublishInstanceFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - check(InClass->IsChildOf(UOpenPypePublishInstance::StaticClass())); - return NewObject(InParent, InClass, InName, Flags); -} - -bool UOpenPypePublishInstanceFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp deleted file mode 100644 index 6ebfc528f0..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "OpenPypePythonBridge.h" - -UOpenPypePythonBridge* UOpenPypePythonBridge::Get() -{ - TArray OpenPypePythonBridgeClasses; - GetDerivedClasses(UOpenPypePythonBridge::StaticClass(), OpenPypePythonBridgeClasses); - int32 NumClasses = OpenPypePythonBridgeClasses.Num(); - if (NumClasses > 0) - { - return Cast(OpenPypePythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); - } - return nullptr; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp deleted file mode 100644 index dd4228dfd0..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "OpenPypeSettings.h" - -#include "Interfaces/IPluginManager.h" - -/** - * Mainly is used for initializing default values if the DefaultOpenPypeSettings.ini file does not exist in the saved config - */ -UOpenPypeSettings::UOpenPypeSettings(const FObjectInitializer& ObjectInitializer) -{ - - const FString ConfigFilePath = OPENPYPE_SETTINGS_FILEPATH; - - // This has to be probably in the future set using the UE Reflection system - FColor Color; - GConfig->GetColor(TEXT("/Script/OpenPype.OpenPypeSettings"), TEXT("FolderColor"), Color, ConfigFilePath); - - FolderColor = Color; -} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp deleted file mode 100644 index 0cc854c5ef..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "OpenPypeStyle.h" -#include "Framework/Application/SlateApplication.h" -#include "Styling/SlateStyle.h" -#include "Styling/SlateStyleRegistry.h" - - -TUniquePtr< FSlateStyleSet > FOpenPypeStyle::OpenPypeStyleInstance = nullptr; - -void FOpenPypeStyle::Initialize() -{ - if (!OpenPypeStyleInstance.IsValid()) - { - OpenPypeStyleInstance = Create(); - FSlateStyleRegistry::RegisterSlateStyle(*OpenPypeStyleInstance); - } -} - -void FOpenPypeStyle::Shutdown() -{ - if (OpenPypeStyleInstance.IsValid()) - { - FSlateStyleRegistry::UnRegisterSlateStyle(*OpenPypeStyleInstance); - OpenPypeStyleInstance.Reset(); - } -} - -FName FOpenPypeStyle::GetStyleSetName() -{ - static FName StyleSetName(TEXT("OpenPypeStyle")); - return StyleSetName; -} - -FName FOpenPypeStyle::GetContextName() -{ - static FName ContextName(TEXT("OpenPype")); - return ContextName; -} - -#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) - -const FVector2D Icon40x40(40.0f, 40.0f); - -TUniquePtr< FSlateStyleSet > FOpenPypeStyle::Create() -{ - TUniquePtr< FSlateStyleSet > Style = MakeUnique(GetStyleSetName()); - Style->SetContentRoot(FPaths::EnginePluginsDir() / TEXT("Marketplace/OpenPype/Resources")); - - return Style; -} - -void FOpenPypeStyle::SetIcon(const FString& StyleName, const FString& ResourcePath) -{ - FSlateStyleSet* Style = OpenPypeStyleInstance.Get(); - - FString Name(GetContextName().ToString()); - Name = Name + "." + StyleName; - Style->Set(*Name, new FSlateImageBrush(Style->RootToContentDir(ResourcePath, TEXT(".png")), Icon40x40)); - - - FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); -} - -#undef IMAGE_BRUSH - -const ISlateStyle& FOpenPypeStyle::Get() -{ - check(OpenPypeStyleInstance); - return *OpenPypeStyleInstance; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/.gitignore b/openpype/hosts/unreal/integration/UE_5.0/Ayon/.gitignore similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/.gitignore rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/.gitignore diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Ayon.uplugin similarity index 52% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Ayon.uplugin index ff08edc13e..70ed8f6b9a 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Ayon.uplugin @@ -2,23 +2,23 @@ "FileVersion": 3, "Version": 1, "VersionName": "1.0", - "FriendlyName": "OpenPype", - "Description": "OpenPype Integration", - "Category": "OpenPype.Integration", + "FriendlyName": "Ayon", + "Description": "Ayon Integration", + "Category": "Ayon.Integration", "CreatedBy": "Ondrej Samohel", - "CreatedByURL": "https://openpype.io", - "DocsURL": "https://openpype.io/docs/artist_hosts_unreal", + "CreatedByURL": "https://ayon.ynput.io", + "DocsURL": "https://ayon.ynput.io/docs/artist_hosts_unreal", "MarketplaceURL": "", - "SupportURL": "https://pype.club/", + "SupportURL": "https://ynput.io/", "CanContainContent": true, "EngineVersion": "5.0", "IsExperimentalVersion": false, "Installed": true, "Modules": [ { - "Name": "OpenPype", + "Name": "Ayon", "Type": "Editor", "LoadingPhase": "Default" } ] -} \ No newline at end of file +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/DefaultAyonSettings.ini b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/DefaultAyonSettings.ini new file mode 100644 index 0000000000..9ad7f55201 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/DefaultAyonSettings.ini @@ -0,0 +1,2 @@ +[/Script/Ayon.AyonSettings] +FolderColor=(R=91,G=197,B=220,A=255) \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/FilterPlugin.ini b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/FilterPlugin.ini similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/FilterPlugin.ini rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Config/FilterPlugin.ini diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Content/Python/init_unreal.py new file mode 100644 index 0000000000..c0b1d0ce5d --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Content/Python/init_unreal.py @@ -0,0 +1,30 @@ +import unreal + +ayon_detected = True +try: + from openpype.pipeline import install_host + from openpype.hosts.unreal.api import UnrealHost + + ayon_host = UnrealHost() +except ImportError as exc: + ayon_host = None + ayon_detected = False + unreal.log_error(f"Ayon: cannot load Ayon integration [ {exc} ]") + +if ayon_detected: + install_host(ayon_host) + + +@unreal.uclass() +class AyonIntegration(unreal.AyonPythonBridge): + @unreal.ufunction(override=True) + def RunInPython_Popup(self): + unreal.log_warning("Ayon: showing tools popup") + if ayon_detected: + ayon_host.show_tools_popup() + + @unreal.ufunction(override=True) + def RunInPython_Dialog(self): + unreal.log_warning("Ayon: showing tools dialog") + if ayon_detected: + ayon_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/README.md b/openpype/hosts/unreal/integration/UE_5.0/Ayon/README.md new file mode 100644 index 0000000000..865c8cafea --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/README.md @@ -0,0 +1,3 @@ +# Ayon Unreal Integration plugin - UE 5.0 + +This is plugin for Unreal Editor, creating menu for [Ayon](https://github.com/ynput/OpenPype) tools to run. diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon128.png b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon128.png new file mode 100644 index 0000000000..799d849aa3 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon128.png differ diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon40.png b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon40.png new file mode 100644 index 0000000000..f5bf40ea16 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon40.png differ diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon512.png b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon512.png new file mode 100644 index 0000000000..990d5917e2 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Resources/ayon512.png differ diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Ayon.Build.cs similarity index 93% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Ayon.Build.cs index e1087fd720..fad0d357dd 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Ayon.Build.cs @@ -2,9 +2,9 @@ using UnrealBuildTool; -public class OpenPype : ModuleRules +public class Ayon : ModuleRules { - public OpenPype(ReadOnlyTargetRules Target) : base(Target) + public Ayon(ReadOnlyTargetRules Target) : base(Target) { DefaultBuildSettings = BuildSettingsVersion.V2; bLegacyPublicIncludePaths = false; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Ayon.cpp similarity index 57% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Ayon.cpp index 65da29da35..5a1878ed1a 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Ayon.cpp @@ -1,58 +1,57 @@ // Copyright 2023, Ayon, All rights reserved. -#include "OpenPype.h" +#include "Ayon.h" #include "ISettingsContainer.h" #include "ISettingsModule.h" #include "ISettingsSection.h" -#include "OpenPypeStyle.h" -#include "OpenPypeCommands.h" -#include "OpenPypePythonBridge.h" -#include "OpenPypeSettings.h" -#include "Misc/MessageDialog.h" +#include "AyonStyle.h" +#include "AyonCommands.h" +#include "AyonPythonBridge.h" +#include "AyonSettings.h" #include "ToolMenus.h" -static const FName OpenPypeTabName("OpenPype"); +static const FName AyonTabName("Ayon"); -#define LOCTEXT_NAMESPACE "FOpenPypeModule" +#define LOCTEXT_NAMESPACE "FAyonModule" // This function is triggered when the plugin is staring up -void FOpenPypeModule::StartupModule() +void FAyonModule::StartupModule() { - FOpenPypeStyle::Initialize(); - FOpenPypeStyle::ReloadTextures(); - FOpenPypeCommands::Register(); + FAyonStyle::Initialize(); + FAyonStyle::ReloadTextures(); + FAyonCommands::Register(); PluginCommands = MakeShareable(new FUICommandList); PluginCommands->MapAction( - FOpenPypeCommands::Get().OpenPypeTools, - FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuPopup), + FAyonCommands::Get().AyonTools, + FExecuteAction::CreateRaw(this, &FAyonModule::MenuPopup), FCanExecuteAction()); PluginCommands->MapAction( - FOpenPypeCommands::Get().OpenPypeToolsDialog, - FExecuteAction::CreateRaw(this, &FOpenPypeModule::MenuDialog), + FAyonCommands::Get().AyonToolsDialog, + FExecuteAction::CreateRaw(this, &FAyonModule::MenuDialog), FCanExecuteAction()); UToolMenus::RegisterStartupCallback( - FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FOpenPypeModule::RegisterMenus)); + FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FAyonModule::RegisterMenus)); RegisterSettings(); } -void FOpenPypeModule::ShutdownModule() +void FAyonModule::ShutdownModule() { UToolMenus::UnRegisterStartupCallback(this); UToolMenus::UnregisterOwner(this); - FOpenPypeStyle::Shutdown(); + FAyonStyle::Shutdown(); - FOpenPypeCommands::Unregister(); + FAyonCommands::Unregister(); } -void FOpenPypeModule::RegisterSettings() +void FAyonModule::RegisterSettings() { ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked("Settings"); @@ -60,10 +59,10 @@ void FOpenPypeModule::RegisterSettings() // TODO: After the movement of the plugin from the game to editor, it might be necessary to move this! ISettingsContainerPtr SettingsContainer = SettingsModule.GetContainer("Project"); - UOpenPypeSettings* Settings = GetMutableDefault(); + UAyonSettings* Settings = GetMutableDefault(); // Register the settings - ISettingsSectionPtr SettingsSection = SettingsModule.RegisterSettings("Project", "OpenPype", "General", + ISettingsSectionPtr SettingsSection = SettingsModule.RegisterSettings("Project", "Ayon", "General", LOCTEXT("RuntimeGeneralSettingsName", "General"), LOCTEXT("RuntimeGeneralSettingsDescription", @@ -75,13 +74,13 @@ void FOpenPypeModule::RegisterSettings() // validate those or just act to settings changes. if (SettingsSection.IsValid()) { - SettingsSection->OnModified().BindRaw(this, &FOpenPypeModule::HandleSettingsSaved); + SettingsSection->OnModified().BindRaw(this, &FAyonModule::HandleSettingsSaved); } } -bool FOpenPypeModule::HandleSettingsSaved() +bool FAyonModule::HandleSettingsSaved() { - UOpenPypeSettings* Settings = GetMutableDefault(); + UAyonSettings* Settings = GetMutableDefault(); bool ResaveSettings = false; // You can put any validation code in here and resave the settings in case an invalid @@ -95,7 +94,7 @@ bool FOpenPypeModule::HandleSettingsSaved() return true; } -void FOpenPypeModule::RegisterMenus() +void FAyonModule::RegisterMenus() { // Owner will be used for cleanup in call to UToolMenus::UnregisterOwner FToolMenuOwnerScoped OwnerScoped(this); @@ -103,21 +102,21 @@ void FOpenPypeModule::RegisterMenus() { UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools"); { - // FToolMenuSection& Section = Menu->FindOrAddSection("OpenPype"); + // FToolMenuSection& Section = Menu->FindOrAddSection("Ayon"); FToolMenuSection& Section = Menu->AddSection( - "OpenPype", - TAttribute(FText::FromString("OpenPype")), + "Ayon", + TAttribute(FText::FromString("Ayon")), FToolMenuInsert("Programming", EToolMenuInsertType::Before) ); - Section.AddMenuEntryWithCommandList(FOpenPypeCommands::Get().OpenPypeTools, PluginCommands); - Section.AddMenuEntryWithCommandList(FOpenPypeCommands::Get().OpenPypeToolsDialog, PluginCommands); + Section.AddMenuEntryWithCommandList(FAyonCommands::Get().AyonTools, PluginCommands); + Section.AddMenuEntryWithCommandList(FAyonCommands::Get().AyonToolsDialog, PluginCommands); } UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar"); { FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools"); { FToolMenuEntry& Entry = Section.AddEntry( - FToolMenuEntry::InitToolBarButton(FOpenPypeCommands::Get().OpenPypeTools)); + FToolMenuEntry::InitToolBarButton(FAyonCommands::Get().AyonTools)); Entry.SetCommandList(PluginCommands); } } @@ -125,16 +124,16 @@ void FOpenPypeModule::RegisterMenus() } -void FOpenPypeModule::MenuPopup() +void FAyonModule::MenuPopup() { - UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get(); + UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); bridge->RunInPython_Popup(); } -void FOpenPypeModule::MenuDialog() +void FAyonModule::MenuDialog() { - UOpenPypePythonBridge* bridge = UOpenPypePythonBridge::Get(); + UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); bridge->RunInPython_Dialog(); } -IMPLEMENT_MODULE(FOpenPypeModule, OpenPype) +IMPLEMENT_MODULE(FAyonModule, Ayon) diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainer.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp similarity index 76% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainer.cpp rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp index 06dcd67808..869aa45256 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainer.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp @@ -1,31 +1,30 @@ // Fill out your copyright notice in the Description page of Project Settings. -#include "AssetContainer.h" +#include "AyonAssetContainer.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Misc/PackageName.h" -#include "Engine.h" #include "Containers/UnrealString.h" -UAssetContainer::UAssetContainer(const FObjectInitializer& ObjectInitializer) +UAyonAssetContainer::UAyonAssetContainer(const FObjectInitializer& ObjectInitializer) : UAssetUserData(ObjectInitializer) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - FString path = UAssetContainer::GetPathName(); - UE_LOG(LogTemp, Warning, TEXT("UAssetContainer %s"), *path); + FString path = UAyonAssetContainer::GetPathName(); + UE_LOG(LogTemp, Warning, TEXT("UAyonAssetContainer %s"), *path); FARFilter Filter; Filter.PackagePaths.Add(FName(*path)); - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAssetContainer::OnAssetAdded); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAssetContainer::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAssetContainer::OnAssetRenamed); + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonAssetContainer::OnAssetAdded); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonAssetContainer::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAyonAssetContainer::OnAssetRenamed); } -void UAssetContainer::OnAssetAdded(const FAssetData& AssetData) +void UAyonAssetContainer::OnAssetAdded(const FAssetData& AssetData) { TArray split; // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); + FString selfFullPath = UAyonAssetContainer::GetPathName(); FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); // get asset path and class @@ -50,12 +49,12 @@ void UAssetContainer::OnAssetAdded(const FAssetData& AssetData) } } -void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData) +void UAyonAssetContainer::OnAssetRemoved(const FAssetData& AssetData) { TArray split; // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); + FString selfFullPath = UAyonAssetContainer::GetPathName(); FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); // get asset path and class @@ -68,7 +67,7 @@ void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData) FString assetDir = FPackageName::GetLongPackagePath(*split[1]); // take interest only in paths starting with path of current container - FString path = UAssetContainer::GetPathName(); + FString path = UAyonAssetContainer::GetPathName(); FString lpp = FPackageName::GetLongPackagePath(*path); if (assetDir.StartsWith(*selfDir)) @@ -83,12 +82,12 @@ void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData) } } -void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) +void UAyonAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) { TArray split; // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); + FString selfFullPath = UAyonAssetContainer::GetPathName(); FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); // get asset path and class diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp new file mode 100644 index 0000000000..086fc1036e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp @@ -0,0 +1,20 @@ +#include "AyonAssetContainerFactory.h" +#include "AyonAssetContainer.h" + +UAyonAssetContainerFactory::UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UAyonAssetContainer::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UAyonAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + UAyonAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); + return AssetContainer; +} + +bool UAyonAssetContainerFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonCommands.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonCommands.cpp new file mode 100644 index 0000000000..566ee1dcd1 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonCommands.cpp @@ -0,0 +1,13 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "AyonCommands.h" + +#define LOCTEXT_NAMESPACE "FAyonModule" + +void FAyonCommands::RegisterCommands() +{ + UI_COMMAND(AyonTools, "Ayon Tools", "Pipeline tools", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(AyonToolsDialog, "Ayon Tools Dialog", "Pipeline tools dialog", EUserInterfaceActionType::Button, FInputChord()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonLib.cpp similarity index 79% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonLib.cpp index 34faba1f49..7cfa0c9c30 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonLib.cpp @@ -1,9 +1,7 @@ // Copyright 2023, Ayon, All rights reserved. -#include "OpenPypeLib.h" +#include "AyonLib.h" #include "AssetViewUtils.h" -#include "Misc/Paths.h" -#include "Misc/ConfigCacheIni.h" #include "UObject/UnrealType.h" /** @@ -13,7 +11,7 @@ * @warning This color will appear only after Editor restart. Is there a better way? */ -bool UOpenPypeLib::SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor, const bool& bForceAdd) +bool UAyonLib::SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor, const bool& bForceAdd) { if (AssetViewUtils::DoesFolderExist(FolderPath)) { @@ -31,11 +29,11 @@ bool UOpenPypeLib::SetFolderColor(const FString& FolderPath, const FLinearColor& } /** - * Returns all properties on given object + * Returns all poperties on given object * @param cls - class * @return TArray of properties */ -TArray UOpenPypeLib::GetAllProperties(UClass* cls) +TArray UAyonLib::GetAllProperties(UClass* cls) { TArray Ret; if (cls != nullptr) diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp new file mode 100644 index 0000000000..8d34090a15 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp @@ -0,0 +1,204 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "AyonPublishInstance.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "Framework/Notifications/NotificationManager.h" +#include "AyonLib.h" +#include "AyonSettings.h" +#include "Widgets/Notifications/SNotificationList.h" + + +//Moves all the invalid pointers to the end to prepare them for the shrinking +#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ + VAR.Shrink(); + +UAyonPublishInstance::UAyonPublishInstance(const FObjectInitializer& ObjectInitializer) + : UPrimaryDataAsset(ObjectInitializer) +{ + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< + FAssetRegistryModule>("AssetRegistry"); + + const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( + "PropertyEditor"); + + FString Left, Right; + GetPathName().Split("/" + GetName(), &Left, &Right); + + FARFilter Filter; + Filter.PackagePaths.Emplace(FName(Left)); + + TArray FoundAssets; + AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); + + for (const FAssetData& AssetData : FoundAssets) + OnAssetCreated(AssetData); + + REMOVE_INVALID_ENTRIES(AssetDataInternal) + REMOVE_INVALID_ENTRIES(AssetDataExternal) + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonPublishInstance::OnAssetCreated); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonPublishInstance::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UAyonPublishInstance::OnAssetUpdated); + +#ifdef WITH_EDITOR + ColorAyonDirs(); +#endif +} + +void UAyonPublishInstance::OnAssetCreated(const FAssetData& InAssetData) +{ + TArray split; + + UObject* Asset = InAssetData.GetAsset(); + + if (!IsValid(Asset)) + { + UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), + *InAssetData.ObjectPath.ToString()); + return; + } + + const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; + + if (result) + { + if (AssetDataInternal.Emplace(Asset).IsValidId()) + { + UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), + *this->GetName(), *Asset->GetName()); + } + } +} + +void UAyonPublishInstance::OnAssetRemoved(const FAssetData& InAssetData) +{ + if (Cast(InAssetData.GetAsset()) == nullptr) + { + if (AssetDataInternal.Contains(nullptr)) + { + AssetDataInternal.Remove(nullptr); + REMOVE_INVALID_ENTRIES(AssetDataInternal) + } + else + { + AssetDataExternal.Remove(nullptr); + REMOVE_INVALID_ENTRIES(AssetDataExternal) + } + } +} + +void UAyonPublishInstance::OnAssetUpdated(const FAssetData& InAssetData) +{ + REMOVE_INVALID_ENTRIES(AssetDataInternal); + REMOVE_INVALID_ENTRIES(AssetDataExternal); +} + +bool UAyonPublishInstance::IsUnderSameDir(const UObject* InAsset) const +{ + FString ThisLeft, ThisRight; + this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); + + return InAsset->GetPathName().StartsWith(ThisLeft); +} + +#ifdef WITH_EDITOR + +void UAyonPublishInstance::ColorAyonDirs() +{ + FString PathName = this->GetPathName(); + + //Check whether the path contains the defined Ayon folder + if (!PathName.Contains(TEXT("Ayon"))) return; + + //Get the base path for open pype + FString PathLeft, PathRight; + PathName.Split(FString("Ayon"), &PathLeft, &PathRight); + + if (PathLeft.IsEmpty() || PathRight.IsEmpty()) + { + UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base Ayon directory!")) + return; + } + + PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); + + //Get the current settings + const UAyonSettings* Settings = GetMutableDefault(); + + //Color the base folder + UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); + + //Get Sub paths, iterate through them and color them according to the folder color in UAyonSettings + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( + "AssetRegistry"); + + TArray PathList; + + AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); + + if (PathList.Num() > 0) + { + for (const FString& Path : PathList) + { + UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); + } + } +} + +void UAyonPublishInstance::SendNotification(const FString& Text) const +{ + FNotificationInfo Info{FText::FromString(Text)}; + + Info.bFireAndForget = true; + Info.bUseLargeFont = false; + Info.bUseThrobber = false; + Info.bUseSuccessFailIcons = false; + Info.ExpireDuration = 4.f; + Info.FadeOutDuration = 2.f; + + FSlateNotificationManager::Get().AddNotification(Info); + + UE_LOG(LogAssetData, Warning, + TEXT( + "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" + ), *GetName() + ) +} + + +void UAyonPublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && + PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( + UAyonPublishInstance, AssetDataExternal)) + { + // Check for duplicated assets + for (const auto& Asset : AssetDataInternal) + { + if (AssetDataExternal.Contains(Asset)) + { + AssetDataExternal.Remove(Asset); + return SendNotification( + "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); + } + } + + // Check if no UAyonPublishInstance type assets are included + for (const auto& Asset : AssetDataExternal) + { + if (Cast(Asset.Get()) != nullptr) + { + AssetDataExternal.Remove(Asset); + return SendNotification("You are not allowed to add publish instances!"); + } + } + } +} + +#endif diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp new file mode 100644 index 0000000000..f79c428a6d --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp @@ -0,0 +1,23 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#include "AyonPublishInstanceFactory.h" +#include "AyonPublishInstance.h" + +UAyonPublishInstanceFactory::UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UAyonPublishInstance::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UAyonPublishInstanceFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + check(InClass->IsChildOf(UAyonPublishInstance::StaticClass())); + return NewObject(InParent, InClass, InName, Flags); +} + +bool UAyonPublishInstanceFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp new file mode 100644 index 0000000000..0ed4b2f704 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp @@ -0,0 +1,14 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "AyonPythonBridge.h" + +UAyonPythonBridge* UAyonPythonBridge::Get() +{ + TArray AyonPythonBridgeClasses; + GetDerivedClasses(UAyonPythonBridge::StaticClass(), AyonPythonBridgeClasses); + int32 NumClasses = AyonPythonBridgeClasses.Num(); + if (NumClasses > 0) + { + return Cast(AyonPythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); + } + return nullptr; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonSettings.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonSettings.cpp new file mode 100644 index 0000000000..da388fbc8f --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonSettings.cpp @@ -0,0 +1,21 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "AyonSettings.h" + +#include "Interfaces/IPluginManager.h" +#include "UObject/UObjectGlobals.h" + +/** + * Mainly is used for initializing default values if the DefaultAyonSettings.ini file does not exist in the saved config + */ +UAyonSettings::UAyonSettings(const FObjectInitializer& ObjectInitializer) +{ + + const FString ConfigFilePath = AYON_SETTINGS_FILEPATH; + + // This has to be probably in the future set using the UE Reflection system + FColor Color; + GConfig->GetColor(TEXT("/Script/Ayon.AyonSettings"), TEXT("FolderColor"), Color, ConfigFilePath); + + FolderColor = Color; +} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonStyle.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonStyle.cpp new file mode 100644 index 0000000000..d88df78735 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/AyonStyle.cpp @@ -0,0 +1,62 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "AyonStyle.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyleRegistry.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" +#include "Styling/SlateStyleMacros.h" + +#define RootToContentDir Style->RootToContentDir + +TSharedPtr FAyonStyle::AyonStyleInstance = nullptr; + +void FAyonStyle::Initialize() +{ + if (!AyonStyleInstance.IsValid()) + { + AyonStyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*AyonStyleInstance); + } +} + +void FAyonStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*AyonStyleInstance); + ensure(AyonStyleInstance.IsUnique()); + AyonStyleInstance.Reset(); +} + +FName FAyonStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("AyonStyle")); + return StyleSetName; +} + +const FVector2D Icon16x16(16.0f, 16.0f); +const FVector2D Icon20x20(20.0f, 20.0f); +const FVector2D Icon40x40(40.0f, 40.0f); + +TSharedRef< FSlateStyleSet > FAyonStyle::Create() +{ + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("AyonStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Resources")); + + Style->Set("Ayon.AyonTools", new IMAGE_BRUSH(TEXT("ayon40"), Icon40x40)); + Style->Set("Ayon.AyonToolsDialog", new IMAGE_BRUSH(TEXT("ayon40"), Icon40x40)); + + return Style; +} + +void FAyonStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FAyonStyle::Get() +{ + return *AyonStyleInstance; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp new file mode 100644 index 0000000000..2a137e3ed7 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp @@ -0,0 +1,40 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "Commandlets/AyonActionResult.h" +#include "Logging/Ayon_Log.h" + +EAyon_ActionResult::Type& FAyon_ActionResult::GetStatus() +{ + return Status; +} + +FText& FAyon_ActionResult::GetReason() +{ + return Reason; +} + +FAyon_ActionResult::FAyon_ActionResult():Status(EAyon_ActionResult::Type::Ok) +{ + +} + +FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum):Status(InEnum) +{ + TryLog(); +} + +FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) +{ + TryLog(); +}; + +bool FAyon_ActionResult::IsProblem() const +{ + return Status != EAyon_ActionResult::Ok; +} + +void FAyon_ActionResult::TryLog() const +{ + if(IsProblem()) + UE_LOG(LogCommandletAyonGenerateProject, Error, TEXT("%s"), *Reason.ToString()); +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp new file mode 100644 index 0000000000..ed876c8128 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp @@ -0,0 +1,140 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "Commandlets/Implementations/AyonGenerateProjectCommandlet.h" + +#include "GameProjectUtils.h" +#include "AyonConstants.h" +#include "Commandlets/AyonActionResult.h" +#include "ProjectDescriptor.h" + +int32 UAyonGenerateProjectCommandlet::Main(const FString& CommandLineParams) +{ + //Parses command line parameters & creates structure FProjectInformation + const FAyonGenerateProjectParams ParsedParams = FAyonGenerateProjectParams(CommandLineParams); + ProjectInformation = ParsedParams.GenerateUEProjectInformation(); + + //Creates .uproject & other UE files + EVALUATE_Ayon_ACTION_RESULT(TryCreateProject()); + + //Loads created .uproject + EVALUATE_Ayon_ACTION_RESULT(TryLoadProjectDescriptor()); + + //Adds needed plugin to .uproject + AttachPluginsToProjectDescriptor(); + + //Saves .uproject + EVALUATE_Ayon_ACTION_RESULT(TrySave()); + + //When we are here, there should not be problems in generating Unreal Project for Ayon + return 0; +} + + +FAyonGenerateProjectParams::FAyonGenerateProjectParams(): FAyonGenerateProjectParams("") +{ +} + +FAyonGenerateProjectParams::FAyonGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( + CommandLineParams) +{ + UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); +} + +FProjectInformation FAyonGenerateProjectParams::GenerateUEProjectInformation() const +{ + FProjectInformation ProjectInformation = FProjectInformation(); + ProjectInformation.ProjectFilename = GetProjectFileName(); + + ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); + + return ProjectInformation; +} + +FString FAyonGenerateProjectParams::TryGetToken(const int32 Index) const +{ + return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; +} + +FString FAyonGenerateProjectParams::GetProjectFileName() const +{ + return TryGetToken(0); +} + +bool FAyonGenerateProjectParams::IsSwitchPresent(const FString& Switch) const +{ + return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool + { + return Item.Equals(Switch); + } + ); +} + + +UAyonGenerateProjectCommandlet::UAyonGenerateProjectCommandlet() +{ + LogToConsole = true; +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TryCreateProject() const +{ + FText FailReason; + FText FailLog; + TArray OutCreatedFiles; + + if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) + return FAyon_ActionResult(EAyon_ActionResult::ProjectNotCreated, FailReason); + return FAyon_ActionResult(); +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TryLoadProjectDescriptor() +{ + FText FailReason; + const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); + + return FAyon_ActionResult(bLoaded ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotLoaded, FailReason); +} + +void UAyonGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() +{ + FPluginReferenceDescriptor AyonPluginDescriptor; + AyonPluginDescriptor.bEnabled = true; + AyonPluginDescriptor.Name = AyonConstants::Ayon_PluginName; + ProjectDescriptor.Plugins.Add(AyonPluginDescriptor); + + FPluginReferenceDescriptor PythonPluginDescriptor; + PythonPluginDescriptor.bEnabled = true; + PythonPluginDescriptor.Name = AyonConstants::PythonScript_PluginName; + ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); + + FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; + SequencerScriptingPluginDescriptor.bEnabled = true; + SequencerScriptingPluginDescriptor.Name = AyonConstants::SequencerScripting_PluginName; + ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); + + FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; + MovieRenderPipelinePluginDescriptor.bEnabled = true; + MovieRenderPipelinePluginDescriptor.Name = AyonConstants::MovieRenderPipeline_PluginName; + ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); + + FPluginReferenceDescriptor EditorScriptingPluginDescriptor; + EditorScriptingPluginDescriptor.bEnabled = true; + EditorScriptingPluginDescriptor.Name = AyonConstants::EditorScriptingUtils_PluginName; + ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TrySave() +{ + FText FailReason; + const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); + + return FAyon_ActionResult(bSaved ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotSaved, FailReason); +} + +FAyonGenerateProjectParams UAyonGenerateProjectCommandlet::ParseParameters(const FString& Params) const +{ + FAyonGenerateProjectParams ParamsResult; + + TArray Tokens, Switches; + ParseCommandLine(*Params, Tokens, Switches); + + return ParamsResult; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp similarity index 93% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp index 05d5c8a87d..7a65fd0c98 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp @@ -1,12 +1,14 @@ // Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. #pragma once #include "OpenPypePublishInstance.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetToolsModule.h" #include "Framework/Notifications/NotificationManager.h" -#include "OpenPypeLib.h" -#include "OpenPypeSettings.h" +#include "AyonLib.h" +#include "AyonSettings.h" #include "Widgets/Notifications/SNotificationList.h" @@ -125,10 +127,10 @@ void UOpenPypePublishInstance::ColorOpenPypeDirs() PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); //Get the current settings - const UOpenPypeSettings* Settings = GetMutableDefault(); + const UAyonSettings* Settings = GetMutableDefault(); //Color the base folder - UOpenPypeLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); + UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); //Get Sub paths, iterate through them and color them according to the folder color in UOpenPypeSettings const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( @@ -142,7 +144,7 @@ void UOpenPypePublishInstance::ColorOpenPypeDirs() { for (const FString& Path : PathList) { - UOpenPypeLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); + UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); } } } diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Ayon.h similarity index 81% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Ayon.h index b89760099b..bb25430411 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Ayon.h @@ -3,10 +3,9 @@ #pragma once #include "CoreMinimal.h" -#include "Modules/ModuleManager.h" -class FOpenPypeModule : public IModuleInterface +class FAyonModule : public IModuleInterface { public: virtual void StartupModule() override; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainer.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainer.h similarity index 80% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainer.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainer.h index 9157569c08..d40642b149 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainer.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainer.h @@ -6,20 +6,17 @@ #include "UObject/NoExportTypes.h" #include "Engine/AssetUserData.h" #include "AssetRegistry/AssetData.h" -#include "AssetContainer.generated.h" +#include "AyonAssetContainer.generated.h" -/** - * - */ UCLASS(Blueprintable) -class OPENPYPE_API UAssetContainer : public UAssetUserData +class AYON_API UAyonAssetContainer : public UAssetUserData { GENERATED_BODY() public: - UAssetContainer(const FObjectInitializer& ObjectInitalizer); - // ~UAssetContainer(); + UAyonAssetContainer(const FObjectInitializer& ObjectInitalizer); + // ~UAyonAssetContainer(); UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Assets") TArray assets; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h similarity index 68% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainerFactory.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h index 9095f8a3d7..da424cde2e 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainerFactory.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h @@ -4,18 +4,15 @@ #include "CoreMinimal.h" #include "Factories/Factory.h" -#include "AssetContainerFactory.generated.h" +#include "AyonAssetContainerFactory.generated.h" -/** - * - */ UCLASS() -class OPENPYPE_API UAssetContainerFactory : public UFactory +class AYON_API UAyonAssetContainerFactory : public UFactory { GENERATED_BODY() public: - UAssetContainerFactory(const FObjectInitializer& ObjectInitializer); + UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer); virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; virtual bool ShouldShowInNewMenu() const override; }; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonCommands.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonCommands.h new file mode 100644 index 0000000000..9c40dc8241 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonCommands.h @@ -0,0 +1,24 @@ +// Copyright 2023, Ayon, All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "AyonStyle.h" + +class FAyonCommands : public TCommands +{ +public: + + FAyonCommands() + : TCommands(TEXT("Ayon"), NSLOCTEXT("Contexts", "Ayon", "Ayon Tools"), NAME_None, FAyonStyle::GetStyleSetName()) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + +public: + TSharedPtr< FUICommandInfo > AyonTools; + TSharedPtr< FUICommandInfo > AyonToolsDialog; +}; diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonConstants.h similarity index 83% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonConstants.h index f4587f7a50..5fe7c14360 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonConstants.h @@ -1,9 +1,9 @@ // Copyright 2023, Ayon, All rights reserved. #pragma once -namespace OPConstants +namespace AyonConstants { - const FString OP_PluginName = "OpenPype"; + const FString Ayon_PluginName = "Ayon"; const FString PythonScript_PluginName = "PythonScriptPlugin"; const FString SequencerScripting_PluginName = "SequencerScripting"; const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline"; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonLib.h similarity index 75% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonLib.h index ef4d1027ea..da83b448fb 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonLib.h @@ -1,12 +1,11 @@ // Copyright 2023, Ayon, All rights reserved. #pragma once -#include "Engine.h" -#include "OpenPypeLib.generated.h" +#include "AyonLib.generated.h" UCLASS(Blueprintable) -class OPENPYPE_API UOpenPypeLib : public UBlueprintFunctionLibrary +class AYON_API UAyonLib : public UBlueprintFunctionLibrary { GENERATED_BODY() diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstance.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstance.h new file mode 100644 index 0000000000..c89388036f --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstance.h @@ -0,0 +1,104 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "AyonPublishInstance.generated.h" + + +UCLASS(Blueprintable) +class AYON_API UAyonPublishInstance : public UPrimaryDataAsset +{ + GENERATED_UCLASS_BODY() + +public: + /** + /** + * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is + * placed in) + * + * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetInternalAssets() const + { + //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. + TSet ResultSet; + + for (const auto& Asset : AssetDataInternal) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + + /** + * Retrieves all the assets which have been added manually by the Publish Instance + * + * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetExternalAssets() const + { + //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. + TSet ResultSet; + + for (const auto& Asset : AssetDataExternal) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + + /** + * Function for returning all the assets in the container combined. + * + * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are + * returning raw pointers. Seems like an issue in UE5 + * + * @attention If the bAddExternalAssets variable is false, external assets won't be included! + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetAllAssets() const + { + const TSet>& IteratedSet = bAddExternalAssets + ? AssetDataInternal.Union(AssetDataExternal) + : AssetDataInternal; + + //Create a new TSet only with raw pointers. + TSet ResultSet; + + for (auto& Asset : IteratedSet) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + +private: + UPROPERTY(VisibleAnywhere, Category="Assets") + TSet> AssetDataInternal; + + /** + * This property allows exposing the array to include other assets from any other directory than what it's currently + * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! + */ + UPROPERTY(EditAnywhere, Category = "Assets") + bool bAddExternalAssets = false; + + UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") + TSet> AssetDataExternal; + + + void OnAssetCreated(const FAssetData& InAssetData); + void OnAssetRemoved(const FAssetData& InAssetData); + void OnAssetUpdated(const FAssetData& InAssetData); + + bool IsUnderSameDir(const UObject* InAsset) const; + +#ifdef WITH_EDITOR + + void ColorAyonDirs(); + + void SendNotification(const FString& Text) const; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + +#endif +}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h similarity index 54% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h index 3fdb984411..3cef8e76b2 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h @@ -1,20 +1,22 @@ // Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. #pragma once #include "CoreMinimal.h" #include "Factories/Factory.h" -#include "OpenPypePublishInstanceFactory.generated.h" +#include "AyonPublishInstanceFactory.generated.h" /** * */ UCLASS() -class OPENPYPE_API UOpenPypePublishInstanceFactory : public UFactory +class AYON_API UAyonPublishInstanceFactory : public UFactory { GENERATED_BODY() public: - UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer); + UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer); virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; virtual bool ShouldShowInNewMenu() const override; }; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPythonBridge.h similarity index 70% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPythonBridge.h index 827f76f56b..3c429fd7d3 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonPythonBridge.h @@ -1,16 +1,15 @@ // Copyright 2023, Ayon, All rights reserved. #pragma once -#include "Engine.h" -#include "OpenPypePythonBridge.generated.h" +#include "AyonPythonBridge.generated.h" UCLASS(Blueprintable) -class UOpenPypePythonBridge : public UObject +class UAyonPythonBridge : public UObject { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category = Python) - static UOpenPypePythonBridge* Get(); + static UAyonPythonBridge* Get(); UFUNCTION(BlueprintImplementableEvent, Category = Python) void RunInPython_Popup() const; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonSettings.h similarity index 59% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonSettings.h index b818fe0e95..4f12d1a5f2 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonSettings.h @@ -1,15 +1,15 @@ -// Copyright 2023, Ayon, All rights reserved. +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "CoreMinimal.h" #include "UObject/Object.h" -#include "OpenPypeSettings.generated.h" +#include "AyonSettings.generated.h" -#define OPENPYPE_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Config") / TEXT("DefaultOpenPypeSettings.ini") +#define AYON_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Config") / TEXT("DefaultAyonSettings.ini") -UCLASS(Config=OpenPypeSettings, DefaultConfig) -class OPENPYPE_API UOpenPypeSettings : public UObject +UCLASS(Config=AyonSettings, DefaultConfig) +class AYON_API UAyonSettings : public UObject { GENERATED_UCLASS_BODY() diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonStyle.h similarity index 79% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonStyle.h index 039abe96ef..58f6af656e 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/AyonStyle.h @@ -3,7 +3,7 @@ #include "CoreMinimal.h" #include "Styling/SlateStyle.h" -class FOpenPypeStyle +class FAyonStyle { public: static void Initialize(); @@ -15,5 +15,5 @@ public: private: static TSharedRef< class FSlateStyleSet > Create(); - static TSharedPtr< class FSlateStyleSet > OpenPypeStyleInstance; + static TSharedPtr< class FSlateStyleSet > AyonStyleInstance; }; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h similarity index 63% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h index 322a23a3e8..bb995ec452 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h @@ -3,23 +3,23 @@ #pragma once #include "CoreMinimal.h" -#include "OPActionResult.generated.h" +#include "AyonActionResult.generated.h" /** * @brief This macro returns error code when is problem or does nothing when there is no problem. - * @param ActionResult FOP_ActionResult structure + * @param ActionResult FAyon_ActionResult structure */ -#define EVALUATE_OP_ACTION_RESULT(ActionResult) \ +#define EVALUATE_Ayon_ACTION_RESULT(ActionResult) \ if(ActionResult.IsProblem()) \ return ActionResult.GetStatus(); /** * @brief This enum values are humanly readable mapping of error codes. * Here should be all error codes to be possible find what went wrong. -* TODO: In the future a web document should exists with the mapped error code & what problem occurred & how to repair it... +* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... */ UENUM() -namespace EOP_ActionResult +namespace EAyon_ActionResult { enum Type { @@ -27,11 +27,11 @@ namespace EOP_ActionResult ProjectNotCreated, ProjectNotLoaded, ProjectNotSaved, - //....Here insert another values + //....Here insert another values //Do not remove! //Usable for looping through enum values - __Last UMETA(Hidden) + __Last UMETA(Hidden) }; } @@ -40,44 +40,44 @@ namespace EOP_ActionResult * @brief This struct holds action result enum and optionally reason of fail */ USTRUCT() -struct FOP_ActionResult +struct FAyon_ActionResult { GENERATED_BODY() public: /** @brief Default constructor usable when there is no problem */ - FOP_ActionResult(); + FAyon_ActionResult(); /** * @brief This constructor initializes variables & attempts to log when is error * @param InEnum Status */ - FOP_ActionResult(const EOP_ActionResult::Type& InEnum); + FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum); /** * @brief This constructor initializes variables & attempts to log when is error * @param InEnum Status * @param InReason Reason of potential fail */ - FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason); + FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason); private: /** @brief Action status */ - EOP_ActionResult::Type Status; + EAyon_ActionResult::Type Status; /** @brief Optional reason of fail */ - FText Reason; + FText Reason; public: /** * @brief Checks if there is problematic state - * @return true when status is not equal to EOP_ActionResult::Ok + * @return true when status is not equal to EAyon_ActionResult::Ok */ bool IsProblem() const; - EOP_ActionResult::Type& GetStatus(); + EAyon_ActionResult::Type& GetStatus(); FText& GetReason(); -private: +private: void TryLog() const; }; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h similarity index 63% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h index 6a6c6406e7..da8e9af661 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h @@ -3,10 +3,10 @@ #include "GameProjectUtils.h" -#include "Commandlets/OPActionResult.h" +#include "Commandlets/AyonActionResult.h" #include "ProjectDescriptor.h" #include "Commandlets/Commandlet.h" -#include "OPGenerateProjectCommandlet.generated.h" +#include "AyonGenerateProjectCommandlet.generated.h" struct FProjectDescriptor; struct FProjectInformation; @@ -15,7 +15,7 @@ struct FProjectInformation; * @brief Structure which parses command line parameters and generates FProjectInformation */ USTRUCT() -struct FOPGenerateProjectParams +struct FAyonGenerateProjectParams { GENERATED_BODY() @@ -25,8 +25,8 @@ private: TArray Switches; public: - FOPGenerateProjectParams(); - FOPGenerateProjectParams(const FString& CommandLineParams); + FAyonGenerateProjectParams(); + FAyonGenerateProjectParams(const FString& CommandLineParams); FProjectInformation GenerateUEProjectInformation() const; @@ -38,7 +38,7 @@ private: }; UCLASS() -class OPENPYPE_API UOPGenerateProjectCommandlet : public UCommandlet +class AYON_API UAyonGenerateProjectCommandlet : public UCommandlet { GENERATED_BODY() @@ -47,15 +47,15 @@ private: FProjectDescriptor ProjectDescriptor; public: - UOPGenerateProjectCommandlet(); + UAyonGenerateProjectCommandlet(); virtual int32 Main(const FString& CommandLineParams) override; private: - FOPGenerateProjectParams ParseParameters(const FString& Params) const; - FOP_ActionResult TryCreateProject() const; - FOP_ActionResult TryLoadProjectDescriptor(); + FAyonGenerateProjectParams ParseParameters(const FString& Params) const; + FAyon_ActionResult TryCreateProject() const; + FAyon_ActionResult TryLoadProjectDescriptor(); void AttachPluginsToProjectDescriptor(); - FOP_ActionResult TrySave(); + FAyon_ActionResult TrySave(); }; diff --git a/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h new file mode 100644 index 0000000000..25b33a63e8 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h @@ -0,0 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. +#pragma once + +DEFINE_LOG_CATEGORY_STATIC(LogCommandletAyonGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h similarity index 94% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h rename to openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h index bce41ef1b1..9c0c4a69e5 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h +++ b/openpype/hosts/unreal/integration/UE_5.0/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h @@ -1,12 +1,13 @@ // Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. #pragma once -#include "Engine.h" #include "OpenPypePublishInstance.generated.h" UCLASS(Blueprintable) -class OPENPYPE_API UOpenPypePublishInstance : public UPrimaryDataAsset +class AYON_API UOpenPypePublishInstance : public UPrimaryDataAsset { GENERATED_UCLASS_BODY() @@ -49,7 +50,7 @@ public: /** * Function for returning all the assets in the container combined. - * + * * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are * returning raw pointers. Seems like an issue in UE5 * diff --git a/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0.bat b/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0.bat new file mode 100644 index 0000000000..473c248cbe --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0.bat @@ -0,0 +1 @@ +"C:\Program Files\Epic Games\UE_5.0\Engine\Build\BatchFiles\RunUAT.bat" BuildPlugin -plugin="D:\OpenPype\openpype\hosts\unreal\integration\UE_5.0\Ayon\Ayon.uplugin" -Package="D:\BuiltPlugins\5.0" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0_Window.bat b/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0_Window.bat new file mode 100644 index 0000000000..b96de6d6c9 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/BuildPlugin_5-0_Window.bat @@ -0,0 +1 @@ +cmd /k "BuildPlugin_5-0.bat" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject index c8dc1c673e..9cf75ebaf2 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject +++ b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject @@ -12,7 +12,7 @@ ] }, { - "Name": "OpenPype", + "Name": "Ayon", "Enabled": true, "Type": "Editor" } diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/DefaultOpenPypeSettings.ini b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/DefaultOpenPypeSettings.ini deleted file mode 100644 index 8a883cf1db..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/DefaultOpenPypeSettings.ini +++ /dev/null @@ -1,2 +0,0 @@ -[/Script/OpenPype.OpenPypeSettings] -FolderColor=(R=91,G=197,B=220,A=255) \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Content/Python/init_unreal.py deleted file mode 100644 index b85f970699..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Content/Python/init_unreal.py +++ /dev/null @@ -1,30 +0,0 @@ -import unreal - -openpype_detected = True -try: - from openpype.pipeline import install_host - from openpype.hosts.unreal.api import UnrealHost - - openpype_host = UnrealHost() -except ImportError as exc: - openpype_host = None - openpype_detected = False - unreal.log_error("OpenPype: cannot load OpenPype [ {} ]".format(exc)) - -if openpype_detected: - install_host(openpype_host) - - -@unreal.uclass() -class OpenPypeIntegration(unreal.OpenPypePythonBridge): - @unreal.ufunction(override=True) - def RunInPython_Popup(self): - unreal.log_warning("OpenPype: showing tools popup") - if openpype_detected: - openpype_host.show_tools_popup() - - @unreal.ufunction(override=True) - def RunInPython_Dialog(self): - unreal.log_warning("OpenPype: showing tools dialog") - if openpype_detected: - openpype_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/README.md b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/README.md deleted file mode 100644 index cf0aa622c2..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# OpenPype Unreal Integration plugin - UE 5.x - -This is plugin for Unreal Editor, creating menu for [OpenPype](https://github.com/getavalon) tools to run. - -## How does this work - -Plugin is creating basic menu items in **Window/OpenPype** section of Unreal Editor main menu and a button -on the main toolbar with associated menu. Clicking on those menu items is calling callbacks that are -declared in C++ but needs to be implemented during Unreal Editor -startup in `Plugins/OpenPype/Content/Python/init_unreal.py` - this should be executed by Unreal Editor -automatically. diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype128.png b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype128.png deleted file mode 100644 index abe8a807ef..0000000000 Binary files a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype128.png and /dev/null differ diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype40.png b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype40.png deleted file mode 100644 index f983e7a1f2..0000000000 Binary files a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype40.png and /dev/null differ diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype512.png b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype512.png deleted file mode 100644 index 97c4d4326b..0000000000 Binary files a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype512.png and /dev/null differ diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainerFactory.cpp deleted file mode 100644 index b943150bdd..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainerFactory.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "AssetContainerFactory.h" -#include "AssetContainer.h" - -UAssetContainerFactory::UAssetContainerFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAssetContainer::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - UAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); - return AssetContainer; -} - -bool UAssetContainerFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp deleted file mode 100644 index abb1975027..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "Commandlets/Implementations/OPGenerateProjectCommandlet.h" - -#include "Editor.h" -#include "GameProjectUtils.h" -#include "OPConstants.h" -#include "Commandlets/OPActionResult.h" -#include "ProjectDescriptor.h" - -int32 UOPGenerateProjectCommandlet::Main(const FString& CommandLineParams) -{ - //Parses command line parameters & creates structure FProjectInformation - const FOPGenerateProjectParams ParsedParams = FOPGenerateProjectParams(CommandLineParams); - ProjectInformation = ParsedParams.GenerateUEProjectInformation(); - - //Creates .uproject & other UE files - EVALUATE_OP_ACTION_RESULT(TryCreateProject()); - - //Loads created .uproject - EVALUATE_OP_ACTION_RESULT(TryLoadProjectDescriptor()); - - //Adds needed plugin to .uproject - AttachPluginsToProjectDescriptor(); - - //Saves .uproject - EVALUATE_OP_ACTION_RESULT(TrySave()); - - //When we are here, there should not be problems in generating Unreal Project for OpenPype - return 0; -} - - -FOPGenerateProjectParams::FOPGenerateProjectParams(): FOPGenerateProjectParams("") -{ -} - -FOPGenerateProjectParams::FOPGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( - CommandLineParams) -{ - UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); -} - -FProjectInformation FOPGenerateProjectParams::GenerateUEProjectInformation() const -{ - FProjectInformation ProjectInformation = FProjectInformation(); - ProjectInformation.ProjectFilename = GetProjectFileName(); - - ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); - - return ProjectInformation; -} - -FString FOPGenerateProjectParams::TryGetToken(const int32 Index) const -{ - return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; -} - -FString FOPGenerateProjectParams::GetProjectFileName() const -{ - return TryGetToken(0); -} - -bool FOPGenerateProjectParams::IsSwitchPresent(const FString& Switch) const -{ - return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool - { - return Item.Equals(Switch); - } - ); -} - - -UOPGenerateProjectCommandlet::UOPGenerateProjectCommandlet() -{ - LogToConsole = true; -} - -FOP_ActionResult UOPGenerateProjectCommandlet::TryCreateProject() const -{ - FText FailReason; - FText FailLog; - TArray OutCreatedFiles; - - if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) - return FOP_ActionResult(EOP_ActionResult::ProjectNotCreated, FailReason); - return FOP_ActionResult(); -} - -FOP_ActionResult UOPGenerateProjectCommandlet::TryLoadProjectDescriptor() -{ - FText FailReason; - const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); - - return FOP_ActionResult(bLoaded ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotLoaded, FailReason); -} - -void UOPGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() -{ - FPluginReferenceDescriptor OPPluginDescriptor; - OPPluginDescriptor.bEnabled = true; - OPPluginDescriptor.Name = OPConstants::OP_PluginName; - ProjectDescriptor.Plugins.Add(OPPluginDescriptor); - - FPluginReferenceDescriptor PythonPluginDescriptor; - PythonPluginDescriptor.bEnabled = true; - PythonPluginDescriptor.Name = OPConstants::PythonScript_PluginName; - ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); - - FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; - SequencerScriptingPluginDescriptor.bEnabled = true; - SequencerScriptingPluginDescriptor.Name = OPConstants::SequencerScripting_PluginName; - ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); - - FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; - MovieRenderPipelinePluginDescriptor.bEnabled = true; - MovieRenderPipelinePluginDescriptor.Name = OPConstants::MovieRenderPipeline_PluginName; - ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); - - FPluginReferenceDescriptor EditorScriptingPluginDescriptor; - EditorScriptingPluginDescriptor.bEnabled = true; - EditorScriptingPluginDescriptor.Name = OPConstants::EditorScriptingUtils_PluginName; - ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); -} - -FOP_ActionResult UOPGenerateProjectCommandlet::TrySave() -{ - FText FailReason; - const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); - - return FOP_ActionResult(bSaved ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotSaved, FailReason); -} - -FOPGenerateProjectParams UOPGenerateProjectCommandlet::ParseParameters(const FString& Params) const -{ - FOPGenerateProjectParams ParamsResult; - - TArray Tokens, Switches; - ParseCommandLine(*Params, Tokens, Switches); - - return ParamsResult; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp deleted file mode 100644 index 23ae2dd329..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "Commandlets/OPActionResult.h" -#include "Logging/OP_Log.h" - -EOP_ActionResult::Type& FOP_ActionResult::GetStatus() -{ - return Status; -} - -FText& FOP_ActionResult::GetReason() -{ - return Reason; -} - -FOP_ActionResult::FOP_ActionResult():Status(EOP_ActionResult::Type::Ok) -{ - -} - -FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum):Status(InEnum) -{ - TryLog(); -} - -FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) -{ - TryLog(); -}; - -bool FOP_ActionResult::IsProblem() const -{ - return Status != EOP_ActionResult::Ok; -} - -void FOP_ActionResult::TryLog() const -{ - if(IsProblem()) - UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString()); -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp deleted file mode 100644 index 198fb9df0c..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "Logging/OP_Log.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp deleted file mode 100644 index 881814e278..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "OpenPypeCommands.h" - -#define LOCTEXT_NAMESPACE "FOpenPypeModule" - -void FOpenPypeCommands::RegisterCommands() -{ - UI_COMMAND(OpenPypeTools, "OpenPype Tools", "Pipeline tools", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(OpenPypeToolsDialog, "OpenPype Tools Dialog", "Pipeline tools dialog", EUserInterfaceActionType::Button, FInputChord()); -} - -#undef LOCTEXT_NAMESPACE diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp deleted file mode 100644 index a32ebe32cb..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "OpenPypePublishInstanceFactory.h" -#include "OpenPypePublishInstance.h" - -UOpenPypePublishInstanceFactory::UOpenPypePublishInstanceFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UOpenPypePublishInstance::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UOpenPypePublishInstanceFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - check(InClass->IsChildOf(UOpenPypePublishInstance::StaticClass())); - return NewObject(InParent, InClass, InName, Flags); -} - -bool UOpenPypePublishInstanceFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp deleted file mode 100644 index 6ebfc528f0..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#include "OpenPypePythonBridge.h" - -UOpenPypePythonBridge* UOpenPypePythonBridge::Get() -{ - TArray OpenPypePythonBridgeClasses; - GetDerivedClasses(UOpenPypePythonBridge::StaticClass(), OpenPypePythonBridgeClasses); - int32 NumClasses = OpenPypePythonBridgeClasses.Num(); - if (NumClasses > 0) - { - return Cast(OpenPypePythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); - } - return nullptr; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp deleted file mode 100644 index 6562a81138..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "OpenPypeSettings.h" - -#include "Interfaces/IPluginManager.h" -#include "UObject/UObjectGlobals.h" - -/** - * Mainly is used for initializing default values if the DefaultOpenPypeSettings.ini file does not exist in the saved config - */ -UOpenPypeSettings::UOpenPypeSettings(const FObjectInitializer& ObjectInitializer) -{ - - const FString ConfigFilePath = OPENPYPE_SETTINGS_FILEPATH; - - // This has to be probably in the future set using the UE Reflection system - FColor Color; - GConfig->GetColor(TEXT("/Script/OpenPype.OpenPypeSettings"), TEXT("FolderColor"), Color, ConfigFilePath); - - FolderColor = Color; -} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp deleted file mode 100644 index a4d75e048e..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#include "OpenPypeStyle.h" -#include "OpenPype.h" -#include "Framework/Application/SlateApplication.h" -#include "Styling/SlateStyleRegistry.h" -#include "Slate/SlateGameResources.h" -#include "Interfaces/IPluginManager.h" -#include "Styling/SlateStyleMacros.h" - -#define RootToContentDir Style->RootToContentDir - -TSharedPtr FOpenPypeStyle::OpenPypeStyleInstance = nullptr; - -void FOpenPypeStyle::Initialize() -{ - if (!OpenPypeStyleInstance.IsValid()) - { - OpenPypeStyleInstance = Create(); - FSlateStyleRegistry::RegisterSlateStyle(*OpenPypeStyleInstance); - } -} - -void FOpenPypeStyle::Shutdown() -{ - FSlateStyleRegistry::UnRegisterSlateStyle(*OpenPypeStyleInstance); - ensure(OpenPypeStyleInstance.IsUnique()); - OpenPypeStyleInstance.Reset(); -} - -FName FOpenPypeStyle::GetStyleSetName() -{ - static FName StyleSetName(TEXT("OpenPypeStyle")); - return StyleSetName; -} - -const FVector2D Icon16x16(16.0f, 16.0f); -const FVector2D Icon20x20(20.0f, 20.0f); -const FVector2D Icon40x40(40.0f, 40.0f); - -TSharedRef< FSlateStyleSet > FOpenPypeStyle::Create() -{ - TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("OpenPypeStyle")); - Style->SetContentRoot(IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Resources")); - - Style->Set("OpenPype.OpenPypeTools", new IMAGE_BRUSH(TEXT("openpype40"), Icon40x40)); - Style->Set("OpenPype.OpenPypeToolsDialog", new IMAGE_BRUSH(TEXT("openpype40"), Icon40x40)); - - return Style; -} - -void FOpenPypeStyle::ReloadTextures() -{ - if (FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); - } -} - -const ISlateStyle& FOpenPypeStyle::Get() -{ - return *OpenPypeStyleInstance; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h deleted file mode 100644 index 3740c5285a..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. -#pragma once - -DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h deleted file mode 100644 index 99b0be26f0..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2023, Ayon, All rights reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Framework/Commands/Commands.h" -#include "OpenPypeStyle.h" - -class FOpenPypeCommands : public TCommands -{ -public: - - FOpenPypeCommands() - : TCommands(TEXT("OpenPype"), NSLOCTEXT("Contexts", "OpenPype", "OpenPype Tools"), NAME_None, FOpenPypeStyle::GetStyleSetName()) - { - } - - // TCommands<> interface - virtual void RegisterCommands() override; - -public: - TSharedPtr< FUICommandInfo > OpenPypeTools; - TSharedPtr< FUICommandInfo > OpenPypeToolsDialog; -}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/.gitignore b/openpype/hosts/unreal/integration/UE_5.1/Ayon/.gitignore new file mode 100644 index 0000000000..b32a6f55e5 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/.gitignore @@ -0,0 +1,35 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +/Binaries +/Intermediate diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Ayon.uplugin b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Ayon.uplugin new file mode 100644 index 0000000000..70ed8f6b9a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Ayon.uplugin @@ -0,0 +1,24 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Ayon", + "Description": "Ayon Integration", + "Category": "Ayon.Integration", + "CreatedBy": "Ondrej Samohel", + "CreatedByURL": "https://ayon.ynput.io", + "DocsURL": "https://ayon.ynput.io/docs/artist_hosts_unreal", + "MarketplaceURL": "", + "SupportURL": "https://ynput.io/", + "CanContainContent": true, + "EngineVersion": "5.0", + "IsExperimentalVersion": false, + "Installed": true, + "Modules": [ + { + "Name": "Ayon", + "Type": "Editor", + "LoadingPhase": "Default" + } + ] +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/DefaultAyonSettings.ini b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/DefaultAyonSettings.ini new file mode 100644 index 0000000000..9ad7f55201 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/DefaultAyonSettings.ini @@ -0,0 +1,2 @@ +[/Script/Ayon.AyonSettings] +FolderColor=(R=91,G=197,B=220,A=255) \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/FilterPlugin.ini b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/FilterPlugin.ini new file mode 100644 index 0000000000..ccebca2f32 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Content/Python/init_unreal.py new file mode 100644 index 0000000000..c0b1d0ce5d --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Content/Python/init_unreal.py @@ -0,0 +1,30 @@ +import unreal + +ayon_detected = True +try: + from openpype.pipeline import install_host + from openpype.hosts.unreal.api import UnrealHost + + ayon_host = UnrealHost() +except ImportError as exc: + ayon_host = None + ayon_detected = False + unreal.log_error(f"Ayon: cannot load Ayon integration [ {exc} ]") + +if ayon_detected: + install_host(ayon_host) + + +@unreal.uclass() +class AyonIntegration(unreal.AyonPythonBridge): + @unreal.ufunction(override=True) + def RunInPython_Popup(self): + unreal.log_warning("Ayon: showing tools popup") + if ayon_detected: + ayon_host.show_tools_popup() + + @unreal.ufunction(override=True) + def RunInPython_Dialog(self): + unreal.log_warning("Ayon: showing tools dialog") + if ayon_detected: + ayon_host.show_tools_dialog() diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/README.md b/openpype/hosts/unreal/integration/UE_5.1/Ayon/README.md new file mode 100644 index 0000000000..417d490548 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/README.md @@ -0,0 +1,3 @@ +# Ayon Unreal Integration plugin - UE 5.1 + +This is plugin for Unreal Editor, creating menu for [Ayon](https://github.com/ynput/OpenPype) tools to run. diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon128.png b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon128.png new file mode 100644 index 0000000000..799d849aa3 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon128.png differ diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon40.png b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon40.png new file mode 100644 index 0000000000..f5bf40ea16 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon40.png differ diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon512.png b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon512.png new file mode 100644 index 0000000000..990d5917e2 Binary files /dev/null and b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Resources/ayon512.png differ diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Ayon.Build.cs b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Ayon.Build.cs new file mode 100644 index 0000000000..fad0d357dd --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Ayon.Build.cs @@ -0,0 +1,65 @@ +// Copyright 2023, Ayon, All rights reserved. + +using UnrealBuildTool; + +public class Ayon : ModuleRules +{ + public Ayon(ReadOnlyTargetRules Target) : base(Target) + { + DefaultBuildSettings = BuildSettingsVersion.V2; + bLegacyPublicIncludePaths = false; + ShadowVariableWarningLevel = WarningLevel.Error; + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + //IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_0; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject" + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "GameProjectGeneration", + "Projects", + "InputCore", + "EditorFramework", + "UnrealEd", + "ToolMenus", + "LevelEditor", + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "AssetTools" + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Ayon.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Ayon.cpp new file mode 100644 index 0000000000..5a1878ed1a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Ayon.cpp @@ -0,0 +1,139 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "Ayon.h" + +#include "ISettingsContainer.h" +#include "ISettingsModule.h" +#include "ISettingsSection.h" +#include "AyonStyle.h" +#include "AyonCommands.h" +#include "AyonPythonBridge.h" +#include "AyonSettings.h" +#include "ToolMenus.h" + + +static const FName AyonTabName("Ayon"); + +#define LOCTEXT_NAMESPACE "FAyonModule" + +// This function is triggered when the plugin is staring up +void FAyonModule::StartupModule() +{ + FAyonStyle::Initialize(); + FAyonStyle::ReloadTextures(); + FAyonCommands::Register(); + + PluginCommands = MakeShareable(new FUICommandList); + + PluginCommands->MapAction( + FAyonCommands::Get().AyonTools, + FExecuteAction::CreateRaw(this, &FAyonModule::MenuPopup), + FCanExecuteAction()); + PluginCommands->MapAction( + FAyonCommands::Get().AyonToolsDialog, + FExecuteAction::CreateRaw(this, &FAyonModule::MenuDialog), + FCanExecuteAction()); + + UToolMenus::RegisterStartupCallback( + FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FAyonModule::RegisterMenus)); + + RegisterSettings(); +} + +void FAyonModule::ShutdownModule() +{ + UToolMenus::UnRegisterStartupCallback(this); + + UToolMenus::UnregisterOwner(this); + + FAyonStyle::Shutdown(); + + FAyonCommands::Unregister(); +} + + +void FAyonModule::RegisterSettings() +{ + ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked("Settings"); + + // Create the new category + // TODO: After the movement of the plugin from the game to editor, it might be necessary to move this! + ISettingsContainerPtr SettingsContainer = SettingsModule.GetContainer("Project"); + + UAyonSettings* Settings = GetMutableDefault(); + + // Register the settings + ISettingsSectionPtr SettingsSection = SettingsModule.RegisterSettings("Project", "Ayon", "General", + LOCTEXT("RuntimeGeneralSettingsName", + "General"), + LOCTEXT("RuntimeGeneralSettingsDescription", + "Base configuration for Open Pype Module"), + Settings + ); + + // Register the save handler to your settings, you might want to use it to + // validate those or just act to settings changes. + if (SettingsSection.IsValid()) + { + SettingsSection->OnModified().BindRaw(this, &FAyonModule::HandleSettingsSaved); + } +} + +bool FAyonModule::HandleSettingsSaved() +{ + UAyonSettings* Settings = GetMutableDefault(); + bool ResaveSettings = false; + + // You can put any validation code in here and resave the settings in case an invalid + // value has been entered + + if (ResaveSettings) + { + Settings->SaveConfig(); + } + + return true; +} + +void FAyonModule::RegisterMenus() +{ + // Owner will be used for cleanup in call to UToolMenus::UnregisterOwner + FToolMenuOwnerScoped OwnerScoped(this); + + { + UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Tools"); + { + // FToolMenuSection& Section = Menu->FindOrAddSection("Ayon"); + FToolMenuSection& Section = Menu->AddSection( + "Ayon", + TAttribute(FText::FromString("Ayon")), + FToolMenuInsert("Programming", EToolMenuInsertType::Before) + ); + Section.AddMenuEntryWithCommandList(FAyonCommands::Get().AyonTools, PluginCommands); + Section.AddMenuEntryWithCommandList(FAyonCommands::Get().AyonToolsDialog, PluginCommands); + } + UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar"); + { + FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("PluginTools"); + { + FToolMenuEntry& Entry = Section.AddEntry( + FToolMenuEntry::InitToolBarButton(FAyonCommands::Get().AyonTools)); + Entry.SetCommandList(PluginCommands); + } + } + } +} + + +void FAyonModule::MenuPopup() +{ + UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); + bridge->RunInPython_Popup(); +} + +void FAyonModule::MenuDialog() +{ + UAyonPythonBridge* bridge = UAyonPythonBridge::Get(); + bridge->RunInPython_Dialog(); +} + +IMPLEMENT_MODULE(FAyonModule, Ayon) diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp new file mode 100644 index 0000000000..3022757dc8 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainer.cpp @@ -0,0 +1,113 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "AyonAssetContainer.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "Misc/PackageName.h" +#include "Containers/UnrealString.h" + +UAyonAssetContainer::UAyonAssetContainer(const FObjectInitializer& ObjectInitializer) +: UAssetUserData(ObjectInitializer) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + FString path = UAyonAssetContainer::GetPathName(); + UE_LOG(LogTemp, Warning, TEXT("UAyonAssetContainer %s"), *path); + FARFilter Filter; + Filter.PackagePaths.Add(FName(*path)); + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonAssetContainer::OnAssetAdded); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonAssetContainer::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAyonAssetContainer::OnAssetRenamed); +} + +void UAyonAssetContainer::OnAssetAdded(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAyonAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.GetObjectPathString(); + UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName); + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + assets.Add(assetPath); + assetsData.Add(AssetData); + UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); + } + } +} + +void UAyonAssetContainer::OnAssetRemoved(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAyonAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.GetObjectPathString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + FString path = UAyonAssetContainer::GetPathName(); + FString lpp = FPackageName::GetLongPackagePath(*path); + + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); + assets.Remove(assetPath); + assetsData.Remove(AssetData); + } + } +} + +void UAyonAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAyonAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.GetObjectPathString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + + assets.Remove(str); + assets.Add(assetPath); + assetsData.Remove(AssetData); + // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); + } + } +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp new file mode 100644 index 0000000000..086fc1036e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonAssetContainerFactory.cpp @@ -0,0 +1,20 @@ +#include "AyonAssetContainerFactory.h" +#include "AyonAssetContainer.h" + +UAyonAssetContainerFactory::UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UAyonAssetContainer::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UAyonAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + UAyonAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); + return AssetContainer; +} + +bool UAyonAssetContainerFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonCommands.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonCommands.cpp new file mode 100644 index 0000000000..566ee1dcd1 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonCommands.cpp @@ -0,0 +1,13 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "AyonCommands.h" + +#define LOCTEXT_NAMESPACE "FAyonModule" + +void FAyonCommands::RegisterCommands() +{ + UI_COMMAND(AyonTools, "Ayon Tools", "Pipeline tools", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(AyonToolsDialog, "Ayon Tools Dialog", "Pipeline tools dialog", EUserInterfaceActionType::Button, FInputChord()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonLib.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonLib.cpp new file mode 100644 index 0000000000..7cfa0c9c30 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonLib.cpp @@ -0,0 +1,51 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "AyonLib.h" + +#include "AssetViewUtils.h" +#include "UObject/UnrealType.h" + +/** + * Sets color on folder icon on given path + * @param InPath - path to folder + * @param InFolderColor - color of the folder + * @warning This color will appear only after Editor restart. Is there a better way? + */ + +bool UAyonLib::SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor, const bool& bForceAdd) +{ + if (AssetViewUtils::DoesFolderExist(FolderPath)) + { + const TSharedPtr LinearColor = MakeShared(FolderColor); + + AssetViewUtils::SaveColor(FolderPath, LinearColor, true); + UE_LOG(LogAssetData, Display, TEXT("A color {%s} has been set to folder \"%s\""), *LinearColor->ToString(), + *FolderPath) + return true; + } + + UE_LOG(LogAssetData, Display, TEXT("Setting a color {%s} to folder \"%s\" has failed! Directory doesn't exist!"), + *FolderColor.ToString(), *FolderPath) + return false; +} + +/** + * Returns all poperties on given object + * @param cls - class + * @return TArray of properties + */ +TArray UAyonLib::GetAllProperties(UClass* cls) +{ + TArray Ret; + if (cls != nullptr) + { + for (TFieldIterator It(cls); It; ++It) + { + FProperty* Property = *It; + if (Property->HasAnyPropertyFlags(EPropertyFlags::CPF_Edit)) + { + Ret.Add(Property->GetName()); + } + } + } + return Ret; +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp new file mode 100644 index 0000000000..d1b47a19d4 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstance.cpp @@ -0,0 +1,204 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "AyonPublishInstance.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "Framework/Notifications/NotificationManager.h" +#include "AyonLib.h" +#include "AyonSettings.h" +#include "Widgets/Notifications/SNotificationList.h" + + +//Moves all the invalid pointers to the end to prepare them for the shrinking +#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ + VAR.Shrink(); + +UAyonPublishInstance::UAyonPublishInstance(const FObjectInitializer& ObjectInitializer) + : UPrimaryDataAsset(ObjectInitializer) +{ + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< + FAssetRegistryModule>("AssetRegistry"); + + const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( + "PropertyEditor"); + + FString Left, Right; + GetPathName().Split("/" + GetName(), &Left, &Right); + + FARFilter Filter; + Filter.PackagePaths.Emplace(FName(Left)); + + TArray FoundAssets; + AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); + + for (const FAssetData& AssetData : FoundAssets) + OnAssetCreated(AssetData); + + REMOVE_INVALID_ENTRIES(AssetDataInternal) + REMOVE_INVALID_ENTRIES(AssetDataExternal) + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAyonPublishInstance::OnAssetCreated); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAyonPublishInstance::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UAyonPublishInstance::OnAssetUpdated); + +#ifdef WITH_EDITOR + ColorAyonDirs(); +#endif +} + +void UAyonPublishInstance::OnAssetCreated(const FAssetData& InAssetData) +{ + TArray split; + + UObject* Asset = InAssetData.GetAsset(); + + if (!IsValid(Asset)) + { + UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), + *InAssetData.GetSoftObjectPath().ToString()); + return; + } + + const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; + + if (result) + { + if (AssetDataInternal.Emplace(Asset).IsValidId()) + { + UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), + *this->GetName(), *Asset->GetName()); + } + } +} + +void UAyonPublishInstance::OnAssetRemoved(const FAssetData& InAssetData) +{ + if (Cast(InAssetData.GetAsset()) == nullptr) + { + if (AssetDataInternal.Contains(nullptr)) + { + AssetDataInternal.Remove(nullptr); + REMOVE_INVALID_ENTRIES(AssetDataInternal) + } + else + { + AssetDataExternal.Remove(nullptr); + REMOVE_INVALID_ENTRIES(AssetDataExternal) + } + } +} + +void UAyonPublishInstance::OnAssetUpdated(const FAssetData& InAssetData) +{ + REMOVE_INVALID_ENTRIES(AssetDataInternal); + REMOVE_INVALID_ENTRIES(AssetDataExternal); +} + +bool UAyonPublishInstance::IsUnderSameDir(const UObject* InAsset) const +{ + FString ThisLeft, ThisRight; + this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); + + return InAsset->GetPathName().StartsWith(ThisLeft); +} + +#ifdef WITH_EDITOR + +void UAyonPublishInstance::ColorAyonDirs() +{ + FString PathName = this->GetPathName(); + + //Check whether the path contains the defined Ayon folder + if (!PathName.Contains(TEXT("Ayon"))) return; + + //Get the base path for open pype + FString PathLeft, PathRight; + PathName.Split(FString("Ayon"), &PathLeft, &PathRight); + + if (PathLeft.IsEmpty() || PathRight.IsEmpty()) + { + UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base Ayon directory!")) + return; + } + + PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); + + //Get the current settings + const UAyonSettings* Settings = GetMutableDefault(); + + //Color the base folder + UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); + + //Get Sub paths, iterate through them and color them according to the folder color in UAyonSettings + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( + "AssetRegistry"); + + TArray PathList; + + AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); + + if (PathList.Num() > 0) + { + for (const FString& Path : PathList) + { + UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); + } + } +} + +void UAyonPublishInstance::SendNotification(const FString& Text) const +{ + FNotificationInfo Info{FText::FromString(Text)}; + + Info.bFireAndForget = true; + Info.bUseLargeFont = false; + Info.bUseThrobber = false; + Info.bUseSuccessFailIcons = false; + Info.ExpireDuration = 4.f; + Info.FadeOutDuration = 2.f; + + FSlateNotificationManager::Get().AddNotification(Info); + + UE_LOG(LogAssetData, Warning, + TEXT( + "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" + ), *GetName() + ) +} + + +void UAyonPublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && + PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( + UAyonPublishInstance, AssetDataExternal)) + { + // Check for duplicated assets + for (const auto& Asset : AssetDataInternal) + { + if (AssetDataExternal.Contains(Asset)) + { + AssetDataExternal.Remove(Asset); + return SendNotification( + "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); + } + } + + // Check if no UAyonPublishInstance type assets are included + for (const auto& Asset : AssetDataExternal) + { + if (Cast(Asset.Get()) != nullptr) + { + AssetDataExternal.Remove(Asset); + return SendNotification("You are not allowed to add publish instances!"); + } + } + } +} + +#endif diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp new file mode 100644 index 0000000000..f79c428a6d --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPublishInstanceFactory.cpp @@ -0,0 +1,23 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#include "AyonPublishInstanceFactory.h" +#include "AyonPublishInstance.h" + +UAyonPublishInstanceFactory::UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UAyonPublishInstance::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UAyonPublishInstanceFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + check(InClass->IsChildOf(UAyonPublishInstance::StaticClass())); + return NewObject(InParent, InClass, InName, Flags); +} + +bool UAyonPublishInstanceFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp new file mode 100644 index 0000000000..0ed4b2f704 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonPythonBridge.cpp @@ -0,0 +1,14 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "AyonPythonBridge.h" + +UAyonPythonBridge* UAyonPythonBridge::Get() +{ + TArray AyonPythonBridgeClasses; + GetDerivedClasses(UAyonPythonBridge::StaticClass(), AyonPythonBridgeClasses); + int32 NumClasses = AyonPythonBridgeClasses.Num(); + if (NumClasses > 0) + { + return Cast(AyonPythonBridgeClasses[NumClasses - 1]->GetDefaultObject()); + } + return nullptr; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonSettings.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonSettings.cpp new file mode 100644 index 0000000000..da388fbc8f --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonSettings.cpp @@ -0,0 +1,21 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "AyonSettings.h" + +#include "Interfaces/IPluginManager.h" +#include "UObject/UObjectGlobals.h" + +/** + * Mainly is used for initializing default values if the DefaultAyonSettings.ini file does not exist in the saved config + */ +UAyonSettings::UAyonSettings(const FObjectInitializer& ObjectInitializer) +{ + + const FString ConfigFilePath = AYON_SETTINGS_FILEPATH; + + // This has to be probably in the future set using the UE Reflection system + FColor Color; + GConfig->GetColor(TEXT("/Script/Ayon.AyonSettings"), TEXT("FolderColor"), Color, ConfigFilePath); + + FolderColor = Color; +} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonStyle.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonStyle.cpp new file mode 100644 index 0000000000..d88df78735 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/AyonStyle.cpp @@ -0,0 +1,62 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "AyonStyle.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyleRegistry.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" +#include "Styling/SlateStyleMacros.h" + +#define RootToContentDir Style->RootToContentDir + +TSharedPtr FAyonStyle::AyonStyleInstance = nullptr; + +void FAyonStyle::Initialize() +{ + if (!AyonStyleInstance.IsValid()) + { + AyonStyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*AyonStyleInstance); + } +} + +void FAyonStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*AyonStyleInstance); + ensure(AyonStyleInstance.IsUnique()); + AyonStyleInstance.Reset(); +} + +FName FAyonStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("AyonStyle")); + return StyleSetName; +} + +const FVector2D Icon16x16(16.0f, 16.0f); +const FVector2D Icon20x20(20.0f, 20.0f); +const FVector2D Icon40x40(40.0f, 40.0f); + +TSharedRef< FSlateStyleSet > FAyonStyle::Create() +{ + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("AyonStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Resources")); + + Style->Set("Ayon.AyonTools", new IMAGE_BRUSH(TEXT("ayon40"), Icon40x40)); + Style->Set("Ayon.AyonToolsDialog", new IMAGE_BRUSH(TEXT("ayon40"), Icon40x40)); + + return Style; +} + +void FAyonStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FAyonStyle::Get() +{ + return *AyonStyleInstance; +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp new file mode 100644 index 0000000000..2a137e3ed7 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/AyonActionResult.cpp @@ -0,0 +1,40 @@ +// Copyright 2023, Ayon, All rights reserved. + +#include "Commandlets/AyonActionResult.h" +#include "Logging/Ayon_Log.h" + +EAyon_ActionResult::Type& FAyon_ActionResult::GetStatus() +{ + return Status; +} + +FText& FAyon_ActionResult::GetReason() +{ + return Reason; +} + +FAyon_ActionResult::FAyon_ActionResult():Status(EAyon_ActionResult::Type::Ok) +{ + +} + +FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum):Status(InEnum) +{ + TryLog(); +} + +FAyon_ActionResult::FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) +{ + TryLog(); +}; + +bool FAyon_ActionResult::IsProblem() const +{ + return Status != EAyon_ActionResult::Ok; +} + +void FAyon_ActionResult::TryLog() const +{ + if(IsProblem()) + UE_LOG(LogCommandletAyonGenerateProject, Error, TEXT("%s"), *Reason.ToString()); +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp new file mode 100644 index 0000000000..ed876c8128 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/Commandlets/Implementations/AyonGenerateProjectCommandlet.cpp @@ -0,0 +1,140 @@ +// Copyright 2023, Ayon, All rights reserved. +#include "Commandlets/Implementations/AyonGenerateProjectCommandlet.h" + +#include "GameProjectUtils.h" +#include "AyonConstants.h" +#include "Commandlets/AyonActionResult.h" +#include "ProjectDescriptor.h" + +int32 UAyonGenerateProjectCommandlet::Main(const FString& CommandLineParams) +{ + //Parses command line parameters & creates structure FProjectInformation + const FAyonGenerateProjectParams ParsedParams = FAyonGenerateProjectParams(CommandLineParams); + ProjectInformation = ParsedParams.GenerateUEProjectInformation(); + + //Creates .uproject & other UE files + EVALUATE_Ayon_ACTION_RESULT(TryCreateProject()); + + //Loads created .uproject + EVALUATE_Ayon_ACTION_RESULT(TryLoadProjectDescriptor()); + + //Adds needed plugin to .uproject + AttachPluginsToProjectDescriptor(); + + //Saves .uproject + EVALUATE_Ayon_ACTION_RESULT(TrySave()); + + //When we are here, there should not be problems in generating Unreal Project for Ayon + return 0; +} + + +FAyonGenerateProjectParams::FAyonGenerateProjectParams(): FAyonGenerateProjectParams("") +{ +} + +FAyonGenerateProjectParams::FAyonGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( + CommandLineParams) +{ + UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); +} + +FProjectInformation FAyonGenerateProjectParams::GenerateUEProjectInformation() const +{ + FProjectInformation ProjectInformation = FProjectInformation(); + ProjectInformation.ProjectFilename = GetProjectFileName(); + + ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); + + return ProjectInformation; +} + +FString FAyonGenerateProjectParams::TryGetToken(const int32 Index) const +{ + return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; +} + +FString FAyonGenerateProjectParams::GetProjectFileName() const +{ + return TryGetToken(0); +} + +bool FAyonGenerateProjectParams::IsSwitchPresent(const FString& Switch) const +{ + return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool + { + return Item.Equals(Switch); + } + ); +} + + +UAyonGenerateProjectCommandlet::UAyonGenerateProjectCommandlet() +{ + LogToConsole = true; +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TryCreateProject() const +{ + FText FailReason; + FText FailLog; + TArray OutCreatedFiles; + + if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) + return FAyon_ActionResult(EAyon_ActionResult::ProjectNotCreated, FailReason); + return FAyon_ActionResult(); +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TryLoadProjectDescriptor() +{ + FText FailReason; + const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); + + return FAyon_ActionResult(bLoaded ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotLoaded, FailReason); +} + +void UAyonGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() +{ + FPluginReferenceDescriptor AyonPluginDescriptor; + AyonPluginDescriptor.bEnabled = true; + AyonPluginDescriptor.Name = AyonConstants::Ayon_PluginName; + ProjectDescriptor.Plugins.Add(AyonPluginDescriptor); + + FPluginReferenceDescriptor PythonPluginDescriptor; + PythonPluginDescriptor.bEnabled = true; + PythonPluginDescriptor.Name = AyonConstants::PythonScript_PluginName; + ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); + + FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; + SequencerScriptingPluginDescriptor.bEnabled = true; + SequencerScriptingPluginDescriptor.Name = AyonConstants::SequencerScripting_PluginName; + ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); + + FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; + MovieRenderPipelinePluginDescriptor.bEnabled = true; + MovieRenderPipelinePluginDescriptor.Name = AyonConstants::MovieRenderPipeline_PluginName; + ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); + + FPluginReferenceDescriptor EditorScriptingPluginDescriptor; + EditorScriptingPluginDescriptor.bEnabled = true; + EditorScriptingPluginDescriptor.Name = AyonConstants::EditorScriptingUtils_PluginName; + ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); +} + +FAyon_ActionResult UAyonGenerateProjectCommandlet::TrySave() +{ + FText FailReason; + const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); + + return FAyon_ActionResult(bSaved ? EAyon_ActionResult::Ok : EAyon_ActionResult::ProjectNotSaved, FailReason); +} + +FAyonGenerateProjectParams UAyonGenerateProjectCommandlet::ParseParameters(const FString& Params) const +{ + FAyonGenerateProjectParams ParamsResult; + + TArray Tokens, Switches; + ParseCommandLine(*Params, Tokens, Switches); + + return ParamsResult; +} diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp new file mode 100644 index 0000000000..02a8ac800a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Private/OpenPypePublishInstance.cpp @@ -0,0 +1,204 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "OpenPypePublishInstance.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetToolsModule.h" +#include "Framework/Notifications/NotificationManager.h" +#include "AyonLib.h" +#include "AyonSettings.h" +#include "Widgets/Notifications/SNotificationList.h" + + +//Moves all the invalid pointers to the end to prepare them for the shrinking +#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ + VAR.Shrink(); + +UOpenPypePublishInstance::UOpenPypePublishInstance(const FObjectInitializer& ObjectInitializer) + : UPrimaryDataAsset(ObjectInitializer) +{ + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< + FAssetRegistryModule>("AssetRegistry"); + + const FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( + "PropertyEditor"); + + FString Left, Right; + GetPathName().Split("/" + GetName(), &Left, &Right); + + FARFilter Filter; + Filter.PackagePaths.Emplace(FName(Left)); + + TArray FoundAssets; + AssetRegistryModule.GetRegistry().GetAssets(Filter, FoundAssets); + + for (const FAssetData& AssetData : FoundAssets) + OnAssetCreated(AssetData); + + REMOVE_INVALID_ENTRIES(AssetDataInternal) + REMOVE_INVALID_ENTRIES(AssetDataExternal) + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UOpenPypePublishInstance::OnAssetCreated); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UOpenPypePublishInstance::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetUpdated().AddUObject(this, &UOpenPypePublishInstance::OnAssetUpdated); + +#ifdef WITH_EDITOR + ColorOpenPypeDirs(); +#endif +} + +void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData) +{ + TArray split; + + UObject* Asset = InAssetData.GetAsset(); + + if (!IsValid(Asset)) + { + UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), + *InAssetData.GetSoftObjectPath().ToString()); + return; + } + + const bool result = IsUnderSameDir(Asset) && Cast(Asset) == nullptr; + + if (result) + { + if (AssetDataInternal.Emplace(Asset).IsValidId()) + { + UE_LOG(LogTemp, Log, TEXT("Added an Asset to PublishInstance - Publish Instance: %s, Asset %s"), + *this->GetName(), *Asset->GetName()); + } + } +} + +void UOpenPypePublishInstance::OnAssetRemoved(const FAssetData& InAssetData) +{ + if (Cast(InAssetData.GetAsset()) == nullptr) + { + if (AssetDataInternal.Contains(nullptr)) + { + AssetDataInternal.Remove(nullptr); + REMOVE_INVALID_ENTRIES(AssetDataInternal) + } + else + { + AssetDataExternal.Remove(nullptr); + REMOVE_INVALID_ENTRIES(AssetDataExternal) + } + } +} + +void UOpenPypePublishInstance::OnAssetUpdated(const FAssetData& InAssetData) +{ + REMOVE_INVALID_ENTRIES(AssetDataInternal); + REMOVE_INVALID_ENTRIES(AssetDataExternal); +} + +bool UOpenPypePublishInstance::IsUnderSameDir(const UObject* InAsset) const +{ + FString ThisLeft, ThisRight; + this->GetPathName().Split(this->GetName(), &ThisLeft, &ThisRight); + + return InAsset->GetPathName().StartsWith(ThisLeft); +} + +#ifdef WITH_EDITOR + +void UOpenPypePublishInstance::ColorOpenPypeDirs() +{ + FString PathName = this->GetPathName(); + + //Check whether the path contains the defined OpenPype folder + if (!PathName.Contains(TEXT("OpenPype"))) return; + + //Get the base path for open pype + FString PathLeft, PathRight; + PathName.Split(FString("OpenPype"), &PathLeft, &PathRight); + + if (PathLeft.IsEmpty() || PathRight.IsEmpty()) + { + UE_LOG(LogAssetData, Error, TEXT("Failed to retrieve the base OpenPype directory!")) + return; + } + + PathName.RemoveFromEnd(PathRight, ESearchCase::CaseSensitive); + + //Get the current settings + const UAyonSettings* Settings = GetMutableDefault(); + + //Color the base folder + UAyonLib::SetFolderColor(PathName, Settings->GetFolderFColor(), false); + + //Get Sub paths, iterate through them and color them according to the folder color in UOpenPypeSettings + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( + "AssetRegistry"); + + TArray PathList; + + AssetRegistryModule.Get().GetSubPaths(PathName, PathList, true); + + if (PathList.Num() > 0) + { + for (const FString& Path : PathList) + { + UAyonLib::SetFolderColor(Path, Settings->GetFolderFColor(), false); + } + } +} + +void UOpenPypePublishInstance::SendNotification(const FString& Text) const +{ + FNotificationInfo Info{FText::FromString(Text)}; + + Info.bFireAndForget = true; + Info.bUseLargeFont = false; + Info.bUseThrobber = false; + Info.bUseSuccessFailIcons = false; + Info.ExpireDuration = 4.f; + Info.FadeOutDuration = 2.f; + + FSlateNotificationManager::Get().AddNotification(Info); + + UE_LOG(LogAssetData, Warning, + TEXT( + "Removed duplicated asset from the AssetsDataExternal in Container \"%s\", Asset is already included in the AssetDataInternal!" + ), *GetName() + ) +} + + +void UOpenPypePublishInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet && + PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED( + UOpenPypePublishInstance, AssetDataExternal)) + { + // Check for duplicated assets + for (const auto& Asset : AssetDataInternal) + { + if (AssetDataExternal.Contains(Asset)) + { + AssetDataExternal.Remove(Asset); + return SendNotification( + "You are not allowed to add assets into AssetDataExternal which are already included in AssetDataInternal!"); + } + } + + // Check if no UOpenPypePublishInstance type assets are included + for (const auto& Asset : AssetDataExternal) + { + if (Cast(Asset.Get()) != nullptr) + { + AssetDataExternal.Remove(Asset); + return SendNotification("You are not allowed to add publish instances!"); + } + } + } +} + +#endif diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Ayon.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Ayon.h new file mode 100644 index 0000000000..bb25430411 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Ayon.h @@ -0,0 +1,24 @@ +// Copyright 2023, Ayon, All rights reserved. + +#pragma once + +#include "CoreMinimal.h" + + +class FAyonModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: + void RegisterMenus(); + void RegisterSettings(); + bool HandleSettingsSaved(); + + void MenuPopup(); + void MenuDialog(); + +private: + TSharedPtr PluginCommands; +}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainer.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainer.h new file mode 100644 index 0000000000..d40642b149 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainer.h @@ -0,0 +1,34 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "Engine/AssetUserData.h" +#include "AssetRegistry/AssetData.h" +#include "AyonAssetContainer.generated.h" + +UCLASS(Blueprintable) +class AYON_API UAyonAssetContainer : public UAssetUserData +{ + GENERATED_BODY() + +public: + + UAyonAssetContainer(const FObjectInitializer& ObjectInitalizer); + // ~UAyonAssetContainer(); + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Assets") + TArray assets; + + // There seems to be no reflection option to expose array of FAssetData + /* + UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) + TArray assetsData; + */ +private: + TArray assetsData; + void OnAssetAdded(const FAssetData& AssetData); + void OnAssetRemoved(const FAssetData& AssetData); + void OnAssetRenamed(const FAssetData& AssetData, const FString& str); +}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h new file mode 100644 index 0000000000..da424cde2e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonAssetContainerFactory.h @@ -0,0 +1,18 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "AyonAssetContainerFactory.generated.h" + +UCLASS() +class AYON_API UAyonAssetContainerFactory : public UFactory +{ + GENERATED_BODY() + +public: + UAyonAssetContainerFactory(const FObjectInitializer& ObjectInitializer); + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override; +}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonCommands.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonCommands.h new file mode 100644 index 0000000000..9c40dc8241 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonCommands.h @@ -0,0 +1,24 @@ +// Copyright 2023, Ayon, All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "AyonStyle.h" + +class FAyonCommands : public TCommands +{ +public: + + FAyonCommands() + : TCommands(TEXT("Ayon"), NSLOCTEXT("Contexts", "Ayon", "Ayon Tools"), NAME_None, FAyonStyle::GetStyleSetName()) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + +public: + TSharedPtr< FUICommandInfo > AyonTools; + TSharedPtr< FUICommandInfo > AyonToolsDialog; +}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonConstants.h similarity index 83% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h rename to openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonConstants.h index f4587f7a50..5fe7c14360 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonConstants.h @@ -1,9 +1,9 @@ // Copyright 2023, Ayon, All rights reserved. #pragma once -namespace OPConstants +namespace AyonConstants { - const FString OP_PluginName = "OpenPype"; + const FString Ayon_PluginName = "Ayon"; const FString PythonScript_PluginName = "PythonScriptPlugin"; const FString SequencerScripting_PluginName = "SequencerScripting"; const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline"; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonLib.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonLib.h new file mode 100644 index 0000000000..da83b448fb --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonLib.h @@ -0,0 +1,19 @@ +// Copyright 2023, Ayon, All rights reserved. +#pragma once + +#include "AyonLib.generated.h" + + +UCLASS(Blueprintable) +class AYON_API UAyonLib : public UBlueprintFunctionLibrary +{ + + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = Python) + static bool SetFolderColor(const FString& FolderPath, const FLinearColor& FolderColor,const bool& bForceAdd); + + UFUNCTION(BlueprintCallable, Category = Python) + static TArray GetAllProperties(UClass* cls); +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstance.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstance.h new file mode 100644 index 0000000000..c89388036f --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstance.h @@ -0,0 +1,104 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "AyonPublishInstance.generated.h" + + +UCLASS(Blueprintable) +class AYON_API UAyonPublishInstance : public UPrimaryDataAsset +{ + GENERATED_UCLASS_BODY() + +public: + /** + /** + * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is + * placed in) + * + * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetInternalAssets() const + { + //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. + TSet ResultSet; + + for (const auto& Asset : AssetDataInternal) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + + /** + * Retrieves all the assets which have been added manually by the Publish Instance + * + * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetExternalAssets() const + { + //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. + TSet ResultSet; + + for (const auto& Asset : AssetDataExternal) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + + /** + * Function for returning all the assets in the container combined. + * + * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are + * returning raw pointers. Seems like an issue in UE5 + * + * @attention If the bAddExternalAssets variable is false, external assets won't be included! + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetAllAssets() const + { + const TSet>& IteratedSet = bAddExternalAssets + ? AssetDataInternal.Union(AssetDataExternal) + : AssetDataInternal; + + //Create a new TSet only with raw pointers. + TSet ResultSet; + + for (auto& Asset : IteratedSet) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + +private: + UPROPERTY(VisibleAnywhere, Category="Assets") + TSet> AssetDataInternal; + + /** + * This property allows exposing the array to include other assets from any other directory than what it's currently + * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! + */ + UPROPERTY(EditAnywhere, Category = "Assets") + bool bAddExternalAssets = false; + + UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") + TSet> AssetDataExternal; + + + void OnAssetCreated(const FAssetData& InAssetData); + void OnAssetRemoved(const FAssetData& InAssetData); + void OnAssetUpdated(const FAssetData& InAssetData); + + bool IsUnderSameDir(const UObject* InAsset) const; + +#ifdef WITH_EDITOR + + void ColorAyonDirs(); + + void SendNotification(const FString& Text) const; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + +#endif +}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h new file mode 100644 index 0000000000..3cef8e76b2 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPublishInstanceFactory.h @@ -0,0 +1,22 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "AyonPublishInstanceFactory.generated.h" + +/** + * + */ +UCLASS() +class AYON_API UAyonPublishInstanceFactory : public UFactory +{ + GENERATED_BODY() + +public: + UAyonPublishInstanceFactory(const FObjectInitializer& ObjectInitializer); + virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override; +}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPythonBridge.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPythonBridge.h new file mode 100644 index 0000000000..3c429fd7d3 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonPythonBridge.h @@ -0,0 +1,20 @@ +// Copyright 2023, Ayon, All rights reserved. +#pragma once +#include "AyonPythonBridge.generated.h" + +UCLASS(Blueprintable) +class UAyonPythonBridge : public UObject +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = Python) + static UAyonPythonBridge* Get(); + + UFUNCTION(BlueprintImplementableEvent, Category = Python) + void RunInPython_Popup() const; + + UFUNCTION(BlueprintImplementableEvent, Category = Python) + void RunInPython_Dialog() const; + +}; diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonSettings.h similarity index 57% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h rename to openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonSettings.h index 88defaa773..4f12d1a5f2 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonSettings.h @@ -1,14 +1,15 @@ -// Copyright 2023, Ayon, All rights reserved. +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "CoreMinimal.h" -#include "OpenPypeSettings.generated.h" +#include "UObject/Object.h" +#include "AyonSettings.generated.h" -#define OPENPYPE_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Config") / TEXT("DefaultOpenPypeSettings.ini") +#define AYON_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("Ayon")->GetBaseDir() / TEXT("Config") / TEXT("DefaultAyonSettings.ini") -UCLASS(Config=OpenPypeSettings, DefaultConfig) -class OPENPYPE_API UOpenPypeSettings : public UObject +UCLASS(Config=AyonSettings, DefaultConfig) +class AYON_API UAyonSettings : public UObject { GENERATED_UCLASS_BODY() diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonStyle.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonStyle.h new file mode 100644 index 0000000000..58f6af656e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/AyonStyle.h @@ -0,0 +1,19 @@ +// Copyright 2023, Ayon, All rights reserved. +#pragma once +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +class FAyonStyle +{ +public: + static void Initialize(); + static void Shutdown(); + static void ReloadTextures(); + static const ISlateStyle& Get(); + static FName GetStyleSetName(); + + +private: + static TSharedRef< class FSlateStyleSet > Create(); + static TSharedPtr< class FSlateStyleSet > AyonStyleInstance; +}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h new file mode 100644 index 0000000000..bb995ec452 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/AyonActionResult.h @@ -0,0 +1,83 @@ +// Copyright 2023, Ayon, All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AyonActionResult.generated.h" + +/** + * @brief This macro returns error code when is problem or does nothing when there is no problem. + * @param ActionResult FAyon_ActionResult structure + */ +#define EVALUATE_Ayon_ACTION_RESULT(ActionResult) \ + if(ActionResult.IsProblem()) \ + return ActionResult.GetStatus(); + +/** +* @brief This enum values are humanly readable mapping of error codes. +* Here should be all error codes to be possible find what went wrong. +* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... +*/ +UENUM() +namespace EAyon_ActionResult +{ + enum Type + { + Ok, + ProjectNotCreated, + ProjectNotLoaded, + ProjectNotSaved, + //....Here insert another values + + //Do not remove! + //Usable for looping through enum values + __Last UMETA(Hidden) + }; +} + + +/** + * @brief This struct holds action result enum and optionally reason of fail + */ +USTRUCT() +struct FAyon_ActionResult +{ + GENERATED_BODY() + +public: + /** @brief Default constructor usable when there is no problem */ + FAyon_ActionResult(); + + /** + * @brief This constructor initializes variables & attempts to log when is error + * @param InEnum Status + */ + FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum); + + /** + * @brief This constructor initializes variables & attempts to log when is error + * @param InEnum Status + * @param InReason Reason of potential fail + */ + FAyon_ActionResult(const EAyon_ActionResult::Type& InEnum, const FText& InReason); + +private: + /** @brief Action status */ + EAyon_ActionResult::Type Status; + + /** @brief Optional reason of fail */ + FText Reason; + +public: + /** + * @brief Checks if there is problematic state + * @return true when status is not equal to EAyon_ActionResult::Ok + */ + bool IsProblem() const; + EAyon_ActionResult::Type& GetStatus(); + FText& GetReason(); + +private: + void TryLog() const; +}; + diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h new file mode 100644 index 0000000000..da8e9af661 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Commandlets/Implementations/AyonGenerateProjectCommandlet.h @@ -0,0 +1,61 @@ +// Copyright 2023, Ayon, All rights reserved. +#pragma once + + +#include "GameProjectUtils.h" +#include "Commandlets/AyonActionResult.h" +#include "ProjectDescriptor.h" +#include "Commandlets/Commandlet.h" +#include "AyonGenerateProjectCommandlet.generated.h" + +struct FProjectDescriptor; +struct FProjectInformation; + +/** +* @brief Structure which parses command line parameters and generates FProjectInformation +*/ +USTRUCT() +struct FAyonGenerateProjectParams +{ + GENERATED_BODY() + +private: + FString CommandLineParams; + TArray Tokens; + TArray Switches; + +public: + FAyonGenerateProjectParams(); + FAyonGenerateProjectParams(const FString& CommandLineParams); + + FProjectInformation GenerateUEProjectInformation() const; + +private: + FString TryGetToken(const int32 Index) const; + FString GetProjectFileName() const; + + bool IsSwitchPresent(const FString& Switch) const; +}; + +UCLASS() +class AYON_API UAyonGenerateProjectCommandlet : public UCommandlet +{ + GENERATED_BODY() + +private: + FProjectInformation ProjectInformation; + FProjectDescriptor ProjectDescriptor; + +public: + UAyonGenerateProjectCommandlet(); + + virtual int32 Main(const FString& CommandLineParams) override; + +private: + FAyonGenerateProjectParams ParseParameters(const FString& Params) const; + FAyon_ActionResult TryCreateProject() const; + FAyon_ActionResult TryLoadProjectDescriptor(); + void AttachPluginsToProjectDescriptor(); + FAyon_ActionResult TrySave(); +}; + diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h new file mode 100644 index 0000000000..25b33a63e8 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/Logging/Ayon_Log.h @@ -0,0 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. +#pragma once + +DEFINE_LOG_CATEGORY_STATIC(LogCommandletAyonGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h new file mode 100644 index 0000000000..9c0c4a69e5 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/Ayon/Source/Ayon/Public/OpenPypePublishInstance.h @@ -0,0 +1,104 @@ +// Copyright 2023, Ayon, All rights reserved. +// Deprecation warning: this is left here just for backwards compatibility +// and will be removed in next versions of Ayon. +#pragma once + +#include "OpenPypePublishInstance.generated.h" + + +UCLASS(Blueprintable) +class AYON_API UOpenPypePublishInstance : public UPrimaryDataAsset +{ + GENERATED_UCLASS_BODY() + +public: + /** + /** + * Retrieves all the assets which are monitored by the Publish Instance (Monitors assets in the directory which is + * placed in) + * + * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetInternalAssets() const + { + //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. + TSet ResultSet; + + for (const auto& Asset : AssetDataInternal) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + + /** + * Retrieves all the assets which have been added manually by the Publish Instance + * + * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetExternalAssets() const + { + //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. + TSet ResultSet; + + for (const auto& Asset : AssetDataExternal) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + + /** + * Function for returning all the assets in the container combined. + * + * @return Returns all the internal and externally added assets into one set (TSet of UObjects). Careful! They are + * returning raw pointers. Seems like an issue in UE5 + * + * @attention If the bAddExternalAssets variable is false, external assets won't be included! + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") + TSet GetAllAssets() const + { + const TSet>& IteratedSet = bAddExternalAssets + ? AssetDataInternal.Union(AssetDataExternal) + : AssetDataInternal; + + //Create a new TSet only with raw pointers. + TSet ResultSet; + + for (auto& Asset : IteratedSet) + ResultSet.Add(Asset.LoadSynchronous()); + + return ResultSet; + } + +private: + UPROPERTY(VisibleAnywhere, Category="Assets") + TSet> AssetDataInternal; + + /** + * This property allows exposing the array to include other assets from any other directory than what it's currently + * monitoring. NOTE: that these assets have to be added manually! They are not automatically registered or added! + */ + UPROPERTY(EditAnywhere, Category = "Assets") + bool bAddExternalAssets = false; + + UPROPERTY(EditAnywhere, meta=(EditCondition="bAddExternalAssets"), Category="Assets") + TSet> AssetDataExternal; + + + void OnAssetCreated(const FAssetData& InAssetData); + void OnAssetRemoved(const FAssetData& InAssetData); + void OnAssetUpdated(const FAssetData& InAssetData); + + bool IsUnderSameDir(const UObject* InAsset) const; + +#ifdef WITH_EDITOR + + void ColorOpenPypeDirs(); + + void SendNotification(const FString& Text) const; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + +#endif +}; diff --git a/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1.bat b/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1.bat new file mode 100644 index 0000000000..3cc82d54af --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1.bat @@ -0,0 +1 @@ +"D:\UE_5.1\Engine\Build\BatchFiles\RunUAT.bat" BuildPlugin -plugin="D:\OpenPype\openpype\hosts\unreal\integration\UE_5.1\Ayon\Ayon.uplugin" -Package="D:\BuiltPlugins\5.1" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1_Window.bat b/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1_Window.bat new file mode 100644 index 0000000000..e10f2c7add --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/BuildPlugin_5-1_Window.bat @@ -0,0 +1 @@ +cmd /k "BuildPlugin_5-1.bat" \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/.gitignore new file mode 100644 index 0000000000..80814ef0a6 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/.gitignore @@ -0,0 +1,41 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +/Saved +/DerivedDataCache +/Intermediate +/Binaries +/Content +/Config +/.idea +/.vs \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/CommandletProject.uproject b/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/CommandletProject.uproject new file mode 100644 index 0000000000..fe83346624 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.1/CommandletProject/CommandletProject.uproject @@ -0,0 +1,20 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.1", + "Category": "", + "Description": "", + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true, + "TargetAllowList": [ + "Editor" + ] + }, + { + "Name": "Ayon", + "Enabled": true, + "Type": "Editor" + } + ] +} \ No newline at end of file diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 05fc87b318..821b4daecc 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -189,7 +189,7 @@ def create_unreal_project(project_name: str, As there is no way I know to create a project via command line, this is easiest option. Unreal project file is basically a JSON file. If we find - the `OPENPYPE_UNREAL_PLUGIN` environment variable we assume this is the + the `AYON_UNREAL_PLUGIN` environment variable we assume this is the location of the Integration Plugin and we copy its content to the project folder and enable this plugin. @@ -203,8 +203,7 @@ def create_unreal_project(project_name: str, sources. This will trigger automatically if `Binaries` directory is not found in plugin folders as this indicates this is only source distribution of the plugin. Dev mode - is also set by preset file `unreal/project_setup.json` in - **OPENPYPE_CONFIG**. + is also set in Settings. env (dict, optional): Environment to use. If not set, `os.environ`. Throws: @@ -237,7 +236,7 @@ def create_unreal_project(project_name: str, print("--- Generating a new project ...") commandlet_cmd = [f'{ue_editor_exe.as_posix()}', f'{cmdlet_project.as_posix()}', - f'-run=OPGenerateProject', + f'-run=AyonGenerateProject', f'{project_file.resolve().as_posix()}'] if dev_mode or preset["dev_mode"]: @@ -318,21 +317,73 @@ def get_path_to_uat(engine_path: Path) -> Path: if platform.system().lower() == "windows": return engine_path / "Engine/Build/BatchFiles/RunUAT.bat" - if platform.system().lower() == "linux" \ - or platform.system().lower() == "darwin": + if platform.system().lower() in ["linux", "darwin"]: return engine_path / "Engine/Build/BatchFiles/RunUAT.sh" +def get_compatible_integration( + ue_version: str, integration_root: Path) -> List[Path]: + """Get path to compatible version of integration plugin. + + This will try to get the closest compatible versions to the one + specified in sorted list. + + Args: + ue_version (str): version of the current Unreal Engine. + integration_root (Path): path to built-in integration plugins. + + Returns: + list of Path: Sorted list of paths closest to the specified + version. + + """ + major, minor = ue_version.split(".") + integration_paths = [p for p in integration_root.iterdir() + if p.is_dir()] + + compatible_versions = [] + for i in integration_paths: + # parse version from path + try: + i_major, i_minor = re.search( + r"(?P\d+).(?P\d+)$", i.name).groups() + except AttributeError: + # in case there is no match, just skip to next + continue + + # consider versions with different major so different that they + # are incompatible + if int(major) != int(i_major): + continue + + compatible_versions.append(i) + + sorted(set(compatible_versions)) + return compatible_versions + + def get_path_to_cmdlet_project(ue_version: str) -> Path: - cmd_project = Path(os.path.dirname(os.path.abspath(openpype.__file__))) + cmd_project = Path( + os.path.abspath(os.getenv("OPENPYPE_ROOT"))) - # For now, only tested on Windows (For Linux and Mac it has to be implemented) - if ue_version.split(".")[0] == "4": - cmd_project /= "hosts/unreal/integration/UE_4.7" - elif ue_version.split(".")[0] == "5": - cmd_project /= "hosts/unreal/integration/UE_5.0" + # For now, only tested on Windows (For Linux and Mac + # it has to be implemented) + cmd_project /= f"openpype/hosts/unreal/integration/UE_{ue_version}" - return cmd_project / "CommandletProject/CommandletProject.uproject" + # if the integration doesn't exist for current engine version + # try to find the closest to it. + if cmd_project.exists(): + return cmd_project / "CommandletProject/CommandletProject.uproject" + + if compatible_versions := get_compatible_integration( + ue_version, cmd_project.parent + ): + return compatible_versions[-1] / "CommandletProject/CommandletProject.uproject" # noqa: E501 + else: + raise RuntimeError( + ("There are no compatible versions of Unreal " + "integration plugin compatible with running version " + f"of Unreal Engine {ue_version}")) def get_path_to_ubt(engine_path: Path, ue_version: str) -> Path: @@ -375,13 +426,13 @@ def get_build_id(engine_path: Path, ue_version: str) -> str: def check_plugin_existence(engine_path: Path, env: dict = None) -> bool: env = env or os.environ - integration_plugin_path: Path = Path(env.get("OPENPYPE_UNREAL_PLUGIN", "")) + integration_plugin_path: Path = Path(env.get("AYON_UNREAL_PLUGIN", "")) if not os.path.isdir(integration_plugin_path): raise RuntimeError("Path to the integration plugin is null!") # Create a path to the plugin in the engine - op_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/OpenPype" + op_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/Ayon" if not op_plugin_path.is_dir(): return False @@ -396,13 +447,13 @@ def check_plugin_existence(engine_path: Path, env: dict = None) -> bool: def try_installing_plugin(engine_path: Path, env: dict = None) -> None: env = env or os.environ - integration_plugin_path: Path = Path(env.get("OPENPYPE_UNREAL_PLUGIN", "")) + integration_plugin_path: Path = Path(env.get("AYON_UNREAL_PLUGIN", "")) if not os.path.isdir(integration_plugin_path): raise RuntimeError("Path to the integration plugin is null!") # Create a path to the plugin in the engine - op_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/OpenPype" + op_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/Ayon" if not op_plugin_path.is_dir(): op_plugin_path.mkdir(parents=True, exist_ok=True) @@ -423,12 +474,12 @@ def _build_and_move_plugin(engine_path: Path, uat_path: Path = get_path_to_uat(engine_path) env = env or os.environ - integration_plugin_path: Path = Path(env.get("OPENPYPE_UNREAL_PLUGIN", "")) + integration_plugin_path: Path = Path(env.get("AYON_UNREAL_PLUGIN", "")) if uat_path.is_file(): temp_dir: Path = integration_plugin_path.parent / "Temp" temp_dir.mkdir(exist_ok=True) - uplugin_path: Path = integration_plugin_path / "OpenPype.uplugin" + uplugin_path: Path = integration_plugin_path / "Ayon.uplugin" # in order to successfully build the plugin, # It must be built outside the Engine directory and then moved @@ -439,7 +490,7 @@ def _build_and_move_plugin(engine_path: Path, subprocess.run(build_plugin_cmd) # Copy the contents of the 'Temp' dir into the - # 'OpenPype' directory in the engine + # 'Ayon' directory in the engine dir_util.copy_tree(temp_dir.as_posix(), plugin_build_path.as_posix()) # We need to also copy the config folder. diff --git a/openpype/hosts/unreal/plugins/create/create_camera.py b/openpype/hosts/unreal/plugins/create/create_camera.py index 642924e2d6..73afb6cefd 100644 --- a/openpype/hosts/unreal/plugins/create/create_camera.py +++ b/openpype/hosts/unreal/plugins/create/create_camera.py @@ -11,7 +11,7 @@ from openpype.hosts.unreal.api.plugin import ( class CreateCamera(UnrealAssetCreator): """Create Camera.""" - identifier = "io.openpype.creators.unreal.camera" + identifier = "io.ayon.creators.unreal.camera" label = "Camera" family = "camera" icon = "fa.camera" diff --git a/openpype/hosts/unreal/plugins/create/create_layout.py b/openpype/hosts/unreal/plugins/create/create_layout.py index 1d2e800a13..e5c7b8ee19 100644 --- a/openpype/hosts/unreal/plugins/create/create_layout.py +++ b/openpype/hosts/unreal/plugins/create/create_layout.py @@ -7,7 +7,7 @@ from openpype.hosts.unreal.api.plugin import ( class CreateLayout(UnrealActorCreator): """Layout output for character rigs.""" - identifier = "io.openpype.creators.unreal.layout" + identifier = "io.ayon.creators.unreal.layout" label = "Layout" family = "layout" icon = "cubes" diff --git a/openpype/hosts/unreal/plugins/create/create_look.py b/openpype/hosts/unreal/plugins/create/create_look.py index f6c73e47e6..e15b57b2ee 100644 --- a/openpype/hosts/unreal/plugins/create/create_look.py +++ b/openpype/hosts/unreal/plugins/create/create_look.py @@ -14,7 +14,7 @@ from openpype.lib import UILabelDef class CreateLook(UnrealAssetCreator): """Shader connections defining shape look.""" - identifier = "io.openpype.creators.unreal.look" + identifier = "io.ayon.creators.unreal.look" label = "Look" family = "look" icon = "paint-brush" @@ -30,7 +30,7 @@ class CreateLook(UnrealAssetCreator): selected_asset = selection[0] - look_directory = "/Game/OpenPype/Looks" + look_directory = "/Game/Ayon/Looks" # Create the folder folder_name = create_folder(look_directory, subset_name) diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py index b9c443c456..5f561e68ad 100644 --- a/openpype/hosts/unreal/plugins/create/create_render.py +++ b/openpype/hosts/unreal/plugins/create/create_render.py @@ -22,7 +22,7 @@ from openpype.lib import ( class CreateRender(UnrealAssetCreator): """Create instance for sequence for rendering""" - identifier = "io.openpype.creators.unreal.render" + identifier = "io.ayon.creators.unreal.render" label = "Render" family = "render" icon = "eye" @@ -50,7 +50,7 @@ class CreateRender(UnrealAssetCreator): # If the option to create a new level sequence is selected, # create a new level sequence and a master level. - root = f"/Game/OpenPype/Sequences" + root = f"/Game/Ayon/Sequences" # Create a new folder for the sequence in root sequence_dir_name = create_folder(root, subset_name) @@ -142,9 +142,9 @@ class CreateRender(UnrealAssetCreator): # "/Game/OpenPype/" and then we split the path by "/". sel_path = selected_asset_path asset_name = sel_path.replace( - "/Game/OpenPype/", "").split("/")[0] + "/Game/Ayon/", "").split("/")[0] - search_path = f"/Game/OpenPype/{asset_name}" + search_path = f"/Game/Ayon/{asset_name}" else: search_path = Path(selected_asset_path).parent.as_posix() diff --git a/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py index 1acf7084d1..80816d8386 100644 --- a/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py @@ -7,7 +7,7 @@ from openpype.hosts.unreal.api.plugin import ( class CreateStaticMeshFBX(UnrealAssetCreator): """Create Static Meshes as FBX geometry.""" - identifier = "io.openpype.creators.unreal.staticmeshfbx" + identifier = "io.ayon.creators.unreal.staticmeshfbx" label = "Static Mesh (FBX)" family = "unrealStaticMesh" icon = "cube" diff --git a/openpype/hosts/unreal/plugins/create/create_uasset.py b/openpype/hosts/unreal/plugins/create/create_uasset.py index 70f17d478b..c78518e86b 100644 --- a/openpype/hosts/unreal/plugins/create/create_uasset.py +++ b/openpype/hosts/unreal/plugins/create/create_uasset.py @@ -12,7 +12,7 @@ from openpype.hosts.unreal.api.plugin import ( class CreateUAsset(UnrealAssetCreator): """Create UAsset.""" - identifier = "io.openpype.creators.unreal.uasset" + identifier = "io.ayon.creators.unreal.uasset" label = "UAsset" family = "uasset" icon = "cube" diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_animation.py b/openpype/hosts/unreal/plugins/load/load_alembic_animation.py index 496b6056ea..52eea4122a 100644 --- a/openpype/hosts/unreal/plugins/load/load_alembic_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_alembic_animation.py @@ -4,7 +4,7 @@ import os from openpype.pipeline import ( get_representation_path, - AVALON_CONTAINER_ID + AYON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -68,8 +68,8 @@ class AnimationAlembicLoader(plugin.Loader): list(str): list of container content """ - # Create directory for asset and openpype container - root = "/Game/OpenPype/Assets" + # Create directory for asset and ayon container + root = "/Game/Ayon/Assets" asset = context.get('asset').get('name') suffix = "_CON" if asset: @@ -97,8 +97,8 @@ class AnimationAlembicLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -109,7 +109,7 @@ class AnimationAlembicLoader(plugin.Loader): "family": context["representation"]["context"]["family"] } unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) + f"{asset_dir}/{container_name}", data) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True diff --git a/openpype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py index f0c08680d3..778ddf693d 100644 --- a/openpype/hosts/unreal/plugins/load/load_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_animation.py @@ -11,7 +11,7 @@ from unreal import MovieSceneSkeletalAnimationSection from openpype.pipeline.context_tools import get_current_project_asset from openpype.pipeline import ( get_representation_path, - AVALON_CONTAINER_ID + AYON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -139,9 +139,9 @@ class AnimationFBXLoader(plugin.Loader): Returns: list(str): list of container content """ - # Create directory for asset and avalon container + # Create directory for asset and Ayon container hierarchy = context.get('asset').get('data').get('parents') - root = "/Game/OpenPype" + root = "/Game/Ayon" asset = context.get('asset').get('name') suffix = "_CON" asset_name = f"{asset}_{name}" if asset else f"{name}" @@ -223,8 +223,8 @@ class AnimationFBXLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, diff --git a/openpype/hosts/unreal/plugins/load/load_camera.py b/openpype/hosts/unreal/plugins/load/load_camera.py index 2496440e5f..2303ed1ffc 100644 --- a/openpype/hosts/unreal/plugins/load/load_camera.py +++ b/openpype/hosts/unreal/plugins/load/load_camera.py @@ -8,7 +8,7 @@ from unreal import EditorLevelLibrary from unreal import EditorLevelUtils from openpype.client import get_assets, get_asset_by_name from openpype.pipeline import ( - AVALON_CONTAINER_ID, + AYON_CONTAINER_ID, legacy_io, ) from openpype.hosts.unreal.api import plugin @@ -100,9 +100,9 @@ class CameraLoader(plugin.Loader): list(str): list of container content """ - # Create directory for asset and avalon container + # Create directory for asset and Ayon container hierarchy = context.get('asset').get('data').get('parents') - root = "/Game/OpenPype" + root = "/Game/Ayon" hierarchy_dir = root hierarchy_dir_list = [] for h in hierarchy: @@ -268,8 +268,8 @@ class CameraLoader(plugin.Loader): data = get_asset_by_name(project_name, asset)["data"] cam_seq.set_display_rate( unreal.FrameRate(data.get("fps"), 1.0)) - cam_seq.set_playback_start(0) - cam_seq.set_playback_end(data.get('clipOut') - data.get('clipIn') + 1) + cam_seq.set_playback_start(data.get('clipIn')) + cam_seq.set_playback_end(data.get('clipOut') + 1) self._set_sequence_hierarchy( sequences[-1], cam_seq, data.get('clipIn'), data.get('clipOut')) @@ -291,8 +291,8 @@ class CameraLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -320,7 +320,7 @@ class CameraLoader(plugin.Loader): def update(self, container, representation): ar = unreal.AssetRegistryHelpers.get_asset_registry() - root = "/Game/OpenPype" + root = "/Game/ayon" asset_dir = container.get('namespace') @@ -378,7 +378,7 @@ class CameraLoader(plugin.Loader): # Remove the Level Sequence from the parent. # We need to traverse the hierarchy from the master sequence to find # the level sequence. - root = "/Game/OpenPype" + root = "/Game/Ayon" namespace = container.get('namespace').replace(f"{root}/", "") ms_asset = namespace.split('/')[0] filter = unreal.ARFilter( @@ -511,7 +511,7 @@ class CameraLoader(plugin.Loader): # Remove the Level Sequence from the parent. # We need to traverse the hierarchy from the master sequence to find # the level sequence. - root = "/Game/OpenPype" + root = "/Game/Ayon" namespace = container.get('namespace').replace(f"{root}/", "") ms_asset = namespace.split('/')[0] filter = unreal.ARFilter( diff --git a/openpype/hosts/unreal/plugins/load/load_geometrycache_abc.py b/openpype/hosts/unreal/plugins/load/load_geometrycache_abc.py index 6ac3531b40..3a292fdbd1 100644 --- a/openpype/hosts/unreal/plugins/load/load_geometrycache_abc.py +++ b/openpype/hosts/unreal/plugins/load/load_geometrycache_abc.py @@ -4,7 +4,7 @@ import os from openpype.pipeline import ( get_representation_path, - AVALON_CONTAINER_ID + AYON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -22,7 +22,8 @@ class PointCacheAlembicLoader(plugin.Loader): color = "orange" def get_task( - self, filename, asset_dir, asset_name, replace, frame_start, frame_end + self, filename, asset_dir, asset_name, replace, + frame_start=None, frame_end=None ): task = unreal.AssetImportTask() options = unreal.AbcImportSettings() @@ -51,8 +52,10 @@ class PointCacheAlembicLoader(plugin.Loader): conversion_settings.set_editor_property( 'rotation', unreal.Vector(x=-90.0, y=0.0, z=180.0)) - sampling_settings.set_editor_property('frame_start', frame_start) - sampling_settings.set_editor_property('frame_end', frame_end) + if frame_start is not None: + sampling_settings.set_editor_property('frame_start', frame_start) + if frame_end is not None: + sampling_settings.set_editor_property('frame_end', frame_end) options.geometry_cache_settings = gc_settings options.conversion_settings = conversion_settings @@ -83,8 +86,8 @@ class PointCacheAlembicLoader(plugin.Loader): list(str): list of container content """ - # Create directory for asset and OpenPype container - root = "/Game/OpenPype/Assets" + # Create directory for asset and Ayon container + root = "/Game/Ayon/Assets" asset = context.get('asset').get('name') suffix = "_CON" if asset: @@ -118,8 +121,8 @@ class PointCacheAlembicLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -145,9 +148,9 @@ class PointCacheAlembicLoader(plugin.Loader): name = container["asset_name"] source_path = get_representation_path(representation) destination_path = container["namespace"] + representation["context"] - task = self.get_task(source_path, destination_path, name, True) - + task = self.get_task(source_path, destination_path, name, False) # do import fbx and replace existing data unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index f0663a8778..e5f32c3412 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -19,7 +19,7 @@ from openpype.pipeline import ( loaders_from_representation, load_container, get_representation_path, - AVALON_CONTAINER_ID, + AYON_CONTAINER_ID, legacy_io, ) from openpype.pipeline.context_tools import get_current_project_asset @@ -37,7 +37,7 @@ class LayoutLoader(plugin.Loader): label = "Load Layout" icon = "code-fork" color = "orange" - ASSET_ROOT = "/Game/OpenPype" + ASSET_ROOT = "/Game/Ayon" def _get_asset_containers(self, path): ar = unreal.AssetRegistryHelpers.get_asset_registry() @@ -50,7 +50,7 @@ class LayoutLoader(plugin.Loader): # Get all the asset containers for a in asset_content: obj = ar.get_asset_by_object_path(a) - if obj.get_asset().get_class().get_name() == 'AssetContainer': + if obj.get_asset().get_class().get_name() == 'AyonAssetContainer': asset_containers.append(obj) return asset_containers @@ -338,7 +338,7 @@ class LayoutLoader(plugin.Loader): ).replace('\\', '/') _filter = unreal.ARFilter( - class_names=["AssetContainer"], + class_names=["AyonAssetContainer"], package_paths=[anim_path], recursive_paths=False) containers = ar.get_assets(_filter) @@ -519,7 +519,7 @@ class LayoutLoader(plugin.Loader): for asset in assets: obj = ar.get_asset_by_object_path(asset).get_asset() - if obj.get_class().get_name() == 'AssetContainer': + if obj.get_class().get_name() == 'AyonAssetContainer': container = obj if obj.get_class().get_name() == 'Skeleton': skeleton = obj @@ -634,7 +634,7 @@ class LayoutLoader(plugin.Loader): data = get_current_project_settings() create_sequences = data["unreal"]["level_sequences_for_layouts"] - # Create directory for asset and avalon container + # Create directory for asset and Ayon container hierarchy = context.get('asset').get('data').get('parents') root = self.ASSET_ROOT hierarchy_dir = root @@ -749,8 +749,8 @@ class LayoutLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -781,7 +781,7 @@ class LayoutLoader(plugin.Loader): ar = unreal.AssetRegistryHelpers.get_asset_registry() - root = "/Game/OpenPype" + root = "/Game/Ayon" asset_dir = container.get('namespace') context = representation.get("context") @@ -867,7 +867,7 @@ class LayoutLoader(plugin.Loader): data = get_current_project_settings() create_sequences = data["unreal"]["level_sequences_for_layouts"] - root = "/Game/OpenPype" + root = "/Game/Ayon" path = Path(container.get("namespace")) containers = unreal_pipeline.ls() diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index 092b273ded..96ee8cfc25 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -10,7 +10,7 @@ from openpype.pipeline import ( loaders_from_representation, load_container, get_representation_path, - AVALON_CONTAINER_ID, + AYON_CONTAINER_ID, legacy_io, ) from openpype.hosts.unreal.api import plugin @@ -28,7 +28,7 @@ class ExistingLayoutLoader(plugin.Loader): label = "Load Layout on Existing Scene" icon = "code-fork" color = "orange" - ASSET_ROOT = "/Game/OpenPype" + ASSET_ROOT = "/Game/Ayon" delete_unmatched_assets = True @@ -59,8 +59,8 @@ class ExistingLayoutLoader(plugin.Loader): container = obj.get_asset() data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -416,8 +416,8 @@ class ExistingLayoutLoader(plugin.Loader): container=container_name, path=curr_level_path) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": curr_level_path, "container_name": container_name, diff --git a/openpype/hosts/unreal/plugins/load/load_skeletalmesh_abc.py b/openpype/hosts/unreal/plugins/load/load_skeletalmesh_abc.py index e316d255e9..7591d5582f 100644 --- a/openpype/hosts/unreal/plugins/load/load_skeletalmesh_abc.py +++ b/openpype/hosts/unreal/plugins/load/load_skeletalmesh_abc.py @@ -4,7 +4,7 @@ import os from openpype.pipeline import ( get_representation_path, - AVALON_CONTAINER_ID + AYON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -70,8 +70,8 @@ class SkeletalMeshAlembicLoader(plugin.Loader): list(str): list of container content """ - # Create directory for asset and openpype container - root = "/Game/OpenPype/Assets" + # Create directory for asset and ayon container + root = "/Game/Ayon/Assets" asset = context.get('asset').get('name') suffix = "_CON" if asset: @@ -98,8 +98,8 @@ class SkeletalMeshAlembicLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -110,7 +110,7 @@ class SkeletalMeshAlembicLoader(plugin.Loader): "family": context["representation"]["context"]["family"] } unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) + f"{asset_dir}/{container_name}", data) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True diff --git a/openpype/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py b/openpype/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py index 227c5c9292..e9676cde3a 100644 --- a/openpype/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py +++ b/openpype/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py @@ -4,7 +4,7 @@ import os from openpype.pipeline import ( get_representation_path, - AVALON_CONTAINER_ID + AYON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -42,8 +42,8 @@ class SkeletalMeshFBXLoader(plugin.Loader): list(str): list of container content """ - # Create directory for asset and OpenPype container - root = "/Game/OpenPype/Assets" + # Create directory for asset and Ayon container + root = "/Game/Ayon/Assets" if options and options.get("asset_dir"): root = options["asset_dir"] asset = context.get('asset').get('name') @@ -103,8 +103,8 @@ class SkeletalMeshFBXLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -115,7 +115,7 @@ class SkeletalMeshFBXLoader(plugin.Loader): "family": context["representation"]["context"]["family"] } unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) + f"{asset_dir}/{container_name}", data) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True diff --git a/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py b/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py index c7841cef53..befc7b0ac9 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmesh_abc.py @@ -4,7 +4,7 @@ import os from openpype.pipeline import ( get_representation_path, - AVALON_CONTAINER_ID + AYON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -75,8 +75,8 @@ class StaticMeshAlembicLoader(plugin.Loader): list(str): list of container content """ - # Create directory for asset and OpenPype container - root = "/Game/OpenPype/Assets" + # Create directory for asset and Ayon container + root = "/Game/Ayon/Assets" asset = context.get('asset').get('name') suffix = "_CON" if asset: @@ -108,8 +108,8 @@ class StaticMeshAlembicLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -119,8 +119,7 @@ class StaticMeshAlembicLoader(plugin.Loader): "parent": context["representation"]["parent"], "family": context["representation"]["context"]["family"] } - unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) + unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True @@ -136,7 +135,7 @@ class StaticMeshAlembicLoader(plugin.Loader): source_path = get_representation_path(representation) destination_path = container["namespace"] - task = self.get_task(source_path, destination_path, name, True) + task = self.get_task(source_path, destination_path, name, True, False) # do import fbx and replace existing data unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) diff --git a/openpype/hosts/unreal/plugins/load/load_staticmesh_fbx.py b/openpype/hosts/unreal/plugins/load/load_staticmesh_fbx.py index 351c686095..e416256486 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmesh_fbx.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmesh_fbx.py @@ -4,7 +4,7 @@ import os from openpype.pipeline import ( get_representation_path, - AVALON_CONTAINER_ID + AYON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -68,8 +68,8 @@ class StaticMeshFBXLoader(plugin.Loader): list(str): list of container content """ - # Create directory for asset and OpenPype container - root = "/Game/OpenPype/Assets" + # Create directory for asset and Ayon container + root = "/Game/Ayon/Assets" if options and options.get("asset_dir"): root = options["asset_dir"] asset = context.get('asset').get('name') @@ -81,7 +81,8 @@ class StaticMeshFBXLoader(plugin.Loader): tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( - "{}/{}/{}".format(root, asset, name), suffix="") + f"{root}/{asset}/{name}", suffix="" + ) container_name += suffix @@ -96,8 +97,8 @@ class StaticMeshFBXLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -107,8 +108,7 @@ class StaticMeshFBXLoader(plugin.Loader): "parent": context["representation"]["parent"], "family": context["representation"]["context"]["family"] } - unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) + unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True diff --git a/openpype/hosts/unreal/plugins/load/load_uasset.py b/openpype/hosts/unreal/plugins/load/load_uasset.py index eccfc7b445..7606bc14e4 100644 --- a/openpype/hosts/unreal/plugins/load/load_uasset.py +++ b/openpype/hosts/unreal/plugins/load/load_uasset.py @@ -5,7 +5,7 @@ import shutil from openpype.pipeline import ( get_representation_path, - AVALON_CONTAINER_ID + AYON_CONTAINER_ID ) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -38,8 +38,8 @@ class UAssetLoader(plugin.Loader): list(str): list of container content """ - # Create directory for asset and OpenPype container - root = "/Game/OpenPype/Assets" + # Create directory for asset and Ayon container + root = "/Game/Ayon/Assets" asset = context.get('asset').get('name') suffix = "_CON" if asset: @@ -49,7 +49,8 @@ class UAssetLoader(plugin.Loader): tools = unreal.AssetToolsHelpers().get_asset_tools() asset_dir, container_name = tools.create_unique_asset_name( - "{}/{}/{}".format(root, asset, name), suffix="") + f"{root}/{asset}/{name}", suffix="" + ) container_name += suffix @@ -67,8 +68,8 @@ class UAssetLoader(plugin.Loader): container=container_name, path=asset_dir) data = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, + "schema": "ayon:container-2.0", + "id": AYON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -78,8 +79,7 @@ class UAssetLoader(plugin.Loader): "parent": context["representation"]["parent"], "family": context["representation"]["context"]["family"] } - unreal_pipeline.imprint( - "{}/{}".format(asset_dir, container_name), data) + unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data) asset_content = unreal.EditorAssetLibrary.list_assets( asset_dir, recursive=True, include_folder=True @@ -107,7 +107,7 @@ class UAssetLoader(plugin.Loader): for asset in asset_content: obj = ar.get_asset_by_object_path(asset).get_asset() - if not obj.get_class().get_name() == 'AssetContainer': + if not obj.get_class().get_name() == 'AyonAssetContainer': unreal.EditorAssetLibrary.delete_asset(asset) update_filepath = get_representation_path(representation) diff --git a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py index cb28f4bf60..a352b2c3f3 100644 --- a/openpype/hosts/unreal/plugins/publish/collect_render_instances.py +++ b/openpype/hosts/unreal/plugins/publish/collect_render_instances.py @@ -3,6 +3,7 @@ from pathlib import Path import unreal +from openpype.pipeline import get_current_project_name from openpype.pipeline import Anatomy from openpype.hosts.unreal.api import pipeline import pyblish.api @@ -72,8 +73,8 @@ class CollectRenderInstances(pyblish.api.InstancePlugin): new_data["level"] = data.get("level") new_data["output"] = s.get('output') new_data["fps"] = seq.get_display_rate().numerator - new_data["frameStart"] = s.get('frame_range')[0] - new_data["frameEnd"] = s.get('frame_range')[1] + new_data["frameStart"] = int(s.get('frame_range')[0]) + new_data["frameEnd"] = int(s.get('frame_range')[1]) new_data["sequence"] = seq.get_path_name() new_data["master_sequence"] = data["master_sequence"] new_data["master_level"] = data["master_level"] @@ -81,12 +82,13 @@ class CollectRenderInstances(pyblish.api.InstancePlugin): self.log.debug(f"new instance data: {new_data}") try: - project = os.environ.get("AVALON_PROJECT") + project = get_current_project_name() anatomy = Anatomy(project) root = anatomy.roots['renders'] - except Exception: - raise Exception( - "Could not find render root in anatomy settings.") + except Exception as e: + raise Exception(( + "Could not find render root " + "in anatomy settings.")) from e render_dir = f"{root}/{project}/{s.get('output')}" render_path = Path(render_dir) diff --git a/openpype/hosts/unreal/plugins/publish/extract_layout.py b/openpype/hosts/unreal/plugins/publish/extract_layout.py index cac7991f00..57e7957575 100644 --- a/openpype/hosts/unreal/plugins/publish/extract_layout.py +++ b/openpype/hosts/unreal/plugins/publish/extract_layout.py @@ -48,7 +48,7 @@ class ExtractLayout(publish.Extractor): # Search the reference to the Asset Container for the object path = unreal.Paths.get_path(mesh.get_path_name()) filter = unreal.ARFilter( - class_names=["AssetContainer"], package_paths=[path]) + class_names=["AyonAssetContainer"], package_paths=[path]) ar = unreal.AssetRegistryHelpers.get_asset_registry() try: asset_container = ar.get_assets(filter)[0].get_asset() diff --git a/openpype/hosts/unreal/plugins/publish/extract_render.py b/openpype/hosts/unreal/plugins/publish/extract_render.py deleted file mode 100644 index 8ff38fbee0..0000000000 --- a/openpype/hosts/unreal/plugins/publish/extract_render.py +++ /dev/null @@ -1,48 +0,0 @@ -from pathlib import Path - -import unreal - -from openpype.pipeline import publish - - -class ExtractRender(publish.Extractor): - """Extract render.""" - - label = "Extract Render" - hosts = ["unreal"] - families = ["render"] - optional = True - - def process(self, instance): - # Define extract output file path - stagingdir = self.staging_dir(instance) - - # Perform extraction - self.log.info("Performing extraction..") - - # Get the render output directory - project_dir = unreal.Paths.project_dir() - render_dir = (f"{project_dir}/Saved/MovieRenders/" - f"{instance.data['subset']}") - - assert unreal.Paths.directory_exists(render_dir), \ - "Render directory does not exist" - - render_path = Path(render_dir) - - frames = [] - - for x in render_path.iterdir(): - if x.is_file() and x.suffix == '.png': - frames.append(str(x)) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - render_representation = { - 'name': 'png', - 'ext': 'png', - 'files': frames, - "stagingDir": stagingdir, - } - instance.data["representations"].append(render_representation) diff --git a/openpype/hosts/unreal/ue_workers.py b/openpype/hosts/unreal/ue_workers.py index d1740124a8..e7a690ac9c 100644 --- a/openpype/hosts/unreal/ue_workers.py +++ b/openpype/hosts/unreal/ue_workers.py @@ -93,7 +93,7 @@ class UEProjectGenerationWorker(QtCore.QObject): commandlet_cmd = [ f"{ue_editor_exe.as_posix()}", f"{cmdlet_project.as_posix()}", - "-run=OPGenerateProject", + "-run=AyonGenerateProject", f"{project_file.resolve().as_posix()}", ] @@ -286,7 +286,7 @@ class UEPluginInstallWorker(QtCore.QObject): def _build_and_move_plugin(self, plugin_build_path: Path): uat_path: Path = ue_lib.get_path_to_uat(self.engine_path) - src_plugin_dir = Path(self.env.get("OPENPYPE_UNREAL_PLUGIN", "")) + src_plugin_dir = Path(self.env.get("AYON_UNREAL_PLUGIN", "")) if not os.path.isdir(src_plugin_dir): msg = "Path to the integration plugin is null!" @@ -300,7 +300,7 @@ class UEPluginInstallWorker(QtCore.QObject): temp_dir: Path = src_plugin_dir.parent / "Temp" temp_dir.mkdir(exist_ok=True) - uplugin_path: Path = src_plugin_dir / "OpenPype.uplugin" + uplugin_path: Path = src_plugin_dir / "Ayon.uplugin" # in order to successfully build the plugin, # It must be built outside the Engine directory and then moved @@ -332,7 +332,7 @@ class UEPluginInstallWorker(QtCore.QObject): raise RuntimeError(msg) # Copy the contents of the 'Temp' dir into the - # 'OpenPype' directory in the engine + # 'Ayon' directory in the engine dir_util.copy_tree(temp_dir.as_posix(), plugin_build_path.as_posix()) @@ -347,7 +347,7 @@ class UEPluginInstallWorker(QtCore.QObject): dir_util.remove_tree(temp_dir.as_posix()) def run(self): - src_plugin_dir = Path(self.env.get("OPENPYPE_UNREAL_PLUGIN", "")) + src_plugin_dir = Path(self.env.get("AYON_UNREAL_PLUGIN", "")) if not os.path.isdir(src_plugin_dir): msg = "Path to the integration plugin is null!" @@ -356,7 +356,7 @@ class UEPluginInstallWorker(QtCore.QObject): # Create a path to the plugin in the engine op_plugin_path = self.engine_path / "Engine/Plugins/Marketplace" \ - "/OpenPype" + "/Ayon" if not op_plugin_path.is_dir(): self.installing.emit("Installing and building the plugin ...") diff --git a/openpype/modules/base.py b/openpype/modules/base.py index ed1eeb04cd..732525b6eb 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -311,6 +311,7 @@ def _load_modules(): # Look for OpenPype modules in paths defined with `get_module_dirs` # - dynamically imported OpenPype modules and addons module_dirs = get_module_dirs() + # Add current directory at first place # - has small differences in import logic current_dir = os.path.abspath(os.path.dirname(__file__)) @@ -318,8 +319,11 @@ def _load_modules(): module_dirs.insert(0, hosts_dir) module_dirs.insert(0, current_dir) + addons_dir = os.path.join(os.path.dirname(current_dir), "addons") + module_dirs.append(addons_dir) + processed_paths = set() - for dirpath in module_dirs: + for dirpath in frozenset(module_dirs): # Skip already processed paths if dirpath in processed_paths: continue diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 9f35424d42..6daaea5f18 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -378,7 +378,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): existing_tasks.append(task_name_low) for instance in instances_by_task_name[task_name_low]: - instance["ftrackTask"] = child + instance.data["ftrackTask"] = child for task_name in tasks: task_type = tasks[task_name]["type"] diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index f8e56377bb..6e5dd056f3 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -9,7 +9,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "Kitsu Note and Status" - families = ["render", "kitsu"] + families = ["render", "image", "online", "plate", "kitsu"] # status settings set_status_note = False @@ -52,8 +52,9 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): for instance in context: # Check if instance is a review by checking its family # Allow a match to primary family or any of families - families = set([instance.data["family"]] + - instance.data.get("families", [])) + families = set( + [instance.data["family"]] + instance.data.get("families", []) + ) if "review" not in families: continue diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index e05ff05f50..bbed4a3024 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -8,11 +8,10 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.01 label = "Kitsu Review" - families = ["render", "kitsu"] + families = ["render", "image", "online", "plate", "kitsu"] optional = True def process(self, instance): - # Check comment has been created comment_id = instance.data.get("kitsu_comment", {}).get("id") if not comment_id: diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 7a2ef59a5a..d656d58adc 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -1,5 +1,6 @@ from .constants import ( AVALON_CONTAINER_ID, + AYON_CONTAINER_ID, HOST_WORKFILE_EXTENSIONS, ) @@ -99,6 +100,7 @@ uninstall = uninstall_host __all__ = ( "AVALON_CONTAINER_ID", + "AYON_CONTAINER_ID", "HOST_WORKFILE_EXTENSIONS", # --- MongoDB --- diff --git a/openpype/pipeline/constants.py b/openpype/pipeline/constants.py index e6496cbf95..755a5fb380 100644 --- a/openpype/pipeline/constants.py +++ b/openpype/pipeline/constants.py @@ -1,5 +1,5 @@ # Metadata ID of loaded container into scene -AVALON_CONTAINER_ID = "pyblish.avalon.container" +AVALON_CONTAINER_ID = AYON_CONTAINER_ID = "pyblish.avalon.container" # TODO get extensions from host implementations HOST_WORKFILE_EXTENSIONS = { diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index dede2b8fce..ada78b989d 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -35,6 +35,7 @@ from . import ( register_inventory_action_path, register_creator_plugin_path, deregister_loader_plugin_path, + deregister_inventory_action_path, ) @@ -54,6 +55,7 @@ PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") # Global plugin paths PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") def _get_modules_manager(): @@ -158,6 +160,7 @@ def install_openpype_plugins(project_name=None, host_name=None): pyblish.api.register_plugin_path(PUBLISH_PATH) pyblish.api.register_discovery_filter(filter_pyblish_plugins) register_loader_plugin_path(LOAD_PATH) + register_inventory_action_path(INVENTORY_PATH) if host_name is None: host_name = os.environ.get("AVALON_APP") @@ -223,6 +226,7 @@ def uninstall_host(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) pyblish.api.deregister_discovery_filter(filter_pyblish_plugins) deregister_loader_plugin_path(LOAD_PATH) + deregister_inventory_action_path(INVENTORY_PATH) log.info("Global plug-ins unregistred") deregister_host() diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 382bbea05e..2fc0669732 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -23,7 +23,7 @@ from openpype.lib.attribute_definitions import ( get_default_values, ) from openpype.host import IPublishHost, IWorkfileHost -from openpype.pipeline import legacy_io +from openpype.pipeline import legacy_io, Anatomy from openpype.pipeline.plugin_discover import DiscoverResult from .creator_plugins import ( @@ -1383,6 +1383,8 @@ class CreateContext: self._current_task_name = None self._current_workfile_path = None + self._current_project_anatomy = None + self._host_is_valid = host_is_valid # Currently unused variable self.headless = headless @@ -1546,6 +1548,18 @@ class CreateContext: return self._current_workfile_path + def get_current_project_anatomy(self): + """Project anatomy for current project. + + Returns: + Anatomy: Anatomy object ready to be used. + """ + + if self._current_project_anatomy is None: + self._current_project_anatomy = Anatomy( + self._current_project_name) + return self._current_project_anatomy + @property def context_has_changed(self): """Host context has changed. @@ -1568,6 +1582,7 @@ class CreateContext: ) project_name = property(get_current_project_name) + project_anatomy = property(get_current_project_anatomy) @property def log(self): @@ -1680,6 +1695,8 @@ class CreateContext: self._current_task_name = task_name self._current_workfile_path = workfile_path + self._current_project_anatomy = None + def reset_plugins(self, discover_publish_plugins=True): """Reload plugins. diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index bd3fbaf78f..9e47e9cc12 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -231,10 +231,24 @@ class BaseCreator: @property def project_name(self): - """Family that plugin represents.""" + """Current project name. + + Returns: + str: Name of a project. + """ return self.create_context.project_name + @property + def project_anatomy(self): + """Current project anatomy. + + Returns: + Anatomy: Project anatomy object. + """ + + return self.create_context.project_anatomy + @property def host(self): return self.create_context.host diff --git a/openpype/pipeline/workfile/build_workfile.py b/openpype/pipeline/workfile/build_workfile.py index 26b17fa151..8329487839 100644 --- a/openpype/pipeline/workfile/build_workfile.py +++ b/openpype/pipeline/workfile/build_workfile.py @@ -186,7 +186,7 @@ class BuildWorkfile: if link_context_profiles: # Find and append linked assets if preset has set linked mapping - link_assets = get_linked_assets(current_asset_entity) + link_assets = get_linked_assets(project_name, current_asset_entity) if link_assets: assets.extend(link_assets) diff --git a/openpype/plugins/inventory/remove_and_load.py b/openpype/plugins/inventory/remove_and_load.py new file mode 100644 index 0000000000..ae66b95f6e --- /dev/null +++ b/openpype/plugins/inventory/remove_and_load.py @@ -0,0 +1,51 @@ +from openpype.pipeline import InventoryAction +from openpype.pipeline import get_current_project_name +from openpype.pipeline.load.plugins import discover_loader_plugins +from openpype.pipeline.load.utils import ( + get_loader_identifier, + remove_container, + load_container, +) +from openpype.client import get_representation_by_id + + +class RemoveAndLoad(InventoryAction): + """Delete inventory item and reload it.""" + + label = "Remove and load" + icon = "refresh" + + def process(self, containers): + project_name = get_current_project_name() + loaders_by_name = { + get_loader_identifier(plugin): plugin + for plugin in discover_loader_plugins(project_name=project_name) + } + for container in containers: + # Get loader + loader_name = container["loader"] + loader = loaders_by_name.get(loader_name, None) + if not loader: + raise RuntimeError( + "Failed to get loader '{}', can't remove " + "and load container".format(loader_name) + ) + + # Get representation + representation = get_representation_by_id( + project_name, container["representation"] + ) + if not representation: + self.log.warning( + "Skipping remove and load because representation id is not" + " found in database: '{}'".format( + container["representation"] + ) + ) + continue + + # Remove container + remove_container(container) + + # Load container + load_container(loader, representation) diff --git a/openpype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py deleted file mode 100644 index 239008ee21..0000000000 --- a/openpype/plugins/publish/validate_sequence_frames.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -import re - -import clique -import pyblish.api - - -class ValidateSequenceFrames(pyblish.api.InstancePlugin): - """Ensure the sequence of frames is complete - - The files found in the folder are checked against the startFrame and - endFrame of the instance. If the first or last file is not - corresponding with the first or last frame it is flagged as invalid. - - Used regular expression pattern handles numbers in the file names - (eg "Main_beauty.v001.1001.exr", "Main_beauty_v001.1001.exr", - "Main_beauty.1001.1001.exr") but not numbers behind frames (eg. - "Main_beauty.1001.v001.exr") - """ - - order = pyblish.api.ValidatorOrder - label = "Validate Sequence Frames" - families = ["imagesequence", "render"] - hosts = ["shell", "unreal"] - - def process(self, instance): - representations = instance.data.get("representations") - if not representations: - return - for repr in representations: - repr_files = repr["files"] - if isinstance(repr_files, str): - continue - - ext = repr.get("ext") - if not ext: - _, ext = os.path.splitext(repr_files[0]) - elif not ext.startswith("."): - ext = ".{}".format(ext) - pattern = r"\D?(?P(?P0*)\d+){}$".format( - re.escape(ext)) - patterns = [pattern] - - collections, remainder = clique.assemble( - repr_files, minimum_items=1, patterns=patterns) - - assert not remainder, "Must not have remainder" - assert len(collections) == 1, "Must detect single collection" - collection = collections[0] - frames = list(collection.indexes) - - if instance.data.get("slate"): - # Slate is not part of the frame range - frames = frames[1:] - - current_range = (frames[0], frames[-1]) - - required_range = (instance.data["frameStart"], - instance.data["frameEnd"]) - - if current_range != required_range: - raise ValueError(f"Invalid frame range: {current_range} - " - f"expected: {required_range}") - - missing = collection.holes().indexes - assert not missing, "Missing frames: %s" % (missing,) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 50b62737d8..75f335f1de 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -82,7 +82,8 @@ "png": { "ext": "png", "tags": [ - "ftrackreview" + "ftrackreview", + "kitsureview" ], "burnins": [], "ffmpeg_args": { diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d59cdf8c4a..a757e08ef5 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -19,5 +19,12 @@ "custFloats": "custFloats", "custVecs": "custVecs" } + }, + "publish": { + "ValidateFrameRange": { + "enabled": true, + "optional": true, + "active": true + } } } diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 72b330ce7a..a2a43eefb5 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -734,6 +734,7 @@ "ValidateShaderName": { "enabled": false, "optional": true, + "active": true, "regex": "(?P.*)_(.*)_SHD" }, "ValidateShadingEngine": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 4fba9aff0a..42506559d0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -73,6 +73,10 @@ } } ] + }, + { + "type": "schema", + "name": "schema_max_publish" } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json new file mode 100644 index 0000000000..ea08c735a6 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -0,0 +1,33 @@ +{ + "type": "dict", + "collapsible": true, + "key": "publish", + "label": "Publish plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateFrameRange", + "label": "Validate Frame Range", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + } + ] + } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 346948c658..07c8d8715b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -126,6 +126,11 @@ "key": "optional", "label": "Optional" }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, { "type": "label", "label": "Shader name regex can use named capture group asset to validate against current asset name.

Example:
^.*(?P=<asset>.+)_SHD

" diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 3279be6094..73d33392b9 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -791,7 +791,7 @@ class SceneInventoryView(QtWidgets.QTreeView): else: version_str = version - dialog = QtWidgets.QMessageBox() + dialog = QtWidgets.QMessageBox(self) dialog.setIcon(QtWidgets.QMessageBox.Warning) dialog.setStyleSheet(style.load_stylesheet()) dialog.setWindowTitle("Update failed") diff --git a/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py b/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py index 6f6d0b5715..8ab621f757 100644 --- a/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py +++ b/openpype/vendor/python/common/scriptsmenu/scriptsmenu.py @@ -19,9 +19,9 @@ class ScriptsMenu(QtWidgets.QMenu): Args: title (str): the name of the root menu which will be created - + parent (QtWidgets.QObject) : the QObject to parent the menu to - + Returns: None @@ -94,7 +94,7 @@ class ScriptsMenu(QtWidgets.QMenu): parent(QtWidgets.QWidget): the object to parent the menu to title(str): the title of the menu - + Returns: QtWidget.QMenu instance """ @@ -111,7 +111,7 @@ class ScriptsMenu(QtWidgets.QMenu): return menu def add_script(self, parent, title, command, sourcetype, icon=None, - tags=None, label=None, tooltip=None): + tags=None, label=None, tooltip=None, shortcut=None): """Create an action item which runs a script when clicked Args: @@ -134,6 +134,8 @@ class ScriptsMenu(QtWidgets.QMenu): tooltip (str): A tip for the user about the usage fo the tool + shortcut (str): A shortcut to run the command + Returns: QtWidget.QAction instance @@ -166,6 +168,9 @@ class ScriptsMenu(QtWidgets.QMenu): raise RuntimeError("Script action can't be " "processed: {}".format(e)) + if shortcut: + script_action.setShortcut(shortcut) + if icon: iconfile = os.path.expandvars(icon) script_action.iconfile = iconfile @@ -253,7 +258,7 @@ class ScriptsMenu(QtWidgets.QMenu): def _update_search(self, search): """Hide all the samples which do not match the user's import - + Returns: None diff --git a/openpype/version.py b/openpype/version.py index dc0a3a8c9f..319a58d384 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.6" +__version__ = "3.15.7-nightly.3" diff --git a/tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py b/tests/unit/openpype/hosts/unreal/plugins/publish/test_validate_sequence_frames.py similarity index 100% rename from tests/unit/openpype/plugins/publish/test_validate_sequence_frames.py rename to tests/unit/openpype/hosts/unreal/plugins/publish/test_validate_sequence_frames.py