From 1ff9e23e5d7342f6715e4e67bad2f7196a723635 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Dec 2022 15:07:46 +0100 Subject: [PATCH 1/4] Draft implementation for Natron integration --- openpype/hosts/natron/__init__.py | 10 ++ openpype/hosts/natron/addon.py | 31 ++++ openpype/hosts/natron/api/__init__.py | 16 ++ openpype/hosts/natron/api/pipeline.py | 138 ++++++++++++++++++ openpype/hosts/natron/startup/init.py | 12 ++ openpype/hosts/natron/startup/initGui.py | 55 +++++++ openpype/resources/app_icons/natron.png | Bin 0 -> 13734 bytes .../system_settings/applications.json | 27 ++++ openpype/settings/entities/enum_entity.py | 1 + .../host_settings/schema_natron.json | 40 +++++ .../system_schema/schema_applications.json | 4 + 11 files changed, 334 insertions(+) create mode 100644 openpype/hosts/natron/__init__.py create mode 100644 openpype/hosts/natron/addon.py create mode 100644 openpype/hosts/natron/api/__init__.py create mode 100644 openpype/hosts/natron/api/pipeline.py create mode 100644 openpype/hosts/natron/startup/init.py create mode 100644 openpype/hosts/natron/startup/initGui.py create mode 100644 openpype/resources/app_icons/natron.png create mode 100644 openpype/settings/entities/schemas/system_schema/host_settings/schema_natron.json diff --git a/openpype/hosts/natron/__init__.py b/openpype/hosts/natron/__init__.py new file mode 100644 index 00000000000..24a5bcba105 --- /dev/null +++ b/openpype/hosts/natron/__init__.py @@ -0,0 +1,10 @@ +from .addon import ( + NatronAddon, + NATRON_HOST_DIR, +) + + +__all__ = ( + "NatronAddon", + "NATRON_HOST_DIR" +) diff --git a/openpype/hosts/natron/addon.py b/openpype/hosts/natron/addon.py new file mode 100644 index 00000000000..563dd95bd80 --- /dev/null +++ b/openpype/hosts/natron/addon.py @@ -0,0 +1,31 @@ +import os +from openpype.modules import OpenPypeModule, IHostAddon + + +NATRON_HOST_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class NatronAddon(OpenPypeModule, IHostAddon): + name = "natron" + host_name = "natron" + + def initialize(self, module_settings): + self.enabled = True + + def add_implementation_envs(self, env, _app): + # Add requirements to natron startup + startup_path = os.path.join(NATRON_HOST_DIR, "startup") + if env.get("NATRON_PLUGIN_PATH"): + startup_path += os.pathsep + env["NATRON_PLUGIN_PATH"] + + env["NATRON_PLUGIN_PATH"] = startup_path + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(NATRON_HOST_DIR, "hooks") + ] + + def get_workfile_extensions(self): + return [".gfr"] diff --git a/openpype/hosts/natron/api/__init__.py b/openpype/hosts/natron/api/__init__.py new file mode 100644 index 00000000000..071620a6a9a --- /dev/null +++ b/openpype/hosts/natron/api/__init__.py @@ -0,0 +1,16 @@ +from .pipeline import ( + NatronHost, + + imprint_container, + + get_app + +) + +__all__ = [ + "NatronHost", + + "imprint_container", + + "get_app" +] diff --git a/openpype/hosts/natron/api/pipeline.py b/openpype/hosts/natron/api/pipeline.py new file mode 100644 index 00000000000..c53f6757a94 --- /dev/null +++ b/openpype/hosts/natron/api/pipeline.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +"""Pipeline tools for OpenPype Gaffer integration.""" +import os +import logging + +from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost + +import pyblish.api + +from openpype.pipeline import ( + register_creator_plugin_path, + register_loader_plugin_path, + AVALON_CONTAINER_ID +) +from openpype.hosts.natron import NATRON_HOST_DIR + +log = logging.getLogger("openpype.hosts.natron") + +PLUGINS_DIR = os.path.join(NATRON_HOST_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + + +def get_app(): + """Return first available Natron app instance?""" + # TODO: Implement + return None + + +class NatronHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): + name = "natron" + + def __init__(self): + super(NatronHost, self).__init__() + self._has_been_setup = False + + def install(self): + pyblish.api.register_host("natron") + + pyblish.api.register_plugin_path(PUBLISH_PATH) + register_loader_plugin_path(LOAD_PATH) + register_creator_plugin_path(CREATE_PATH) + + log.info("Installing callbacks ... ") + # register_event_callback("init", on_init) + # self._register_callbacks() + # register_event_callback("before.save", before_save) + # register_event_callback("save", on_save) + # register_event_callback("open", on_open) + # register_event_callback("new", on_new) + + # pyblish.api.register_callback( + # "instanceToggled", on_pyblish_instance_toggled + # ) + + self._has_been_setup = True + + def has_unsaved_changes(self): + # TODO: Implement + return False + + def get_workfile_extensions(self): + return [".ntp"] + + def save_workfile(self, dst_path=None): + if not dst_path: + dst_path = self.get_current_workfile() + + app = get_app() + return app.saveProjectAs(dst_path) + + def open_workfile(self, filepath): + + if not os.path.exists(filepath): + raise RuntimeError("File does not exist: {}".format(filepath)) + + app = get_app() + app.loadProject(filepath) + return filepath + + def get_current_workfile(self): + app = get_app() + return app.getProjectParam("projectPath").getValue() + + def get_containers(self): + return [] + + @staticmethod + def create_context_node(): + pass + + def update_context_data(self, data, changes): + pass + + def get_context_data(self): + pass + + +def imprint_container(node, + name, + namespace, + context, + loader=None): + """Imprint a Loader with metadata + + Containerisation enables a tracking of version, author and origin + for loaded assets. + + Arguments: + tool (object): The node in Fusion to imprint as container, usually a + Loader. + name (str): Name of resulting assembly + namespace (str): Namespace under which to host container + context (dict): Asset information + loader (str, optional): Name of loader used to produce this container. + + Returns: + None + + """ + + data = [ + ("schema", "openpype:container-2.0"), + ("id", AVALON_CONTAINER_ID), + ("name", str(name)), + ("namespace", str(namespace)), + ("loader", str(loader)), + ("representation", str(context["representation"]["_id"])), + ] + + imprint(node, data) + + +def imprint(node, data, section="OpenPype"): + # TODO: Implement + pass diff --git a/openpype/hosts/natron/startup/init.py b/openpype/hosts/natron/startup/init.py new file mode 100644 index 00000000000..c1d4be49811 --- /dev/null +++ b/openpype/hosts/natron/startup/init.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +"""OpenPype startup script for Natron.""" +from openpype.pipeline import install_host +from openpype.hosts.natron.api import NatronHost + + +def _install_openpype(): + print("Installing OpenPype ...") + install_host(NatronHost()) + + +_install_openpype() diff --git a/openpype/hosts/natron/startup/initGui.py b/openpype/hosts/natron/startup/initGui.py new file mode 100644 index 00000000000..0152d78f991 --- /dev/null +++ b/openpype/hosts/natron/startup/initGui.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +"""OpenPype startup script for Natron in GUI mode.""" +from openpype.tools.utils import host_tools + +import NatronGui + + +def get_main_window(): + # TODO: Find a way to reliably find the main window + return None + + +# Menu commands to be called by Natron must be global functions in init.py +# (or must at least be available to globals?) +def openpype_show_loader(): + print("Show loader..") + host_tools.show_loader(parent=get_main_window(), + use_context=True) + + +def openpype_show_publisher(): + host_tools.show_publisher(parent=get_main_window()) + + +def openpype_show_scene_inventory(): + host_tools.show_scene_inventory(parent=get_main_window()) + + +def openpype_show_library_loader(): + host_tools.show_library_loader(parent=get_main_window()) + + +def openpype_show_workfiles(): + host_tools.show_workfiles(parent=get_main_window()) + + +def _install_openpype_menu(): + from openpype.tools.utils import host_tools + + add_menu = NatronGui.natron.addMenuCommand + + # Add a custom menu entry with a shortcut to create our icon viewer + add_menu("OpenPype/Load...", "openpype_show_loader()") + add_menu("OpenPype/Publish...", "openpype_show_publisher()") + add_menu("OpenPype/Manage...", "openpype_show_scene_inventory()") + add_menu("OpenPype/Library...", "openpype_show_library_loader()") + # todo: how to add a divider? + #add_menu("OpenPype/---", "") + add_menu("OpenPype/Work Files...", "openpype_show_workfiles()") + + def get_main_window(app): + raise NotImplementedError("TODO") + + +_install_openpype_menu() diff --git a/openpype/resources/app_icons/natron.png b/openpype/resources/app_icons/natron.png new file mode 100644 index 0000000000000000000000000000000000000000..123a9847cbcdcb63e7feee8e2fb2671817b053de GIT binary patch literal 13734 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%0D0IqIW^lgUBDu;`7eZV%Ob?t% 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 Pyj+t^vd+kpB literal 0 HcmV?d00001 diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 936407a49b9..65b763cab18 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -932,6 +932,33 @@ } } }, + "natron": { + "enabled": true, + "label": "Natron", + "icon": "{}/app_icons/natron.png", + "host_name": "natron", + "environment": {}, + "variants": { + "2-5-0": { + "executables": { + "windows": [ + "C:\\Natron-2.5.0-Windows-x86_64\\bin\\Natron.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": {} + }, + "__dynamic_keys_labels__": { + "2-5-0": "2.5.0" + } + } + }, "houdini": { "enabled": true, "label": "Houdini", diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index c0c103ea10e..d01ef420402 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -162,6 +162,7 @@ class HostsEnumEntity(BaseEnumEntity): "hiero", "houdini", "maya", + "natron", "nuke", "photoshop", "resolve", diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_natron.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_natron.json new file mode 100644 index 00000000000..85e678415c7 --- /dev/null +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_natron.json @@ -0,0 +1,40 @@ +{ + "type": "dict", + "key": "natron", + "label": "Natron", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema_template", + "name": "template_host_unchangables" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json" + }, + { + "type": "dict-modifiable", + "key": "variants", + "collapsible_key": true, + "use_label_wrap": false, + "object_type": { + "type": "dict", + "collapsible": true, + "children": [ + { + "type": "schema_template", + "name": "template_host_variant_items", + "skip_paths": ["use_python_2"] + } + ] + } + } + ] +} diff --git a/openpype/settings/entities/schemas/system_schema/schema_applications.json b/openpype/settings/entities/schemas/system_schema/schema_applications.json index 36c58114965..82c36882bcf 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_applications.json +++ b/openpype/settings/entities/schemas/system_schema/schema_applications.json @@ -57,6 +57,10 @@ "type": "schema", "name": "schema_resolve" }, + { + "type": "schema", + "name": "schema_natron" + }, { "type": "schema", "name": "schema_houdini" From 4a1fe8a640cf55948fcff3a9aacc4d6cb1c2ec1d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Dec 2022 15:11:53 +0100 Subject: [PATCH 2/4] Add placeholder actions (to be completed) --- openpype/hosts/natron/plugins/load/actions.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 openpype/hosts/natron/plugins/load/actions.py diff --git a/openpype/hosts/natron/plugins/load/actions.py b/openpype/hosts/natron/plugins/load/actions.py new file mode 100644 index 00000000000..1005822a936 --- /dev/null +++ b/openpype/hosts/natron/plugins/load/actions.py @@ -0,0 +1,80 @@ +"""A module containing generic loader actions that will display in the Loader. + +""" + +from openpype.pipeline import load + + +class NatronSetFrameRangeLoader(load.LoaderPlugin): + """Set frame range excluding pre- and post-handles""" + + families = ["animation", + "camera", + "imagesequence", + "yeticache", + "pointcache", + "render"] + representations = ["*"] + + label = "Set frame range" + order = 11 + icon = "clock-o" + color = "white" + + def load(self, context, name, namespace, data): + + from openpype.hosts.natron.api import get_app + + version = context['version'] + version_data = version.get("data", {}) + + start = version_data.get("frameStart", None) + end = version_data.get("frameEnd", None) + + if start is None or end is None: + print("Skipping setting frame range because start or " + "end frame data is missing..") + return + + app = get_app() + app.getProjectParam('frameRange').set(int(start), int(end)) + + +class NatronSetFrameRangeWithHandlesLoader(load.LoaderPlugin): + """Set frame range including pre- and post-handles""" + + families = ["animation", + "camera", + "imagesequence", + "yeticache", + "pointcache", + "render"] + representations = ["*"] + + label = "Set frame range (with handles)" + order = 12 + icon = "clock-o" + color = "white" + + def load(self, context, name, namespace, data): + + from openpype.hosts.natron.api import get_app + + version = context['version'] + version_data = version.get("data", {}) + + start = version_data.get("frameStart", None) + end = version_data.get("frameEnd", None) + + if start is None or end is None: + print("Skipping setting frame range because start or " + "end frame data is missing..") + return + + # Include handles + handles = version_data.get("handles", 0) + start -= handles + end += handles + + app = get_app() + app.getProjectParam('frameRange').set(int(start), int(end)) From 3d3c0dee8c4cd5fe6df8198d02cfd5a6f37edff5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Dec 2022 15:17:44 +0100 Subject: [PATCH 3/4] Implement placeholder draft for loading image --- .../hosts/natron/plugins/load/load_image.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 openpype/hosts/natron/plugins/load/load_image.py diff --git a/openpype/hosts/natron/plugins/load/load_image.py b/openpype/hosts/natron/plugins/load/load_image.py new file mode 100644 index 00000000000..f51b1146b8d --- /dev/null +++ b/openpype/hosts/natron/plugins/load/load_image.py @@ -0,0 +1,64 @@ +import os + +from openpype.pipeline import ( + load, + get_representation_path, +) +from openpype.hosts.natron.api import get_app, imprint_container + + +class NatronLoadImage(load.LoaderPlugin): + """Load Image or Image sequence""" + + families = ["imagesequence", "review", "render", "plate"] + representations = ["*"] + + label = "Load sequence" + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, data): + # Create the Loader with the filename path set + app = get_app() + node = app.createNode("fr.inria.openfx.ReadOIIO") + + path = self._convert_path(self.fname) + node.getParam("filename").setValue(path) + + imprint_container(node, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__) + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + + path = get_representation_path(representation) + path = self._convert_path(path) + + node = container["_node"] + node.getParam("filename").setValue(path) + + # Update the imprinted representation + node.getParam("openpype.representation").setValue( + str(representation["_id"]) + ) + + def remove(self, container): + node = container["_node"] + + parent = node.parent() + parent.removeChild(node) + + def _convert_path(self, path): + root = os.path.dirname(path) + fname = os.path.basename(path) + + # TODO: Actually detect whether it's a sequence. And support _ too. + prefix, padding, suffix = fname.rsplit(".", 2) + fname = ".".join([prefix, "#" * len(padding), suffix]) + return os.path.join(root, fname).replace("\\", "/") From aa11798c7b706fe16b50222445971855a397cc2e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Dec 2022 15:28:25 +0100 Subject: [PATCH 4/4] Cleanup code --- openpype/hosts/natron/startup/initGui.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/natron/startup/initGui.py b/openpype/hosts/natron/startup/initGui.py index 0152d78f991..f802ee6a213 100644 --- a/openpype/hosts/natron/startup/initGui.py +++ b/openpype/hosts/natron/startup/initGui.py @@ -35,7 +35,6 @@ def openpype_show_workfiles(): def _install_openpype_menu(): - from openpype.tools.utils import host_tools add_menu = NatronGui.natron.addMenuCommand @@ -45,11 +44,8 @@ def _install_openpype_menu(): add_menu("OpenPype/Manage...", "openpype_show_scene_inventory()") add_menu("OpenPype/Library...", "openpype_show_library_loader()") # todo: how to add a divider? - #add_menu("OpenPype/---", "") + # add_menu("OpenPype/---", "") add_menu("OpenPype/Work Files...", "openpype_show_workfiles()") - def get_main_window(app): - raise NotImplementedError("TODO") - _install_openpype_menu()