From e629e40b4f95718e94de5c63180c38251c3c8e54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 4 Mar 2022 18:58:33 +0100 Subject: [PATCH 01/67] created base of event system --- openpype/pipeline/__init__.py | 8 ++ openpype/pipeline/events.py | 221 ++++++++++++++++++++++++++++++ openpype/pipeline/lib/__init__.py | 8 -- openpype/pipeline/lib/events.py | 51 ------- 4 files changed, 229 insertions(+), 59 deletions(-) create mode 100644 openpype/pipeline/events.py delete mode 100644 openpype/pipeline/lib/events.py diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index e968df4011..673608bded 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -1,5 +1,10 @@ from .lib import attribute_definitions +from .events import ( + emit_event, + register_event_callback +) + from .create import ( BaseCreator, Creator, @@ -17,6 +22,9 @@ from .publish import ( __all__ = ( "attribute_definitions", + "emit_event", + "register_event_callback", + "BaseCreator", "Creator", "AutoCreator", diff --git a/openpype/pipeline/events.py b/openpype/pipeline/events.py new file mode 100644 index 0000000000..cae8b250f7 --- /dev/null +++ b/openpype/pipeline/events.py @@ -0,0 +1,221 @@ +"""Events holding data about specific event.""" +import os +import re +import inspect +import logging +import weakref +from uuid import uuid4 +try: + from weakref import WeakMethod +except Exception: + from .python_2_comp import WeakMethod + + +class EventCallback(object): + def __init__(self, topic, func_ref, func_name, func_path): + self._topic = topic + # Replace '*' with any character regex and escape rest of text + # - when callback is registered for '*' topic it will receive all + # events + # - it is possible to register to a partial topis 'my.event.*' + # - it will receive all matching event topics + # e.g. 'my.event.start' and 'my.event.end' + topic_regex_str = "^{}$".format( + ".+".join( + re.escape(part) + for part in topic.split("*") + ) + ) + topic_regex = re.compile(topic_regex_str) + self._topic_regex = topic_regex + self._func_ref = func_ref + self._func_name = func_name + self._func_path = func_path + self._ref_valid = True + self._enabled = True + + self._log = None + + def __repr__(self): + return "< {} - {} > {}".format( + self.__class__.__name__, self._func_name, self._func_path + ) + + @property + def log(self): + if self._log is None: + self._log = logging.getLogger(self.__class__.__name__) + return self._log + + @property + def is_ref_valid(self): + return self._ref_valid + + def validate_ref(self): + if not self._ref_valid: + return + + callback = self._func_ref() + if not callback: + self._ref_valid = False + + @property + def enabled(self): + """Is callback enabled.""" + return self._enabled + + def set_enabled(self, enabled): + """Change if callback is enabled.""" + self._enabled = enabled + + def deregister(self): + """Calling this funcion will cause that callback will be removed.""" + # Fake reference + self._ref_valid = False + + def topic_matches(self, topic): + """Check if event topic matches callback's topic.""" + return self._topic_regex.match(topic) + + def process_event(self, event): + """Process event. + + Args: + event(Event): Event that was triggered. + """ + # Skip if callback is not enabled or has invalid reference + if not self._ref_valid or not self._enabled: + return + + # Get reference + callback = self._func_ref() + # Check if reference is valid or callback's topic matches the event + if not callback: + # Change state if is invalid so the callback is removed + self._ref_valid = False + + elif self.topic_matches(event.topic): + # Try execute callback + sig = inspect.signature(callback) + try: + if len(sig.parameters) == 0: + callback() + else: + callback(event) + except Exception: + self.log.warning( + "Failed to execute event callback {}".format( + str(repr(self)) + ), + exc_info=True + ) + + +# Inherit from 'object' for Python 2 hosts +class Event(object): + """Base event object. + + Can be used to anything because data are not much specific. Only required + argument is topic which defines why event is happening and may be used for + filtering. + + Arg: + topic (str): Identifier of event. + data (Any): Data specific for event. Dictionary is recommended. + """ + _data = {} + + def __init__(self, topic, data=None, source=None): + self._id = str(uuid4()) + self._topic = topic + if data is None: + data = {} + self._data = data + self._source = source + + def __getitem__(self, key): + return self._data[key] + + def get(self, key, *args, **kwargs): + return self._data.get(key, *args, **kwargs) + + @property + def id(self): + return self._id + + @property + def source(self): + return self._source + + @property + def data(self): + return self._data + + @property + def topic(self): + return self._topic + + def emit(self): + """Emit event and trigger callbacks.""" + StoredCallbacks.emit_event(self) + + +class StoredCallbacks: + _registered_callbacks = [] + + @classmethod + def add_callback(cls, topic, callback): + # Convert callback into references + # - deleted functions won't cause crashes + if inspect.ismethod(callback): + ref = WeakMethod(callback) + elif callable(callback): + ref = weakref.ref(callback) + else: + # TODO add logs + return + + function_name = callback.__name__ + function_path = os.path.abspath(inspect.getfile(callback)) + callback = EventCallback(topic, ref, function_name, function_path) + cls._registered_callbacks.append(callback) + return callback + + @classmethod + def validate(cls): + invalid_callbacks = [] + for callbacks in cls._registered_callbacks: + for callback in tuple(callbacks): + callback.validate_ref() + if not callback.is_ref_valid: + invalid_callbacks.append(callback) + + for callback in invalid_callbacks: + cls._registered_callbacks.remove(callback) + + @classmethod + def emit_event(cls, event): + invalid_callbacks = [] + for callback in cls._registered_callbacks: + callback.process_event() + if not callback.is_ref_valid: + invalid_callbacks.append(callback) + + for callback in invalid_callbacks: + cls._registered_callbacks.remove(callback) + + +def register_event_callback(topic, callback): + """Add callback that will be executed on specific topic.""" + return StoredCallbacks.add_callback(topic, callback) + + +def emit_event(topic, data=None, source=None): + """Emit event with topic and data. + + Returns: + Event: Object of event that was emitted. + """ + event = Event(topic, data, source) + event.emit() + return event diff --git a/openpype/pipeline/lib/__init__.py b/openpype/pipeline/lib/__init__.py index ed38889c66..f762c4205d 100644 --- a/openpype/pipeline/lib/__init__.py +++ b/openpype/pipeline/lib/__init__.py @@ -1,8 +1,3 @@ -from .events import ( - BaseEvent, - BeforeWorkfileSave -) - from .attribute_definitions import ( AbtractAttrDef, @@ -20,9 +15,6 @@ from .attribute_definitions import ( __all__ = ( - "BaseEvent", - "BeforeWorkfileSave", - "AbtractAttrDef", "UIDef", diff --git a/openpype/pipeline/lib/events.py b/openpype/pipeline/lib/events.py deleted file mode 100644 index 05dea20e8c..0000000000 --- a/openpype/pipeline/lib/events.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Events holding data about specific event.""" - - -# Inherit from 'object' for Python 2 hosts -class BaseEvent(object): - """Base event object. - - Can be used to anything because data are not much specific. Only required - argument is topic which defines why event is happening and may be used for - filtering. - - Arg: - topic (str): Identifier of event. - data (Any): Data specific for event. Dictionary is recommended. - """ - _data = {} - - def __init__(self, topic, data=None): - self._topic = topic - if data is None: - data = {} - self._data = data - - @property - def data(self): - return self._data - - @property - def topic(self): - return self._topic - - @classmethod - def emit(cls, *args, **kwargs): - """Create object of event and emit. - - Args: - Same args as '__init__' expects which may be class specific. - """ - from avalon import pipeline - - obj = cls(*args, **kwargs) - pipeline.emit(obj.topic, [obj]) - return obj - - -class BeforeWorkfileSave(BaseEvent): - """Before workfile changes event data.""" - def __init__(self, filename, workdir): - super(BeforeWorkfileSave, self).__init__("before.workfile.save") - self.filename = filename - self.workdir_path = workdir From 9c111fa9d448b9371807f7812c03d21c4aa5b6f8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 4 Mar 2022 19:01:31 +0100 Subject: [PATCH 02/67] use new event system in openpype --- openpype/__init__.py | 7 +-- openpype/hosts/aftereffects/api/pipeline.py | 3 +- openpype/hosts/blender/api/pipeline.py | 25 ++++++---- openpype/hosts/harmony/api/pipeline.py | 5 +- openpype/hosts/hiero/api/events.py | 4 +- openpype/hosts/hiero/api/menu.py | 2 +- openpype/hosts/houdini/api/pipeline.py | 30 +++++++----- openpype/hosts/maya/api/pipeline.py | 49 ++++++++++--------- openpype/hosts/nuke/api/pipeline.py | 7 +-- openpype/hosts/photoshop/api/pipeline.py | 4 +- .../hosts/tvpaint/api/communication_server.py | 6 +-- openpype/hosts/tvpaint/api/pipeline.py | 5 +- openpype/hosts/webpublisher/api/__init__.py | 5 -- openpype/tools/loader/app.py | 5 +- openpype/tools/workfiles/app.py | 16 ++++-- 15 files changed, 97 insertions(+), 76 deletions(-) diff --git a/openpype/__init__.py b/openpype/__init__.py index 11b563ebfe..9175727a54 100644 --- a/openpype/__init__.py +++ b/openpype/__init__.py @@ -10,8 +10,9 @@ from .lib import ( Anatomy, filter_pyblish_plugins, set_plugin_attributes_from_settings, - change_timer_to_current_context + change_timer_to_current_context, ) +from .pipeline import register_event_callback pyblish = avalon = _original_discover = None @@ -122,10 +123,10 @@ def install(): avalon.discover = patched_discover pipeline.discover = patched_discover - avalon.on("taskChanged", _on_task_change) + register_event_callback("taskChanged", _on_task_change) -def _on_task_change(*args): +def _on_task_change(): change_timer_to_current_context() diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index 94f1e3d105..6f7cd8c46d 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -10,6 +10,7 @@ from avalon import io, pipeline from openpype import lib from openpype.api import Logger import openpype.hosts.aftereffects +from openpype.pipeline import register_event_callback from .launch_logic import get_stub @@ -73,7 +74,7 @@ def install(): "instanceToggled", on_pyblish_instance_toggled ) - avalon.api.on("application.launched", application_launch) + register_event_callback("application.launched", application_launch) def uninstall(): diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 6da0ba3dcb..38312316cc 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -15,6 +15,10 @@ from avalon import io, schema from avalon.pipeline import AVALON_CONTAINER_ID from openpype.api import Logger +from openpype.pipeline import ( + register_event_callback, + emit_event +) import openpype.hosts.blender HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__)) @@ -50,8 +54,9 @@ def install(): lib.append_user_scripts() - avalon.api.on("new", on_new) - avalon.api.on("open", on_open) + register_event_callback("new", on_new) + register_event_callback("open", on_open) + _register_callbacks() _register_events() @@ -113,22 +118,22 @@ def set_start_end_frames(): scene.render.resolution_y = resolution_y -def on_new(arg1, arg2): +def on_new(): set_start_end_frames() -def on_open(arg1, arg2): +def on_open(): set_start_end_frames() @bpy.app.handlers.persistent def _on_save_pre(*args): - avalon.api.emit("before_save", args) + emit_event("before.save") @bpy.app.handlers.persistent def _on_save_post(*args): - avalon.api.emit("save", args) + emit_event("save") @bpy.app.handlers.persistent @@ -136,9 +141,9 @@ def _on_load_post(*args): # Detect new file or opening an existing file if bpy.data.filepath: # Likely this was an open operation since it has a filepath - avalon.api.emit("open", args) + emit_event("open") else: - avalon.api.emit("new", args) + emit_event("new") ops.OpenFileCacher.post_load() @@ -169,7 +174,7 @@ def _register_callbacks(): log.info("Installed event handler _on_load_post...") -def _on_task_changed(*args): +def _on_task_changed(): """Callback for when the task in the context is changed.""" # TODO (jasper): Blender has no concept of projects or workspace. @@ -186,7 +191,7 @@ def _on_task_changed(*args): def _register_events(): """Install callbacks for specific events.""" - avalon.api.on("taskChanged", _on_task_changed) + register_event_callback("taskChanged", _on_task_changed) log.info("Installed event callback for 'taskChanged'...") diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index 17d2870876..a94d30210e 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -9,6 +9,7 @@ import avalon.api from avalon.pipeline import AVALON_CONTAINER_ID from openpype import lib +from openpype.pipeline import register_event_callback import openpype.hosts.harmony import openpype.hosts.harmony.api as harmony @@ -129,7 +130,7 @@ def check_inventory(): harmony.send({"function": "PypeHarmony.message", "args": msg}) -def application_launch(): +def application_launch(event): """Event that is executed after Harmony is launched.""" # FIXME: This is breaking server <-> client communication. # It is now moved so it it manually called. @@ -187,7 +188,7 @@ def install(): "instanceToggled", on_pyblish_instance_toggled ) - avalon.api.on("application.launched", application_launch) + register_event_callback("application.launched", application_launch) def uninstall(): diff --git a/openpype/hosts/hiero/api/events.py b/openpype/hosts/hiero/api/events.py index 7563503593..6e2580ed8c 100644 --- a/openpype/hosts/hiero/api/events.py +++ b/openpype/hosts/hiero/api/events.py @@ -1,7 +1,7 @@ import os import hiero.core.events -import avalon.api as avalon from openpype.api import Logger +from openpype.pipeline import register_event_callback from .lib import ( sync_avalon_data_to_workfile, launch_workfiles_app, @@ -126,5 +126,5 @@ def register_events(): """ # if task changed then change notext of hiero - avalon.on("taskChanged", update_menu_task_label) + register_event_callback("taskChanged", update_menu_task_label) log.info("Installed event callback for 'taskChanged'..") diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index 306bef87ca..de20b86f30 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -14,7 +14,7 @@ self = sys.modules[__name__] self._change_context_menu = None -def update_menu_task_label(*args): +def update_menu_task_label(): """Update the task label in Avalon menu to current session""" object_name = self._change_context_menu diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 1c08e72d65..86c85ad3a1 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -11,6 +11,10 @@ import avalon.api from avalon.pipeline import AVALON_CONTAINER_ID from avalon.lib import find_submodule +from openpype.pipeline import ( + register_event_callback, + emit_event +) import openpype.hosts.houdini from openpype.hosts.houdini.api import lib @@ -51,11 +55,11 @@ def install(): avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH) log.info("Installing callbacks ... ") - # avalon.on("init", on_init) - avalon.api.before("save", before_save) - avalon.api.on("save", on_save) - avalon.api.on("open", on_open) - avalon.api.on("new", on_new) + # register_event_callback("init", on_init) + 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 @@ -101,13 +105,13 @@ def _register_callbacks(): def on_file_event_callback(event): if event == hou.hipFileEventType.AfterLoad: - avalon.api.emit("open", [event]) + emit_event("open") elif event == hou.hipFileEventType.AfterSave: - avalon.api.emit("save", [event]) + emit_event("save") elif event == hou.hipFileEventType.BeforeSave: - avalon.api.emit("before_save", [event]) + emit_event("before_save") elif event == hou.hipFileEventType.AfterClear: - avalon.api.emit("new", [event]) + emit_event("new") def get_main_window(): @@ -229,11 +233,11 @@ def ls(): yield data -def before_save(*args): +def before_save(): return lib.validate_fps() -def on_save(*args): +def on_save(): log.info("Running callback on save..") @@ -242,7 +246,7 @@ def on_save(*args): lib.set_id(node, new_id, overwrite=False) -def on_open(*args): +def on_open(): if not hou.isUIAvailable(): log.debug("Batch mode detected, ignoring `on_open` callbacks..") @@ -279,7 +283,7 @@ def on_open(*args): dialog.show() -def on_new(_): +def on_new(): """Set project resolution and fps when create a new file""" if hou.hipFile.isLoadingHipFile(): diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 1b3bb9feb3..05db1b7b26 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -2,7 +2,6 @@ import os import sys import errno import logging -import contextlib from maya import utils, cmds, OpenMaya import maya.api.OpenMaya as om @@ -16,6 +15,10 @@ from avalon.pipeline import AVALON_CONTAINER_ID import openpype.hosts.maya from openpype.tools.utils import host_tools from openpype.lib import any_outdated +from openpype.pipeline import ( + register_event_callback, + emit_event +) from openpype.lib.path_tools import HostDirmap from openpype.hosts.maya.lib import copy_workspace_mel from . import menu, lib @@ -55,7 +58,7 @@ def install(): log.info(PUBLISH_PATH) log.info("Installing callbacks ... ") - avalon.api.on("init", on_init) + register_event_callback("init", on_init) # Callbacks below are not required for headless mode, the `init` however # is important to load referenced Alembics correctly at rendertime. @@ -69,12 +72,12 @@ def install(): menu.install() - avalon.api.on("save", on_save) - avalon.api.on("open", on_open) - avalon.api.on("new", on_new) - avalon.api.before("save", on_before_save) - avalon.api.on("taskChanged", on_task_changed) - avalon.api.on("before.workfile.save", before_workfile_save) + register_event_callback("save", on_save) + register_event_callback("open", on_open) + register_event_callback("new", on_new) + register_event_callback("before.save", on_before_save) + register_event_callback("taskChanged", on_task_changed) + register_event_callback("before.workfile.save", before_workfile_save) def _set_project(): @@ -137,7 +140,7 @@ def _register_callbacks(): def _on_maya_initialized(*args): - avalon.api.emit("init", args) + emit_event("init") if cmds.about(batch=True): log.warning("Running batch mode ...") @@ -147,16 +150,16 @@ def _on_maya_initialized(*args): lib.get_main_window() -def _on_scene_new(*args): - avalon.api.emit("new", args) +def _on_scene_new(): + emit_event("new") -def _on_scene_save(*args): - avalon.api.emit("save", args) +def _on_scene_save(): + emit_event("save") -def _on_scene_open(*args): - avalon.api.emit("open", args) +def _on_scene_open(): + emit_event("open") def _before_scene_save(return_code, client_data): @@ -166,7 +169,7 @@ def _before_scene_save(return_code, client_data): # in order to block the operation. OpenMaya.MScriptUtil.setBool(return_code, True) - avalon.api.emit("before_save", [return_code, client_data]) + emit_event("before.save") def uninstall(): @@ -343,7 +346,7 @@ def containerise(name, return container -def on_init(_): +def on_init(): log.info("Running callback on init..") def safe_deferred(fn): @@ -384,12 +387,12 @@ def on_init(_): safe_deferred(override_toolbox_ui) -def on_before_save(return_code, _): +def on_before_save(): """Run validation for scene's FPS prior to saving""" return lib.validate_fps() -def on_save(_): +def on_save(): """Automatically add IDs to new nodes Any transform of a mesh, without an existing ID, is given one @@ -407,7 +410,7 @@ def on_save(_): lib.set_id(node, new_id, overwrite=False) -def on_open(_): +def on_open(): """On scene open let's assume the containers have changed.""" from Qt import QtWidgets @@ -455,7 +458,7 @@ def on_open(_): dialog.show() -def on_new(_): +def on_new(): """Set project resolution and fps when create a new file""" log.info("Running callback on new..") with lib.suspended_refresh(): @@ -471,7 +474,7 @@ def on_new(_): lib.set_context_settings() -def on_task_changed(*args): +def on_task_changed(): """Wrapped function of app initialize and maya's on task changed""" # Run menu.update_menu_task_label() @@ -509,7 +512,7 @@ def on_task_changed(*args): def before_workfile_save(event): - workdir_path = event.workdir_path + workdir_path = event["workdir_path"] if workdir_path: copy_workspace_mel(workdir_path) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 8c6c9ca55b..1a5116c9ea 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -14,6 +14,7 @@ from openpype.api import ( BuildWorkfile, get_current_project_settings ) +from openpype.pipeline import register_event_callback from openpype.tools.utils import host_tools from .command import viewer_update_and_undo_stop @@ -102,8 +103,8 @@ def install(): avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) # Register Avalon event for workfiles loading. - avalon.api.on("workio.open_file", check_inventory_versions) - avalon.api.on("taskChanged", change_context_label) + register_event_callback("workio.open_file", check_inventory_versions) + register_event_callback("taskChanged", change_context_label) pyblish.api.register_callback( "instanceToggled", on_pyblish_instance_toggled) @@ -226,7 +227,7 @@ def _uninstall_menu(): menu.removeItem(item.name()) -def change_context_label(*args): +def change_context_label(): menubar = nuke.menu("Nuke") menu = menubar.findItem(MENU_LABEL) diff --git a/openpype/hosts/photoshop/api/pipeline.py b/openpype/hosts/photoshop/api/pipeline.py index 25983f2471..2aade59812 100644 --- a/openpype/hosts/photoshop/api/pipeline.py +++ b/openpype/hosts/photoshop/api/pipeline.py @@ -1,5 +1,4 @@ import os -import sys from Qt import QtWidgets import pyblish.api @@ -7,6 +6,7 @@ import avalon.api from avalon import pipeline, io from openpype.api import Logger +from openpype.pipeline import register_event_callback import openpype.hosts.photoshop from . import lib @@ -75,7 +75,7 @@ def install(): "instanceToggled", on_pyblish_instance_toggled ) - avalon.api.on("application.launched", on_application_launch) + register_event_callback("application.launched", on_application_launch) def uninstall(): diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index e9c5f4c73e..b001b84203 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -21,7 +21,7 @@ from aiohttp_json_rpc.protocol import ( ) from aiohttp_json_rpc.exceptions import RpcError -from avalon import api +from openpype.pipeline import emit_event from openpype.hosts.tvpaint.tvpaint_plugin import get_plugin_files_path log = logging.getLogger(__name__) @@ -754,7 +754,7 @@ class BaseCommunicator: self._on_client_connect() - api.emit("application.launched") + emit_event("application.launched") def _on_client_connect(self): self._initial_textfile_write() @@ -938,5 +938,5 @@ class QtCommunicator(BaseCommunicator): def _exit(self, *args, **kwargs): super()._exit(*args, **kwargs) - api.emit("application.exit") + emit_event("application.exit") self.qt_app.exit(self.exit_code) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 74eb41892c..b999478fb1 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -14,6 +14,7 @@ from avalon.pipeline import AVALON_CONTAINER_ID from openpype.hosts import tvpaint from openpype.api import get_current_project_settings +from openpype.pipeline import register_event_callback from .lib import ( execute_george, @@ -84,8 +85,8 @@ def install(): if on_instance_toggle not in registered_callbacks: pyblish.api.register_callback("instanceToggled", on_instance_toggle) - avalon.api.on("application.launched", initial_launch) - avalon.api.on("application.exit", application_exit) + register_event_callback("application.launched", initial_launch) + register_event_callback("application.exit", application_exit) def uninstall(): diff --git a/openpype/hosts/webpublisher/api/__init__.py b/openpype/hosts/webpublisher/api/__init__.py index e40d46d662..f338c92a5e 100644 --- a/openpype/hosts/webpublisher/api/__init__.py +++ b/openpype/hosts/webpublisher/api/__init__.py @@ -16,10 +16,6 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -def application_launch(): - pass - - def install(): print("Installing Pype config...") @@ -29,7 +25,6 @@ def install(): log.info(PUBLISH_PATH) io.install() - avalon.on("application.launched", application_launch) def uninstall(): diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index aa743b05fe..afb94bf8fc 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -1,9 +1,10 @@ import sys from Qt import QtWidgets, QtCore -from avalon import api, io, pipeline +from avalon import api, io from openpype import style +from openpype.pipeline import register_event_callback from openpype.tools.utils import ( lib, PlaceholderLineEdit @@ -33,7 +34,7 @@ def on_context_task_change(*args, **kwargs): module.window.on_context_task_change(*args, **kwargs) -pipeline.on("taskChanged", on_context_task_change) +register_event_callback("taskChanged", on_context_task_change) class LoaderWindow(QtWidgets.QDialog): diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index aece7bfb4f..280fe2d8a2 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -9,10 +9,10 @@ import datetime import Qt from Qt import QtWidgets, QtCore -from avalon import io, api, pipeline +from avalon import io, api from openpype import style -from openpype.pipeline.lib import BeforeWorkfileSave +from openpype.pipeline import emit_event from openpype.tools.utils.lib import ( qt_app_context ) @@ -823,7 +823,11 @@ class FilesWidget(QtWidgets.QWidget): return # Trigger before save event - BeforeWorkfileSave.emit(work_filename, self._workdir_path) + emit_event( + "before.workfile.save", + {"filename": work_filename, "workdir_path": self._workdir_path}, + source="workfiles.tool" + ) # Make sure workfiles root is updated # - this triggers 'workio.work_root(...)' which may change value of @@ -853,7 +857,11 @@ class FilesWidget(QtWidgets.QWidget): api.Session["AVALON_PROJECT"] ) # Trigger after save events - pipeline.emit("after.workfile.save", [filepath]) + emit_event( + "after.workfile.save", + {"filepath": filepath}, + source="workfiles.tool" + ) self.workfile_created.emit(filepath) # Refresh files model From 502785021e5fcf364bb1b01217c1dc522114d17e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 5 Mar 2022 08:30:29 +0100 Subject: [PATCH 03/67] moved events to openpype.lib --- openpype/__init__.py | 2 +- openpype/hosts/aftereffects/api/pipeline.py | 2 +- openpype/hosts/blender/api/pipeline.py | 2 +- openpype/hosts/harmony/api/pipeline.py | 2 +- openpype/hosts/hiero/api/events.py | 4 ++-- openpype/hosts/houdini/api/pipeline.py | 8 +++----- openpype/hosts/maya/api/pipeline.py | 10 +++++----- openpype/hosts/nuke/api/pipeline.py | 2 +- openpype/hosts/photoshop/api/pipeline.py | 2 +- openpype/hosts/tvpaint/api/communication_server.py | 2 +- openpype/hosts/tvpaint/api/pipeline.py | 2 +- openpype/lib/__init__.py | 7 +++++++ openpype/{pipeline => lib}/events.py | 11 ++++++++--- openpype/pipeline/__init__.py | 8 -------- openpype/tools/loader/app.py | 2 +- openpype/tools/workfiles/app.py | 2 +- 16 files changed, 35 insertions(+), 33 deletions(-) rename openpype/{pipeline => lib}/events.py (94%) diff --git a/openpype/__init__.py b/openpype/__init__.py index 9175727a54..63ad81d3ca 100644 --- a/openpype/__init__.py +++ b/openpype/__init__.py @@ -11,8 +11,8 @@ from .lib import ( filter_pyblish_plugins, set_plugin_attributes_from_settings, change_timer_to_current_context, + register_event_callback, ) -from .pipeline import register_event_callback pyblish = avalon = _original_discover = None diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index 6f7cd8c46d..96d04aad14 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -10,7 +10,7 @@ from avalon import io, pipeline from openpype import lib from openpype.api import Logger import openpype.hosts.aftereffects -from openpype.pipeline import register_event_callback +from openpype.lib import register_event_callback from .launch_logic import get_stub diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 38312316cc..d1e7df3a93 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -15,7 +15,7 @@ from avalon import io, schema from avalon.pipeline import AVALON_CONTAINER_ID from openpype.api import Logger -from openpype.pipeline import ( +from openpype.lib import ( register_event_callback, emit_event ) diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index a94d30210e..8d7ac19eb9 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -9,7 +9,7 @@ import avalon.api from avalon.pipeline import AVALON_CONTAINER_ID from openpype import lib -from openpype.pipeline import register_event_callback +from openpype.lib import register_event_callback import openpype.hosts.harmony import openpype.hosts.harmony.api as harmony diff --git a/openpype/hosts/hiero/api/events.py b/openpype/hosts/hiero/api/events.py index 6e2580ed8c..9439199933 100644 --- a/openpype/hosts/hiero/api/events.py +++ b/openpype/hosts/hiero/api/events.py @@ -1,12 +1,12 @@ import os import hiero.core.events from openpype.api import Logger -from openpype.pipeline import register_event_callback from .lib import ( sync_avalon_data_to_workfile, launch_workfiles_app, selection_changed_timeline, - before_project_save + before_project_save, + register_event_callback ) from .tags import add_tags_to_workfile from .menu import update_menu_task_label diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 86c85ad3a1..bbb7a7c512 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -11,15 +11,13 @@ import avalon.api from avalon.pipeline import AVALON_CONTAINER_ID from avalon.lib import find_submodule -from openpype.pipeline import ( - register_event_callback, - emit_event -) import openpype.hosts.houdini from openpype.hosts.houdini.api import lib from openpype.lib import ( - any_outdated + register_event_callback, + emit_event, + any_outdated, ) from .lib import get_asset_fps diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 05db1b7b26..4945a9ba56 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -14,8 +14,8 @@ from avalon.pipeline import AVALON_CONTAINER_ID import openpype.hosts.maya from openpype.tools.utils import host_tools -from openpype.lib import any_outdated -from openpype.pipeline import ( +from openpype.lib import ( + any_outdated, register_event_callback, emit_event ) @@ -150,15 +150,15 @@ def _on_maya_initialized(*args): lib.get_main_window() -def _on_scene_new(): +def _on_scene_new(*args): emit_event("new") -def _on_scene_save(): +def _on_scene_save(*args): emit_event("save") -def _on_scene_open(): +def _on_scene_open(*args): emit_event("open") diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 1a5116c9ea..419cfb1ac2 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -14,7 +14,7 @@ from openpype.api import ( BuildWorkfile, get_current_project_settings ) -from openpype.pipeline import register_event_callback +from openpype.lib import register_event_callback from openpype.tools.utils import host_tools from .command import viewer_update_and_undo_stop diff --git a/openpype/hosts/photoshop/api/pipeline.py b/openpype/hosts/photoshop/api/pipeline.py index 2aade59812..e424400465 100644 --- a/openpype/hosts/photoshop/api/pipeline.py +++ b/openpype/hosts/photoshop/api/pipeline.py @@ -6,7 +6,7 @@ import avalon.api from avalon import pipeline, io from openpype.api import Logger -from openpype.pipeline import register_event_callback +from openpype.lib import register_event_callback import openpype.hosts.photoshop from . import lib diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index b001b84203..65cb9aa2f3 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -21,7 +21,7 @@ from aiohttp_json_rpc.protocol import ( ) from aiohttp_json_rpc.exceptions import RpcError -from openpype.pipeline import emit_event +from openpype.lib import emit_event from openpype.hosts.tvpaint.tvpaint_plugin import get_plugin_files_path log = logging.getLogger(__name__) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index b999478fb1..381c2c62f0 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -14,7 +14,7 @@ from avalon.pipeline import AVALON_CONTAINER_ID from openpype.hosts import tvpaint from openpype.api import get_current_project_settings -from openpype.pipeline import register_event_callback +from openpype.lib import register_event_callback from .lib import ( execute_george, diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 6a24f30455..1ee9129fa7 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -16,6 +16,10 @@ sys.path.insert(0, python_version_dir) site.addsitedir(python_version_dir) +from .events import ( + emit_event, + register_event_callback +) from .env_tools import ( env_value_to_bool, get_paths_from_environ, @@ -193,6 +197,9 @@ from .openpype_version import ( terminal = Terminal __all__ = [ + "emit_event", + "register_event_callback", + "get_openpype_execute_args", "get_pype_execute_args", "get_linux_launcher_args", diff --git a/openpype/pipeline/events.py b/openpype/lib/events.py similarity index 94% rename from openpype/pipeline/events.py rename to openpype/lib/events.py index cae8b250f7..62c480d8e0 100644 --- a/openpype/pipeline/events.py +++ b/openpype/lib/events.py @@ -8,7 +8,7 @@ from uuid import uuid4 try: from weakref import WeakMethod except Exception: - from .python_2_comp import WeakMethod + from openpype.lib.python_2_comp import WeakMethod class EventCallback(object): @@ -83,6 +83,7 @@ class EventCallback(object): Args: event(Event): Event that was triggered. """ + self.log.info("Processing event {}".format(event.topic)) # Skip if callback is not enabled or has invalid reference if not self._ref_valid or not self._enabled: return @@ -93,9 +94,11 @@ class EventCallback(object): if not callback: # Change state if is invalid so the callback is removed self._ref_valid = False + self.log.info("Invalid reference") elif self.topic_matches(event.topic): # Try execute callback + self.log.info("Triggering callback") sig = inspect.signature(callback) try: if len(sig.parameters) == 0: @@ -109,6 +112,8 @@ class EventCallback(object): ), exc_info=True ) + else: + self.log.info("Not matchin callback") # Inherit from 'object' for Python 2 hosts @@ -172,7 +177,7 @@ class StoredCallbacks: elif callable(callback): ref = weakref.ref(callback) else: - # TODO add logs + print("Invalid callback") return function_name = callback.__name__ @@ -197,7 +202,7 @@ class StoredCallbacks: def emit_event(cls, event): invalid_callbacks = [] for callback in cls._registered_callbacks: - callback.process_event() + callback.process_event(event) if not callback.is_ref_valid: invalid_callbacks.append(callback) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 673608bded..e968df4011 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -1,10 +1,5 @@ from .lib import attribute_definitions -from .events import ( - emit_event, - register_event_callback -) - from .create import ( BaseCreator, Creator, @@ -22,9 +17,6 @@ from .publish import ( __all__ = ( "attribute_definitions", - "emit_event", - "register_event_callback", - "BaseCreator", "Creator", "AutoCreator", diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index afb94bf8fc..ec8f56e74b 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -4,7 +4,7 @@ from Qt import QtWidgets, QtCore from avalon import api, io from openpype import style -from openpype.pipeline import register_event_callback +from openpype.lib import register_event_callback from openpype.tools.utils import ( lib, PlaceholderLineEdit diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 280fe2d8a2..87e1492a20 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -12,7 +12,6 @@ from Qt import QtWidgets, QtCore from avalon import io, api from openpype import style -from openpype.pipeline import emit_event from openpype.tools.utils.lib import ( qt_app_context ) @@ -21,6 +20,7 @@ from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget from openpype.tools.utils.tasks_widget import TasksWidget from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( + emit_event, Anatomy, get_workfile_doc, create_workfile_doc, From 70cf30041aa581f65fd5227cae508e42b7aeac88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 5 Mar 2022 09:45:36 +0100 Subject: [PATCH 04/67] fix python 2 compatibility --- openpype/lib/events.py | 89 ++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 62c480d8e0..f71cb74c10 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -12,7 +12,32 @@ except Exception: class EventCallback(object): - def __init__(self, topic, func_ref, func_name, func_path): + """Callback registered to a topic. + + The callback function is registered to a topic. Topic is a string which + may contain '*' that will be handled as "any characters". + + # Examples: + - "workfile.save" Callback will be triggered if the event topic is exactly + "workfile.save" . + - "workfile.*" Callback will be triggered an event topic starts with + "workfile." so "workfile.save" and "workfile.open" + will trigger the callback. + - "*" Callback will listen to all events. + + Callback can be function or method. In both cases it should expect one + or none arguments. When 1 argument is expected then the processed 'Event' + object is passed in. + + The registered callbacks don't keep function in memory so it is not + possible to store lambda function as callback. + + Args: + topic(str): Topic which will be listened. + func(func): Callback to a topic. + """ + def __init__(self, topic, func): + self._log = None self._topic = topic # Replace '*' with any character regex and escape rest of text # - when callback is registered for '*' topic it will receive all @@ -28,14 +53,42 @@ class EventCallback(object): ) topic_regex = re.compile(topic_regex_str) self._topic_regex = topic_regex + + # Convert callback into references + # - deleted functions won't cause crashes + if inspect.ismethod(func): + func_ref = WeakMethod(func) + elif callable(func): + func_ref = weakref.ref(func) + else: + func_ref = None + self.log.warning(( + "Registered callback is not callable. \"{}\"" + ).format(str(func))) + + func_name = None + func_path = None + expect_args = False + # Collect additional data about function + # - name + # - path + # - if expect argument or not + if func_ref is not None: + func_name = func.__name__ + func_path = os.path.abspath(inspect.getfile(func)) + if hasattr(inspect, "signature"): + sig = inspect.signature(func) + expect_args = len(sig.parameters) > 0 + else: + expect_args = len(inspect.getargspec(func)[0]) > 0 + self._func_ref = func_ref self._func_name = func_name self._func_path = func_path - self._ref_valid = True + self._expect_args = expect_args + self._ref_valid = func_ref is not None self._enabled = True - self._log = None - def __repr__(self): return "< {} - {} > {}".format( self.__class__.__name__, self._func_name, self._func_path @@ -83,7 +136,7 @@ class EventCallback(object): Args: event(Event): Event that was triggered. """ - self.log.info("Processing event {}".format(event.topic)) + # Skip if callback is not enabled or has invalid reference if not self._ref_valid or not self._enabled: return @@ -94,17 +147,15 @@ class EventCallback(object): if not callback: # Change state if is invalid so the callback is removed self._ref_valid = False - self.log.info("Invalid reference") elif self.topic_matches(event.topic): # Try execute callback - self.log.info("Triggering callback") - sig = inspect.signature(callback) try: - if len(sig.parameters) == 0: - callback() - else: + if self._expect_args: callback(event) + else: + callback() + except Exception: self.log.warning( "Failed to execute event callback {}".format( @@ -112,8 +163,6 @@ class EventCallback(object): ), exc_info=True ) - else: - self.log.info("Not matchin callback") # Inherit from 'object' for Python 2 hosts @@ -170,19 +219,7 @@ class StoredCallbacks: @classmethod def add_callback(cls, topic, callback): - # Convert callback into references - # - deleted functions won't cause crashes - if inspect.ismethod(callback): - ref = WeakMethod(callback) - elif callable(callback): - ref = weakref.ref(callback) - else: - print("Invalid callback") - return - - function_name = callback.__name__ - function_path = os.path.abspath(inspect.getfile(callback)) - callback = EventCallback(topic, ref, function_name, function_path) + callback = EventCallback(topic, callback) cls._registered_callbacks.append(callback) return callback From 17c88141c154003736e59e93f75bc405fc604845 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 7 Mar 2022 12:20:50 +0100 Subject: [PATCH 05/67] fix emit of taskChanged topic --- openpype/lib/avalon_context.py | 4 ++-- openpype/lib/events.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 0bfd3f6de0..67a5515100 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -15,6 +15,7 @@ from openpype.settings import ( ) from .anatomy import Anatomy from .profiles_filtering import filter_profiles +from .events import emit_event # avalon module is not imported at the top # - may not be in path at the time of pype.lib initialization @@ -779,7 +780,6 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): """ import avalon.api - from avalon.pipeline import emit changes = compute_session_changes( avalon.api.Session, @@ -799,7 +799,7 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): os.environ[key] = value # Emit session change - emit("taskChanged", changes.copy()) + emit_event("taskChanged", changes.copy()) return changes diff --git a/openpype/lib/events.py b/openpype/lib/events.py index f71cb74c10..9496d71ca8 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -248,13 +248,30 @@ class StoredCallbacks: def register_event_callback(topic, callback): - """Add callback that will be executed on specific topic.""" + """Add callback that will be executed on specific topic. + + Args: + topic(str): Topic on which will callback be triggered. + callback(function): Callback that will be triggered when a topic + is triggered. Callback should expect none or 1 argument where + `Event` object is passed. + + Returns: + EventCallback: Object wrapping the callback. It can be used to + enable/disable listening to a topic or remove the callback from + the topic completely. + """ return StoredCallbacks.add_callback(topic, callback) def emit_event(topic, data=None, source=None): """Emit event with topic and data. + Arg: + topic(str): Event's topic. + data(dict): Event's additional data. Optional. + source(str): Who emitted the topic. Optional. + Returns: Event: Object of event that was emitted. """ From 38b6ad8042647c490c18fc8624e435b9218b83d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 7 Mar 2022 12:21:53 +0100 Subject: [PATCH 06/67] Loader UI is using registering callback from init --- openpype/tools/loader/app.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index ec8f56e74b..d73a977ac6 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -26,17 +26,6 @@ module = sys.modules[__name__] module.window = None -# Register callback on task change -# - callback can't be defined in Window as it is weak reference callback -# so `WeakSet` will remove it immediately -def on_context_task_change(*args, **kwargs): - if module.window: - module.window.on_context_task_change(*args, **kwargs) - - -register_event_callback("taskChanged", on_context_task_change) - - class LoaderWindow(QtWidgets.QDialog): """Asset loader interface""" @@ -195,6 +184,8 @@ class LoaderWindow(QtWidgets.QDialog): self._first_show = True + register_event_callback("taskChanged", self.on_context_task_change) + def resizeEvent(self, event): super(LoaderWindow, self).resizeEvent(event) self._overlay_frame.resize(self.size()) From e0395fd0afadb39cd789e46cd5ae12eed7863ddc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 7 Mar 2022 12:42:43 +0100 Subject: [PATCH 07/67] hound fix --- openpype/lib/events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 9496d71ca8..9cb80b2a6e 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -18,8 +18,8 @@ class EventCallback(object): may contain '*' that will be handled as "any characters". # Examples: - - "workfile.save" Callback will be triggered if the event topic is exactly - "workfile.save" . + - "workfile.save" Callback will be triggered if the event topic is + exactly "workfile.save" . - "workfile.*" Callback will be triggered an event topic starts with "workfile." so "workfile.save" and "workfile.open" will trigger the callback. From 319ec9e8684843e3562e3dc7680bbec35e919f94 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Mar 2022 12:32:29 +0100 Subject: [PATCH 08/67] removed unused validate method --- openpype/lib/events.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 9cb80b2a6e..f4ad82d919 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -223,18 +223,6 @@ class StoredCallbacks: cls._registered_callbacks.append(callback) return callback - @classmethod - def validate(cls): - invalid_callbacks = [] - for callbacks in cls._registered_callbacks: - for callback in tuple(callbacks): - callback.validate_ref() - if not callback.is_ref_valid: - invalid_callbacks.append(callback) - - for callback in invalid_callbacks: - cls._registered_callbacks.remove(callback) - @classmethod def emit_event(cls, event): invalid_callbacks = [] From 605e8dabb0467826ba160767c7102ebfedf828c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Mar 2022 12:33:29 +0100 Subject: [PATCH 09/67] modified docstring --- openpype/lib/events.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index f4ad82d919..2ae3efa55d 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -169,13 +169,14 @@ class EventCallback(object): class Event(object): """Base event object. - Can be used to anything because data are not much specific. Only required - argument is topic which defines why event is happening and may be used for + Can be used for any event because is not specific. Only required argument + is topic which defines why event is happening and may be used for filtering. Arg: topic (str): Identifier of event. data (Any): Data specific for event. Dictionary is recommended. + source (str): Identifier of source. """ _data = {} From 42a185fef9008b58e28d2686ca02013afa719b87 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Mar 2022 12:35:59 +0100 Subject: [PATCH 10/67] change arguments for after.workfile.save event --- openpype/tools/workfiles/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 87e1492a20..df16498df5 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -859,7 +859,7 @@ class FilesWidget(QtWidgets.QWidget): # Trigger after save events emit_event( "after.workfile.save", - {"filepath": filepath}, + {"filename": work_filename, "workdir_path": self._workdir_path}, source="workfiles.tool" ) From 663d425635d63e89ffd1c893d03b88ef40b6af90 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Mar 2022 12:39:15 +0100 Subject: [PATCH 11/67] changed topics in workfiles tool to have before and after as last part --- openpype/hosts/maya/api/pipeline.py | 2 +- openpype/tools/workfiles/app.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 4945a9ba56..1dbcfbad6b 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -77,7 +77,7 @@ def install(): register_event_callback("new", on_new) register_event_callback("before.save", on_before_save) register_event_callback("taskChanged", on_task_changed) - register_event_callback("before.workfile.save", before_workfile_save) + register_event_callback("workfile.save.before", before_workfile_save) def _set_project(): diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index df16498df5..63958ac57b 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -824,7 +824,7 @@ class FilesWidget(QtWidgets.QWidget): # Trigger before save event emit_event( - "before.workfile.save", + "workfile.save.before", {"filename": work_filename, "workdir_path": self._workdir_path}, source="workfiles.tool" ) @@ -858,7 +858,7 @@ class FilesWidget(QtWidgets.QWidget): ) # Trigger after save events emit_event( - "after.workfile.save", + "workfile.save.after", {"filename": work_filename, "workdir_path": self._workdir_path}, source="workfiles.tool" ) From 2c75a20b340042c40ff924c6e24f5c7b8042653f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Mar 2022 12:42:17 +0100 Subject: [PATCH 12/67] added returncode in maya's 'before.save' --- openpype/hosts/maya/api/pipeline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 1dbcfbad6b..07743b7fc4 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -169,7 +169,10 @@ def _before_scene_save(return_code, client_data): # in order to block the operation. OpenMaya.MScriptUtil.setBool(return_code, True) - emit_event("before.save") + emit_event( + "before.save", + {"return_code": return_code} + ) def uninstall(): From c84abf1faa9606c70d8fc3e877e14e6276f09df1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Mar 2022 12:50:11 +0100 Subject: [PATCH 13/67] raise TypeError when function is not callable --- openpype/lib/events.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/openpype/lib/events.py b/openpype/lib/events.py index 2ae3efa55d..7bec6ee30d 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -35,7 +35,11 @@ class EventCallback(object): Args: topic(str): Topic which will be listened. func(func): Callback to a topic. + + Raises: + TypeError: When passed function is not a callable object. """ + def __init__(self, topic, func): self._log = None self._topic = topic @@ -61,26 +65,21 @@ class EventCallback(object): elif callable(func): func_ref = weakref.ref(func) else: - func_ref = None - self.log.warning(( + raise TypeError(( "Registered callback is not callable. \"{}\"" ).format(str(func))) - func_name = None - func_path = None - expect_args = False # Collect additional data about function # - name # - path # - if expect argument or not - if func_ref is not None: - func_name = func.__name__ - func_path = os.path.abspath(inspect.getfile(func)) - if hasattr(inspect, "signature"): - sig = inspect.signature(func) - expect_args = len(sig.parameters) > 0 - else: - expect_args = len(inspect.getargspec(func)[0]) > 0 + func_name = func.__name__ + func_path = os.path.abspath(inspect.getfile(func)) + if hasattr(inspect, "signature"): + sig = inspect.signature(func) + expect_args = len(sig.parameters) > 0 + else: + expect_args = len(inspect.getargspec(func)[0]) > 0 self._func_ref = func_ref self._func_name = func_name From 342fa4c817c740eb8afb5d4c823595f9bd5eeaad Mon Sep 17 00:00:00 2001 From: joblet Date: Wed, 9 Mar 2022 17:50:06 +0100 Subject: [PATCH 14/67] fix phooshop and after effects openPype plugin path --- website/docs/artist_hosts_aftereffects.md | 2 +- website/docs/artist_hosts_photoshop.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md index f9ef40fe1a..a9660bd13c 100644 --- a/website/docs/artist_hosts_aftereffects.md +++ b/website/docs/artist_hosts_aftereffects.md @@ -15,7 +15,7 @@ sidebar_label: AfterEffects ## Setup -To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select AfterEffects in menu. Then go to `{path to pype}/repos/avalon-core/avalon/aftereffects/extension.zxp`. +To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select AfterEffects in menu. Then go to `{path to pype}hosts/aftereffects/api/extension.zxp`. Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself. diff --git a/website/docs/artist_hosts_photoshop.md b/website/docs/artist_hosts_photoshop.md index 16539bcf79..b2b5fd58da 100644 --- a/website/docs/artist_hosts_photoshop.md +++ b/website/docs/artist_hosts_photoshop.md @@ -14,7 +14,7 @@ sidebar_label: Photoshop ## Setup -To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select Photoshop in menu. Then go to `{path to pype}/repos/avalon-core/avalon/photoshop/extension.zxp`. Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself. +To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select Photoshop in menu. Then go to `{path to pype}hosts/photoshop/api/extension.zxp`. Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself. ## Usage From c274e64c6aac478fac799df50b0cdc8c1d209fe0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 10 Mar 2022 09:47:39 +0100 Subject: [PATCH 15/67] fix houdini topic --- openpype/hosts/houdini/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index e34b8811b9..6cfb713661 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -108,7 +108,7 @@ def on_file_event_callback(event): elif event == hou.hipFileEventType.AfterSave: emit_event("save") elif event == hou.hipFileEventType.BeforeSave: - emit_event("before_save") + emit_event("before.save") elif event == hou.hipFileEventType.AfterClear: emit_event("new") From f3f781b43578dc1cef8cbbbf5c9276c5e48e7469 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 11 Mar 2022 11:03:15 +0100 Subject: [PATCH 16/67] added subset name filtering in ExtractReview --- openpype/plugins/publish/extract_review.py | 25 ++++++++++++++++--- .../defaults/project_settings/global.json | 3 ++- .../schemas/schema_global_publish.json | 9 +++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 0b139a73e4..b8599454ee 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -103,9 +103,10 @@ class ExtractReview(pyblish.api.InstancePlugin): self.log.debug("Matching profile: \"{}\"".format(json.dumps(profile))) + subset_name = instance.data.get("subset") instance_families = self.families_from_instance(instance) - filtered_outputs = self.filter_outputs_by_families( - profile, instance_families + filtered_outputs = self.filter_output_defs( + profile, subset_name, instance_families ) # Store `filename_suffix` to save arguments profile_outputs = [] @@ -1651,7 +1652,7 @@ class ExtractReview(pyblish.api.InstancePlugin): return True return False - def filter_outputs_by_families(self, profile, families): + def filter_output_defs(self, profile, subset_name, families): """Return outputs matching input instance families. Output definitions without families filter are marked as valid. @@ -1684,6 +1685,24 @@ class ExtractReview(pyblish.api.InstancePlugin): if not self.families_filter_validation(families, families_filters): continue + # Subsets name filters + subset_filters = [ + subset_filter + for subset_filter in output_filters.get("subsets", []) + # Skip empty strings + if subset_filter + ] + if subset_name and subset_filters: + match = False + for subset_filter in subset_filters: + compiled = re.compile(subset_filter) + if compiled.search(subset_name): + match = True + break + + if not match: + continue + filtered_outputs[filename_suffix] = output_def return filtered_outputs diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 9c44d9bc86..30a71b044a 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -87,7 +87,8 @@ "render", "review", "ftrack" - ] + ], + "subsets": [] }, "overscan_crop": "", "overscan_color": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 3eea7ccb30..12043d4205 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -291,6 +291,15 @@ "label": "Families", "type": "list", "object_type": "text" + }, + { + "type": "separator" + }, + { + "key": "subsets", + "label": "Subsets", + "type": "list", + "object_type": "text" } ] }, From 6706e90d1852f063670fcba5a1e630dde0c19a17 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Mar 2022 12:41:14 +0100 Subject: [PATCH 17/67] =?UTF-8?q?Fixes=20for=20=F0=9F=8E=AB=20OP-2825?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/maya/api/plugin.py | 3 ++- .../hosts/maya/plugins/load/load_reference.py | 8 +------- .../plugins/publish/extract_maya_scene_raw.py | 18 +++++++----------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index e0c21645e4..feaceacebc 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -178,7 +178,7 @@ class ReferenceLoader(Loader): loader=self.__class__.__name__ ) loaded_containers.append(container) - self._organize_containers([ref_node], container) + self._organize_containers(nodes, container) c += 1 namespace = None @@ -318,6 +318,7 @@ class ReferenceLoader(Loader): @staticmethod def _organize_containers(nodes, container): # type: (list, str) -> None + """Put containers in loaded data to correct hierarchy.""" for node in nodes: id_attr = "{}.id".format(node) if not cmds.attributeQuery("id", node=node, exists=True): diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 66cf95a643..d358c62724 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -121,18 +121,12 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): if family == "rig": self._post_process_rig(name, namespace, context, options) else: - if "translate" in options: cmds.setAttr(group_name + ".t", *options["translate"]) + print(new_nodes) return new_nodes - def load(self, context, name=None, namespace=None, options=None): - container = super(ReferenceLoader, self).load( - context, name, namespace, options) - # clean containers if present to AVALON_CONTAINERS - self._organize_containers(self[:], container[0]) - def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py index 5cc7b52090..2e1260c374 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -58,16 +58,12 @@ class ExtractMayaSceneRaw(openpype.api.Extractor): else: members = instance[:] - loaded_containers = None - if set(self.add_for_families).intersection( - set(instance.data.get("families")), - set(instance.data.get("family").lower())): - loaded_containers = self._add_loaded_containers(members) - selection = members - if loaded_containers: - self.log.info(loaded_containers) - selection += loaded_containers + if set(self.add_for_families).intersection( + set(instance.data.get("families"))) or \ + instance.data.get("family") in self.add_for_families: + selection += self._add_loaded_containers(members) + self.log.info(selection) # Perform extraction self.log.info("Performing extraction ...") @@ -105,7 +101,7 @@ class ExtractMayaSceneRaw(openpype.api.Extractor): if cmds.referenceQuery(ref, isNodeReferenced=True) ] - refs_to_include = set(refs_to_include) + members_with_refs = set(refs_to_include).union(members) obj_sets = cmds.ls("*.id", long=True, type="objectSet", recursive=True, objectsOnly=True) @@ -121,7 +117,7 @@ class ExtractMayaSceneRaw(openpype.api.Extractor): continue set_content = set(cmds.sets(obj_set, query=True)) - if set_content.intersection(refs_to_include): + if set_content.intersection(members_with_refs): loaded_containers.append(obj_set) return loaded_containers From e8b2299c00c5989430855faef2dd28005f5203ba Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Mar 2022 09:55:14 +0100 Subject: [PATCH 18/67] OP-2815 - copied webserver tool for AE from avalon to openpype --- .../hosts/aftereffects/api/launch_logic.py | 2 +- openpype/hosts/aftereffects/api/ws_stub.py | 2 +- openpype/tools/adobe_webserver/app.py | 237 ++++++++++++++++++ openpype/tools/adobe_webserver/readme.txt | 12 + 4 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 openpype/tools/adobe_webserver/app.py create mode 100644 openpype/tools/adobe_webserver/readme.txt diff --git a/openpype/hosts/aftereffects/api/launch_logic.py b/openpype/hosts/aftereffects/api/launch_logic.py index 97f14c9332..c549268978 100644 --- a/openpype/hosts/aftereffects/api/launch_logic.py +++ b/openpype/hosts/aftereffects/api/launch_logic.py @@ -15,7 +15,7 @@ from Qt import QtCore from openpype.tools.utils import host_tools from avalon import api -from avalon.tools.webserver.app import WebServerTool +from openpype.tools.adobe_webserver.app import WebServerTool from .ws_stub import AfterEffectsServerStub diff --git a/openpype/hosts/aftereffects/api/ws_stub.py b/openpype/hosts/aftereffects/api/ws_stub.py index 5a0600e92e..b0893310c1 100644 --- a/openpype/hosts/aftereffects/api/ws_stub.py +++ b/openpype/hosts/aftereffects/api/ws_stub.py @@ -8,7 +8,7 @@ import logging import attr from wsrpc_aiohttp import WebSocketAsync -from avalon.tools.webserver.app import WebServerTool +from openpype.tools.adobe_webserver.app import WebServerTool @attr.s diff --git a/openpype/tools/adobe_webserver/app.py b/openpype/tools/adobe_webserver/app.py new file mode 100644 index 0000000000..b79d6c6c60 --- /dev/null +++ b/openpype/tools/adobe_webserver/app.py @@ -0,0 +1,237 @@ +"""This Webserver tool is python 3 specific. + +Don't import directly to avalon.tools or implementation of Python 2 hosts +would break. +""" +import os +import logging +import urllib +import threading +import asyncio +import socket + +from aiohttp import web + +from wsrpc_aiohttp import ( + WSRPCClient +) + +from avalon import api + +log = logging.getLogger(__name__) + + +class WebServerTool: + """ + Basic POC implementation of asychronic websocket RPC server. + Uses class in external_app_1.py to mimic implementation for single + external application. + 'test_client' folder contains two test implementations of client + """ + _instance = None + + def __init__(self): + WebServerTool._instance = self + + self.client = None + self.handlers = {} + self.on_stop_callbacks = [] + + port = None + host_name = "localhost" + websocket_url = os.getenv("WEBSOCKET_URL") + if websocket_url: + parsed = urllib.parse.urlparse(websocket_url) + port = parsed.port + host_name = parsed.netloc.split(":")[0] + if not port: + port = 8098 # fallback + + self.port = port + self.host_name = host_name + + self.app = web.Application() + + # add route with multiple methods for single "external app" + self.webserver_thread = WebServerThread(self, self.port) + + def add_route(self, *args, **kwargs): + self.app.router.add_route(*args, **kwargs) + + def add_static(self, *args, **kwargs): + self.app.router.add_static(*args, **kwargs) + + def start_server(self): + if self.webserver_thread and not self.webserver_thread.is_alive(): + self.webserver_thread.start() + + def stop_server(self): + self.stop() + + async def send_context_change(self, host): + """ + Calls running webserver to inform about context change + + Used when new PS/AE should be triggered, + but one already running, without + this publish would point to old context. + """ + client = WSRPCClient(os.getenv("WEBSOCKET_URL"), + loop=asyncio.get_event_loop()) + await client.connect() + + project = api.Session["AVALON_PROJECT"] + asset = api.Session["AVALON_ASSET"] + task = api.Session["AVALON_TASK"] + log.info("Sending context change to {}-{}-{}".format(project, + asset, + task)) + + await client.call('{}.set_context'.format(host), + project=project, asset=asset, task=task) + await client.close() + + def port_occupied(self, host_name, port): + """ + Check if 'url' is already occupied. + + This could mean, that app is already running and we are trying open it + again. In that case, use existing running webserver. + Check here is easier than capturing exception from thread. + """ + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = True + try: + sock.bind((host_name, port)) + result = False + except: + print("Port is in use") + + return result + + def call(self, func): + log.debug("websocket.call {}".format(func)) + future = asyncio.run_coroutine_threadsafe( + func, + self.webserver_thread.loop + ) + result = future.result() + return result + + @staticmethod + def get_instance(): + if WebServerTool._instance is None: + WebServerTool() + return WebServerTool._instance + + @property + def is_running(self): + if not self.webserver_thread: + return False + return self.webserver_thread.is_running + + def stop(self): + if not self.is_running: + return + try: + log.debug("Stopping websocket server") + self.webserver_thread.is_running = False + self.webserver_thread.stop() + except Exception: + log.warning( + "Error has happened during Killing websocket server", + exc_info=True + ) + + def thread_stopped(self): + for callback in self.on_stop_callbacks: + callback() + + +class WebServerThread(threading.Thread): + """ Listener for websocket rpc requests. + + It would be probably better to "attach" this to main thread (as for + example Harmony needs to run something on main thread), but currently + it creates separate thread and separate asyncio event loop + """ + def __init__(self, module, port): + super(WebServerThread, self).__init__() + + self.is_running = False + self.port = port + self.module = module + self.loop = None + self.runner = None + self.site = None + self.tasks = [] + + def run(self): + self.is_running = True + + try: + log.info("Starting web server") + self.loop = asyncio.new_event_loop() # create new loop for thread + asyncio.set_event_loop(self.loop) + + self.loop.run_until_complete(self.start_server()) + + websocket_url = "ws://localhost:{}/ws".format(self.port) + + log.debug( + "Running Websocket server on URL: \"{}\"".format(websocket_url) + ) + + asyncio.ensure_future(self.check_shutdown(), loop=self.loop) + self.loop.run_forever() + except Exception: + self.is_running = False + log.warning( + "Websocket Server service has failed", exc_info=True + ) + raise + finally: + self.loop.close() # optional + + self.is_running = False + self.module.thread_stopped() + log.info("Websocket server stopped") + + async def start_server(self): + """ Starts runner and TCPsite """ + self.runner = web.AppRunner(self.module.app) + await self.runner.setup() + self.site = web.TCPSite(self.runner, 'localhost', self.port) + await self.site.start() + + def stop(self): + """Sets is_running flag to false, 'check_shutdown' shuts server down""" + self.is_running = False + + async def check_shutdown(self): + """ Future that is running and checks if server should be running + periodically. + """ + while self.is_running: + while self.tasks: + task = self.tasks.pop(0) + log.debug("waiting for task {}".format(task)) + await task + log.debug("returned value {}".format(task.result)) + + await asyncio.sleep(0.5) + + log.debug("Starting shutdown") + await self.site.stop() + log.debug("Site stopped") + await self.runner.cleanup() + log.debug("Runner stopped") + tasks = [task for task in asyncio.all_tasks() if + task is not asyncio.current_task()] + list(map(lambda task: task.cancel(), tasks)) # cancel all the tasks + results = await asyncio.gather(*tasks, return_exceptions=True) + log.debug(f'Finished awaiting cancelled tasks, results: {results}...') + await self.loop.shutdown_asyncgens() + # to really make sure everything else has time to stop + await asyncio.sleep(0.07) + self.loop.stop() diff --git a/openpype/tools/adobe_webserver/readme.txt b/openpype/tools/adobe_webserver/readme.txt new file mode 100644 index 0000000000..06cf140fc4 --- /dev/null +++ b/openpype/tools/adobe_webserver/readme.txt @@ -0,0 +1,12 @@ +Adobe webserver +--------------- +Aiohttp (Asyncio) based websocket server used for communication with host +applications, currently only for Adobe (but could be used for any non python +DCC which has websocket client). + +This webserver is started in spawned Python process that opens DCC during +its launch, waits for connection from DCC and handles communication going +forward. Server is closed before Python process is killed. + +(Different from `openpype/modules/webserver` as that one is running in Tray, +this one is running in spawn Python process.) \ No newline at end of file From 8a3be301414af9542ecb50b4424d8895d479ae4f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Mar 2022 14:31:00 +0100 Subject: [PATCH 19/67] OP-2815 - moved webserver tool to Openpype repo for PS --- openpype/hosts/photoshop/api/launch_logic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/api/launch_logic.py b/openpype/hosts/photoshop/api/launch_logic.py index 112cd8fe3f..0021905cb5 100644 --- a/openpype/hosts/photoshop/api/launch_logic.py +++ b/openpype/hosts/photoshop/api/launch_logic.py @@ -14,7 +14,7 @@ from openpype.api import Logger from openpype.tools.utils import host_tools from avalon import api -from avalon.tools.webserver.app import WebServerTool +from openpype.tools.adobe_webserver.app import WebServerTool from .ws_stub import PhotoshopServerStub From 7c34eb7331f4e67dc4ac24f257c77602d2bbd0e5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Mar 2022 14:43:26 +0100 Subject: [PATCH 20/67] OP-2815 - added missed import for PS --- openpype/hosts/photoshop/api/ws_stub.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/photoshop/api/ws_stub.py b/openpype/hosts/photoshop/api/ws_stub.py index d4406d17b9..64d89f5420 100644 --- a/openpype/hosts/photoshop/api/ws_stub.py +++ b/openpype/hosts/photoshop/api/ws_stub.py @@ -2,12 +2,11 @@ Stub handling connection from server to client. Used anywhere solution is calling client methods. """ -import sys import json import attr from wsrpc_aiohttp import WebSocketAsync -from avalon.tools.webserver.app import WebServerTool +from openpype.tools.adobe_webserver.app import WebServerTool @attr.s From c033eaad65afcb43752f9c5da85b94bab31bffb6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Mar 2022 15:13:43 +0100 Subject: [PATCH 21/67] fix update --- openpype/hosts/maya/api/plugin.py | 5 +++++ openpype/hosts/maya/plugins/load/load_reference.py | 2 -- .../hosts/maya/plugins/publish/extract_maya_scene_raw.py | 7 +++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index feaceacebc..1f90b3ffbd 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -247,6 +247,11 @@ class ReferenceLoader(Loader): self.log.warning("Ignoring file read error:\n%s", exc) + shapes = cmds.ls(content, shapes=True, long=True) + new_nodes = (list(set(content) - set(shapes))) + + self._organize_containers(new_nodes, container["objectName"]) + # Reapply alembic settings. if representation["name"] == "abc" and alembic_data: alembic_nodes = cmds.ls( diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index d358c62724..04a25f6493 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -123,8 +123,6 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): else: if "translate" in options: cmds.setAttr(group_name + ".t", *options["translate"]) - - print(new_nodes) return new_nodes def switch(self, container, representation): diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py index 2e1260c374..9d73bea7d2 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -60,10 +60,9 @@ class ExtractMayaSceneRaw(openpype.api.Extractor): selection = members if set(self.add_for_families).intersection( - set(instance.data.get("families"))) or \ + set(instance.data.get("families", []))) or \ instance.data.get("family") in self.add_for_families: - selection += self._add_loaded_containers(members) - self.log.info(selection) + selection += self._get_loaded_containers(members) # Perform extraction self.log.info("Performing extraction ...") @@ -93,7 +92,7 @@ class ExtractMayaSceneRaw(openpype.api.Extractor): self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) @staticmethod - def _add_loaded_containers(members): + def _get_loaded_containers(members): # type: (list) -> list refs_to_include = [ cmds.referenceQuery(ref, referenceNode=True) From 78d566b654074935141968e91429dc4be89510d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 11 Mar 2022 16:40:41 +0100 Subject: [PATCH 22/67] replaced usage of avalon.lib.time with new function get_formatted_current_time --- .../hosts/harmony/plugins/publish/collect_farm_render.py | 3 ++- openpype/hosts/maya/plugins/publish/collect_render.py | 3 ++- openpype/hosts/maya/plugins/publish/collect_vrayscene.py | 3 ++- openpype/lib/__init__.py | 6 +++++- openpype/lib/abstract_collect_render.py | 2 +- openpype/lib/config.py | 6 ++++++ openpype/plugins/publish/collect_time.py | 6 +++--- 7 files changed, 21 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py index 85237094e4..35b123f97d 100644 --- a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py +++ b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py @@ -5,6 +5,7 @@ from pathlib import Path import attr from avalon import api +from openpype.lib import get_formatted_current_time import openpype.lib.abstract_collect_render import openpype.hosts.harmony.api as harmony from openpype.lib.abstract_collect_render import RenderInstance @@ -138,7 +139,7 @@ class CollectFarmRender(openpype.lib.abstract_collect_render. render_instance = HarmonyRenderInstance( version=version, - time=api.time(), + time=get_formatted_current_time(), source=context.data["currentFile"], label=node.split("/")[1], subset=subset_name, diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index d99e81573b..a525b562f3 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -50,6 +50,7 @@ import maya.app.renderSetup.model.renderSetup as renderSetup import pyblish.api from avalon import api +from openpype.lib import get_formatted_current_time from openpype.hosts.maya.api.lib_renderproducts import get as get_layer_render_products # noqa: E501 from openpype.hosts.maya.api import lib @@ -328,7 +329,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "family": "renderlayer", "families": ["renderlayer"], "asset": asset, - "time": api.time(), + "time": get_formatted_current_time(), "author": context.data["user"], # Add source to allow tracing back to the scene from # which was submitted originally diff --git a/openpype/hosts/maya/plugins/publish/collect_vrayscene.py b/openpype/hosts/maya/plugins/publish/collect_vrayscene.py index c1e5d388af..327fc836dc 100644 --- a/openpype/hosts/maya/plugins/publish/collect_vrayscene.py +++ b/openpype/hosts/maya/plugins/publish/collect_vrayscene.py @@ -7,6 +7,7 @@ from maya import cmds import pyblish.api from avalon import api +from openpype.lib import get_formatted_current_time from openpype.hosts.maya.api import lib @@ -117,7 +118,7 @@ class CollectVrayScene(pyblish.api.InstancePlugin): "family": "vrayscene_layer", "families": ["vrayscene_layer"], "asset": api.Session["AVALON_ASSET"], - "time": api.time(), + "time": get_formatted_current_time(), "author": context.data["user"], # Add source to allow tracing back to the scene from # which was submitted originally diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 34b217f690..761ad3e9a0 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -63,7 +63,10 @@ from .anatomy import ( Anatomy ) -from .config import get_datetime_data +from .config import ( + get_datetime_data, + get_formatted_current_time +) from .python_module_tools import ( import_filepath, @@ -309,6 +312,7 @@ __all__ = [ "Anatomy", "get_datetime_data", + "get_formatted_current_time", "PypeLogger", "get_default_components", diff --git a/openpype/lib/abstract_collect_render.py b/openpype/lib/abstract_collect_render.py index 3839aad45d..7c768e280c 100644 --- a/openpype/lib/abstract_collect_render.py +++ b/openpype/lib/abstract_collect_render.py @@ -26,7 +26,7 @@ class RenderInstance(object): # metadata version = attr.ib() # instance version - time = attr.ib() # time of instance creation (avalon.api.time()) + time = attr.ib() # time of instance creation (get_formatted_current_time) source = attr.ib() # path to source scene file label = attr.ib() # label to show in GUI subset = attr.ib() # subset name diff --git a/openpype/lib/config.py b/openpype/lib/config.py index ba394cfd56..57e8efa57d 100644 --- a/openpype/lib/config.py +++ b/openpype/lib/config.py @@ -74,3 +74,9 @@ def get_datetime_data(datetime_obj=None): "S": str(int(seconds)), "SS": str(seconds), } + + +def get_formatted_current_time(): + return datetime.datetime.now().strftime( + "%Y%m%dT%H%M%SZ" + ) diff --git a/openpype/plugins/publish/collect_time.py b/openpype/plugins/publish/collect_time.py index e0adc7dfc3..7a005cc9cb 100644 --- a/openpype/plugins/publish/collect_time.py +++ b/openpype/plugins/publish/collect_time.py @@ -1,12 +1,12 @@ import pyblish.api -from avalon import api +from openpype.lib import get_formatted_current_time class CollectTime(pyblish.api.ContextPlugin): """Store global time at the time of publish""" label = "Collect Current Time" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder - 0.499 def process(self, context): - context.data["time"] = api.time() + context.data["time"] = get_formatted_current_time() From cf1453029f14dde78a18d8560ee55e69871bfe46 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 11 Mar 2022 17:13:26 +0100 Subject: [PATCH 23/67] remove obsolete code and set cast --- openpype/hosts/maya/api/plugin.py | 6 +----- .../maya/plugins/publish/extract_maya_scene_raw.py | 12 ++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index 1f90b3ffbd..48d7c465ec 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -247,10 +247,7 @@ class ReferenceLoader(Loader): self.log.warning("Ignoring file read error:\n%s", exc) - shapes = cmds.ls(content, shapes=True, long=True) - new_nodes = (list(set(content) - set(shapes))) - - self._organize_containers(new_nodes, container["objectName"]) + self._organize_containers(content, container["objectName"]) # Reapply alembic settings. if representation["name"] == "abc" and alembic_data: @@ -289,7 +286,6 @@ class ReferenceLoader(Loader): to remove from scene. """ - from maya import cmds node = container["objectName"] diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py index 9d73bea7d2..389995d30c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -94,13 +94,13 @@ class ExtractMayaSceneRaw(openpype.api.Extractor): @staticmethod def _get_loaded_containers(members): # type: (list) -> list - refs_to_include = [ - cmds.referenceQuery(ref, referenceNode=True) - for ref in members - if cmds.referenceQuery(ref, isNodeReferenced=True) - ] + refs_to_include = { + cmds.referenceQuery(node, referenceNode=True) + for node in members + if cmds.referenceQuery(node, isNodeReferenced=True) + } - members_with_refs = set(refs_to_include).union(members) + members_with_refs = refs_to_include.union(members) obj_sets = cmds.ls("*.id", long=True, type="objectSet", recursive=True, objectsOnly=True) From 9b0fba249068179a4608e31b9b5d406f241d63c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 11 Mar 2022 17:55:00 +0100 Subject: [PATCH 24/67] moved ffprobe_streams into transcoding, seprated and renamed to get_ffprobe_streams --- .../publish/extract_trim_video_audio.py | 7 +- .../publish/collect_published_files.py | 8 ++- openpype/lib/__init__.py | 8 ++- openpype/lib/transcoding.py | 65 ++++++++++++++++++- openpype/lib/vendor_bin_utils.py | 52 --------------- openpype/plugins/publish/extract_review.py | 4 +- .../plugins/publish/extract_review_slate.py | 20 +++--- .../tests/test_lib_restructuralization.py | 2 +- 8 files changed, 95 insertions(+), 71 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py index c18de5bc1c..f327895b83 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py @@ -2,6 +2,9 @@ import os import pyblish.api import openpype.api +from openpype.lib import ( + get_ffmpeg_tool_path, +) from pprint import pformat @@ -27,7 +30,7 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): instance.data["representations"] = list() # get ffmpet path - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") # get staging dir staging_dir = self.staging_dir(instance) @@ -44,7 +47,7 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): clip_trimed_path = os.path.join( staging_dir, instance.data["name"] + ext) # # check video file metadata - # input_data = plib.ffprobe_streams(video_file_path)[0] + # input_data = plib.get_ffprobe_streams(video_file_path)[0] # self.log.debug(f"__ input_data: `{input_data}`") start = float(instance.data["clipInH"]) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index afd6f349db..be33e6bb4e 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -14,7 +14,11 @@ import math from avalon import io import pyblish.api -from openpype.lib import prepare_template_data, get_asset, ffprobe_streams +from openpype.lib import ( + prepare_template_data, + get_asset, + get_ffprobe_streams +) from openpype.lib.vendor_bin_utils import get_fps from openpype.lib.plugin_tools import ( parse_json, @@ -265,7 +269,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): def _get_number_of_frames(self, file_url): """Return duration in frames""" try: - streams = ffprobe_streams(file_url, self.log) + streams = get_ffprobe_streams(file_url, self.log) except Exception as exc: raise AssertionError(( "FFprobe couldn't read information about input file: \"{}\"." diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 761ad3e9a0..bf4a3781bc 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -21,7 +21,6 @@ from .vendor_bin_utils import ( get_vendor_bin_path, get_oiio_tools_path, get_ffmpeg_tool_path, - ffprobe_streams, is_oiio_supported ) from .env_tools import ( @@ -84,7 +83,9 @@ from .profiles_filtering import ( from .transcoding import ( get_transcode_temp_directory, should_convert_for_ffmpeg, - convert_for_ffmpeg + convert_for_ffmpeg, + get_ffprobe_data, + get_ffprobe_streams, ) from .avalon_context import ( CURRENT_DOC_SCHEMAS, @@ -216,7 +217,6 @@ __all__ = [ "get_vendor_bin_path", "get_oiio_tools_path", "get_ffmpeg_tool_path", - "ffprobe_streams", "is_oiio_supported", "import_filepath", @@ -228,6 +228,8 @@ __all__ = [ "get_transcode_temp_directory", "should_convert_for_ffmpeg", "convert_for_ffmpeg", + "get_ffprobe_data", + "get_ffprobe_streams", "CURRENT_DOC_SCHEMAS", "PROJECT_NAME_ALLOWED_SYMBOLS", diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 462745bcda..8137c4ae11 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1,15 +1,18 @@ import os import re import logging +import json import collections import tempfile +import subprocess import xml.etree.ElementTree from .execute import run_subprocess from .vendor_bin_utils import ( + get_ffmpeg_tool_path, get_oiio_tools_path, - is_oiio_supported + is_oiio_supported, ) # Max length of string that is supported by ffmpeg @@ -483,3 +486,63 @@ def convert_for_ffmpeg( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) + + +# FFMPEG functions +def get_ffprobe_data(path_to_file, logger=None): + """Load data about entered filepath via ffprobe. + + Args: + path_to_file (str): absolute path + logger (logging.Logger): injected logger, if empty new is created + """ + if not logger: + logger = logging.getLogger(__name__) + logger.info( + "Getting information about input \"{}\".".format(path_to_file) + ) + args = [ + get_ffmpeg_tool_path("ffprobe"), + "-hide_banner", + "-loglevel", "fatal", + "-show_error", + "-show_format", + "-show_streams", + "-show_programs", + "-show_chapters", + "-show_private_data", + "-print_format", "json", + path_to_file + ] + + logger.debug("FFprobe command: {}".format( + subprocess.list2cmdline(args) + )) + popen = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + popen_stdout, popen_stderr = popen.communicate() + if popen_stdout: + logger.debug("FFprobe stdout:\n{}".format( + popen_stdout.decode("utf-8") + )) + + if popen_stderr: + logger.warning("FFprobe stderr:\n{}".format( + popen_stderr.decode("utf-8") + )) + + return json.loads(popen_stdout) + + +def get_ffprobe_streams(path_to_file, logger=None): + """Load streams from entered filepath via ffprobe. + + Args: + path_to_file (str): absolute path + logger (logging.Logger): injected logger, if empty new is created + """ + return get_ffprobe_data(path_to_file, logger)["streams"] diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index 4b11f1c046..36510f4238 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -1,8 +1,6 @@ import os import logging -import json import platform -import subprocess log = logging.getLogger("Vendor utils") @@ -138,56 +136,6 @@ def get_ffmpeg_tool_path(tool="ffmpeg"): return find_executable(os.path.join(ffmpeg_dir, tool)) -def ffprobe_streams(path_to_file, logger=None): - """Load streams from entered filepath via ffprobe. - - Args: - path_to_file (str): absolute path - logger (logging.getLogger): injected logger, if empty new is created - - """ - if not logger: - logger = log - logger.info( - "Getting information about input \"{}\".".format(path_to_file) - ) - args = [ - get_ffmpeg_tool_path("ffprobe"), - "-hide_banner", - "-loglevel", "fatal", - "-show_error", - "-show_format", - "-show_streams", - "-show_programs", - "-show_chapters", - "-show_private_data", - "-print_format", "json", - path_to_file - ] - - logger.debug("FFprobe command: {}".format( - subprocess.list2cmdline(args) - )) - popen = subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - - popen_stdout, popen_stderr = popen.communicate() - if popen_stdout: - logger.debug("FFprobe stdout:\n{}".format( - popen_stdout.decode("utf-8") - )) - - if popen_stderr: - logger.warning("FFprobe stderr:\n{}".format( - popen_stderr.decode("utf-8") - )) - - return json.loads(popen_stdout)["streams"] - - def is_oiio_supported(): """Checks if oiiotool is configured for this platform. diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index b8599454ee..f046194c0d 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -13,7 +13,7 @@ import pyblish.api import openpype.api from openpype.lib import ( get_ffmpeg_tool_path, - ffprobe_streams, + get_ffprobe_streams, path_to_subprocess_arg, @@ -1146,7 +1146,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # NOTE Skipped using instance's resolution full_input_path_single_file = temp_data["full_input_path_single_file"] try: - streams = ffprobe_streams( + streams = get_ffprobe_streams( full_input_path_single_file, self.log ) except Exception as exc: diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 5442cf2211..e27a0b28bd 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,7 +1,11 @@ import os import openpype.api -import openpype.lib import pyblish +from openpype.lib import ( + path_to_subprocess_arg, + get_ffmpeg_tool_path, + get_ffprobe_streams, +) class ExtractReviewSlate(openpype.api.Extractor): @@ -24,9 +28,9 @@ class ExtractReviewSlate(openpype.api.Extractor): suffix = "_slate" slate_path = inst_data.get("slateFrame") - ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - slate_streams = openpype.lib.ffprobe_streams(slate_path, self.log) + slate_streams = get_ffprobe_streams(slate_path, self.log) # Try to find first stream with defined 'width' and 'height' # - this is to avoid order of streams where audio can be as first # - there may be a better way (checking `codec_type`?)+ @@ -66,7 +70,7 @@ class ExtractReviewSlate(openpype.api.Extractor): os.path.normpath(stagingdir), repre["files"]) self.log.debug("__ input_path: {}".format(input_path)) - video_streams = openpype.lib.ffprobe_streams( + video_streams = get_ffprobe_streams( input_path, self.log ) @@ -143,7 +147,7 @@ class ExtractReviewSlate(openpype.api.Extractor): else: input_args.extend(repre["outputDef"].get('input', [])) input_args.append("-loop 1 -i {}".format( - openpype.lib.path_to_subprocess_arg(slate_path) + path_to_subprocess_arg(slate_path) )) input_args.extend([ "-r {}".format(fps), @@ -216,12 +220,12 @@ class ExtractReviewSlate(openpype.api.Extractor): slate_v_path = slate_path.replace(".png", ext) output_args.append( - openpype.lib.path_to_subprocess_arg(slate_v_path) + path_to_subprocess_arg(slate_v_path) ) _remove_at_end.append(slate_v_path) slate_args = [ - openpype.lib.path_to_subprocess_arg(ffmpeg_path), + path_to_subprocess_arg(ffmpeg_path), " ".join(input_args), " ".join(output_args) ] @@ -345,7 +349,7 @@ class ExtractReviewSlate(openpype.api.Extractor): try: # Get information about input file via ffprobe tool - streams = openpype.lib.ffprobe_streams(full_input_path, self.log) + streams = get_ffprobe_streams(full_input_path, self.log) except Exception: self.log.warning( "Could not get codec data from input.", diff --git a/openpype/tests/test_lib_restructuralization.py b/openpype/tests/test_lib_restructuralization.py index d0461e55fb..94080e550d 100644 --- a/openpype/tests/test_lib_restructuralization.py +++ b/openpype/tests/test_lib_restructuralization.py @@ -24,7 +24,7 @@ def test_backward_compatibility(printer): from openpype.lib import get_hierarchy from openpype.lib import get_linked_assets from openpype.lib import get_latest_version - from openpype.lib import ffprobe_streams + from openpype.lib import get_ffprobe_streams from openpype.hosts.fusion.lib import switch_item From fbf57760abe67c0d39b7851fd70f688628ddf767 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 11 Mar 2022 17:55:36 +0100 Subject: [PATCH 25/67] added functions to replicate ffmpeg format and codec based on input data --- openpype/lib/__init__.py | 4 + openpype/lib/transcoding.py | 186 ++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index bf4a3781bc..523ced8022 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -86,6 +86,8 @@ from .transcoding import ( convert_for_ffmpeg, get_ffprobe_data, get_ffprobe_streams, + get_ffmpeg_codec_args, + get_ffmpeg_format_args, ) from .avalon_context import ( CURRENT_DOC_SCHEMAS, @@ -230,6 +232,8 @@ __all__ = [ "convert_for_ffmpeg", "get_ffprobe_data", "get_ffprobe_streams", + "get_ffmpeg_codec_args", + "get_ffmpeg_format_args", "CURRENT_DOC_SCHEMAS", "PROJECT_NAME_ALLOWED_SYMBOLS", diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 8137c4ae11..554fad8813 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -546,3 +546,189 @@ def get_ffprobe_streams(path_to_file, logger=None): logger (logging.Logger): injected logger, if empty new is created """ return get_ffprobe_data(path_to_file, logger)["streams"] + + +def get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd=None): + """Copy format from input metadata for output. + + Args: + ffprobe_data(dict): Data received from ffprobe. + source_ffmpeg_cmd(str): Command that created input if available. + """ + input_format = ffprobe_data.get("format") or {} + if input_format.get("format_name") == "mxf": + return _ffmpeg_mxf_format_args(ffprobe_data, source_ffmpeg_cmd) + return [] + + +def _ffmpeg_mxf_format_args(ffprobe_data, source_ffmpeg_cmd): + input_format = ffprobe_data["format"] + format_tags = input_format.get("tags") or {} + product_name = format_tags.get("product_name") or "" + output = [] + if "opatom" in product_name.lower(): + output.extend(["-f", "mxf_opatom"]) + return output + + +def get_ffmpeg_codec_args(ffprobe_data, source_ffmpeg_cmd=None, logger=None): + """Copy codec from input metadata for output. + + Args: + ffprobe_data(dict): Data received from ffprobe. + source_ffmpeg_cmd(str): Command that created input if available. + """ + if logger is None: + logger = logging.getLogger(__name__) + + video_stream = None + no_audio_stream = None + for stream in ffprobe_data["streams"]: + codec_type = stream["codec_type"] + if codec_type == "video": + video_stream = stream + break + elif no_audio_stream is None and codec_type != "audio": + no_audio_stream = stream + + if video_stream is None: + if no_audio_stream is None: + logger.warning( + "Couldn't find stream that is not an audio file." + ) + return [] + logger.info( + "Didn't find video stream. Using first non audio stream." + ) + video_stream = no_audio_stream + + codec_name = video_stream.get("codec_name") + # Codec "prores" + if codec_name == "prores": + return _ffmpeg_prores_codec_args(video_stream, source_ffmpeg_cmd) + + # Codec "h264" + if codec_name == "h264": + return _ffmpeg_h264_codec_args(video_stream, source_ffmpeg_cmd) + + # Coded DNxHD + if codec_name == "dnxhd": + return _ffmpeg_dnxhd_codec_args(video_stream, source_ffmpeg_cmd) + + output = [] + if codec_name: + output.extend(["-codec:v", codec_name]) + + bit_rate = video_stream.get("bit_rate") + if bit_rate: + output.extend(["-b:v", bit_rate]) + + pix_fmt = video_stream.get("pix_fmt") + if pix_fmt: + output.extend(["-pix_fmt", pix_fmt]) + + output.extend(["-g", "1"]) + + return output + + +def _ffmpeg_prores_codec_args(stream_data, source_ffmpeg_cmd): + output = [] + + tags = stream_data.get("tags") or {} + encoder = tags.get("encoder") or "" + if encoder.endswith("prores_ks"): + codec_name = "prores_ks" + + elif encoder.endswith("prores_aw"): + codec_name = "prores_aw" + + else: + codec_name = "prores" + + output.extend(["-codec:v", codec_name]) + + pix_fmt = stream_data.get("pix_fmt") + if pix_fmt: + output.extend(["-pix_fmt", pix_fmt]) + + # Rest of arguments is prores_kw specific + if codec_name == "prores_ks": + codec_tag_to_profile_map = { + "apco": "proxy", + "apcs": "lt", + "apcn": "standard", + "apch": "hq", + "ap4h": "4444", + "ap4x": "4444xq" + } + codec_tag_str = stream_data.get("codec_tag_string") + if codec_tag_str: + profile = codec_tag_to_profile_map.get(codec_tag_str) + if profile: + output.extend(["-profile:v", profile]) + + return output + + +def _ffmpeg_h264_codec_args(stream_data, source_ffmpeg_cmd): + output = ["-codec:v", "h264"] + + # Use arguments from source if are available source arguments + if source_ffmpeg_cmd: + copy_args = ( + "-crf", + "-b:v", "-vb", + "-minrate", "-minrate:", + "-maxrate", "-maxrate:", + "-bufsize", "-bufsize:" + ) + args = source_ffmpeg_cmd.split(" ") + for idx, arg in enumerate(args): + if arg in copy_args: + output.extend([arg, args[idx + 1]]) + + pix_fmt = stream_data.get("pix_fmt") + if pix_fmt: + output.extend(["-pix_fmt", pix_fmt]) + + output.extend(["-intra"]) + output.extend(["-g", "1"]) + + return output + + +def _ffmpeg_dnxhd_codec_args(stream_data, source_ffmpeg_cmd): + output = ["-codec:v", "dnxhd"] + + # Use source profile (profiles in metadata are not usable in args directly) + profile = stream_data.get("profile") or "" + # Lower profile and replace space with underscore + cleaned_profile = profile.lower().replace(" ", "_") + dnx_profiles = { + "dnxhd", + "dnxhr_lb", + "dnxhr_sq", + "dnxhr_hq", + "dnxhr_hqx", + "dnxhr_444" + } + if cleaned_profile in dnx_profiles: + output.extend(["-profile:v", cleaned_profile]) + + pix_fmt = stream_data.get("pix_fmt") + if pix_fmt: + output.extend(["-pix_fmt", pix_fmt]) + + # Use arguments from source if are available source arguments + if source_ffmpeg_cmd: + copy_args = ( + "-b:v", "-vb", + ) + args = source_ffmpeg_cmd.split(" ") + for idx, arg in enumerate(args): + if arg in copy_args: + output.extend([arg, args[idx + 1]]) + + output.extend(["-g", "1"]) + return output From 50c0580feff4142db32f183452d17a801d2dcf13 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 11 Mar 2022 17:59:52 +0100 Subject: [PATCH 26/67] use new functions in extract review slate and otio burnins --- .../publish/collect_published_files.py | 8 +- openpype/lib/__init__.py | 2 + openpype/lib/transcoding.py | 20 ++ openpype/lib/vendor_bin_utils.py | 20 -- .../plugins/publish/extract_review_slate.py | 52 ++---- openpype/scripts/otio_burnin.py | 172 ++---------------- 6 files changed, 53 insertions(+), 221 deletions(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index be33e6bb4e..65cef14703 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -17,9 +17,9 @@ import pyblish.api from openpype.lib import ( prepare_template_data, get_asset, - get_ffprobe_streams + get_ffprobe_streams, + convert_ffprobe_fps_value, ) -from openpype.lib.vendor_bin_utils import get_fps from openpype.lib.plugin_tools import ( parse_json, get_subset_name_with_asset_doc @@ -292,7 +292,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): "nb_frames {} not convertible".format(nb_frames)) duration = stream.get("duration") - frame_rate = get_fps(stream.get("r_frame_rate", '0/0')) + frame_rate = convert_ffprobe_fps_value( + stream.get("r_frame_rate", '0/0') + ) self.log.debug("duration:: {} frame_rate:: {}".format( duration, frame_rate)) try: diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 523ced8022..d5cde3031f 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -88,6 +88,7 @@ from .transcoding import ( get_ffprobe_streams, get_ffmpeg_codec_args, get_ffmpeg_format_args, + convert_ffprobe_fps_value, ) from .avalon_context import ( CURRENT_DOC_SCHEMAS, @@ -234,6 +235,7 @@ __all__ = [ "get_ffprobe_streams", "get_ffmpeg_codec_args", "get_ffmpeg_format_args", + "convert_ffprobe_fps_value", "CURRENT_DOC_SCHEMAS", "PROJECT_NAME_ALLOWED_SYMBOLS", diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 554fad8813..6181ff6d13 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -732,3 +732,23 @@ def _ffmpeg_dnxhd_codec_args(stream_data, source_ffmpeg_cmd): output.extend(["-g", "1"]) return output + + +def convert_ffprobe_fps_value(str_value): + """Returns (str) value of fps from ffprobe frame format (120/1)""" + if str_value == "0/0": + print("WARNING: Source has \"r_frame_rate\" value set to \"0/0\".") + return "Unknown" + + items = str_value.split("/") + if len(items) == 1: + fps = float(items[0]) + + elif len(items) == 2: + fps = float(items[0]) / float(items[1]) + + # Check if fps is integer or float number + if int(fps) == fps: + fps = int(fps) + + return str(fps) diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py index 36510f4238..23e28ea304 100644 --- a/openpype/lib/vendor_bin_utils.py +++ b/openpype/lib/vendor_bin_utils.py @@ -152,23 +152,3 @@ def is_oiio_supported(): )) return False return True - - -def get_fps(str_value): - """Returns (str) value of fps from ffprobe frame format (120/1)""" - if str_value == "0/0": - print("WARNING: Source has \"r_frame_rate\" value set to \"0/0\".") - return "Unknown" - - items = str_value.split("/") - if len(items) == 1: - fps = float(items[0]) - - elif len(items) == 2: - fps = float(items[0]) / float(items[1]) - - # Check if fps is integer or float number - if int(fps) == fps: - fps = int(fps) - - return str(fps) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index e27a0b28bd..460d546340 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -4,7 +4,10 @@ import pyblish from openpype.lib import ( path_to_subprocess_arg, get_ffmpeg_tool_path, + get_ffprobe_data, get_ffprobe_streams, + get_ffmpeg_codec_args, + get_ffmpeg_format_args, ) @@ -161,7 +164,7 @@ class ExtractReviewSlate(openpype.api.Extractor): output_args.extend(repre["_profile"].get('output', [])) else: # Codecs are copied from source for whole input - codec_args = self.codec_args(repre) + codec_args = self._get_codec_args(repre) output_args.extend(codec_args) # make sure colors are correct @@ -335,7 +338,7 @@ class ExtractReviewSlate(openpype.api.Extractor): return vf_back - def codec_args(self, repre): + def _get_codec_args(self, repre): """Detect possible codec arguments from representation.""" codec_args = [] @@ -349,7 +352,7 @@ class ExtractReviewSlate(openpype.api.Extractor): try: # Get information about input file via ffprobe tool - streams = get_ffprobe_streams(full_input_path, self.log) + ffprobe_data = get_ffprobe_data(full_input_path, self.log) except Exception: self.log.warning( "Could not get codec data from input.", @@ -357,42 +360,11 @@ class ExtractReviewSlate(openpype.api.Extractor): ) return codec_args - # Try to find first stream that is not an audio - no_audio_stream = None - for stream in streams: - if stream.get("codec_type") != "audio": - no_audio_stream = stream - break + codec_args.extend( + get_ffmpeg_format_args(ffprobe_data) + ) + codec_args.extend( + get_ffmpeg_codec_args(ffprobe_data, logger=self.log) + ) - if no_audio_stream is None: - self.log.warning(( - "Couldn't find stream that is not an audio from file \"{}\"" - ).format(full_input_path)) - return codec_args - - codec_name = no_audio_stream.get("codec_name") - if codec_name: - codec_args.append("-codec:v {}".format(codec_name)) - - profile_name = no_audio_stream.get("profile") - if profile_name: - # Rest of arguments is prores_kw specific - if codec_name == "prores_ks": - codec_tag_to_profile_map = { - "apco": "proxy", - "apcs": "lt", - "apcn": "standard", - "apch": "hq", - "ap4h": "4444", - "ap4x": "4444xq" - } - codec_tag_str = no_audio_stream.get("codec_tag_string") - if codec_tag_str: - profile = codec_tag_to_profile_map.get(codec_tag_str) - if profile: - codec_args.extend(["-profile:v", profile]) - - pix_fmt = no_audio_stream.get("pix_fmt") - if pix_fmt: - codec_args.append("-pix_fmt {}".format(pix_fmt)) return codec_args diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index 874c08064a..1f57891b84 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -5,12 +5,17 @@ import subprocess import platform import json import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins -import openpype.lib -from openpype.lib.vendor_bin_utils import get_fps + +from openpype.lib import ( + get_ffmpeg_tool_path, + get_ffmpeg_codec_args, + get_ffmpeg_format_args, + convert_ffprobe_fps_value, +) -ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") -ffprobe_path = openpype.lib.get_ffmpeg_tool_path("ffprobe") +ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") +ffprobe_path = get_ffmpeg_tool_path("ffprobe") FFMPEG = ( @@ -51,157 +56,6 @@ def _get_ffprobe_data(source): return json.loads(out) -def _prores_codec_args(stream_data, source_ffmpeg_cmd): - output = [] - - tags = stream_data.get("tags") or {} - encoder = tags.get("encoder") or "" - if encoder.endswith("prores_ks"): - codec_name = "prores_ks" - - elif encoder.endswith("prores_aw"): - codec_name = "prores_aw" - - else: - codec_name = "prores" - - output.extend(["-codec:v", codec_name]) - - pix_fmt = stream_data.get("pix_fmt") - if pix_fmt: - output.extend(["-pix_fmt", pix_fmt]) - - # Rest of arguments is prores_kw specific - if codec_name == "prores_ks": - codec_tag_to_profile_map = { - "apco": "proxy", - "apcs": "lt", - "apcn": "standard", - "apch": "hq", - "ap4h": "4444", - "ap4x": "4444xq" - } - codec_tag_str = stream_data.get("codec_tag_string") - if codec_tag_str: - profile = codec_tag_to_profile_map.get(codec_tag_str) - if profile: - output.extend(["-profile:v", profile]) - - return output - - -def _h264_codec_args(stream_data, source_ffmpeg_cmd): - output = ["-codec:v", "h264"] - - # Use arguments from source if are available source arguments - if source_ffmpeg_cmd: - copy_args = ( - "-crf", - "-b:v", "-vb", - "-minrate", "-minrate:", - "-maxrate", "-maxrate:", - "-bufsize", "-bufsize:" - ) - args = source_ffmpeg_cmd.split(" ") - for idx, arg in enumerate(args): - if arg in copy_args: - output.extend([arg, args[idx + 1]]) - - pix_fmt = stream_data.get("pix_fmt") - if pix_fmt: - output.extend(["-pix_fmt", pix_fmt]) - - output.extend(["-intra"]) - output.extend(["-g", "1"]) - - return output - - -def _dnxhd_codec_args(stream_data, source_ffmpeg_cmd): - output = ["-codec:v", "dnxhd"] - - # Use source profile (profiles in metadata are not usable in args directly) - profile = stream_data.get("profile") or "" - # Lower profile and replace space with underscore - cleaned_profile = profile.lower().replace(" ", "_") - dnx_profiles = { - "dnxhd", - "dnxhr_lb", - "dnxhr_sq", - "dnxhr_hq", - "dnxhr_hqx", - "dnxhr_444" - } - if cleaned_profile in dnx_profiles: - output.extend(["-profile:v", cleaned_profile]) - - pix_fmt = stream_data.get("pix_fmt") - if pix_fmt: - output.extend(["-pix_fmt", pix_fmt]) - - # Use arguments from source if are available source arguments - if source_ffmpeg_cmd: - copy_args = ( - "-b:v", "-vb", - ) - args = source_ffmpeg_cmd.split(" ") - for idx, arg in enumerate(args): - if arg in copy_args: - output.extend([arg, args[idx + 1]]) - - output.extend(["-g", "1"]) - return output - - -def _mxf_format_args(ffprobe_data, source_ffmpeg_cmd): - input_format = ffprobe_data["format"] - format_tags = input_format.get("tags") or {} - product_name = format_tags.get("product_name") or "" - output = [] - if "opatom" in product_name.lower(): - output.extend(["-f", "mxf_opatom"]) - return output - - -def get_format_args(ffprobe_data, source_ffmpeg_cmd): - input_format = ffprobe_data.get("format") or {} - if input_format.get("format_name") == "mxf": - return _mxf_format_args(ffprobe_data, source_ffmpeg_cmd) - return [] - - -def get_codec_args(ffprobe_data, source_ffmpeg_cmd): - stream_data = ffprobe_data["streams"][0] - codec_name = stream_data.get("codec_name") - # Codec "prores" - if codec_name == "prores": - return _prores_codec_args(stream_data, source_ffmpeg_cmd) - - # Codec "h264" - if codec_name == "h264": - return _h264_codec_args(stream_data, source_ffmpeg_cmd) - - # Coded DNxHD - if codec_name == "dnxhd": - return _dnxhd_codec_args(stream_data, source_ffmpeg_cmd) - - output = [] - if codec_name: - output.extend(["-codec:v", codec_name]) - - bit_rate = stream_data.get("bit_rate") - if bit_rate: - output.extend(["-b:v", bit_rate]) - - pix_fmt = stream_data.get("pix_fmt") - if pix_fmt: - output.extend(["-pix_fmt", pix_fmt]) - - output.extend(["-g", "1"]) - - return output - - class ModifiedBurnins(ffmpeg_burnins.Burnins): ''' This is modification of OTIO FFmpeg Burnin adapter. @@ -592,7 +446,9 @@ def burnins_from_data( data["resolution_height"] = stream.get("height", MISSING_KEY_VALUE) if "fps" not in data: - data["fps"] = get_fps(stream.get("r_frame_rate", "0/0")) + data["fps"] = convert_ffprobe_fps_value( + stream.get("r_frame_rate", "0/0") + ) # Check frame start and add expression if is available if frame_start is not None: @@ -703,10 +559,10 @@ def burnins_from_data( else: ffmpeg_args.extend( - get_format_args(burnin.ffprobe_data, source_ffmpeg_cmd) + get_ffmpeg_format_args(burnin.ffprobe_data, source_ffmpeg_cmd) ) ffmpeg_args.extend( - get_codec_args(burnin.ffprobe_data, source_ffmpeg_cmd) + get_ffmpeg_codec_args(burnin.ffprobe_data, source_ffmpeg_cmd) ) # Use arguments from source if are available source arguments if source_ffmpeg_cmd: From 3ef05cc480598ea1c900f48bd704c4d808647e3d Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 12 Mar 2022 03:37:54 +0000 Subject: [PATCH 27/67] [Automated] Bump version --- CHANGELOG.md | 10 +++++----- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f971c33208..ebc563c90b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,16 @@ # Changelog -## [3.9.0-nightly.8](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.9.0-nightly.9](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...HEAD) **Deprecated:** - AssetCreator: Remove the tool [\#2845](https://github.com/pypeclub/OpenPype/pull/2845) -- Houdini: Remove unused code [\#2779](https://github.com/pypeclub/OpenPype/pull/2779) **🚀 Enhancements** +- General: Subset name filtering in ExtractReview outpus [\#2872](https://github.com/pypeclub/OpenPype/pull/2872) - NewPublisher: Descriptions and Icons in creator dialog [\#2867](https://github.com/pypeclub/OpenPype/pull/2867) - NewPublisher: Changing task on publishing instance [\#2863](https://github.com/pypeclub/OpenPype/pull/2863) - TrayPublisher: Choose project widget is more clear [\#2859](https://github.com/pypeclub/OpenPype/pull/2859) @@ -22,10 +22,10 @@ - global: letter box calculated on output as last process [\#2812](https://github.com/pypeclub/OpenPype/pull/2812) - Nuke: adding Reformat to baking mov plugin [\#2811](https://github.com/pypeclub/OpenPype/pull/2811) - Manager: Update all to latest button [\#2805](https://github.com/pypeclub/OpenPype/pull/2805) -- General: Set context environments for non host applications [\#2803](https://github.com/pypeclub/OpenPype/pull/2803) **🐛 Bug fixes** +- General: Missing time function [\#2877](https://github.com/pypeclub/OpenPype/pull/2877) - Deadline: Fix plugin name for tile assemble [\#2868](https://github.com/pypeclub/OpenPype/pull/2868) - General: Fix hardlink for windows [\#2864](https://github.com/pypeclub/OpenPype/pull/2864) - General: ffmpeg was crashing on slate merge [\#2860](https://github.com/pypeclub/OpenPype/pull/2860) @@ -47,13 +47,13 @@ - Ftrack: Job killer with missing user [\#2819](https://github.com/pypeclub/OpenPype/pull/2819) - Nuke: Use AVALON\_APP to get value for "app" key [\#2818](https://github.com/pypeclub/OpenPype/pull/2818) - StandalonePublisher: use dynamic groups in subset names [\#2816](https://github.com/pypeclub/OpenPype/pull/2816) -- Settings UI: Search case sensitivity [\#2810](https://github.com/pypeclub/OpenPype/pull/2810) -- Flame Babypublisher optimalization [\#2806](https://github.com/pypeclub/OpenPype/pull/2806) **🔀 Refactored code** +- Refactor: move webserver tool to openpype [\#2876](https://github.com/pypeclub/OpenPype/pull/2876) - General: Move create logic from avalon to OpenPype [\#2854](https://github.com/pypeclub/OpenPype/pull/2854) - General: Add vendors from avalon [\#2848](https://github.com/pypeclub/OpenPype/pull/2848) +- General: Basic event system [\#2846](https://github.com/pypeclub/OpenPype/pull/2846) - General: Move change context functions [\#2839](https://github.com/pypeclub/OpenPype/pull/2839) - Tools: Don't use avalon tools code [\#2829](https://github.com/pypeclub/OpenPype/pull/2829) - Move Unreal Implementation to OpenPype [\#2823](https://github.com/pypeclub/OpenPype/pull/2823) diff --git a/openpype/version.py b/openpype/version.py index d4af8d760f..39d3037221 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.9.0-nightly.8" +__version__ = "3.9.0-nightly.9" diff --git a/pyproject.toml b/pyproject.toml index fe681266ca..7a7411fdfd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.9.0-nightly.8" # OpenPype +version = "3.9.0-nightly.9" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 8d14f5fb7017adac8401137a6815ca49f1bd1a8b Mon Sep 17 00:00:00 2001 From: OpenPype Date: Mon, 14 Mar 2022 08:17:30 +0000 Subject: [PATCH 28/67] [Automated] Release --- CHANGELOG.md | 11 ++++------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc563c90b..5acb161bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.9.0-nightly.9](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.9.0](https://github.com/pypeclub/OpenPype/tree/3.9.0) (2022-03-14) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...3.9.0) **Deprecated:** @@ -27,6 +27,7 @@ - General: Missing time function [\#2877](https://github.com/pypeclub/OpenPype/pull/2877) - Deadline: Fix plugin name for tile assemble [\#2868](https://github.com/pypeclub/OpenPype/pull/2868) +- Nuke: gizmo precollect fix [\#2866](https://github.com/pypeclub/OpenPype/pull/2866) - General: Fix hardlink for windows [\#2864](https://github.com/pypeclub/OpenPype/pull/2864) - General: ffmpeg was crashing on slate merge [\#2860](https://github.com/pypeclub/OpenPype/pull/2860) - WebPublisher: Video file was published with one too many frame [\#2858](https://github.com/pypeclub/OpenPype/pull/2858) @@ -35,6 +36,7 @@ - Nuke: slate resolution to input video resolution [\#2853](https://github.com/pypeclub/OpenPype/pull/2853) - WebPublisher: Fix username stored in DB [\#2852](https://github.com/pypeclub/OpenPype/pull/2852) - WebPublisher: Fix wrong number of frames for video file [\#2851](https://github.com/pypeclub/OpenPype/pull/2851) +- Nuke: Fix family test in validate\_write\_legacy to work with stillImage [\#2847](https://github.com/pypeclub/OpenPype/pull/2847) - Nuke: fix multiple baking profile farm publishing [\#2842](https://github.com/pypeclub/OpenPype/pull/2842) - Blender: Fixed parameters for FBX export of the camera [\#2840](https://github.com/pypeclub/OpenPype/pull/2840) - Maya: Stop creation of reviews for Cryptomattes [\#2832](https://github.com/pypeclub/OpenPype/pull/2832) @@ -59,11 +61,6 @@ - Move Unreal Implementation to OpenPype [\#2823](https://github.com/pypeclub/OpenPype/pull/2823) - General: Extract template formatting from anatomy [\#2766](https://github.com/pypeclub/OpenPype/pull/2766) -**Merged pull requests:** - -- Nuke: gizmo precollect fix [\#2866](https://github.com/pypeclub/OpenPype/pull/2866) -- Nuke: Fix family test in validate\_write\_legacy to work with stillImage [\#2847](https://github.com/pypeclub/OpenPype/pull/2847) - ## [3.8.2](https://github.com/pypeclub/OpenPype/tree/3.8.2) (2022-02-07) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.2-nightly.3...3.8.2) diff --git a/openpype/version.py b/openpype/version.py index 39d3037221..d2182ac7da 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.9.0-nightly.9" +__version__ = "3.9.0" diff --git a/pyproject.toml b/pyproject.toml index 7a7411fdfd..681702560a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.9.0-nightly.9" # OpenPype +version = "3.9.0" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 3e840894137cbf40f9aff29e59cc4663c5dc29ec Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 14 Mar 2022 10:07:11 +0100 Subject: [PATCH 29/67] update avalon submodule --- repos/avalon-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/avalon-core b/repos/avalon-core index ffe9e910f1..7753d15507 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit ffe9e910f1f382e222d457d8e4a8426c41ed43ae +Subproject commit 7753d15507afadc143b7d49db8fcfaa6a29fed91 From 4fed92a1e542c88fa6f4c5091efebf280d2fc02b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 10:57:23 +0100 Subject: [PATCH 30/67] copied base of load logic into openpype --- openpype/pipeline/load/__init__.py | 78 ++++ openpype/pipeline/load/plugins.py | 129 ++++++ openpype/pipeline/load/utils.py | 706 +++++++++++++++++++++++++++++ 3 files changed, 913 insertions(+) create mode 100644 openpype/pipeline/load/__init__.py create mode 100644 openpype/pipeline/load/plugins.py create mode 100644 openpype/pipeline/load/utils.py diff --git a/openpype/pipeline/load/__init__.py b/openpype/pipeline/load/__init__.py new file mode 100644 index 0000000000..2af15e8705 --- /dev/null +++ b/openpype/pipeline/load/__init__.py @@ -0,0 +1,78 @@ +from .utils import ( + HeroVersionType, + IncompatibleLoaderError, + + get_repres_contexts, + get_subset_contexts, + get_representation_context, + + load_with_repre_context, + load_with_subset_context, + load_with_subset_contexts, + + load_representation, + remove_container, + update_container, + switch_container, + + get_loader_identifier, + + get_representation_path_from_context, + get_representation_path, + + is_compatible_loader, + + loaders_from_repre_context, + loaders_from_representation, +) + +from .plugins import ( + LoaderPlugin, + SubsetLoaderPlugin, + + discover_loader_plugins, + register_loader_plugin, + deregister_loader_plugins_path, + register_loader_plugins_path, + deregister_loader_plugin, +) + + +__all__ = ( + # utils.py + "HeroVersionType", + "IncompatibleLoaderError", + + "get_repres_contexts", + "get_subset_contexts", + "get_representation_context", + + "load_with_repre_context", + "load_with_subset_context", + "load_with_subset_contexts", + + "load_representation", + "remove_container", + "update_container", + "switch_container", + + "get_loader_identifier", + + "get_representation_path_from_context", + "get_representation_path", + + "is_compatible_loader", + + "loaders_from_repre_context", + "loaders_from_representation", + + # plugins.py + "LoaderPlugin", + "SubsetLoaderPlugin", + + "discover_loader_plugins", + "register_loader_plugin", + "deregister_loader_plugins_path", + "register_loader_plugins_path", + "deregister_loader_plugin", +) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py new file mode 100644 index 0000000000..d7e21e1248 --- /dev/null +++ b/openpype/pipeline/load/plugins.py @@ -0,0 +1,129 @@ +import logging + +from avalon.api import ( + discover, + register_plugin, + deregister_plugin, + register_plugin_path, + deregister_plugin_path, +) + +from .utils import get_representation_path_from_context + + +class LoaderPlugin(list): + """Load representation into host application + + Arguments: + context (dict): avalon-core:context-1.0 + name (str, optional): Use pre-defined name + namespace (str, optional): Use pre-defined namespace + + .. versionadded:: 4.0 + This class was introduced + + """ + + families = list() + representations = list() + order = 0 + is_multiple_contexts_compatible = False + + options = [] + + log = logging.getLogger("SubsetLoader") + log.propagate = True + + def __init__(self, context): + self.fname = self.filepath_from_context(context) + + @classmethod + def get_representations(cls): + return cls.representations + + def filepath_from_context(self, context): + return get_representation_path_from_context(context) + + def load(self, context, name=None, namespace=None, options=None): + """Load asset via database + + Arguments: + context (dict): Full parenthood of representation to load + name (str, optional): Use pre-defined name + namespace (str, optional): Use pre-defined namespace + options (dict, optional): Additional settings dictionary + + """ + raise NotImplementedError("Loader.load() must be " + "implemented by subclass") + + def update(self, container, representation): + """Update `container` to `representation` + + Arguments: + container (avalon-core:container-1.0): Container to update, + from `host.ls()`. + representation (dict): Update the container to this representation. + + """ + raise NotImplementedError("Loader.update() must be " + "implemented by subclass") + + def remove(self, container): + """Remove a container + + Arguments: + container (avalon-core:container-1.0): Container to remove, + from `host.ls()`. + + Returns: + bool: Whether the container was deleted + + """ + + raise NotImplementedError("Loader.remove() must be " + "implemented by subclass") + + @classmethod + def get_options(cls, contexts): + """ + Returns static (cls) options or could collect from 'contexts'. + + Args: + contexts (list): of repre or subset contexts + Returns: + (list) + """ + return cls.options or [] + + +class SubsetLoaderPlugin(LoaderPlugin): + """Load subset into host application + Arguments: + context (dict): avalon-core:context-1.0 + name (str, optional): Use pre-defined name + namespace (str, optional): Use pre-defined namespace + """ + + def __init__(self, context): + pass + + +def discover_loader_plugins(): + return discover(LoaderPlugin) + + +def register_loader_plugin(plugin): + return register_plugin(LoaderPlugin, plugin) + + +def deregister_loader_plugins_path(path): + deregister_plugin_path(LoaderPlugin, path) + + +def register_loader_plugins_path(path): + return register_plugin_path(LoaderPlugin, path) + + +def deregister_loader_plugin(plugin): + deregister_plugin(LoaderPlugin, plugin) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py new file mode 100644 index 0000000000..4ef0f099d7 --- /dev/null +++ b/openpype/pipeline/load/utils.py @@ -0,0 +1,706 @@ +import os +import platform +import copy +import getpass +import logging +import inspect +import numbers + +import six + +from avalon import io, schema +from avalon.api import Session, registered_root + +from openpype.lib import Anatomy + +log = logging.getLogger(__name__) + + +class HeroVersionType(object): + def __init__(self, version): + assert isinstance(version, numbers.Integral), ( + "Version is not an integer. \"{}\" {}".format( + version, str(type(version)) + ) + ) + self.version = version + + def __str__(self): + return str(self.version) + + def __int__(self): + return int(self.version) + + def __format__(self, format_spec): + return self.version.__format__(format_spec) + + +class IncompatibleLoaderError(ValueError): + """Error when Loader is incompatible with a representation.""" + pass + + +def get_repres_contexts(representation_ids, dbcon=None): + """Return parenthood context for representation. + + Args: + representation_ids (list): The representation ids. + dbcon (AvalonMongoDB): Mongo connection object. `avalon.io` used when + not entered. + + Returns: + dict: The full representation context by representation id. + keys are repre_id, value is dictionary with full: + asset_doc + version_doc + subset_doc + repre_doc + + """ + if not dbcon: + dbcon = io + + contexts = {} + if not representation_ids: + return contexts + + _representation_ids = [] + for repre_id in representation_ids: + if isinstance(repre_id, six.string_types): + repre_id = io.ObjectId(repre_id) + _representation_ids.append(repre_id) + + repre_docs = dbcon.find({ + "type": "representation", + "_id": {"$in": _representation_ids} + }) + repre_docs_by_id = {} + version_ids = set() + for repre_doc in repre_docs: + version_ids.add(repre_doc["parent"]) + repre_docs_by_id[repre_doc["_id"]] = repre_doc + + version_docs = dbcon.find({ + "type": {"$in": ["version", "hero_version"]}, + "_id": {"$in": list(version_ids)} + }) + + version_docs_by_id = {} + hero_version_docs = [] + versions_for_hero = set() + subset_ids = set() + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version_docs.append(version_doc) + versions_for_hero.add(version_doc["version_id"]) + version_docs_by_id[version_doc["_id"]] = version_doc + subset_ids.add(version_doc["parent"]) + + if versions_for_hero: + _version_docs = dbcon.find({ + "type": "version", + "_id": {"$in": list(versions_for_hero)} + }) + _version_data_by_id = { + version_doc["_id"]: version_doc["data"] + for version_doc in _version_docs + } + + for hero_version_doc in hero_version_docs: + hero_version_id = hero_version_doc["_id"] + version_id = hero_version_doc["version_id"] + version_data = copy.deepcopy(_version_data_by_id[version_id]) + version_docs_by_id[hero_version_id]["data"] = version_data + + subset_docs = dbcon.find({ + "type": "subset", + "_id": {"$in": list(subset_ids)} + }) + subset_docs_by_id = {} + asset_ids = set() + for subset_doc in subset_docs: + subset_docs_by_id[subset_doc["_id"]] = subset_doc + asset_ids.add(subset_doc["parent"]) + + asset_docs = dbcon.find({ + "type": "asset", + "_id": {"$in": list(asset_ids)} + }) + asset_docs_by_id = { + asset_doc["_id"]: asset_doc + for asset_doc in asset_docs + } + + project_doc = dbcon.find_one({"type": "project"}) + + for repre_id, repre_doc in repre_docs_by_id.items(): + version_doc = version_docs_by_id[repre_doc["parent"]] + subset_doc = subset_docs_by_id[version_doc["parent"]] + asset_doc = asset_docs_by_id[subset_doc["parent"]] + context = { + "project": { + "name": project_doc["name"], + "code": project_doc["data"].get("code") + }, + "asset": asset_doc, + "subset": subset_doc, + "version": version_doc, + "representation": repre_doc, + } + contexts[repre_id] = context + + return contexts + + +def get_subset_contexts(subset_ids, dbcon=None): + """Return parenthood context for subset. + + Provides context on subset granularity - less detail than + 'get_repre_contexts'. + Args: + subset_ids (list): The subset ids. + dbcon (AvalonMongoDB): Mongo connection object. `avalon.io` used when + not entered. + Returns: + dict: The full representation context by representation id. + """ + if not dbcon: + dbcon = io + + contexts = {} + if not subset_ids: + return contexts + + _subset_ids = set() + for subset_id in subset_ids: + if isinstance(subset_id, six.string_types): + subset_id = io.ObjectId(subset_id) + _subset_ids.add(subset_id) + + subset_docs = dbcon.find({ + "type": "subset", + "_id": {"$in": list(_subset_ids)} + }) + subset_docs_by_id = {} + asset_ids = set() + for subset_doc in subset_docs: + subset_docs_by_id[subset_doc["_id"]] = subset_doc + asset_ids.add(subset_doc["parent"]) + + asset_docs = dbcon.find({ + "type": "asset", + "_id": {"$in": list(asset_ids)} + }) + asset_docs_by_id = { + asset_doc["_id"]: asset_doc + for asset_doc in asset_docs + } + + project_doc = dbcon.find_one({"type": "project"}) + + for subset_id, subset_doc in subset_docs_by_id.items(): + asset_doc = asset_docs_by_id[subset_doc["parent"]] + context = { + "project": { + "name": project_doc["name"], + "code": project_doc["data"].get("code") + }, + "asset": asset_doc, + "subset": subset_doc + } + contexts[subset_id] = context + + return contexts + + +def get_representation_context(representation): + """Return parenthood context for representation. + + Args: + representation (str or io.ObjectId or dict): The representation id + or full representation as returned by the database. + + Returns: + dict: The full representation context. + + """ + + assert representation is not None, "This is a bug" + + if isinstance(representation, (six.string_types, io.ObjectId)): + representation = io.find_one( + {"_id": io.ObjectId(str(representation))}) + + version, subset, asset, project = io.parenthood(representation) + + assert all([representation, version, subset, asset, project]), ( + "This is a bug" + ) + + context = { + "project": { + "name": project["name"], + "code": project["data"].get("code", '') + }, + "asset": asset, + "subset": subset, + "version": version, + "representation": representation, + } + + return context + + +def load_with_repre_context( + Loader, repre_context, namespace=None, name=None, options=None, **kwargs +): + + # Ensure the Loader is compatible for the representation + if not is_compatible_loader(Loader, repre_context): + raise IncompatibleLoaderError( + "Loader {} is incompatible with {}".format( + Loader.__name__, repre_context["subset"]["name"] + ) + ) + + # Ensure options is a dictionary when no explicit options provided + if options is None: + options = kwargs.get("data", dict()) # "data" for backward compat + + assert isinstance(options, dict), "Options must be a dictionary" + + # Fallback to subset when name is None + if name is None: + name = repre_context["subset"]["name"] + + log.info( + "Running '%s' on '%s'" % ( + Loader.__name__, repre_context["asset"]["name"] + ) + ) + + loader = Loader(repre_context) + return loader.load(repre_context, name, namespace, options) + + +def load_with_subset_context( + Loader, subset_context, namespace=None, name=None, options=None, **kwargs +): + + # Ensure options is a dictionary when no explicit options provided + if options is None: + options = kwargs.get("data", dict()) # "data" for backward compat + + assert isinstance(options, dict), "Options must be a dictionary" + + # Fallback to subset when name is None + if name is None: + name = subset_context["subset"]["name"] + + log.info( + "Running '%s' on '%s'" % ( + Loader.__name__, subset_context["asset"]["name"] + ) + ) + + loader = Loader(subset_context) + return loader.load(subset_context, name, namespace, options) + + +def load_with_subset_contexts( + Loader, subset_contexts, namespace=None, name=None, options=None, **kwargs +): + + # Ensure options is a dictionary when no explicit options provided + if options is None: + options = kwargs.get("data", dict()) # "data" for backward compat + + assert isinstance(options, dict), "Options must be a dictionary" + + # Fallback to subset when name is None + joined_subset_names = " | ".join( + context["subset"]["name"] + for context in subset_contexts + ) + if name is None: + name = joined_subset_names + + log.info( + "Running '{}' on '{}'".format(Loader.__name__, joined_subset_names) + ) + + loader = Loader(subset_contexts) + return loader.load(subset_contexts, name, namespace, options) + + +def load_representation( + Loader, representation, namespace=None, name=None, options=None, **kwargs +): + """Use Loader to load a representation. + + Args: + Loader (Loader): The loader class to trigger. + representation (str or io.ObjectId or dict): The representation id + or full representation as returned by the database. + namespace (str, Optional): The namespace to assign. Defaults to None. + name (str, Optional): The name to assign. Defaults to subset name. + options (dict, Optional): Additional options to pass on to the loader. + + Returns: + The return of the `loader.load()` method. + + Raises: + IncompatibleLoaderError: When the loader is not compatible with + the representation. + + """ + + context = get_representation_context(representation) + return load_with_repre_context( + Loader, + context, + namespace=namespace, + name=name, + options=options, + **kwargs + ) + + +def get_loader_identifier(loader): + """Loader identifier from loader plugin or object. + + Identifier should be stored to container for future management. + """ + if not inspect.isclass(loader): + loader = loader.__class__ + return loader.__name__ + + +def _get_container_loader(container): + """Return the Loader corresponding to the container""" + from .plugins import discover_loader_plugins + + loader = container["loader"] + for Plugin in discover_loader_plugins(): + # TODO: Ensure the loader is valid + if get_loader_identifier(Plugin) == loader: + return Plugin + return None + + +def remove_container(container): + """Remove a container""" + + Loader = _get_container_loader(container) + if not Loader: + raise RuntimeError("Can't remove container. See log for details.") + + loader = Loader(get_representation_context(container["representation"])) + return loader.remove(container) + + +def update_container(container, version=-1): + """Update a container""" + + # Compute the different version from 'representation' + current_representation = io.find_one({ + "_id": io.ObjectId(container["representation"]) + }) + + assert current_representation is not None, "This is a bug" + + current_version, subset, asset, project = io.parenthood( + current_representation) + + if version == -1: + new_version = io.find_one({ + "type": "version", + "parent": subset["_id"] + }, sort=[("name", -1)]) + else: + if isinstance(version, HeroVersionType): + version_query = { + "parent": subset["_id"], + "type": "hero_version" + } + else: + version_query = { + "parent": subset["_id"], + "type": "version", + "name": version + } + new_version = io.find_one(version_query) + + assert new_version is not None, "This is a bug" + + new_representation = io.find_one({ + "type": "representation", + "parent": new_version["_id"], + "name": current_representation["name"] + }) + + assert new_representation is not None, "Representation wasn't found" + + path = get_representation_path(new_representation) + assert os.path.exists(path), "Path {} doesn't exist".format(path) + + # Run update on the Loader for this container + Loader = _get_container_loader(container) + if not Loader: + raise RuntimeError("Can't update container. See log for details.") + + loader = Loader(get_representation_context(container["representation"])) + return loader.update(container, new_representation) + + +def switch_container(container, representation, loader_plugin=None): + """Switch a container to representation + + Args: + container (dict): container information + representation (dict): representation data from document + + Returns: + function call + """ + + # Get the Loader for this container + if loader_plugin is None: + loader_plugin = _get_container_loader(container) + + if not loader_plugin: + raise RuntimeError("Can't switch container. See log for details.") + + if not hasattr(loader_plugin, "switch"): + # Backwards compatibility (classes without switch support + # might be better to just have "switch" raise NotImplementedError + # on the base class of Loader\ + raise RuntimeError("Loader '{}' does not support 'switch'".format( + loader_plugin.label + )) + + # Get the new representation to switch to + new_representation = io.find_one({ + "type": "representation", + "_id": representation["_id"], + }) + + new_context = get_representation_context(new_representation) + if not is_compatible_loader(loader_plugin, new_context): + raise AssertionError("Must be compatible Loader") + + loader = loader_plugin(new_context) + + return loader.switch(container, new_representation) + + +def get_representation_path_from_context(context): + """Preparation wrapper using only context as a argument""" + representation = context['representation'] + project_doc = context.get("project") + root = None + session_project = Session.get("AVALON_PROJECT") + if project_doc and project_doc["name"] != session_project: + anatomy = Anatomy(project_doc["name"]) + root = anatomy.roots_obj + + return get_representation_path(representation, root) + + +def get_representation_path(representation, root=None, dbcon=None): + """Get filename from representation document + + There are three ways of getting the path from representation which are + tried in following sequence until successful. + 1. Get template from representation['data']['template'] and data from + representation['context']. Then format template with the data. + 2. Get template from project['config'] and format it with default data set + 3. Get representation['data']['path'] and use it directly + + Args: + representation(dict): representation document from the database + + Returns: + str: fullpath of the representation + + """ + + from openpype.lib import StringTemplate + + if dbcon is None: + dbcon = io + + if root is None: + root = registered_root() + + def path_from_represenation(): + try: + template = representation["data"]["template"] + except KeyError: + return None + + try: + context = representation["context"] + context["root"] = root + template_obj = StringTemplate(template) + path = str(template_obj.format(context)) + # Force replacing backslashes with forward slashed if not on + # windows + if platform.system().lower() != "windows": + path = path.replace("\\", "/") + except KeyError: + # Template references unavailable data + return None + + if not path: + return path + + normalized_path = os.path.normpath(path) + if os.path.exists(normalized_path): + return normalized_path + return path + + def path_from_config(): + try: + version_, subset, asset, project = dbcon.parenthood(representation) + except ValueError: + log.debug( + "Representation %s wasn't found in database, " + "like a bug" % representation["name"] + ) + return None + + try: + template = project["config"]["template"]["publish"] + except KeyError: + log.debug( + "No template in project %s, " + "likely a bug" % project["name"] + ) + return None + + # default list() in get would not discover missing parents on asset + parents = asset.get("data", {}).get("parents") + if parents is not None: + hierarchy = "/".join(parents) + + # Cannot fail, required members only + data = { + "root": root, + "project": { + "name": project["name"], + "code": project.get("data", {}).get("code") + }, + "asset": asset["name"], + "silo": asset.get("silo"), + "hierarchy": hierarchy, + "subset": subset["name"], + "version": version_["name"], + "representation": representation["name"], + "family": representation.get("context", {}).get("family"), + "user": dbcon.Session.get("AVALON_USER", getpass.getuser()), + "app": dbcon.Session.get("AVALON_APP", ""), + "task": dbcon.Session.get("AVALON_TASK", "") + } + + try: + template_obj = StringTemplate(template) + path = str(template_obj.format(data)) + # Force replacing backslashes with forward slashed if not on + # windows + if platform.system().lower() != "windows": + path = path.replace("\\", "/") + + except KeyError as e: + log.debug("Template references unavailable data: %s" % e) + return None + + normalized_path = os.path.normpath(path) + if os.path.exists(normalized_path): + return normalized_path + return path + + def path_from_data(): + if "path" not in representation["data"]: + return None + + path = representation["data"]["path"] + # Force replacing backslashes with forward slashed if not on + # windows + if platform.system().lower() != "windows": + path = path.replace("\\", "/") + + if os.path.exists(path): + return os.path.normpath(path) + + dir_path, file_name = os.path.split(path) + if not os.path.exists(dir_path): + return + + base_name, ext = os.path.splitext(file_name) + file_name_items = None + if "#" in base_name: + file_name_items = [part for part in base_name.split("#") if part] + elif "%" in base_name: + file_name_items = base_name.split("%") + + if not file_name_items: + return + + filename_start = file_name_items[0] + + for _file in os.listdir(dir_path): + if _file.startswith(filename_start) and _file.endswith(ext): + return os.path.normpath(path) + + return ( + path_from_represenation() or + path_from_config() or + path_from_data() + ) + + +def is_compatible_loader(Loader, context): + """Return whether a loader is compatible with a context. + + This checks the version's families and the representation for the given + Loader. + + Returns: + bool + + """ + maj_version, _ = schema.get_schema_version(context["subset"]["schema"]) + if maj_version < 3: + families = context["version"]["data"].get("families", []) + else: + families = context["subset"]["data"]["families"] + + representation = context["representation"] + has_family = ( + "*" in Loader.families or any( + family in Loader.families for family in families + ) + ) + representations = Loader.get_representations() + has_representation = ( + "*" in representations or representation["name"] in representations + ) + return has_family and has_representation + + +def loaders_from_repre_context(loaders, repre_context): + """Return compatible loaders for by representaiton's context.""" + + return [ + loader + for loader in loaders + if is_compatible_loader(loader, repre_context) + ] + + +def loaders_from_representation(loaders, representation): + """Return all compatible loaders for a representation.""" + + context = get_representation_context(representation) + return loaders_from_repre_context(loaders, context) From 6fe1b5b96554266b1e426fb61016ec5891b1563b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 10:58:43 +0100 Subject: [PATCH 31/67] porpagate some load function in openpype.pipeline --- openpype/pipeline/__init__.py | 45 ++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 7147e56dd2..d582ef1d07 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -12,6 +12,27 @@ from .create import ( legacy_create, ) +from .load import ( + HeroVersionType, + IncompatibleLoaderError, + LoaderPlugin, + SubsetLoaderPlugin, + + discover_loader_plugins, + register_loader_plugin, + deregister_loader_plugins_path, + register_loader_plugins_path, + deregister_loader_plugin, + + load_representation, + remove_container, + update_container, + switch_container, + + loaders_from_representation, + get_representation_path, +) + from .publish import ( PublishValidationError, PublishXmlValidationError, @@ -23,6 +44,7 @@ from .publish import ( __all__ = ( "attribute_definitions", + # --- Create --- "BaseCreator", "Creator", "AutoCreator", @@ -30,10 +52,31 @@ __all__ = ( "CreatorError", - # Legacy creation + # - legacy creation "LegacyCreator", "legacy_create", + # --- Load --- + "HeroVersionType", + "IncompatibleLoaderError", + "LoaderPlugin", + "SubsetLoaderPlugin", + + "discover_loader_plugins", + "register_loader_plugin", + "deregister_loader_plugins_path", + "register_loader_plugins_path", + "deregister_loader_plugin", + + "load_representation", + "remove_container", + "update_container", + "switch_container", + + "loaders_from_representation", + "get_representation_path", + + # --- Publish --- "PublishValidationError", "PublishXmlValidationError", "KnownPublishError", From 25c7e56613c78a69e22afe49b1c4fd6ea18adabc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 11:16:02 +0100 Subject: [PATCH 32/67] move import of LegacyCreator to function --- openpype/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/__init__.py b/openpype/__init__.py index 7d046e4ef4..7720c9dfb8 100644 --- a/openpype/__init__.py +++ b/openpype/__init__.py @@ -5,7 +5,6 @@ import platform import functools import logging -from openpype.pipeline import LegacyCreator from .settings import get_project_settings from .lib import ( Anatomy, @@ -76,6 +75,7 @@ def install(): """Install Pype to Avalon.""" from pyblish.lib import MessageHandler from openpype.modules import load_modules + from openpype.pipeline import LegacyCreator from avalon import pipeline # Make sure modules are loaded From c5ac2290f69566b6b4a52ede5551056270e373e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 11:36:17 +0100 Subject: [PATCH 33/67] use moved functions in hosts and tools --- openpype/__init__.py | 13 +++-- openpype/hosts/aftereffects/api/pipeline.py | 10 ++-- openpype/hosts/aftereffects/api/plugin.py | 5 +- .../plugins/load/load_background.py | 5 +- .../aftereffects/plugins/load/load_file.py | 4 +- openpype/hosts/blender/api/pipeline.py | 10 ++-- openpype/hosts/blender/api/plugin.py | 10 ++-- .../hosts/blender/plugins/load/load_abc.py | 4 +- .../hosts/blender/plugins/load/load_action.py | 35 ++++++------- .../hosts/blender/plugins/load/load_audio.py | 4 +- .../blender/plugins/load/load_camera_blend.py | 4 +- .../blender/plugins/load/load_camera_fbx.py | 4 +- .../hosts/blender/plugins/load/load_fbx.py | 4 +- .../blender/plugins/load/load_layout_blend.py | 8 +-- .../blender/plugins/load/load_layout_json.py | 18 ++++--- .../hosts/blender/plugins/load/load_look.py | 4 +- .../hosts/blender/plugins/load/load_model.py | 4 +- .../hosts/blender/plugins/load/load_rig.py | 16 +++--- openpype/hosts/flame/api/pipeline.py | 10 ++-- openpype/hosts/flame/api/plugin.py | 8 +-- .../hosts/flame/plugins/load/load_clip.py | 2 +- openpype/hosts/fusion/api/lib.py | 4 +- openpype/hosts/fusion/api/pipeline.py | 10 ++-- openpype/hosts/fusion/plugins/load/actions.py | 6 +-- .../fusion/plugins/load/load_sequence.py | 10 ++-- openpype/hosts/harmony/api/README.md | 2 +- openpype/hosts/harmony/api/pipeline.py | 10 ++-- .../hosts/harmony/plugins/load/load_audio.py | 9 ++-- .../harmony/plugins/load/load_background.py | 11 ++-- .../plugins/load/load_imagesequence.py | 9 ++-- .../harmony/plugins/load/load_palette.py | 9 ++-- .../harmony/plugins/load/load_template.py | 9 ++-- .../plugins/load/load_template_workfile.py | 9 ++-- openpype/hosts/hiero/api/pipeline.py | 10 ++-- openpype/hosts/hiero/api/plugin.py | 6 +-- .../hosts/hiero/plugins/load/load_clip.py | 5 +- openpype/hosts/houdini/api/pipeline.py | 7 ++- .../hosts/houdini/plugins/load/actions.py | 6 +-- .../houdini/plugins/load/load_alembic.py | 10 ++-- .../hosts/houdini/plugins/load/load_ass.py | 12 +++-- .../hosts/houdini/plugins/load/load_camera.py | 9 ++-- .../hosts/houdini/plugins/load/load_hda.py | 12 +++-- .../hosts/houdini/plugins/load/load_image.py | 9 ++-- .../houdini/plugins/load/load_usd_layer.py | 9 ++-- .../plugins/load/load_usd_reference.py | 9 ++-- .../hosts/houdini/plugins/load/load_vdb.py | 9 ++-- .../houdini/plugins/load/show_usdview.py | 4 +- .../plugins/publish/extract_usd_layered.py | 3 +- openpype/hosts/maya/api/lib.py | 16 ++++-- openpype/hosts/maya/api/pipeline.py | 10 ++-- openpype/hosts/maya/api/plugin.py | 11 ++-- openpype/hosts/maya/api/setdress.py | 46 ++++++++++------- .../plugins/inventory/import_modelrender.py | 14 +++-- openpype/hosts/maya/plugins/load/actions.py | 8 +-- openpype/hosts/maya/plugins/load/load_ass.py | 11 ++-- .../hosts/maya/plugins/load/load_assembly.py | 13 ++--- .../hosts/maya/plugins/load/load_audio.py | 10 ++-- .../hosts/maya/plugins/load/load_gpucache.py | 10 ++-- .../maya/plugins/load/load_image_plane.py | 10 ++-- openpype/hosts/maya/plugins/load/load_look.py | 5 +- .../hosts/maya/plugins/load/load_matchmove.py | 4 +- .../maya/plugins/load/load_redshift_proxy.py | 9 ++-- .../maya/plugins/load/load_rendersetup.py | 11 ++-- .../maya/plugins/load/load_vdb_to_redshift.py | 5 +- .../maya/plugins/load/load_vdb_to_vray.py | 10 ++-- .../hosts/maya/plugins/load/load_vrayproxy.py | 15 ++++-- .../hosts/maya/plugins/load/load_vrayscene.py | 9 ++-- .../maya/plugins/load/load_yeti_cache.py | 12 +++-- openpype/hosts/nuke/api/pipeline.py | 10 ++-- openpype/hosts/nuke/api/plugin.py | 9 ++-- openpype/hosts/nuke/plugins/load/actions.py | 6 +-- .../hosts/nuke/plugins/load/load_backdrop.py | 10 ++-- .../nuke/plugins/load/load_camera_abc.py | 10 ++-- openpype/hosts/nuke/plugins/load/load_clip.py | 5 +- .../hosts/nuke/plugins/load/load_effects.py | 10 ++-- .../nuke/plugins/load/load_effects_ip.py | 10 ++-- .../hosts/nuke/plugins/load/load_gizmo.py | 10 ++-- .../hosts/nuke/plugins/load/load_gizmo_ip.py | 10 ++-- .../hosts/nuke/plugins/load/load_image.py | 10 ++-- .../hosts/nuke/plugins/load/load_matchmove.py | 4 +- .../hosts/nuke/plugins/load/load_model.py | 10 ++-- .../nuke/plugins/load/load_script_precomp.py | 10 ++-- .../nuke/plugins/publish/precollect_writes.py | 5 +- .../plugins/publish/validate_read_legacy.py | 12 +++-- openpype/hosts/photoshop/api/README.md | 7 +-- openpype/hosts/photoshop/api/pipeline.py | 10 ++-- openpype/hosts/photoshop/api/plugin.py | 4 +- .../photoshop/plugins/load/load_image.py | 4 +- .../plugins/load/load_image_from_sequence.py | 2 +- .../photoshop/plugins/load/load_reference.py | 5 +- openpype/hosts/resolve/api/pipeline.py | 10 ++-- openpype/hosts/resolve/api/plugin.py | 11 ++-- .../hosts/resolve/plugins/load/load_clip.py | 9 ++-- openpype/hosts/tvpaint/api/pipeline.py | 10 ++-- openpype/hosts/tvpaint/api/plugin.py | 9 ++-- openpype/hosts/unreal/api/pipeline.py | 10 ++-- openpype/hosts/unreal/api/plugin.py | 8 +-- .../load/load_alembic_geometrycache.py | 5 +- .../plugins/load/load_alembic_skeletalmesh.py | 5 +- .../plugins/load/load_alembic_staticmesh.py | 5 +- .../unreal/plugins/load/load_animation.py | 5 +- .../hosts/unreal/plugins/load/load_layout.py | 20 +++++--- .../hosts/unreal/plugins/load/load_rig.py | 5 +- .../unreal/plugins/load/load_staticmeshfbx.py | 5 +- openpype/hosts/webpublisher/api/__init__.py | 7 --- openpype/lib/avalon_context.py | 13 +++-- openpype/lib/path_templates.py | 10 ++++ openpype/lib/plugin_tools.py | 7 +-- .../plugins/publish/submit_publish_job.py | 4 +- .../ftrack/event_handlers_user/action_rv.py | 5 +- openpype/pipeline/create/legacy_create.py | 1 + openpype/plugins/load/add_site.py | 4 +- openpype/plugins/load/copy_file.py | 6 ++- openpype/plugins/load/copy_file_path.py | 4 +- openpype/plugins/load/delete_old_versions.py | 19 +++---- openpype/plugins/load/delivery.py | 4 +- openpype/plugins/load/open_djv.py | 5 +- openpype/plugins/load/open_file.py | 4 +- openpype/plugins/load/remove_site.py | 4 +- openpype/tools/loader/model.py | 2 +- openpype/tools/loader/widgets.py | 51 +++++++++++-------- openpype/tools/mayalookassigner/commands.py | 7 +-- .../tools/mayalookassigner/vray_proxies.py | 14 +++-- openpype/tools/sceneinventory/model.py | 5 +- .../tools/sceneinventory/switch_dialog.py | 13 +++-- openpype/tools/sceneinventory/view.py | 18 ++++--- openpype/tools/utils/delegates.py | 2 +- 127 files changed, 711 insertions(+), 427 deletions(-) diff --git a/openpype/__init__.py b/openpype/__init__.py index 942112835b..755036168d 100644 --- a/openpype/__init__.py +++ b/openpype/__init__.py @@ -5,7 +5,12 @@ import platform import functools import logging -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) + from .settings import get_project_settings from .lib import ( Anatomy, @@ -90,7 +95,7 @@ def install(): log.info("Registering global plug-ins..") pyblish.register_plugin_path(PUBLISH_PATH) pyblish.register_discovery_filter(filter_pyblish_plugins) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) project_name = os.environ.get("AVALON_PROJECT") @@ -118,7 +123,7 @@ def install(): continue pyblish.register_plugin_path(path) - avalon.register_plugin_path(avalon.Loader, path) + register_loader_plugins_path(path) avalon.register_plugin_path(LegacyCreator, path) avalon.register_plugin_path(avalon.InventoryAction, path) @@ -141,7 +146,7 @@ def uninstall(): log.info("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) pyblish.deregister_discovery_filter(filter_pyblish_plugins) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) log.info("Global plug-ins unregistred") # restore original discover diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index ef56e96155..2dc41bd8b9 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -9,7 +9,11 @@ from avalon import io, pipeline from openpype import lib from openpype.api import Logger -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) import openpype.hosts.aftereffects from .launch_logic import get_stub @@ -66,7 +70,7 @@ def install(): pyblish.api.register_host("aftereffects") pyblish.api.register_plugin_path(PUBLISH_PATH) - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info(PUBLISH_PATH) @@ -79,7 +83,7 @@ def install(): def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) - avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) diff --git a/openpype/hosts/aftereffects/api/plugin.py b/openpype/hosts/aftereffects/api/plugin.py index fbe07663dd..29705cc5be 100644 --- a/openpype/hosts/aftereffects/api/plugin.py +++ b/openpype/hosts/aftereffects/api/plugin.py @@ -1,9 +1,8 @@ -import avalon.api +from openpype.pipeline import LoaderPlugin from .launch_logic import get_stub -class AfterEffectsLoader(avalon.api.Loader): +class AfterEffectsLoader(LoaderPlugin): @staticmethod def get_stub(): return get_stub() - diff --git a/openpype/hosts/aftereffects/plugins/load/load_background.py b/openpype/hosts/aftereffects/plugins/load/load_background.py index 1a2d6fc432..be43cae44e 100644 --- a/openpype/hosts/aftereffects/plugins/load/load_background.py +++ b/openpype/hosts/aftereffects/plugins/load/load_background.py @@ -1,11 +1,10 @@ import re -import avalon.api - from openpype.lib import ( get_background_layers, get_unique_layer_name ) +from openpype.pipeline import get_representation_path from openpype.hosts.aftereffects.api import ( AfterEffectsLoader, containerise @@ -78,7 +77,7 @@ class BackgroundLoader(AfterEffectsLoader): else: # switching version - keep same name comp_name = container["namespace"] - path = avalon.api.get_representation_path(representation) + path = get_representation_path(representation) layers = get_background_layers(path) comp = stub.reload_background(container["members"][1], diff --git a/openpype/hosts/aftereffects/plugins/load/load_file.py b/openpype/hosts/aftereffects/plugins/load/load_file.py index 9dbbf7aae1..9eb9e80a2c 100644 --- a/openpype/hosts/aftereffects/plugins/load/load_file.py +++ b/openpype/hosts/aftereffects/plugins/load/load_file.py @@ -1,8 +1,8 @@ import re -import avalon.api from openpype import lib +from openpype.pipeline import get_representation_path from openpype.hosts.aftereffects.api import ( AfterEffectsLoader, containerise @@ -92,7 +92,7 @@ class FileLoader(AfterEffectsLoader): "{}_{}".format(context["asset"], context["subset"])) else: # switching version - keep same name layer_name = container["namespace"] - path = avalon.api.get_representation_path(representation) + path = get_representation_path(representation) # with aftereffects.maintained_selection(): # TODO stub.replace_item(layer.id, path, stub.LOADED_ICON + layer_name) stub.imprint( diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 1c9820ff22..f3a5c941eb 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -14,7 +14,11 @@ import avalon.api from avalon import io, schema from avalon.pipeline import AVALON_CONTAINER_ID -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) from openpype.api import Logger import openpype.hosts.blender @@ -46,7 +50,7 @@ def install(): pyblish.api.register_host("blender") pyblish.api.register_plugin_path(str(PUBLISH_PATH)) - avalon.api.register_plugin_path(avalon.api.Loader, str(LOAD_PATH)) + register_loader_plugins_path(str(LOAD_PATH)) avalon.api.register_plugin_path(LegacyCreator, str(CREATE_PATH)) lib.append_user_scripts() @@ -67,7 +71,7 @@ def uninstall(): pyblish.api.deregister_host("blender") pyblish.api.deregister_plugin_path(str(PUBLISH_PATH)) - avalon.api.deregister_plugin_path(avalon.api.Loader, str(LOAD_PATH)) + deregister_loader_plugins_path(str(LOAD_PATH)) avalon.api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH)) if not IS_HEADLESS: diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py index 20d1e4c8db..3207f543b7 100644 --- a/openpype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -5,8 +5,10 @@ from typing import Dict, List, Optional import bpy -import avalon.api -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + LoaderPlugin, +) from .pipeline import AVALON_CONTAINERS from .ops import ( MainThreadItem, @@ -145,13 +147,13 @@ class Creator(LegacyCreator): return collection -class Loader(avalon.api.Loader): +class Loader(LoaderPlugin): """Base class for Loader plug-ins.""" hosts = ["blender"] -class AssetLoader(avalon.api.Loader): +class AssetLoader(LoaderPlugin): """A basic AssetLoader for Blender This will implement the basic logic for linking/appending assets diff --git a/openpype/hosts/blender/plugins/load/load_abc.py b/openpype/hosts/blender/plugins/load/load_abc.py index 07800521c9..3daaeceffe 100644 --- a/openpype/hosts/blender/plugins/load/load_abc.py +++ b/openpype/hosts/blender/plugins/load/load_abc.py @@ -6,7 +6,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api +from openpype.pipeline import get_representation_path from openpype.hosts.blender.api.pipeline import ( AVALON_CONTAINERS, AVALON_PROPERTY, @@ -178,7 +178,7 @@ class CacheModelLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/blender/plugins/load/load_action.py b/openpype/hosts/blender/plugins/load/load_action.py index a9d8522220..3c8fe988f0 100644 --- a/openpype/hosts/blender/plugins/load/load_action.py +++ b/openpype/hosts/blender/plugins/load/load_action.py @@ -5,9 +5,13 @@ from pathlib import Path from pprint import pformat from typing import Dict, List, Optional -from avalon import api, blender import bpy +from openpype.pipeline import get_representation_path import openpype.hosts.blender.api.plugin +from openpype.hosts.blender.api.pipeline import ( + containerise_existing, + AVALON_PROPERTY, +) logger = logging.getLogger("openpype").getChild("blender").getChild("load_action") @@ -49,7 +53,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): container = bpy.data.collections.new(lib_container) container.name = container_name - blender.pipeline.containerise_existing( + containerise_existing( container, name, namespace, @@ -57,8 +61,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): self.__class__.__name__, ) - container_metadata = container.get( - blender.pipeline.AVALON_PROPERTY) + container_metadata = container.get(AVALON_PROPERTY) container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container @@ -90,16 +93,16 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): anim_data.action.make_local() - if not obj.get(blender.pipeline.AVALON_PROPERTY): + if not obj.get(AVALON_PROPERTY): - obj[blender.pipeline.AVALON_PROPERTY] = dict() + obj[AVALON_PROPERTY] = dict() - avalon_info = obj[blender.pipeline.AVALON_PROPERTY] + avalon_info = obj[AVALON_PROPERTY] avalon_info.update({"container_name": container_name}) objects_list.append(obj) - animation_container.pop(blender.pipeline.AVALON_PROPERTY) + animation_container.pop(AVALON_PROPERTY) # Save the list of objects in the metadata container container_metadata["objects"] = objects_list @@ -128,7 +131,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): container["objectName"] ) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() logger.info( @@ -153,8 +156,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): f"Unsupported file: {libpath}" ) - collection_metadata = collection.get( - blender.pipeline.AVALON_PROPERTY) + collection_metadata = collection.get(AVALON_PROPERTY) collection_libpath = collection_metadata["libpath"] normalized_collection_libpath = ( @@ -225,16 +227,16 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): strip.action = anim_data.action strip.action_frame_end = anim_data.action.frame_range[1] - if not obj.get(blender.pipeline.AVALON_PROPERTY): + if not obj.get(AVALON_PROPERTY): - obj[blender.pipeline.AVALON_PROPERTY] = dict() + obj[AVALON_PROPERTY] = dict() - avalon_info = obj[blender.pipeline.AVALON_PROPERTY] + avalon_info = obj[AVALON_PROPERTY] avalon_info.update({"container_name": collection.name}) objects_list.append(obj) - anim_container.pop(blender.pipeline.AVALON_PROPERTY) + anim_container.pop(AVALON_PROPERTY) # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list @@ -266,8 +268,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): "Nested collections are not supported." ) - collection_metadata = collection.get( - blender.pipeline.AVALON_PROPERTY) + collection_metadata = collection.get(AVALON_PROPERTY) objects = collection_metadata["objects"] lib_container = collection_metadata["lib_container"] diff --git a/openpype/hosts/blender/plugins/load/load_audio.py b/openpype/hosts/blender/plugins/load/load_audio.py index e065150c15..b95c5db270 100644 --- a/openpype/hosts/blender/plugins/load/load_audio.py +++ b/openpype/hosts/blender/plugins/load/load_audio.py @@ -6,7 +6,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api +from openpype.pipeline import get_representation_path from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import ( AVALON_CONTAINERS, @@ -102,7 +102,7 @@ class AudioLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) self.log.info( "Container: %s\nRepresentation: %s", diff --git a/openpype/hosts/blender/plugins/load/load_camera_blend.py b/openpype/hosts/blender/plugins/load/load_camera_blend.py index 61955f124d..6ed2e8a575 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_blend.py +++ b/openpype/hosts/blender/plugins/load/load_camera_blend.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api +from openpype.pipeline import get_representation_path from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import ( AVALON_CONTAINERS, @@ -155,7 +155,7 @@ class BlendCameraLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/blender/plugins/load/load_camera_fbx.py b/openpype/hosts/blender/plugins/load/load_camera_fbx.py index 175ddacf9f..626ed44f08 100644 --- a/openpype/hosts/blender/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_camera_fbx.py @@ -6,7 +6,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api +from openpype.pipeline import get_representation_path from openpype.hosts.blender.api import plugin, lib from openpype.hosts.blender.api.pipeline import ( AVALON_CONTAINERS, @@ -143,7 +143,7 @@ class FbxCameraLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/blender/plugins/load/load_fbx.py b/openpype/hosts/blender/plugins/load/load_fbx.py index c6e6af5592..2d249ef647 100644 --- a/openpype/hosts/blender/plugins/load/load_fbx.py +++ b/openpype/hosts/blender/plugins/load/load_fbx.py @@ -6,7 +6,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api +from openpype.pipeline import get_representation_path from openpype.hosts.blender.api import plugin, lib from openpype.hosts.blender.api.pipeline import ( AVALON_CONTAINERS, @@ -187,7 +187,7 @@ class FbxModelLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/blender/plugins/load/load_layout_blend.py b/openpype/hosts/blender/plugins/load/load_layout_blend.py index 7f8ae610c6..d87df3c010 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_blend.py +++ b/openpype/hosts/blender/plugins/load/load_layout_blend.py @@ -6,9 +6,11 @@ from typing import Dict, List, Optional import bpy -from avalon import api from openpype import lib -from openpype.pipeline import legacy_create +from openpype.pipeline import ( + legacy_create, + get_representation_path, +) from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import ( AVALON_CONTAINERS, @@ -309,7 +311,7 @@ class BlendLayoutLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index 5b5f9ab83d..499d2c49f3 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -7,7 +7,13 @@ from typing import Dict, Optional import bpy -from avalon import api +from openpype.pipeline import ( + discover_loader_plugins, + remove_container, + load_representation, + get_representation_path, + loaders_from_representation, +) from openpype.hosts.blender.api.pipeline import ( AVALON_INSTANCES, AVALON_CONTAINERS, @@ -33,7 +39,7 @@ class JsonLayoutLoader(plugin.AssetLoader): objects = list(asset_group.children) for obj in objects: - api.remove(obj.get(AVALON_PROPERTY)) + remove_container(obj.get(AVALON_PROPERTY)) def _remove_animation_instances(self, asset_group): instances = bpy.data.collections.get(AVALON_INSTANCES) @@ -66,13 +72,13 @@ class JsonLayoutLoader(plugin.AssetLoader): with open(libpath, "r") as fp: data = json.load(fp) - all_loaders = api.discover(api.Loader) + all_loaders = discover_loader_plugins() for element in data: reference = element.get('reference') family = element.get('family') - loaders = api.loaders_from_representation(all_loaders, reference) + loaders = loaders_from_representation(all_loaders, reference) loader = self._get_loader(loaders, family) if not loader: @@ -102,7 +108,7 @@ class JsonLayoutLoader(plugin.AssetLoader): # at this time it will not return anything. The assets will be # loaded in the next Blender cycle, so we use the options to # set the transform, parent and assign the action, if there is one. - api.load( + load_representation( loader, reference, namespace=instance_name, @@ -188,7 +194,7 @@ class JsonLayoutLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/blender/plugins/load/load_look.py b/openpype/hosts/blender/plugins/load/load_look.py index 066ec0101b..70d1b95f02 100644 --- a/openpype/hosts/blender/plugins/load/load_look.py +++ b/openpype/hosts/blender/plugins/load/load_look.py @@ -8,7 +8,7 @@ import os import json import bpy -from avalon import api +from openpype.pipeline import get_representation_path from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import ( containerise_existing, @@ -140,7 +140,7 @@ class BlendLookLoader(plugin.AssetLoader): def update(self, container: Dict, representation: Dict): collection = bpy.data.collections.get(container["objectName"]) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py index 04ece0b338..18d01dcb29 100644 --- a/openpype/hosts/blender/plugins/load/load_model.py +++ b/openpype/hosts/blender/plugins/load/load_model.py @@ -6,7 +6,7 @@ from typing import Dict, List, Optional import bpy -from avalon import api +from openpype.pipeline import get_representation_path from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import ( AVALON_CONTAINERS, @@ -195,7 +195,7 @@ class BlendModelLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/blender/plugins/load/load_rig.py b/openpype/hosts/blender/plugins/load/load_rig.py index eacabd3447..cec088076c 100644 --- a/openpype/hosts/blender/plugins/load/load_rig.py +++ b/openpype/hosts/blender/plugins/load/load_rig.py @@ -6,11 +6,15 @@ from typing import Dict, List, Optional import bpy -from avalon import api -from avalon.blender import lib as avalon_lib from openpype import lib -from openpype.pipeline import legacy_create -from openpype.hosts.blender.api import plugin +from openpype.pipeline import ( + legacy_create, + get_representation_path, +) +from openpype.hosts.blender.api import ( + plugin, + get_selection, +) from openpype.hosts.blender.api.pipeline import ( AVALON_CONTAINERS, AVALON_PROPERTY, @@ -263,7 +267,7 @@ class BlendRigLoader(plugin.AssetLoader): if anim_file: bpy.ops.import_scene.fbx(filepath=anim_file, anim_offset=0.0) - imported = avalon_lib.get_selection() + imported = get_selection() armature = [ o for o in asset_group.children if o.type == 'ARMATURE'][0] @@ -307,7 +311,7 @@ class BlendRigLoader(plugin.AssetLoader): """ object_name = container["objectName"] asset_group = bpy.data.objects.get(object_name) - libpath = Path(api.get_representation_path(representation)) + libpath = Path(get_representation_path(representation)) extension = libpath.suffix.lower() self.log.info( diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index f802cf160b..6a045214c3 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -7,7 +7,11 @@ from avalon import api as avalon from avalon.pipeline import AVALON_CONTAINER_ID from pyblish import api as pyblish from openpype.api import Logger -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) from .lib import ( set_segment_data_marker, set_publish_attribute, @@ -33,7 +37,7 @@ def install(): pyblish.register_host("flame") pyblish.register_plugin_path(PUBLISH_PATH) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) log.info("OpenPype Flame plug-ins registred ...") @@ -48,7 +52,7 @@ def uninstall(): log.info("Deregistering Flame plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH) avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index 5221701a2f..4c9d3c5383 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -7,9 +7,11 @@ import six import qargparse from Qt import QtWidgets, QtCore import openpype.api as openpype -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + LoaderPlugin, +) from openpype import style -import avalon.api as avalon from . import ( lib as flib, pipeline as fpipeline, @@ -660,7 +662,7 @@ class PublishableClip: # Publishing plugin functions # Loader plugin functions -class ClipLoader(avalon.Loader): +class ClipLoader(LoaderPlugin): """A basic clip loader for Flame This will implement the basic behavior for a loader to inherit from that diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index 8ba01d6937..8980f72cb8 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -172,7 +172,7 @@ class LoadClip(opfapi.ClipLoader): # version_name = version.get("name", None) # colorspace = version_data.get("colorspace", None) # object_name = "{}_{}".format(name, namespace) - # file = api.get_representation_path(representation).replace("\\", "/") + # file = get_representation_path(representation).replace("\\", "/") # clip = track_item.source() # # reconnect media to new path diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py index 5d97f83032..2bb5ea8aae 100644 --- a/openpype/hosts/fusion/api/lib.py +++ b/openpype/hosts/fusion/api/lib.py @@ -5,8 +5,8 @@ import contextlib from Qt import QtGui -import avalon.api from avalon import io +from openpype.pipeline import switch_container from .pipeline import get_current_comp, comp_lock_and_undo_chunk self = sys.modules[__name__] @@ -142,7 +142,7 @@ def switch_item(container, assert representation, ("Could not find representation in the database " "with the name '%s'" % representation_name) - avalon.api.switch(container, representation) + switch_container(container, representation) return representation diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 5ac56fcbed..3f5da7fcc7 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -11,7 +11,11 @@ import avalon.api from avalon.pipeline import AVALON_CONTAINER_ID from openpype.api import Logger -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) import openpype.hosts.fusion log = Logger().get_logger(__name__) @@ -63,7 +67,7 @@ def install(): pyblish.api.register_plugin_path(PUBLISH_PATH) log.info("Registering Fusion plug-ins..") - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) @@ -87,7 +91,7 @@ def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) log.info("Deregistering Fusion plug-ins..") - avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.deregister_plugin_path( avalon.api.InventoryAction, INVENTORY_PATH diff --git a/openpype/hosts/fusion/plugins/load/actions.py b/openpype/hosts/fusion/plugins/load/actions.py index 6af99e4c56..bc59cec77f 100644 --- a/openpype/hosts/fusion/plugins/load/actions.py +++ b/openpype/hosts/fusion/plugins/load/actions.py @@ -2,10 +2,10 @@ """ -from avalon import api +from openpype.pipeline import load -class FusionSetFrameRangeLoader(api.Loader): +class FusionSetFrameRangeLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["animation", @@ -39,7 +39,7 @@ class FusionSetFrameRangeLoader(api.Loader): lib.update_frame_range(start, end) -class FusionSetFrameRangeWithHandlesLoader(api.Loader): +class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["animation", diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index ea118585bf..075820de35 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -1,8 +1,12 @@ import os import contextlib -from avalon import api, io +from avalon import io +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.fusion.api import ( imprint_container, get_current_comp, @@ -117,7 +121,7 @@ def loader_shift(loader, frame, relative=True): return int(shift) -class FusionLoadSequence(api.Loader): +class FusionLoadSequence(load.LoaderPlugin): """Load image sequence into Fusion""" families = ["imagesequence", "review", "render"] @@ -204,7 +208,7 @@ class FusionLoadSequence(api.Loader): assert tool.ID == "Loader", "Must be Loader" comp = tool.Comp() - root = os.path.dirname(api.get_representation_path(representation)) + root = os.path.dirname(get_representation_path(representation)) path = self._get_first_image(root) # Get start frame from version data diff --git a/openpype/hosts/harmony/api/README.md b/openpype/hosts/harmony/api/README.md index a8d182736a..e8d354e1e6 100644 --- a/openpype/hosts/harmony/api/README.md +++ b/openpype/hosts/harmony/api/README.md @@ -575,7 +575,7 @@ replace_files = """function %s_replace_files(args) """ % (signature, signature) -class ImageSequenceLoader(api.Loader): +class ImageSequenceLoader(load.LoaderPlugin): """Load images Stores the imported asset in a container named after the asset. """ diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index 6d0f5e9416..be183902a7 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -9,7 +9,11 @@ import avalon.api from avalon.pipeline import AVALON_CONTAINER_ID from openpype import lib -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) import openpype.hosts.harmony import openpype.hosts.harmony.api as harmony @@ -179,7 +183,7 @@ def install(): pyblish.api.register_host("harmony") pyblish.api.register_plugin_path(PUBLISH_PATH) - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info(PUBLISH_PATH) @@ -193,7 +197,7 @@ def install(): def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) - avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) diff --git a/openpype/hosts/harmony/plugins/load/load_audio.py b/openpype/hosts/harmony/plugins/load/load_audio.py index 57ea8ae312..e18a6de097 100644 --- a/openpype/hosts/harmony/plugins/load/load_audio.py +++ b/openpype/hosts/harmony/plugins/load/load_audio.py @@ -1,4 +1,7 @@ -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) import openpype.hosts.harmony.api as harmony sig = harmony.signature() @@ -29,7 +32,7 @@ function %s(args) """ % (sig, sig) -class ImportAudioLoader(api.Loader): +class ImportAudioLoader(load.LoaderPlugin): """Import audio.""" families = ["shot", "audio"] @@ -37,7 +40,7 @@ class ImportAudioLoader(api.Loader): label = "Import Audio" def load(self, context, name=None, namespace=None, data=None): - wav_file = api.get_representation_path(context["representation"]) + wav_file = get_representation_path(context["representation"]) harmony.send( {"function": func, "args": [context["subset"]["name"], wav_file]} ) diff --git a/openpype/hosts/harmony/plugins/load/load_background.py b/openpype/hosts/harmony/plugins/load/load_background.py index 686d6b5b7b..9c01fe3cd8 100644 --- a/openpype/hosts/harmony/plugins/load/load_background.py +++ b/openpype/hosts/harmony/plugins/load/load_background.py @@ -1,7 +1,10 @@ import os import json -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) import openpype.hosts.harmony.api as harmony import openpype.lib @@ -226,7 +229,7 @@ replace_files """ -class BackgroundLoader(api.Loader): +class BackgroundLoader(load.LoaderPlugin): """Load images Stores the imported asset in a container named after the asset. """ @@ -278,7 +281,7 @@ class BackgroundLoader(api.Loader): def update(self, container, representation): - path = api.get_representation_path(representation) + path = get_representation_path(representation) with open(path) as json_file: data = json.load(json_file) @@ -297,7 +300,7 @@ class BackgroundLoader(api.Loader): bg_folder = os.path.dirname(path) - path = api.get_representation_path(representation) + path = get_representation_path(representation) print(container) diff --git a/openpype/hosts/harmony/plugins/load/load_imagesequence.py b/openpype/hosts/harmony/plugins/load/load_imagesequence.py index 310f9bdb61..18695438d5 100644 --- a/openpype/hosts/harmony/plugins/load/load_imagesequence.py +++ b/openpype/hosts/harmony/plugins/load/load_imagesequence.py @@ -6,12 +6,15 @@ from pathlib import Path import clique -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) import openpype.hosts.harmony.api as harmony import openpype.lib -class ImageSequenceLoader(api.Loader): +class ImageSequenceLoader(load.LoaderPlugin): """Load image sequences. Stores the imported asset in a container named after the asset. @@ -79,7 +82,7 @@ class ImageSequenceLoader(api.Loader): self_name = self.__class__.__name__ node = container.get("nodes").pop() - path = api.get_representation_path(representation) + path = get_representation_path(representation) collections, remainder = clique.assemble( os.listdir(os.path.dirname(path)) ) diff --git a/openpype/hosts/harmony/plugins/load/load_palette.py b/openpype/hosts/harmony/plugins/load/load_palette.py index 2e0f70d135..1da3e61e1b 100644 --- a/openpype/hosts/harmony/plugins/load/load_palette.py +++ b/openpype/hosts/harmony/plugins/load/load_palette.py @@ -1,11 +1,14 @@ import os import shutil -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) import openpype.hosts.harmony.api as harmony -class ImportPaletteLoader(api.Loader): +class ImportPaletteLoader(load.LoaderPlugin): """Import palettes.""" families = ["palette", "harmony.palette"] @@ -31,7 +34,7 @@ class ImportPaletteLoader(api.Loader): scene_path = harmony.send( {"function": "scene.currentProjectPath"} )["result"] - src = api.get_representation_path(representation) + src = get_representation_path(representation) dst = os.path.join( scene_path, "palette-library", diff --git a/openpype/hosts/harmony/plugins/load/load_template.py b/openpype/hosts/harmony/plugins/load/load_template.py index 112e613ae6..c6dc9d913b 100644 --- a/openpype/hosts/harmony/plugins/load/load_template.py +++ b/openpype/hosts/harmony/plugins/load/load_template.py @@ -6,12 +6,15 @@ import os import shutil import uuid -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) import openpype.hosts.harmony.api as harmony import openpype.lib -class TemplateLoader(api.Loader): +class TemplateLoader(load.LoaderPlugin): """Load Harmony template as container. .. todo:: @@ -38,7 +41,7 @@ class TemplateLoader(api.Loader): # Load template. self_name = self.__class__.__name__ temp_dir = tempfile.mkdtemp() - zip_file = api.get_representation_path(context["representation"]) + zip_file = get_representation_path(context["representation"]) template_path = os.path.join(temp_dir, "temp.tpl") with zipfile.ZipFile(zip_file, "r") as zip_ref: zip_ref.extractall(template_path) diff --git a/openpype/hosts/harmony/plugins/load/load_template_workfile.py b/openpype/hosts/harmony/plugins/load/load_template_workfile.py index c21b8194b1..2b84a43b35 100644 --- a/openpype/hosts/harmony/plugins/load/load_template_workfile.py +++ b/openpype/hosts/harmony/plugins/load/load_template_workfile.py @@ -3,11 +3,14 @@ import zipfile import os import shutil -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) import openpype.hosts.harmony.api as harmony -class ImportTemplateLoader(api.Loader): +class ImportTemplateLoader(load.LoaderPlugin): """Import templates.""" families = ["harmony.template", "workfile"] @@ -17,7 +20,7 @@ class ImportTemplateLoader(api.Loader): def load(self, context, name=None, namespace=None, data=None): # Import template. temp_dir = tempfile.mkdtemp() - zip_file = api.get_representation_path(context["representation"]) + zip_file = get_representation_path(context["representation"]) template_path = os.path.join(temp_dir, "temp.tpl") with zipfile.ZipFile(zip_file, "r") as zip_ref: zip_ref.extractall(template_path) diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 5cb23ea355..f27b7a4f81 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -9,7 +9,11 @@ from avalon import api as avalon from avalon import schema from pyblish import api as pyblish from openpype.api import Logger -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) from openpype.tools.utils import host_tools from . import lib, menu, events @@ -45,7 +49,7 @@ def install(): log.info("Registering Hiero plug-ins..") pyblish.register_host("hiero") pyblish.register_plugin_path(PUBLISH_PATH) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) @@ -67,7 +71,7 @@ def uninstall(): log.info("Deregistering Hiero plug-ins..") pyblish.deregister_host("hiero") pyblish.deregister_plugin_path(PUBLISH_PATH) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH) # register callback for switching publishable diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py index 53928aca41..54e66bf99a 100644 --- a/openpype/hosts/hiero/api/plugin.py +++ b/openpype/hosts/hiero/api/plugin.py @@ -6,9 +6,9 @@ import hiero from Qt import QtWidgets, QtCore import qargparse -import avalon.api as avalon + import openpype.api as openpype -from openpype.pipeline import LegacyCreator +from openpype.pipeline import LoaderPlugin, LegacyCreator from . import lib log = openpype.Logger().get_logger(__name__) @@ -306,7 +306,7 @@ def get_reference_node_parents(ref): return parents -class SequenceLoader(avalon.Loader): +class SequenceLoader(LoaderPlugin): """A basic SequenceLoader for Resolve This will implement the basic behavior for a loader to inherit from that diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index b905dd4431..d3908695a2 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -1,4 +1,5 @@ -from avalon import io, api +from avalon import io +from openpype.pipeline import get_representation_path import openpype.hosts.hiero.api as phiero # from openpype.hosts.hiero.api import plugin, lib # reload(lib) @@ -112,7 +113,7 @@ class LoadClip(phiero.SequenceLoader): version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") clip = track_item.source() # reconnect media to new path diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 21027dad2e..66c1c84308 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -11,7 +11,10 @@ import avalon.api from avalon.pipeline import AVALON_CONTAINER_ID from avalon.lib import find_submodule -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugin_path, +) import openpype.hosts.houdini from openpype.hosts.houdini.api import lib @@ -48,7 +51,7 @@ def install(): pyblish.api.register_host("hpython") pyblish.api.register_plugin_path(PUBLISH_PATH) - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info("Installing callbacks ... ") diff --git a/openpype/hosts/houdini/plugins/load/actions.py b/openpype/hosts/houdini/plugins/load/actions.py index acdb998c16..63d74c39a5 100644 --- a/openpype/hosts/houdini/plugins/load/actions.py +++ b/openpype/hosts/houdini/plugins/load/actions.py @@ -2,10 +2,10 @@ """ -from avalon import api +from openpype.pipeline import load -class SetFrameRangeLoader(api.Loader): +class SetFrameRangeLoader(load.LoaderPlugin): """Set Houdini frame range""" families = [ @@ -43,7 +43,7 @@ class SetFrameRangeLoader(api.Loader): hou.playbar.setPlaybackRange(start, end) -class SetFrameRangeWithHandlesLoader(api.Loader): +class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Set Maya frame range including pre- and post-handles""" families = [ diff --git a/openpype/hosts/houdini/plugins/load/load_alembic.py b/openpype/hosts/houdini/plugins/load/load_alembic.py index eaab81f396..0214229d5a 100644 --- a/openpype/hosts/houdini/plugins/load/load_alembic.py +++ b/openpype/hosts/houdini/plugins/load/load_alembic.py @@ -1,10 +1,12 @@ import os -from avalon import api - +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.houdini.api import pipeline -class AbcLoader(api.Loader): +class AbcLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["model", "animation", "pointcache", "gpuCache"] @@ -90,7 +92,7 @@ class AbcLoader(api.Loader): return # Update the file path - file_path = api.get_representation_path(representation) + file_path = get_representation_path(representation) file_path = file_path.replace("\\", "/") alembic_node.setParms({"fileName": file_path}) diff --git a/openpype/hosts/houdini/plugins/load/load_ass.py b/openpype/hosts/houdini/plugins/load/load_ass.py index 8c272044ec..0144bbaefd 100644 --- a/openpype/hosts/houdini/plugins/load/load_ass.py +++ b/openpype/hosts/houdini/plugins/load/load_ass.py @@ -1,11 +1,15 @@ import os -from avalon import api -from avalon.houdini import pipeline import clique +from openpype.pipeline import ( + load, + get_representation_path, +) + +from openpype.hosts.houdini.api import pipeline -class AssLoader(api.Loader): +class AssLoader(load.LoaderPlugin): """Load .ass with Arnold Procedural""" families = ["ass"] @@ -88,7 +92,7 @@ class AssLoader(api.Loader): def update(self, container, representation): # Update the file path - file_path = api.get_representation_path(representation) + file_path = get_representation_path(representation) file_path = file_path.replace("\\", "/") procedural = container["node"] diff --git a/openpype/hosts/houdini/plugins/load/load_camera.py b/openpype/hosts/houdini/plugins/load/load_camera.py index 8916d3b9b7..ef57d115da 100644 --- a/openpype/hosts/houdini/plugins/load/load_camera.py +++ b/openpype/hosts/houdini/plugins/load/load_camera.py @@ -1,4 +1,7 @@ -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.houdini.api import pipeline @@ -74,7 +77,7 @@ def transfer_non_default_values(src, dest, ignore=None): dest_parm.setFromParm(parm) -class CameraLoader(api.Loader): +class CameraLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["camera"] @@ -129,7 +132,7 @@ class CameraLoader(api.Loader): node = container["node"] # Update the file path - file_path = api.get_representation_path(representation) + file_path = get_representation_path(representation) file_path = file_path.replace("\\", "/") # Update attributes diff --git a/openpype/hosts/houdini/plugins/load/load_hda.py b/openpype/hosts/houdini/plugins/load/load_hda.py index f5f2fb7481..2438570c6e 100644 --- a/openpype/hosts/houdini/plugins/load/load_hda.py +++ b/openpype/hosts/houdini/plugins/load/load_hda.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- -from avalon import api - +import os +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.houdini.api import pipeline -class HdaLoader(api.Loader): +class HdaLoader(load.LoaderPlugin): """Load Houdini Digital Asset file.""" families = ["hda"] @@ -15,7 +18,6 @@ class HdaLoader(api.Loader): color = "orange" def load(self, context, name=None, namespace=None, data=None): - import os import hou # Format file name, Houdini only wants forward slashes @@ -49,7 +51,7 @@ class HdaLoader(api.Loader): import hou hda_node = container["node"] - file_path = api.get_representation_path(representation) + file_path = get_representation_path(representation) file_path = file_path.replace("\\", "/") hou.hda.installFile(file_path) defs = hda_node.type().allInstalledDefinitions() diff --git a/openpype/hosts/houdini/plugins/load/load_image.py b/openpype/hosts/houdini/plugins/load/load_image.py index 39f583677b..bd9ea3eee3 100644 --- a/openpype/hosts/houdini/plugins/load/load_image.py +++ b/openpype/hosts/houdini/plugins/load/load_image.py @@ -1,6 +1,9 @@ import os -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.houdini.api import lib, pipeline import hou @@ -37,7 +40,7 @@ def get_image_avalon_container(): return image_container -class ImageLoader(api.Loader): +class ImageLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.imagesequence"] @@ -87,7 +90,7 @@ class ImageLoader(api.Loader): node = container["node"] # Update the file path - file_path = api.get_representation_path(representation) + file_path = get_representation_path(representation) file_path = file_path.replace("\\", "/") file_path = self._get_file_sequence(file_path) diff --git a/openpype/hosts/houdini/plugins/load/load_usd_layer.py b/openpype/hosts/houdini/plugins/load/load_usd_layer.py index 0d4378b480..d803e6abfe 100644 --- a/openpype/hosts/houdini/plugins/load/load_usd_layer.py +++ b/openpype/hosts/houdini/plugins/load/load_usd_layer.py @@ -1,8 +1,11 @@ -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.houdini.api import lib, pipeline -class USDSublayerLoader(api.Loader): +class USDSublayerLoader(load.LoaderPlugin): """Sublayer USD file in Solaris""" families = [ @@ -57,7 +60,7 @@ class USDSublayerLoader(api.Loader): node = container["node"] # Update the file path - file_path = api.get_representation_path(representation) + file_path = get_representation_path(representation) file_path = file_path.replace("\\", "/") # Update attributes diff --git a/openpype/hosts/houdini/plugins/load/load_usd_reference.py b/openpype/hosts/houdini/plugins/load/load_usd_reference.py index 0edd8d9af6..fdb443f4cf 100644 --- a/openpype/hosts/houdini/plugins/load/load_usd_reference.py +++ b/openpype/hosts/houdini/plugins/load/load_usd_reference.py @@ -1,8 +1,11 @@ -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.houdini.api import lib, pipeline -class USDReferenceLoader(api.Loader): +class USDReferenceLoader(load.LoaderPlugin): """Reference USD file in Solaris""" families = [ @@ -57,7 +60,7 @@ class USDReferenceLoader(api.Loader): node = container["node"] # Update the file path - file_path = api.get_representation_path(representation) + file_path = get_representation_path(representation) file_path = file_path.replace("\\", "/") # Update attributes diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py index 40aa7a1d18..06bb9e45e4 100644 --- a/openpype/hosts/houdini/plugins/load/load_vdb.py +++ b/openpype/hosts/houdini/plugins/load/load_vdb.py @@ -1,11 +1,14 @@ import os import re -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.houdini.api import pipeline -class VdbLoader(api.Loader): +class VdbLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["vdbcache"] @@ -96,7 +99,7 @@ class VdbLoader(api.Loader): return # Update the file path - file_path = api.get_representation_path(representation) + file_path = get_representation_path(representation) file_path = self.format_path(file_path) file_node.setParms({"fileName": file_path}) diff --git a/openpype/hosts/houdini/plugins/load/show_usdview.py b/openpype/hosts/houdini/plugins/load/show_usdview.py index f23974094e..8066615181 100644 --- a/openpype/hosts/houdini/plugins/load/show_usdview.py +++ b/openpype/hosts/houdini/plugins/load/show_usdview.py @@ -1,7 +1,7 @@ -from avalon import api +from openpype.pipeline import load -class ShowInUsdview(api.Loader): +class ShowInUsdview(load.LoaderPlugin): """Open USD file in usdview""" families = ["colorbleed.usd"] diff --git a/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py b/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py index 645bd05d4b..3e842ae766 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py +++ b/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py @@ -7,6 +7,7 @@ from collections import deque import pyblish.api import openpype.api +from openpype.pipeline import get_representation_path import openpype.hosts.houdini.api.usd as hou_usdlib from openpype.hosts.houdini.api.lib import render_rop @@ -308,7 +309,7 @@ class ExtractUSDLayered(openpype.api.Extractor): self.log.debug("No existing representation..") return False - old_file = api.get_representation_path(representation) + old_file = get_representation_path(representation) if not os.path.exists(old_file): return False diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 41c67a6209..94efbb7a07 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -17,10 +17,16 @@ import bson from maya import cmds, mel import maya.api.OpenMaya as om -from avalon import api, io, pipeline +from avalon import api, io from openpype import lib from openpype.api import get_anatomy_settings +from openpype.pipeline import ( + discover_loader_plugins, + loaders_from_representation, + get_representation_path, + load_representation, +) from .commands import reset_frame_range @@ -1580,21 +1586,21 @@ def assign_look_by_version(nodes, version_id): log.info("Using look for the first time ..") # Load file - loaders = api.loaders_from_representation(api.discover(api.Loader), - representation_id) + _loaders = discover_loader_plugins() + loaders = loaders_from_representation(_loaders, representation_id) Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) if Loader is None: raise RuntimeError("Could not find LookLoader, this is a bug") # Reference the look file with maintained_selection(): - container_node = pipeline.load(Loader, look_representation) + container_node = load_representation(Loader, look_representation) # Get container members shader_nodes = get_container_members(container_node) # Load relationships - shader_relation = api.get_representation_path(json_representation) + shader_relation = get_representation_path(json_representation) with open(shader_relation, "r") as f: relationships = json.load(f) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 8c3669c5d1..23e21894bd 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -16,7 +16,11 @@ import openpype.hosts.maya from openpype.tools.utils import host_tools from openpype.lib import any_outdated from openpype.lib.path_tools import HostDirmap -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) from openpype.hosts.maya.lib import copy_workspace_mel from . import menu, lib @@ -49,7 +53,7 @@ def install(): pyblish.api.register_host("mayapy") pyblish.api.register_host("maya") - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) log.info(PUBLISH_PATH) @@ -175,7 +179,7 @@ def uninstall(): pyblish.api.deregister_host("mayapy") pyblish.api.deregister_host("maya") - avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.deregister_plugin_path( avalon.api.InventoryAction, INVENTORY_PATH diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index e0c21645e4..12cbd00257 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -4,9 +4,12 @@ from maya import cmds import qargparse -from avalon import api from avalon.pipeline import AVALON_CONTAINER_ID -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + LoaderPlugin, + get_representation_path, +) from .pipeline import containerise from . import lib @@ -95,7 +98,7 @@ class Creator(LegacyCreator): return instance -class Loader(api.Loader): +class Loader(LoaderPlugin): hosts = ["maya"] @@ -194,7 +197,7 @@ class ReferenceLoader(Loader): node = container["objectName"] - path = api.get_representation_path(representation) + path = get_representation_path(representation) # Get reference node from container members members = get_container_members(node) diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index 1a7c3933a1..74ee292eb2 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -8,7 +8,15 @@ import copy import six from maya import cmds -from avalon import api, io +from avalon import io +from openpype.pipeline import ( + discover_loader_plugins, + loaders_from_representation, + load_representation, + update_container, + remove_container, + get_representation_path, +) from openpype.hosts.maya.api.lib import ( matrix_equals, unique_namespace @@ -120,12 +128,13 @@ def load_package(filepath, name, namespace=None): root = "{}:{}".format(namespace, name) containers = [] - all_loaders = api.discover(api.Loader) + all_loaders = discover_loader_plugins() for representation_id, instances in data.items(): # Find the compatible loaders - loaders = api.loaders_from_representation(all_loaders, - representation_id) + loaders = loaders_from_representation( + all_loaders, representation_id + ) for instance in instances: container = _add(instance=instance, @@ -180,9 +189,11 @@ def _add(instance, representation_id, loaders, namespace, root="|"): instance['loader'], instance) raise RuntimeError("Loader is missing.") - container = api.load(Loader, - representation_id, - namespace=instance['namespace']) + container = load_representation( + Loader, + representation_id, + namespace=instance['namespace'] + ) # Get the root from the loaded container loaded_root = get_container_transforms({"objectName": container}, @@ -320,13 +331,13 @@ def update_package(set_container, representation): "type": "representation" }) - current_file = api.get_representation_path(current_representation) + current_file = get_representation_path(current_representation) assert current_file.endswith(".json") with open(current_file, "r") as fp: current_data = json.load(fp) # Load the new package data - new_file = api.get_representation_path(representation) + new_file = get_representation_path(representation) assert new_file.endswith(".json") with open(new_file, "r") as fp: new_data = json.load(fp) @@ -460,12 +471,12 @@ def update_scene(set_container, containers, current_data, new_data, new_file): # considered as new element and added afterwards. processed_containers.pop() processed_namespaces.remove(container_ns) - api.remove(container) + remove_container(container) continue # Check whether the conversion can be done by the Loader. # They *must* use the same asset, subset and Loader for - # `api.update` to make sense. + # `update_container` to make sense. old = io.find_one({ "_id": io.ObjectId(representation_current) }) @@ -479,20 +490,21 @@ def update_scene(set_container, containers, current_data, new_data, new_file): continue new_version = new["context"]["version"] - api.update(container, version=new_version) + update_container(container, version=new_version) else: # Remove this container because it's not in the new data log.warning("Removing content: %s", container_ns) - api.remove(container) + remove_container(container) # Add new assets - all_loaders = api.discover(api.Loader) + all_loaders = discover_loader_plugins() for representation_id, instances in new_data.items(): # Find the compatible loaders - loaders = api.loaders_from_representation(all_loaders, - representation_id) + loaders = loaders_from_representation( + all_loaders, representation_id + ) for instance in instances: # Already processed in update functionality @@ -517,7 +529,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file): def compare_representations(old, new): """Check if the old representation given can be updated - Due to limitations of the `api.update` function we cannot allow + Due to limitations of the `update_container` function we cannot allow differences in the following data: * Representation name (extension) diff --git a/openpype/hosts/maya/plugins/inventory/import_modelrender.py b/openpype/hosts/maya/plugins/inventory/import_modelrender.py index 119edccb7a..c5d3d0c8f4 100644 --- a/openpype/hosts/maya/plugins/inventory/import_modelrender.py +++ b/openpype/hosts/maya/plugins/inventory/import_modelrender.py @@ -1,5 +1,9 @@ import json -from avalon import api, io, pipeline +from avalon import api, io +from openpype.pipeline import ( + get_representation_context, + get_representation_path_from_context, +) from openpype.hosts.maya.api.lib import ( maintained_selection, apply_shaders @@ -73,11 +77,11 @@ class ImportModelRender(api.InventoryAction): "name": self.look_data_type, }) - context = pipeline.get_representation_context(look_repr["_id"]) - maya_file = pipeline.get_representation_path_from_context(context) + context = get_representation_context(look_repr["_id"]) + maya_file = get_representation_path_from_context(context) - context = pipeline.get_representation_context(json_repr["_id"]) - json_file = pipeline.get_representation_path_from_context(context) + context = get_representation_context(json_repr["_id"]) + json_file = get_representation_path_from_context(context) # Import the look file with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index 1cb63c8a7a..483ad32402 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -2,14 +2,14 @@ """ -from avalon import api +from openpype.pipeline import load from openpype.hosts.maya.api.lib import ( maintained_selection, unique_namespace ) -class SetFrameRangeLoader(api.Loader): +class SetFrameRangeLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["animation", @@ -43,7 +43,7 @@ class SetFrameRangeLoader(api.Loader): animationEndTime=end) -class SetFrameRangeWithHandlesLoader(api.Loader): +class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["animation", @@ -81,7 +81,7 @@ class SetFrameRangeWithHandlesLoader(api.Loader): animationEndTime=end) -class ImportMayaLoader(api.Loader): +class ImportMayaLoader(load.LoaderPlugin): """Import action for Maya (unmanaged) Warning: diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 18b34d2233..18de4df3b1 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -1,8 +1,11 @@ import os import clique -from avalon import api from openpype.api import get_project_settings +from openpype.pipeline import ( + load, + get_representation_path +) import openpype.hosts.maya.api.plugin from openpype.hosts.maya.api.plugin import get_reference_node from openpype.hosts.maya.api.lib import ( @@ -106,7 +109,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): node = container["objectName"] representation["context"].pop("frame", None) - path = api.get_representation_path(representation) + path = get_representation_path(representation) print(path) # path = self.fname print(self.fname) @@ -164,7 +167,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): type="string") -class AssStandinLoader(api.Loader): +class AssStandinLoader(load.LoaderPlugin): """Load .ASS file as standin""" families = ["ass"] @@ -240,7 +243,7 @@ class AssStandinLoader(api.Loader): import pymel.core as pm - path = api.get_representation_path(representation) + path = get_representation_path(representation) files_in_path = os.listdir(os.path.split(path)[0]) sequence = 0 diff --git a/openpype/hosts/maya/plugins/load/load_assembly.py b/openpype/hosts/maya/plugins/load/load_assembly.py index 0151da7253..902f38695c 100644 --- a/openpype/hosts/maya/plugins/load/load_assembly.py +++ b/openpype/hosts/maya/plugins/load/load_assembly.py @@ -1,7 +1,10 @@ -from avalon import api +from openpype.pipeline import ( + load, + remove_container +) -class AssemblyLoader(api.Loader): +class AssemblyLoader(load.LoaderPlugin): families = ["assembly"] representations = ["json"] @@ -48,13 +51,11 @@ class AssemblyLoader(api.Loader): def update(self, container, representation): from openpype import setdress - return setdress.update_package(container, - representation) + return setdress.update_package(container, representation) def remove(self, container): """Remove all sub containers""" - from avalon import api from openpype import setdress import maya.cmds as cmds @@ -63,7 +64,7 @@ class AssemblyLoader(api.Loader): for member_container in member_containers: self.log.info("Removing container %s", member_container['objectName']) - api.remove(member_container) + remove_container(member_container) # Remove alembic hierarchy reference # TODO: Check whether removing all contained references is safe enough diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 99f1f7c172..d8844ffea6 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -1,10 +1,14 @@ from maya import cmds, mel -from avalon import api, io +from avalon import io +from openpype.pipeline import ( + load, + get_representation_path +) from openpype.hosts.maya.api.pipeline import containerise from openpype.hosts.maya.api.lib import unique_namespace -class AudioLoader(api.Loader): +class AudioLoader(load.LoaderPlugin): """Specific loader of audio.""" families = ["audio"] @@ -51,7 +55,7 @@ class AudioLoader(api.Loader): assert audio_node is not None, "Audio node not found." - path = api.get_representation_path(representation) + path = get_representation_path(representation) audio_node.filename.set(path) cmds.setAttr( container["objectName"] + ".representation", diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index 2e0b7bb810..591e568e4c 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -1,9 +1,13 @@ import os -from avalon import api + +from openpype.pipeline import ( + load, + get_representation_path +) from openpype.api import get_project_settings -class GpuCacheLoader(api.Loader): +class GpuCacheLoader(load.LoaderPlugin): """Load model Alembic as gpuCache""" families = ["model"] @@ -73,7 +77,7 @@ class GpuCacheLoader(api.Loader): import maya.cmds as cmds - path = api.get_representation_path(representation) + path = get_representation_path(representation) # Update the cache members = cmds.sets(container['objectName'], query=True) diff --git a/openpype/hosts/maya/plugins/load/load_image_plane.py b/openpype/hosts/maya/plugins/load/load_image_plane.py index 8e33f51389..b250986489 100644 --- a/openpype/hosts/maya/plugins/load/load_image_plane.py +++ b/openpype/hosts/maya/plugins/load/load_image_plane.py @@ -1,6 +1,10 @@ from Qt import QtWidgets, QtCore -from avalon import api, io +from avalon import io +from openpype.pipeline import ( + load, + get_representation_path +) from openpype.hosts.maya.api.pipeline import containerise from openpype.hosts.maya.api.lib import unique_namespace @@ -74,7 +78,7 @@ class CameraWindow(QtWidgets.QDialog): self.close() -class ImagePlaneLoader(api.Loader): +class ImagePlaneLoader(load.LoaderPlugin): """Specific loader of plate for image planes on selected camera.""" families = ["image", "plate", "render"] @@ -203,7 +207,7 @@ class ImagePlaneLoader(api.Loader): assert image_plane_shape is not None, "Image plane not found." - path = api.get_representation_path(representation) + path = get_representation_path(representation) image_plane_shape.imageName.set(path) cmds.setAttr( container["objectName"] + ".representation", diff --git a/openpype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py index 96c1ecbb20..8f02ed59b8 100644 --- a/openpype/hosts/maya/plugins/load/load_look.py +++ b/openpype/hosts/maya/plugins/load/load_look.py @@ -5,7 +5,8 @@ from collections import defaultdict from Qt import QtWidgets -from avalon import api, io +from avalon import io +from openpype.pipeline import get_representation_path import openpype.hosts.maya.api.plugin from openpype.hosts.maya.api import lib from openpype.widgets.message_window import ScrollMessageBox @@ -77,7 +78,7 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): }) # Load relationships - shader_relation = api.get_representation_path(json_representation) + shader_relation = get_representation_path(json_representation) with open(shader_relation, "r") as f: json_data = json.load(f) diff --git a/openpype/hosts/maya/plugins/load/load_matchmove.py b/openpype/hosts/maya/plugins/load/load_matchmove.py index abc702cde8..ee3332bd09 100644 --- a/openpype/hosts/maya/plugins/load/load_matchmove.py +++ b/openpype/hosts/maya/plugins/load/load_matchmove.py @@ -1,8 +1,8 @@ -from avalon import api from maya import mel +from openpype.pipeline import load -class MatchmoveLoader(api.Loader): +class MatchmoveLoader(load.LoaderPlugin): """ This will run matchmove script to create track in scene. diff --git a/openpype/hosts/maya/plugins/load/load_redshift_proxy.py b/openpype/hosts/maya/plugins/load/load_redshift_proxy.py index fd2ae0f1d3..d93a9f02a2 100644 --- a/openpype/hosts/maya/plugins/load/load_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/load/load_redshift_proxy.py @@ -5,8 +5,11 @@ import clique import maya.cmds as cmds -from avalon import api from openpype.api import get_project_settings +from openpype.pipeline import ( + load, + get_representation_path +) from openpype.hosts.maya.api.lib import ( namespaced, maintained_selection, @@ -15,7 +18,7 @@ from openpype.hosts.maya.api.lib import ( from openpype.hosts.maya.api.pipeline import containerise -class RedshiftProxyLoader(api.Loader): +class RedshiftProxyLoader(load.LoaderPlugin): """Load Redshift proxy""" families = ["redshiftproxy"] @@ -78,7 +81,7 @@ class RedshiftProxyLoader(api.Loader): rs_meshes = cmds.ls(members, type="RedshiftProxyMesh") assert rs_meshes, "Cannot find RedshiftProxyMesh in container" - filename = api.get_representation_path(representation) + filename = get_representation_path(representation) for rs_mesh in rs_meshes: cmds.setAttr("{}.fileName".format(rs_mesh), diff --git a/openpype/hosts/maya/plugins/load/load_rendersetup.py b/openpype/hosts/maya/plugins/load/load_rendersetup.py index efeff2f193..7a2d8b1002 100644 --- a/openpype/hosts/maya/plugins/load/load_rendersetup.py +++ b/openpype/hosts/maya/plugins/load/load_rendersetup.py @@ -7,10 +7,13 @@ instance. """ import json -import six import sys +import six -from avalon import api +from openpype.pipeline import ( + load, + get_representation_path +) from openpype.hosts.maya.api import lib from openpype.hosts.maya.api.pipeline import containerise @@ -18,7 +21,7 @@ from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup -class RenderSetupLoader(api.Loader): +class RenderSetupLoader(load.LoaderPlugin): """Load json preset for RenderSetup overwriting current one.""" families = ["rendersetup"] @@ -87,7 +90,7 @@ class RenderSetupLoader(api.Loader): "Render setup setting will be overwritten by new version. All " "setting specified by user not included in loaded version " "will be lost.") - path = api.get_representation_path(representation) + path = get_representation_path(representation) with open(path, "r") as file: try: renderSetup.instance().decode( diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py index 3e1d67ae9a..70bd9d22e2 100644 --- a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -1,9 +1,10 @@ import os -from avalon import api + from openpype.api import get_project_settings +from openpype.pipeline import load -class LoadVDBtoRedShift(api.Loader): +class LoadVDBtoRedShift(load.LoaderPlugin): """Load OpenVDB in a Redshift Volume Shape""" families = ["vdbcache"] diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py index 6d5544103d..4f14235bfb 100644 --- a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py @@ -1,6 +1,10 @@ import os -from avalon import api + from openpype.api import get_project_settings +from openpype.pipeline import ( + load, + get_representation_path +) from maya import cmds @@ -69,7 +73,7 @@ def _fix_duplicate_vvg_callbacks(): matched.add(callback) -class LoadVDBtoVRay(api.Loader): +class LoadVDBtoVRay(load.LoaderPlugin): families = ["vdbcache"] representations = ["vdb"] @@ -252,7 +256,7 @@ class LoadVDBtoVRay(api.Loader): def update(self, container, representation): - path = api.get_representation_path(representation) + path = get_representation_path(representation) # Find VRayVolumeGrid members = cmds.sets(container['objectName'], query=True) diff --git a/openpype/hosts/maya/plugins/load/load_vrayproxy.py b/openpype/hosts/maya/plugins/load/load_vrayproxy.py index ac2fe635b3..5b79b1efb3 100644 --- a/openpype/hosts/maya/plugins/load/load_vrayproxy.py +++ b/openpype/hosts/maya/plugins/load/load_vrayproxy.py @@ -9,8 +9,12 @@ import os import maya.cmds as cmds -from avalon import api, io +from avalon import io from openpype.api import get_project_settings +from openpype.pipeline import ( + load, + get_representation_path +) from openpype.hosts.maya.api.lib import ( maintained_selection, namespaced, @@ -19,7 +23,7 @@ from openpype.hosts.maya.api.lib import ( from openpype.hosts.maya.api.pipeline import containerise -class VRayProxyLoader(api.Loader): +class VRayProxyLoader(load.LoaderPlugin): """Load VRay Proxy with Alembic or VrayMesh.""" families = ["vrayproxy", "model", "pointcache", "animation"] @@ -100,7 +104,10 @@ class VRayProxyLoader(api.Loader): assert vraymeshes, "Cannot find VRayMesh in container" # get all representations for this version - filename = self._get_abc(representation["parent"]) or api.get_representation_path(representation) # noqa: E501 + filename = ( + self._get_abc(representation["parent"]) + or get_representation_path(representation) + ) for vray_mesh in vraymeshes: cmds.setAttr("{}.fileName".format(vray_mesh), @@ -185,7 +192,7 @@ class VRayProxyLoader(api.Loader): if abc_rep: self.log.debug("Found, we'll link alembic to vray proxy.") - file_name = api.get_representation_path(abc_rep) + file_name = get_representation_path(abc_rep) self.log.debug("File: {}".format(self.fname)) return file_name diff --git a/openpype/hosts/maya/plugins/load/load_vrayscene.py b/openpype/hosts/maya/plugins/load/load_vrayscene.py index dfe2b85edc..61132088cc 100644 --- a/openpype/hosts/maya/plugins/load/load_vrayscene.py +++ b/openpype/hosts/maya/plugins/load/load_vrayscene.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- import os import maya.cmds as cmds # noqa -from avalon import api from openpype.api import get_project_settings +from openpype.pipeline import ( + load, + get_representation_path +) from openpype.hosts.maya.api.lib import ( maintained_selection, namespaced, @@ -11,7 +14,7 @@ from openpype.hosts.maya.api.lib import ( from openpype.hosts.maya.api.pipeline import containerise -class VRaySceneLoader(api.Loader): +class VRaySceneLoader(load.LoaderPlugin): """Load Vray scene""" families = ["vrayscene_layer"] @@ -78,7 +81,7 @@ class VRaySceneLoader(api.Loader): vraymeshes = cmds.ls(members, type="VRayScene") assert vraymeshes, "Cannot find VRayScene in container" - filename = api.get_representation_path(representation) + filename = get_representation_path(representation) for vray_mesh in vraymeshes: cmds.setAttr("{}.FilePath".format(vray_mesh), diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index dfe75173ac..c64e1c540b 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -7,13 +7,17 @@ from pprint import pprint from maya import cmds -from avalon import api, io +from avalon import io from openpype.api import get_project_settings +from openpype.pipeline import ( + load, + get_representation_path +) from openpype.hosts.maya.api import lib from openpype.hosts.maya.api.pipeline import containerise -class YetiCacheLoader(api.Loader): +class YetiCacheLoader(load.LoaderPlugin): families = ["yeticache", "yetiRig"] representations = ["fur"] @@ -121,8 +125,8 @@ class YetiCacheLoader(api.Loader): "cannot find fursettings representation" ) - settings_fname = api.get_representation_path(fur_settings) - path = api.get_representation_path(representation) + settings_fname = get_representation_path(fur_settings) + path = get_representation_path(representation) # Get all node data with open(settings_fname, "r") as fp: settings = json.load(fp) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index d98a951491..7011b3bed1 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -14,7 +14,11 @@ from openpype.api import ( BuildWorkfile, get_current_project_settings ) -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) from openpype.tools.utils import host_tools from .command import viewer_update_and_undo_stop @@ -98,7 +102,7 @@ def install(): log.info("Registering Nuke plug-ins..") pyblish.api.register_plugin_path(PUBLISH_PATH) - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) @@ -124,7 +128,7 @@ def uninstall(): log.info("Deregistering Nuke plug-ins..") pyblish.deregister_host("nuke") pyblish.api.deregister_plugin_path(PUBLISH_PATH) - avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) pyblish.api.deregister_callback( diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index ff186cd685..d0bb45a05d 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -4,10 +4,11 @@ import string import nuke -import avalon.api - from openpype.api import get_current_project_settings -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + LoaderPlugin, +) from .lib import ( Knobby, check_subsetname_exists, @@ -85,7 +86,7 @@ def get_review_presets_config(): return [str(name) for name, _prop in outputs.items()] -class NukeLoader(avalon.api.Loader): +class NukeLoader(LoaderPlugin): container_id_knob = "containerId" container_id = None diff --git a/openpype/hosts/nuke/plugins/load/actions.py b/openpype/hosts/nuke/plugins/load/actions.py index 07dcf2d8e1..81840b3a38 100644 --- a/openpype/hosts/nuke/plugins/load/actions.py +++ b/openpype/hosts/nuke/plugins/load/actions.py @@ -2,13 +2,13 @@ """ -from avalon import api from openpype.api import Logger +from openpype.pipeline import load log = Logger().get_logger(__name__) -class SetFrameRangeLoader(api.Loader): +class SetFrameRangeLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["animation", @@ -42,7 +42,7 @@ class SetFrameRangeLoader(api.Loader): lib.update_frame_range(start, end) -class SetFrameRangeWithHandlesLoader(api.Loader): +class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): """Specific loader of Alembic for the avalon.animation family""" families = ["animation", diff --git a/openpype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py index 6619cfb414..05ce4d08d3 100644 --- a/openpype/hosts/nuke/plugins/load/load_backdrop.py +++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py @@ -1,7 +1,11 @@ -from avalon import api, style, io +from avalon import style, io import nuke import nukescripts +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api.lib import ( find_free_space_to_paste_nodes, maintained_selection, @@ -14,7 +18,7 @@ from openpype.hosts.nuke.api.commands import viewer_update_and_undo_stop from openpype.hosts.nuke.api import containerise, update_container -class LoadBackdropNodes(api.Loader): +class LoadBackdropNodes(load.LoaderPlugin): """Loading Published Backdrop nodes (workfile, nukenodes)""" representations = ["nk"] @@ -191,7 +195,7 @@ class LoadBackdropNodes(api.Loader): # get corresponding node GN = nuke.toNode(container['objectName']) - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") context = representation["context"] name = container['name'] version_data = version.get("data", {}) diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index 9610940619..fb5f7f8ede 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -1,6 +1,10 @@ import nuke -from avalon import api, io +from avalon import io +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api import ( containerise, update_container, @@ -11,7 +15,7 @@ from openpype.hosts.nuke.api.lib import ( ) -class AlembicCameraLoader(api.Loader): +class AlembicCameraLoader(load.LoaderPlugin): """ This will load alembic camera into script. """ @@ -127,7 +131,7 @@ class AlembicCameraLoader(api.Loader): data_imprint.update({k: version_data[k]}) # getting file path - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") with maintained_selection(): camera_node = nuke.toNode(object_name) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index a253ba4a9d..563a325a83 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -1,7 +1,8 @@ import nuke import qargparse -from avalon import api, io +from avalon import io +from openpype.pipeline import get_representation_path from openpype.hosts.nuke.api.lib import ( get_imageio_input_colorspace, maintained_selection @@ -186,7 +187,7 @@ class LoadClip(plugin.NukeLoader): is_sequence = len(representation["files"]) > 1 read_node = nuke.toNode(container['objectName']) - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") start_at_workfile = bool("start at" in read_node['frame_mode'].value()) diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index f636c6b510..2f8333e4f2 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -1,7 +1,11 @@ import json from collections import OrderedDict import nuke -from avalon import api, style, io +from avalon import style, io +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api import ( containerise, update_container, @@ -9,7 +13,7 @@ from openpype.hosts.nuke.api import ( ) -class LoadEffects(api.Loader): +class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" representations = ["effectJson"] @@ -149,7 +153,7 @@ class LoadEffects(api.Loader): # get corresponding node GN = nuke.toNode(container['objectName']) - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") name = container['name'] version_data = version.get("data", {}) vname = version.get("name", None) diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index 990bce54f1..b998eda69b 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -3,7 +3,11 @@ from collections import OrderedDict import nuke -from avalon import api, style, io +from avalon import style, io +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api import lib from openpype.hosts.nuke.api import ( containerise, @@ -12,7 +16,7 @@ from openpype.hosts.nuke.api import ( ) -class LoadEffectsInputProcess(api.Loader): +class LoadEffectsInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" representations = ["effectJson"] @@ -156,7 +160,7 @@ class LoadEffectsInputProcess(api.Loader): # get corresponding node GN = nuke.toNode(container['objectName']) - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") name = container['name'] version_data = version.get("data", {}) vname = version.get("name", None) diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo.py b/openpype/hosts/nuke/plugins/load/load_gizmo.py index 659977d789..0eea6f784b 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo.py @@ -1,5 +1,9 @@ import nuke -from avalon import api, style, io +from avalon import style, io +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api.lib import ( maintained_selection, get_avalon_knob_data, @@ -12,7 +16,7 @@ from openpype.hosts.nuke.api import ( ) -class LoadGizmo(api.Loader): +class LoadGizmo(load.LoaderPlugin): """Loading nuke Gizmo""" representations = ["gizmo"] @@ -103,7 +107,7 @@ class LoadGizmo(api.Loader): # get corresponding node GN = nuke.toNode(container['objectName']) - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") name = container['name'] version_data = version.get("data", {}) vname = version.get("name", None) diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py index 240bfd467d..8b3f35a29a 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -1,5 +1,9 @@ -from avalon import api, style, io +from avalon import style, io import nuke +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api.lib import ( maintained_selection, create_backdrop, @@ -13,7 +17,7 @@ from openpype.hosts.nuke.api import ( ) -class LoadGizmoInputProcess(api.Loader): +class LoadGizmoInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" representations = ["gizmo"] @@ -109,7 +113,7 @@ class LoadGizmoInputProcess(api.Loader): # get corresponding node GN = nuke.toNode(container['objectName']) - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") name = container['name'] version_data = version.get("data", {}) vname = version.get("name", None) diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index 27c634ec57..e04ccf3bf1 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -1,8 +1,12 @@ import nuke import qargparse -from avalon import api, io +from avalon import io +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api.lib import ( get_imageio_input_colorspace ) @@ -13,7 +17,7 @@ from openpype.hosts.nuke.api import ( ) -class LoadImage(api.Loader): +class LoadImage(load.LoaderPlugin): """Load still image into Nuke""" families = [ @@ -161,7 +165,7 @@ class LoadImage(api.Loader): repr_cont = representation["context"] - file = api.get_representation_path(representation) + file = get_representation_path(representation) if not file: repr_id = representation["_id"] diff --git a/openpype/hosts/nuke/plugins/load/load_matchmove.py b/openpype/hosts/nuke/plugins/load/load_matchmove.py index 60d5dc026f..f5a90706c7 100644 --- a/openpype/hosts/nuke/plugins/load/load_matchmove.py +++ b/openpype/hosts/nuke/plugins/load/load_matchmove.py @@ -1,8 +1,8 @@ -from avalon import api import nuke +from openpype.pipeline import load -class MatchmoveLoader(api.Loader): +class MatchmoveLoader(load.LoaderPlugin): """ This will run matchmove script to create track in script. """ diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index 2b52bbf00f..e445beca05 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -1,5 +1,9 @@ import nuke -from avalon import api, io +from avalon import io +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api.lib import maintained_selection from openpype.hosts.nuke.api import ( containerise, @@ -8,7 +12,7 @@ from openpype.hosts.nuke.api import ( ) -class AlembicModelLoader(api.Loader): +class AlembicModelLoader(load.LoaderPlugin): """ This will load alembic model into script. """ @@ -124,7 +128,7 @@ class AlembicModelLoader(api.Loader): data_imprint.update({k: version_data[k]}) # getting file path - file = api.get_representation_path(representation).replace("\\", "/") + file = get_representation_path(representation).replace("\\", "/") with maintained_selection(): model_node = nuke.toNode(object_name) diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py index aa48b631c5..cd47a840ae 100644 --- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py +++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py @@ -1,5 +1,9 @@ import nuke -from avalon import api, style, io +from avalon import style, io +from openpype.pipeline import ( + load, + get_representation_path, +) from openpype.hosts.nuke.api.lib import get_avalon_knob_data from openpype.hosts.nuke.api import ( containerise, @@ -8,7 +12,7 @@ from openpype.hosts.nuke.api import ( ) -class LinkAsGroup(api.Loader): +class LinkAsGroup(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" representations = ["nk"] @@ -108,7 +112,7 @@ class LinkAsGroup(api.Loader): """ node = nuke.toNode(container['objectName']) - root = api.get_representation_path(representation).replace("\\", "/") + root = get_representation_path(representation).replace("\\", "/") # Get start frame from version data version = io.find_one({ diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index 189f28f7c6..85e98db7ed 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -3,8 +3,9 @@ import re from pprint import pformat import nuke import pyblish.api +from avalon import io import openpype.api as pype -from avalon import io, api +from openpype.pipeline import get_representation_path @pyblish.api.log @@ -182,7 +183,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if repre_doc: instance.data["audio"] = [{ "offset": 0, - "filename": api.get_representation_path(repre_doc) + "filename": get_representation_path(repre_doc) }] self.log.debug("instance.data: {}".format(pformat(instance.data))) diff --git a/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py index 22a9b3678e..39fe011d85 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py +++ b/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py @@ -1,12 +1,16 @@ import os -import toml import nuke +import toml import pyblish.api -from avalon import api from bson.objectid import ObjectId +from openpype.pipeline import ( + discover_loader_plugins, + load_representation, +) + class RepairReadLegacyAction(pyblish.api.Action): @@ -49,13 +53,13 @@ class RepairReadLegacyAction(pyblish.api.Action): loader_name = "LoadMov" loader_plugin = None - for Loader in api.discover(api.Loader): + for Loader in discover_loader_plugins(): if Loader.__name__ != loader_name: continue loader_plugin = Loader - api.load( + load_representation( Loader=loader_plugin, representation=ObjectId(data["representation"]) ) diff --git a/openpype/hosts/photoshop/api/README.md b/openpype/hosts/photoshop/api/README.md index b958f53803..80792a4da0 100644 --- a/openpype/hosts/photoshop/api/README.md +++ b/openpype/hosts/photoshop/api/README.md @@ -195,11 +195,12 @@ class ExtractImage(openpype.api.Extractor): #### Loader Plugin ```python from avalon import api, photoshop +from openpype.pipeline import load, get_representation_path stub = photoshop.stub() -class ImageLoader(api.Loader): +class ImageLoader(load.LoaderPlugin): """Load images Stores the imported asset in a container named after the asset. @@ -227,7 +228,7 @@ class ImageLoader(api.Loader): with photoshop.maintained_selection(): stub.replace_smart_object( - layer, api.get_representation_path(representation) + layer, get_representation_path(representation) ) stub.imprint( @@ -245,7 +246,7 @@ https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/ Add --enable-blink-features=ShadowDOMV0,CustomElementsV0 when starting Chrome then localhost:8078 (port set in `photoshop\extension\.debug`) -Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 +Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01 Or install CEF client from https://github.com/Adobe-CEP/CEP-Resources/tree/master/CEP_9.x ## Resources diff --git a/openpype/hosts/photoshop/api/pipeline.py b/openpype/hosts/photoshop/api/pipeline.py index 662e9dbebc..a7bd64585d 100644 --- a/openpype/hosts/photoshop/api/pipeline.py +++ b/openpype/hosts/photoshop/api/pipeline.py @@ -6,7 +6,11 @@ import avalon.api from avalon import pipeline, io from openpype.api import Logger -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) import openpype.hosts.photoshop from . import lib @@ -67,7 +71,7 @@ def install(): pyblish.api.register_host("photoshop") pyblish.api.register_plugin_path(PUBLISH_PATH) - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info(PUBLISH_PATH) @@ -80,7 +84,7 @@ def install(): def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) - avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) diff --git a/openpype/hosts/photoshop/api/plugin.py b/openpype/hosts/photoshop/api/plugin.py index c577c67d82..c80e6bbd06 100644 --- a/openpype/hosts/photoshop/api/plugin.py +++ b/openpype/hosts/photoshop/api/plugin.py @@ -1,6 +1,6 @@ import re -import avalon.api +from openpype.pipeline import LoaderPlugin from .launch_logic import stub @@ -29,7 +29,7 @@ def get_unique_layer_name(layers, asset_name, subset_name): return "{}_{:0>3d}".format(name, occurrences + 1) -class PhotoshopLoader(avalon.api.Loader): +class PhotoshopLoader(LoaderPlugin): @staticmethod def get_stub(): return stub() diff --git a/openpype/hosts/photoshop/plugins/load/load_image.py b/openpype/hosts/photoshop/plugins/load/load_image.py index 3b1cfe9636..0a9421b8f2 100644 --- a/openpype/hosts/photoshop/plugins/load/load_image.py +++ b/openpype/hosts/photoshop/plugins/load/load_image.py @@ -1,6 +1,6 @@ import re -from avalon import api +from openpype.pipeline import get_representation_path from openpype.hosts.photoshop import api as photoshop from openpype.hosts.photoshop.api import get_unique_layer_name @@ -54,7 +54,7 @@ class ImageLoader(photoshop.PhotoshopLoader): else: # switching version - keep same name layer_name = container["namespace"] - path = api.get_representation_path(representation) + path = get_representation_path(representation) with photoshop.maintained_selection(): stub.replace_smart_object( layer, path, layer_name diff --git a/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py b/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py index 12e0503dfc..5f39121ae1 100644 --- a/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py +++ b/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py @@ -1,8 +1,8 @@ import os import qargparse -from avalon.pipeline import get_representation_path_from_context +from openpype.pipeline import get_representation_path_from_context from openpype.hosts.photoshop import api as photoshop from openpype.hosts.photoshop.api import get_unique_layer_name diff --git a/openpype/hosts/photoshop/plugins/load/load_reference.py b/openpype/hosts/photoshop/plugins/load/load_reference.py index 60142d4a1f..f5f0545d39 100644 --- a/openpype/hosts/photoshop/plugins/load/load_reference.py +++ b/openpype/hosts/photoshop/plugins/load/load_reference.py @@ -1,7 +1,6 @@ import re -from avalon import api - +from openpype.pipeline import get_representation_path from openpype.hosts.photoshop import api as photoshop from openpype.hosts.photoshop.api import get_unique_layer_name @@ -55,7 +54,7 @@ class ReferenceLoader(photoshop.PhotoshopLoader): else: # switching version - keep same name layer_name = container["namespace"] - path = api.get_representation_path(representation) + path = get_representation_path(representation) with photoshop.maintained_selection(): stub.replace_smart_object( layer, path, layer_name diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py index c82545268b..829794dd41 100644 --- a/openpype/hosts/resolve/api/pipeline.py +++ b/openpype/hosts/resolve/api/pipeline.py @@ -9,7 +9,11 @@ from avalon import schema from avalon.pipeline import AVALON_CONTAINER_ID from pyblish import api as pyblish from openpype.api import Logger -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) from . import lib from . import PLUGINS_DIR from openpype.tools.utils import host_tools @@ -42,7 +46,7 @@ def install(): pyblish.register_plugin_path(PUBLISH_PATH) log.info("Registering DaVinci Resovle plug-ins..") - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) @@ -67,7 +71,7 @@ def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) log.info("Deregistering DaVinci Resovle plug-ins..") - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH) avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index e7793d6e95..8e1436021c 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -4,14 +4,15 @@ import uuid import qargparse from Qt import QtWidgets, QtCore -from avalon import api import openpype.api as pype -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + LoaderPlugin, +) from openpype.hosts import resolve from . import lib - class CreatorWidget(QtWidgets.QDialog): # output items @@ -292,7 +293,7 @@ class ClipLoader: """ Initialize object Arguments: - cls (avalon.api.Loader): plugin object + cls (openpype.pipeline.load.LoaderPlugin): plugin object context (dict): loader plugin context options (dict)[optional]: possible keys: projectBinPath: "path/to/binItem" @@ -448,7 +449,7 @@ class ClipLoader: return timeline_item -class TimelineItemLoader(api.Loader): +class TimelineItemLoader(LoaderPlugin): """A basic SequenceLoader for Resolve This will implement the basic behavior for a loader to inherit from that diff --git a/openpype/hosts/resolve/plugins/load/load_clip.py b/openpype/hosts/resolve/plugins/load/load_clip.py index e20384ee6c..71850d95f6 100644 --- a/openpype/hosts/resolve/plugins/load/load_clip.py +++ b/openpype/hosts/resolve/plugins/load/load_clip.py @@ -1,11 +1,14 @@ -from avalon import io, api -from openpype.hosts import resolve from copy import deepcopy from importlib import reload + +from avalon import io +from openpype.hosts import resolve +from openpype.pipeline import get_representation_path from openpype.hosts.resolve.api import lib, plugin reload(plugin) reload(lib) + class LoadClip(resolve.TimelineItemLoader): """Load a subset to timeline as clip @@ -99,7 +102,7 @@ class LoadClip(resolve.TimelineItemLoader): version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) - self.fname = api.get_representation_path(representation) + self.fname = get_representation_path(representation) context["version"] = {"data": version_data} loader = resolve.ClipLoader(self, context) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index f4599047b4..6a26446226 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -14,7 +14,11 @@ from avalon.pipeline import AVALON_CONTAINER_ID from openpype.hosts import tvpaint from openpype.api import get_current_project_settings -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) from .lib import ( execute_george, @@ -76,7 +80,7 @@ def install(): pyblish.api.register_host("tvpaint") pyblish.api.register_plugin_path(PUBLISH_PATH) - avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) registered_callbacks = ( @@ -98,7 +102,7 @@ def uninstall(): log.info("OpenPype - Uninstalling TVPaint integration") pyblish.api.deregister_host("tvpaint") pyblish.api.deregister_plugin_path(PUBLISH_PATH) - avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + deregister_loader_plugins_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index 8510794f06..15ad8905e0 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -1,9 +1,10 @@ import re import uuid -import avalon.api - -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + LoaderPlugin, +) from openpype.hosts.tvpaint.api import ( pipeline, lib @@ -74,7 +75,7 @@ class Creator(LegacyCreator): self.write_instances(data) -class Loader(avalon.api.Loader): +class Loader(LoaderPlugin): hosts = ["tvpaint"] @staticmethod diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index 8ab19bd697..7100ff3a83 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -7,7 +7,11 @@ import pyblish.api from avalon.pipeline import AVALON_CONTAINER_ID from avalon import api -from openpype.pipeline import LegacyCreator +from openpype.pipeline import ( + LegacyCreator, + register_loader_plugins_path, + deregister_loader_plugins_path, +) from openpype.tools.utils import host_tools import openpype.hosts.unreal @@ -44,7 +48,7 @@ def install(): print("-=" * 40) logger.info("installing OpenPype for Unreal") pyblish.api.register_plugin_path(str(PUBLISH_PATH)) - api.register_plugin_path(api.Loader, str(LOAD_PATH)) + register_loader_plugins_path(str(LOAD_PATH)) api.register_plugin_path(LegacyCreator, str(CREATE_PATH)) _register_callbacks() _register_events() @@ -53,7 +57,7 @@ def install(): def uninstall(): """Uninstall Unreal configuration for Avalon.""" pyblish.api.deregister_plugin_path(str(PUBLISH_PATH)) - api.deregister_plugin_path(api.Loader, str(LOAD_PATH)) + deregister_loader_plugins_path(str(LOAD_PATH)) api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH)) diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py index dd2e7750f0..b24bab831d 100644 --- a/openpype/hosts/unreal/api/plugin.py +++ b/openpype/hosts/unreal/api/plugin.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- from abc import ABC -from openpype.pipeline import LegacyCreator -import avalon.api +from openpype.pipeline import ( + LegacyCreator, + LoaderPlugin, +) class Creator(LegacyCreator): @@ -10,6 +12,6 @@ class Creator(LegacyCreator): defaults = ['Main'] -class Loader(avalon.api.Loader, ABC): +class Loader(LoaderPlugin, ABC): """This serves as skeleton for future OpenPype specific functionality""" pass diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py b/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py index 027e9f4cd3..3508fe5ed7 100644 --- a/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py +++ b/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py @@ -2,7 +2,8 @@ """Loader for published alembics.""" import os -from avalon import api, pipeline +from avalon import pipeline +from openpype.pipeline import get_representation_path from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -140,7 +141,7 @@ class PointCacheAlembicLoader(plugin.Loader): def update(self, container, representation): name = container["asset_name"] - source_path = api.get_representation_path(representation) + source_path = get_representation_path(representation) destination_path = container["namespace"] task = self.get_task(source_path, destination_path, name, True) diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py b/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py index 0236bab138..180942de51 100644 --- a/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py +++ b/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py @@ -2,7 +2,8 @@ """Load Skeletal Mesh alembics.""" import os -from avalon import api, pipeline +from avalon import pipeline +from openpype.pipeline import get_representation_path from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline import unreal # noqa @@ -104,7 +105,7 @@ class SkeletalMeshAlembicLoader(plugin.Loader): def update(self, container, representation): name = container["asset_name"] - source_path = api.get_representation_path(representation) + source_path = get_representation_path(representation) destination_path = container["namespace"] task = unreal.AssetImportTask() diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py b/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py index 3bcc8b476f..4e00af1d97 100644 --- a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py +++ b/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py @@ -2,7 +2,8 @@ """Loader for Static Mesh alembics.""" import os -from avalon import api, pipeline +from avalon import pipeline +from openpype.pipeline import get_representation_path from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline import unreal # noqa @@ -123,7 +124,7 @@ class StaticMeshAlembicLoader(plugin.Loader): def update(self, container, representation): name = container["asset_name"] - source_path = api.get_representation_path(representation) + source_path = get_representation_path(representation) destination_path = container["namespace"] task = self.get_task(source_path, destination_path, name, True) diff --git a/openpype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py index 63c734b969..8ef81f7851 100644 --- a/openpype/hosts/unreal/plugins/load/load_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_animation.py @@ -3,7 +3,8 @@ import os import json -from avalon import api, pipeline +from avalon import pipeline +from openpype.pipeline import get_representation_path from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline import unreal # noqa @@ -173,7 +174,7 @@ class AnimationFBXLoader(plugin.Loader): def update(self, container, representation): name = container["asset_name"] - source_path = api.get_representation_path(representation) + source_path = get_representation_path(representation) destination_path = container["namespace"] task = unreal.AssetImportTask() diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index b802f5940a..b987a32a61 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -11,7 +11,13 @@ from unreal import AssetToolsHelpers from unreal import FBXImportType from unreal import MathLibrary as umath -from avalon import api, pipeline +from avalon.pipeline import AVALON_CONTAINER_ID +from openpype.pipeline import ( + discover_loader_plugins, + loaders_from_representation, + load_representation, + get_representation_path, +) from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline @@ -205,7 +211,7 @@ class LayoutLoader(plugin.Loader): with open(lib_path, "r") as fp: data = json.load(fp) - all_loaders = api.discover(api.Loader) + all_loaders = discover_loader_plugins() if not loaded: loaded = [] @@ -235,7 +241,7 @@ class LayoutLoader(plugin.Loader): loaded.append(reference) family = element.get('family') - loaders = api.loaders_from_representation( + loaders = loaders_from_representation( all_loaders, reference) loader = None @@ -252,7 +258,7 @@ class LayoutLoader(plugin.Loader): "asset_dir": asset_dir } - assets = api.load( + assets = load_representation( loader, reference, namespace=instance_name, @@ -387,7 +393,7 @@ class LayoutLoader(plugin.Loader): data = { "schema": "openpype:container-2.0", - "id": pipeline.AVALON_CONTAINER_ID, + "id": AVALON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, "container_name": container_name, @@ -411,9 +417,9 @@ class LayoutLoader(plugin.Loader): def update(self, container, representation): ar = unreal.AssetRegistryHelpers.get_asset_registry() - source_path = api.get_representation_path(representation) + source_path = get_representation_path(representation) destination_path = container["namespace"] - lib_path = Path(api.get_representation_path(representation)) + lib_path = Path(get_representation_path(representation)) self._remove_actors(destination_path) diff --git a/openpype/hosts/unreal/plugins/load/load_rig.py b/openpype/hosts/unreal/plugins/load/load_rig.py index a7ecb0ef7d..3d5616364c 100644 --- a/openpype/hosts/unreal/plugins/load/load_rig.py +++ b/openpype/hosts/unreal/plugins/load/load_rig.py @@ -2,7 +2,8 @@ """Load Skeletal Meshes form FBX.""" import os -from avalon import api, pipeline +from avalon import pipeline +from openpype.pipeline import get_representation_path from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline import unreal # noqa @@ -124,7 +125,7 @@ class SkeletalMeshFBXLoader(plugin.Loader): def update(self, container, representation): name = container["asset_name"] - source_path = api.get_representation_path(representation) + source_path = get_representation_path(representation) destination_path = container["namespace"] task = unreal.AssetImportTask() diff --git a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py index c8a6964ffb..587fc83a77 100644 --- a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py @@ -2,7 +2,8 @@ """Load Static meshes form FBX.""" import os -from avalon import api, pipeline +from avalon import pipeline +from openpype.pipeline import get_representation_path from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as unreal_pipeline import unreal # noqa @@ -118,7 +119,7 @@ class StaticMeshFBXLoader(plugin.Loader): def update(self, container, representation): name = container["asset_name"] - source_path = api.get_representation_path(representation) + source_path = get_representation_path(representation) destination_path = container["namespace"] task = self.get_task(source_path, destination_path, name, True) diff --git a/openpype/hosts/webpublisher/api/__init__.py b/openpype/hosts/webpublisher/api/__init__.py index 6ce8a58fc2..4542ddbba4 100644 --- a/openpype/hosts/webpublisher/api/__init__.py +++ b/openpype/hosts/webpublisher/api/__init__.py @@ -5,7 +5,6 @@ from avalon import api as avalon from avalon import io from pyblish import api as pyblish import openpype.hosts.webpublisher -from openpype.pipeline import LegacyCreator log = logging.getLogger("openpype.hosts.webpublisher") @@ -13,8 +12,6 @@ HOST_DIR = os.path.dirname(os.path.abspath( openpype.hosts.webpublisher.__file__)) PLUGINS_DIR = os.path.join(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") def application_launch(): @@ -25,8 +22,6 @@ def install(): print("Installing Pype config...") pyblish.register_plugin_path(PUBLISH_PATH) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) - avalon.register_plugin_path(LegacyCreator, CREATE_PATH) log.info(PUBLISH_PATH) io.install() @@ -35,8 +30,6 @@ def install(): def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH) # to have required methods for interface diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 0bfd3f6de0..bd3fcba950 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -980,6 +980,8 @@ class BuildWorkfile: ... }] """ + from openpype.pipeline import discover_loader_plugins + # Get current asset name and entity current_asset_name = avalon.io.Session["AVALON_ASSET"] current_asset_entity = avalon.io.find_one({ @@ -996,7 +998,7 @@ class BuildWorkfile: # Prepare available loaders loaders_by_name = {} - for loader in avalon.api.discover(avalon.api.Loader): + for loader in discover_loader_plugins(): loader_name = loader.__name__ if loader_name in loaders_by_name: raise KeyError( @@ -1390,6 +1392,11 @@ class BuildWorkfile: Returns: (list) Objects of loaded containers. """ + from openpype.pipeline import ( + IncompatibleLoaderError, + load_representation, + ) + loaded_containers = [] # Get subset id order from build presets. @@ -1451,7 +1458,7 @@ class BuildWorkfile: if not loader: continue try: - container = avalon.api.load( + container = load_representation( loader, repre["_id"], name=subset_name @@ -1460,7 +1467,7 @@ class BuildWorkfile: is_loaded = True except Exception as exc: - if exc == avalon.pipeline.IncompatibleLoaderError: + if exc == IncompatibleLoaderError: self.log.info(( "Loader `{}` is not compatible with" " representation `{}`" diff --git a/openpype/lib/path_templates.py b/openpype/lib/path_templates.py index 62bfdf774a..14e5fe59f8 100644 --- a/openpype/lib/path_templates.py +++ b/openpype/lib/path_templates.py @@ -187,6 +187,16 @@ class StringTemplate(object): result.validate() return result + @classmethod + def format_template(cls, template, data): + objected_template = cls(template) + return objected_template.format(data) + + @classmethod + def format_strict_template(cls, template, data): + objected_template = cls(template) + return objected_template.format_strict(data) + @staticmethod def find_optional_parts(parts): new_parts = [] diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py index 19765a6f4a..f11ba56865 100644 --- a/openpype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -280,6 +280,7 @@ def set_plugin_attributes_from_settings( project_name (str): Name of project for which settings will be loaded. Value from environment `AVALON_PROJECT` is used if not entered. """ + from openpype.pipeline import LegacyCreator, LoaderPlugin # determine host application to use for finding presets if host_name is None: @@ -289,11 +290,11 @@ def set_plugin_attributes_from_settings( project_name = os.environ.get("AVALON_PROJECT") # map plugin superclass to preset json. Currently supported is load and - # create (avalon.api.Loader and avalon.api.Creator) + # create (LoaderPlugin and LegacyCreator) plugin_type = None - if superclass.__name__.split(".")[-1] in ("Loader", "SubsetLoader"): + if superclass is LoaderPlugin or issubclass(superclass, LoaderPlugin): plugin_type = "load" - elif superclass.__name__.split(".")[-1] in ("Creator", "LegacyCreator"): + elif superclass is LegacyCreator or issubclass(superclass, LegacyCreator): plugin_type = "create" if not host_name or not project_name or plugin_type is None: diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 1de1c37575..19d504b6c9 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -13,6 +13,8 @@ from avalon import api, io import pyblish.api +from openpype.pipeline import get_representation_path + def get_resources(version, extension=None): """Get the files from the specific version.""" @@ -23,7 +25,7 @@ def get_resources(version, extension=None): representation = io.find_one(query) assert representation, "This is a bug" - directory = api.get_representation_path(representation) + directory = get_representation_path(representation) print("Source: ", directory) resources = sorted( [ diff --git a/openpype/modules/ftrack/event_handlers_user/action_rv.py b/openpype/modules/ftrack/event_handlers_user/action_rv.py index 71d790f7e7..bdb0eaf250 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_rv.py +++ b/openpype/modules/ftrack/event_handlers_user/action_rv.py @@ -3,9 +3,10 @@ import subprocess import traceback import json -from openpype_modules.ftrack.lib import BaseAction, statics_icon import ftrack_api from avalon import io, api +from openpype.pipeline import get_representation_path +from openpype_modules.ftrack.lib import BaseAction, statics_icon class RVAction(BaseAction): @@ -307,7 +308,7 @@ class RVAction(BaseAction): "name": "preview" } ) - paths.append(api.get_representation_path(representation)) + paths.append(get_representation_path(representation)) return paths diff --git a/openpype/pipeline/create/legacy_create.py b/openpype/pipeline/create/legacy_create.py index d05cdff689..cf6629047e 100644 --- a/openpype/pipeline/create/legacy_create.py +++ b/openpype/pipeline/create/legacy_create.py @@ -21,6 +21,7 @@ class LegacyCreator(object): dynamic_subset_keys = [] log = logging.getLogger("LegacyCreator") + log.propagate = True def __init__(self, name, asset, options=None, data=None): self.name = name # For backwards compatibility diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py index 09448d553c..95001691e2 100644 --- a/openpype/plugins/load/add_site.py +++ b/openpype/plugins/load/add_site.py @@ -1,8 +1,8 @@ -from avalon import api from openpype.modules import ModulesManager +from openpype.pipeline import load -class AddSyncSite(api.Loader): +class AddSyncSite(load.LoaderPlugin): """Add sync site to representation""" representations = ["*"] families = ["*"] diff --git a/openpype/plugins/load/copy_file.py b/openpype/plugins/load/copy_file.py index eaf5853035..c3c8e132d4 100644 --- a/openpype/plugins/load/copy_file.py +++ b/openpype/plugins/load/copy_file.py @@ -1,7 +1,9 @@ -from avalon import api, style +from avalon import style + +from openpype.pipeline import load -class CopyFile(api.Loader): +class CopyFile(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" representations = ["*"] diff --git a/openpype/plugins/load/copy_file_path.py b/openpype/plugins/load/copy_file_path.py index 2041c79f6d..565d8d1ff1 100644 --- a/openpype/plugins/load/copy_file_path.py +++ b/openpype/plugins/load/copy_file_path.py @@ -1,9 +1,9 @@ import os -from avalon import api +from openpype.pipeline import load -class CopyFilePath(api.Loader): +class CopyFilePath(load.LoaderPlugin): """Copy published file path to clipboard""" representations = ["*"] families = ["*"] diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index e8612745fb..7cadb8bb14 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -8,13 +8,14 @@ import ftrack_api import qargparse from Qt import QtWidgets, QtCore -from avalon import api, style +from avalon import style from avalon.api import AvalonMongoDB -import avalon.pipeline +from openpype.pipeline import load +from openpype.lib import StringTemplate from openpype.api import Anatomy -class DeleteOldVersions(api.SubsetLoader): +class DeleteOldVersions(load.SubsetLoaderPlugin): """Deletes specific number of old version""" is_multiple_contexts_compatible = True @@ -89,16 +90,12 @@ class DeleteOldVersions(api.SubsetLoader): try: context = representation["context"] context["root"] = anatomy.roots - path = avalon.pipeline.format_template_with_optional_keys( - context, template - ) + path = str(StringTemplate.format_template(template, context)) if "frame" in context: context["frame"] = self.sequence_splitter - sequence_path = os.path.normpath( - avalon.pipeline.format_template_with_optional_keys( - context, template - ) - ) + sequence_path = os.path.normpath(str( + StringTemplate.format_template(template, context) + )) except KeyError: # Template references unavailable data diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 1037d6dc16..04080053e3 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -3,9 +3,9 @@ from collections import defaultdict from Qt import QtWidgets, QtCore, QtGui -from avalon import api from avalon.api import AvalonMongoDB +from openpype.pipeline import load from openpype.api import Anatomy, config from openpype import resources, style @@ -20,7 +20,7 @@ from openpype.lib.delivery import ( ) -class Delivery(api.SubsetLoader): +class Delivery(load.SubsetLoaderPlugin): """Export selected versions to folder structure from Template""" is_multiple_contexts_compatible = True diff --git a/openpype/plugins/load/open_djv.py b/openpype/plugins/load/open_djv.py index 4b0e8411c8..273c77c93f 100644 --- a/openpype/plugins/load/open_djv.py +++ b/openpype/plugins/load/open_djv.py @@ -1,6 +1,6 @@ import os -from avalon import api from openpype.api import ApplicationManager +from openpype.pipeline import load def existing_djv_path(): @@ -13,7 +13,8 @@ def existing_djv_path(): return djv_list -class OpenInDJV(api.Loader): + +class OpenInDJV(load.LoaderPlugin): """Open Image Sequence with system default""" djv_list = existing_djv_path() diff --git a/openpype/plugins/load/open_file.py b/openpype/plugins/load/open_file.py index 4133a64eb3..f21cd07c7f 100644 --- a/openpype/plugins/load/open_file.py +++ b/openpype/plugins/load/open_file.py @@ -2,7 +2,7 @@ import sys import os import subprocess -from avalon import api +from openpype.pipeline import load def open(filepath): @@ -15,7 +15,7 @@ def open(filepath): subprocess.call(('xdg-open', filepath)) -class Openfile(api.Loader): +class Openfile(load.LoaderPlugin): """Open Image Sequence with system default""" families = ["render2d"] diff --git a/openpype/plugins/load/remove_site.py b/openpype/plugins/load/remove_site.py index aedb5d1f2f..adffec9986 100644 --- a/openpype/plugins/load/remove_site.py +++ b/openpype/plugins/load/remove_site.py @@ -1,8 +1,8 @@ -from avalon import api from openpype.modules import ModulesManager +from openpype.pipeline import load -class RemoveSyncSite(api.Loader): +class RemoveSyncSite(load.LoaderPlugin): """Remove sync site and its files on representation""" representations = ["*"] families = ["*"] diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index baee569239..dc3cda1725 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -10,7 +10,7 @@ from avalon import ( from Qt import QtCore, QtGui import qtawesome -from avalon.lib import HeroVersionType +from openpype.pipeline import HeroVersionType from openpype.tools.utils.models import TreeModel, Item from openpype.tools.utils import lib diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index f145756cc5..b14bdd0e93 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1,6 +1,5 @@ import os import sys -import inspect import datetime import pprint import traceback @@ -9,8 +8,19 @@ import collections from Qt import QtWidgets, QtCore, QtGui from avalon import api, pipeline -from avalon.lib import HeroVersionType +from openpype.pipeline import HeroVersionType +from openpype.pipeline.load import ( + discover_loader_plugins, + SubsetLoaderPlugin, + loaders_from_repre_context, + get_repres_contexts, + get_subset_contexts, + load_with_repre_context, + load_with_subset_context, + load_with_subset_contexts, + IncompatibleLoaderError, +) from openpype.tools.utils import ( ErrorMessageBox, lib as tools_lib @@ -425,7 +435,7 @@ class SubsetWidget(QtWidgets.QWidget): # Get all representation->loader combinations available for the # index under the cursor, so we can list the user the options. - available_loaders = api.discover(api.Loader) + available_loaders = discover_loader_plugins() if self.tool_name: available_loaders = lib.remove_tool_name_from_loaders( available_loaders, self.tool_name @@ -435,7 +445,7 @@ class SubsetWidget(QtWidgets.QWidget): subset_loaders = [] for loader in available_loaders: # Skip if its a SubsetLoader. - if api.SubsetLoader in inspect.getmro(loader): + if issubclass(loader, SubsetLoaderPlugin): subset_loaders.append(loader) else: repre_loaders.append(loader) @@ -459,7 +469,7 @@ class SubsetWidget(QtWidgets.QWidget): repre_docs = repre_docs_by_version_id[version_id] for repre_doc in repre_docs: repre_context = repre_context_by_id[repre_doc["_id"]] - for loader in pipeline.loaders_from_repre_context( + for loader in loaders_from_repre_context( repre_loaders, repre_context ): @@ -515,7 +525,7 @@ class SubsetWidget(QtWidgets.QWidget): action = lib.get_no_loader_action(menu, one_item_selected) menu.addAction(action) else: - repre_contexts = pipeline.get_repres_contexts( + repre_contexts = get_repres_contexts( repre_context_by_id.keys(), self.dbcon) menu = lib.add_representation_loaders_to_menu( @@ -532,7 +542,7 @@ class SubsetWidget(QtWidgets.QWidget): self.load_started.emit() - if api.SubsetLoader in inspect.getmro(loader): + if issubclass(loader, SubsetLoaderPlugin): subset_ids = [] subset_version_docs = {} for item in items: @@ -541,8 +551,7 @@ class SubsetWidget(QtWidgets.QWidget): subset_version_docs[subset_id] = item["version_document"] # get contexts only for selected menu option - subset_contexts_by_id = pipeline.get_subset_contexts(subset_ids, - self.dbcon) + subset_contexts_by_id = get_subset_contexts(subset_ids, self.dbcon) subset_contexts = list(subset_contexts_by_id.values()) options = lib.get_options(action, loader, self, subset_contexts) @@ -575,8 +584,7 @@ class SubsetWidget(QtWidgets.QWidget): repre_ids.append(representation["_id"]) # get contexts only for selected menu option - repre_contexts = pipeline.get_repres_contexts(repre_ids, - self.dbcon) + repre_contexts = get_repres_contexts(repre_ids, self.dbcon) options = lib.get_options(action, loader, self, list(repre_contexts.values())) @@ -1339,12 +1347,12 @@ class RepresentationWidget(QtWidgets.QWidget): selected_side = self._get_selected_side(point_index, rows) # Get all representation->loader combinations available for the # index under the cursor, so we can list the user the options. - available_loaders = api.discover(api.Loader) + available_loaders = discover_loader_plugins() filtered_loaders = [] for loader in available_loaders: # Skip subset loaders - if api.SubsetLoader in inspect.getmro(loader): + if issubclass(loader, SubsetLoaderPlugin): continue if ( @@ -1370,7 +1378,7 @@ class RepresentationWidget(QtWidgets.QWidget): for item in items: repre_context = repre_context_by_id[item["_id"]] - for loader in pipeline.loaders_from_repre_context( + for loader in loaders_from_repre_context( filtered_loaders, repre_context ): @@ -1426,7 +1434,7 @@ class RepresentationWidget(QtWidgets.QWidget): action = lib.get_no_loader_action(menu) menu.addAction(action) else: - repre_contexts = pipeline.get_repres_contexts( + repre_contexts = get_repres_contexts( repre_context_by_id.keys(), self.dbcon) menu = lib.add_representation_loaders_to_menu(loaders, menu, repre_contexts) @@ -1472,8 +1480,7 @@ class RepresentationWidget(QtWidgets.QWidget): repre_ids.append(item.get("_id")) - repre_contexts = pipeline.get_repres_contexts(repre_ids, - self.dbcon) + repre_contexts = get_repres_contexts(repre_ids, self.dbcon) options = lib.get_options(action, loader, self, list(repre_contexts.values())) @@ -1540,7 +1547,7 @@ def _load_representations_by_loader(loader, repre_contexts, """Loops through list of repre_contexts and loads them with one loader Args: - loader (cls of api.Loader) - not initialized yet + loader (cls of LoaderPlugin) - not initialized yet repre_contexts (dicts) - full info about selected representations (containing repre_doc, version_doc, subset_doc, project info) options (dict) - qargparse arguments to fill OptionDialog @@ -1558,12 +1565,12 @@ def _load_representations_by_loader(loader, repre_contexts, _id = repre_context["representation"]["_id"] data = data_by_repre_id.get(_id) options.update(data) - pipeline.load_with_repre_context( + load_with_repre_context( loader, repre_context, options=options ) - except pipeline.IncompatibleLoaderError as exc: + except IncompatibleLoaderError as exc: print(exc) error_info.append(( "Incompatible Loader", @@ -1612,7 +1619,7 @@ def _load_subsets_by_loader(loader, subset_contexts, options, context["version"] = subset_version_docs[context["subset"]["_id"]] try: - pipeline.load_with_subset_contexts( + load_with_subset_contexts( loader, subset_contexts, options=options @@ -1638,7 +1645,7 @@ def _load_subsets_by_loader(loader, subset_contexts, options, version_doc = subset_version_docs[subset_context["subset"]["_id"]] subset_context["version"] = version_doc try: - pipeline.load_with_subset_context( + load_with_subset_context( loader, subset_context, options=options diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index 96fc28243b..df72e41354 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -4,10 +4,11 @@ import os import maya.cmds as cmds -from openpype.hosts.maya.api import lib - from avalon import io, api +from openpype.pipeline import remove_container +from openpype.hosts.maya.api import lib + from .vray_proxies import get_alembic_ids_cache log = logging.getLogger(__name__) @@ -206,6 +207,6 @@ def remove_unused_looks(): for container in unused: log.info("Removing unused look container: %s", container['objectName']) - api.remove(container) + remove_container(container) log.info("Finished removing unused looks. (see log for details)") diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index b22ec95a4d..3179ba1445 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -12,6 +12,12 @@ from maya import cmds from avalon import io, api +from openpype.pipeline import ( + load_representation, + loaders_from_representation, + discover_loader_plugins, + get_representation_path, +) from openpype.hosts.maya.api import lib @@ -155,7 +161,7 @@ def get_look_relationships(version_id): "name": "json"}) # Load relationships - shader_relation = api.get_representation_path(json_representation) + shader_relation = get_representation_path(json_representation) with open(shader_relation, "r") as f: relationships = json.load(f) @@ -193,8 +199,8 @@ def load_look(version_id): log.info("Using look for the first time ...") # Load file - loaders = api.loaders_from_representation(api.discover(api.Loader), - representation_id) + all_loaders = discover_loader_plugins() + loaders = loaders_from_representation(all_loaders, representation_id) loader = next( (i for i in loaders if i.__name__ == "LookLoader"), None) if loader is None: @@ -202,7 +208,7 @@ def load_look(version_id): # Reference the look file with lib.maintained_selection(): - container_node = api.load(loader, look_representation) + container_node = load_representation(loader, look_representation) # Get container members shader_nodes = lib.get_container_members(container_node) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index cba60be355..85bf18fbb7 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -7,8 +7,9 @@ from Qt import QtCore, QtGui import qtawesome from avalon import api, io, style, schema -from avalon.lib import HeroVersionType +from openpype.pipeline import HeroVersionType from openpype.tools.utils.models import TreeModel, Item +from openpype.modules import ModulesManager from .lib import ( get_site_icons, @@ -16,8 +17,6 @@ from .lib import ( get_progress_for_repre ) -from openpype.modules import ModulesManager - class InventoryModel(TreeModel): """The model for the inventory""" diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 93ea68beb4..0e7b1b759a 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -3,7 +3,12 @@ import logging from Qt import QtWidgets, QtCore import qtawesome -from avalon import io, api, pipeline +from avalon import io, pipeline +from openpype.pipeline import ( + discover_loader_plugins, + switch_container, + get_repres_contexts, +) from .widgets import ( ButtonWithMenu, @@ -343,13 +348,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _get_loaders(self, repre_ids): repre_contexts = None if repre_ids: - repre_contexts = pipeline.get_repres_contexts(repre_ids) + repre_contexts = get_repres_contexts(repre_ids) if not repre_contexts: return list() available_loaders = [] - for loader_plugin in api.discover(api.Loader): + for loader_plugin in discover_loader_plugins(): # Skip loaders without switch method if not hasattr(loader_plugin, "switch"): continue @@ -1352,7 +1357,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_doc = repres_by_name[container_repre_name] try: - api.switch(container, repre_doc, loader) + switch_container(container, repre_doc, loader) except Exception: msg = ( "Couldn't switch asset." diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 32c1883de6..fa61ec4384 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -6,8 +6,12 @@ from Qt import QtWidgets, QtCore import qtawesome from avalon import io, api, style -from avalon.lib import HeroVersionType +from openpype.pipeline import ( + HeroVersionType, + update_container, + remove_container, +) from openpype.modules import ModulesManager from openpype.tools.utils.lib import ( get_progress_for_repre, @@ -195,7 +199,7 @@ class SceneInventoryView(QtWidgets.QTreeView): version_name = version_name_by_id.get(version_id) if version_name is not None: try: - api.update(item, version_name) + update_container(item, version_name) except AssertionError: self._show_version_error_dialog( version_name, [item] @@ -223,7 +227,7 @@ class SceneInventoryView(QtWidgets.QTreeView): def _on_update_to_latest(items): for item in items: try: - api.update(item, -1) + update_container(item, -1) except AssertionError: self._show_version_error_dialog(None, [item]) log.warning("Update failed", exc_info=True) @@ -248,7 +252,7 @@ class SceneInventoryView(QtWidgets.QTreeView): def _on_update_to_hero(items): for item in items: try: - api.update(item, HeroVersionType(-1)) + update_container(item, HeroVersionType(-1)) except AssertionError: self._show_version_error_dialog('hero', [item]) log.warning("Update failed", exc_info=True) @@ -727,7 +731,7 @@ class SceneInventoryView(QtWidgets.QTreeView): version = versions_by_label[label] for item in items: try: - api.update(item, version) + update_container(item, version) except AssertionError: self._show_version_error_dialog(version, [item]) log.warning("Update failed", exc_info=True) @@ -758,7 +762,7 @@ class SceneInventoryView(QtWidgets.QTreeView): return for item in items: - api.remove(item) + remove_container(item) self.data_changed.emit() def _show_version_error_dialog(self, version, items): @@ -828,7 +832,7 @@ class SceneInventoryView(QtWidgets.QTreeView): # Trigger update to latest for item in outdated_items: try: - api.update(item, -1) + update_container(item, -1) except AssertionError: self._show_version_error_dialog(None, [item]) log.warning("Update failed", exc_info=True) diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 4ec6079bb7..d3718b1734 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -6,7 +6,7 @@ import numbers import Qt from Qt import QtWidgets, QtGui, QtCore -from avalon.lib import HeroVersionType +from openpype.pipeline import HeroVersionType from .models import TreeModel from . import lib From d4434fd1b71e5e492119132e2b329f4f3e9dac52 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 11:42:02 +0100 Subject: [PATCH 34/67] use 'LegacyCreator' instead of 'avalon.api.Creator' --- openpype/lib/avalon_context.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 67a5515100..03ad69a5e6 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1594,11 +1594,13 @@ def get_creator_by_name(creator_name, case_sensitive=False): Returns: Creator: Return first matching plugin or `None`. """ + from openpype.pipeline import LegacyCreator + # Lower input creator name if is not case sensitive if not case_sensitive: creator_name = creator_name.lower() - for creator_plugin in avalon.api.discover(avalon.api.Creator): + for creator_plugin in avalon.api.discover(LegacyCreator): _creator_name = creator_plugin.__name__ # Lower creator plugin name if is not case sensitive From 3d01ba27113eb82901b4a7aa5a550a55932125fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 13:00:27 +0100 Subject: [PATCH 35/67] define class attribute '_representations' loaded from settings --- openpype/hosts/nuke/plugins/load/load_clip.py | 3 +++ openpype/hosts/nuke/plugins/load/load_image.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 563a325a83..2b4315a830 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -42,6 +42,9 @@ class LoadClip(plugin.NukeLoader): icon = "file-video-o" color = "white" + # Loaded from settings + _representations = [] + script_start = int(nuke.root()["first_frame"].value()) # option gui diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index e04ccf3bf1..9a175a0cba 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -36,6 +36,9 @@ class LoadImage(load.LoaderPlugin): icon = "image" color = "white" + # Loaded from settings + _representations = [] + node_name_template = "{class_name}_{ext}" options = [ From 4e83ff6c27940fac9ca38d6c6a9df8d8b8712eed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 14:03:51 +0100 Subject: [PATCH 36/67] moved avalon.api imports into functions using them --- openpype/pipeline/load/plugins.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index d7e21e1248..ea92cf962b 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -1,13 +1,5 @@ import logging -from avalon.api import ( - discover, - register_plugin, - deregister_plugin, - register_plugin_path, - deregister_plugin_path, -) - from .utils import get_representation_path_from_context @@ -110,20 +102,29 @@ class SubsetLoaderPlugin(LoaderPlugin): def discover_loader_plugins(): - return discover(LoaderPlugin) + import avalon.api + + return avalon.api.discover(LoaderPlugin) def register_loader_plugin(plugin): - return register_plugin(LoaderPlugin, plugin) + import avalon.api + + return avalon.api.register_plugin(LoaderPlugin, plugin) def deregister_loader_plugins_path(path): - deregister_plugin_path(LoaderPlugin, path) + import avalon.api + + avalon.api.deregister_plugin_path(LoaderPlugin, path) def register_loader_plugins_path(path): - return register_plugin_path(LoaderPlugin, path) + import avalon.api + + return avalon.apiregister_plugin_path(LoaderPlugin, path) def deregister_loader_plugin(plugin): - deregister_plugin(LoaderPlugin, plugin) + import avalon.api + avalon.api.deregister_plugin(LoaderPlugin, plugin) From 105d794358e97dbba654b38383c91dc366ddc0d1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 14:04:37 +0100 Subject: [PATCH 37/67] fix typo --- openpype/pipeline/load/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index ea92cf962b..5648236739 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -122,7 +122,7 @@ def deregister_loader_plugins_path(path): def register_loader_plugins_path(path): import avalon.api - return avalon.apiregister_plugin_path(LoaderPlugin, path) + return avalon.api.register_plugin_path(LoaderPlugin, path) def deregister_loader_plugin(plugin): From d5c35d18730ca35eee0d01e7d5e235380adde6c3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 15:20:20 +0100 Subject: [PATCH 38/67] use color hex instead of constans in nuke plugins --- openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py | 4 ++-- openpype/hosts/nuke/plugins/load/load_backdrop.py | 4 ++-- openpype/hosts/nuke/plugins/load/load_effects.py | 5 +++-- openpype/hosts/nuke/plugins/load/load_effects_ip.py | 5 +++-- openpype/hosts/nuke/plugins/load/load_gizmo.py | 5 +++-- openpype/hosts/nuke/plugins/load/load_gizmo_ip.py | 5 +++-- openpype/hosts/nuke/plugins/load/load_script_precomp.py | 5 +++-- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py b/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py index 49405fd213..5f834be557 100644 --- a/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py +++ b/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py @@ -1,4 +1,4 @@ -from avalon import api, style +from avalon import api from openpype.api import Logger from openpype.hosts.nuke.api.lib import set_avalon_knob_data @@ -7,7 +7,7 @@ class RepairOldLoaders(api.InventoryAction): label = "Repair Old Loaders" icon = "gears" - color = style.colors.alert + color = "#cc0000" log = Logger.get_logger(__name__) diff --git a/openpype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py index 6619cfb414..58ebcc7d49 100644 --- a/openpype/hosts/nuke/plugins/load/load_backdrop.py +++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py @@ -1,4 +1,4 @@ -from avalon import api, style, io +from avalon import api, io import nuke import nukescripts @@ -23,7 +23,7 @@ class LoadBackdropNodes(api.Loader): label = "Iport Nuke Nodes" order = 0 icon = "eye" - color = style.colors.light + color = "white" node_color = "0x7533c1ff" def load(self, context, name, namespace, data): diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index f636c6b510..4d83da1a78 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -1,7 +1,8 @@ import json from collections import OrderedDict import nuke -from avalon import api, style, io +from avalon import api, io + from openpype.hosts.nuke.api import ( containerise, update_container, @@ -18,7 +19,7 @@ class LoadEffects(api.Loader): label = "Load Effects - nodes" order = 0 icon = "cc" - color = style.colors.light + color = "white" ignore_attr = ["useLifetime"] diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index 990bce54f1..4d30e0f93c 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -3,7 +3,8 @@ from collections import OrderedDict import nuke -from avalon import api, style, io +from avalon import api, io + from openpype.hosts.nuke.api import lib from openpype.hosts.nuke.api import ( containerise, @@ -21,7 +22,7 @@ class LoadEffectsInputProcess(api.Loader): label = "Load Effects - Input Process" order = 0 icon = "eye" - color = style.colors.alert + color = "#cc0000" ignore_attr = ["useLifetime"] def load(self, context, name, namespace, data): diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo.py b/openpype/hosts/nuke/plugins/load/load_gizmo.py index 659977d789..9c726d8fe6 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo.py @@ -1,5 +1,6 @@ import nuke -from avalon import api, style, io +from avalon import api, io + from openpype.hosts.nuke.api.lib import ( maintained_selection, get_avalon_knob_data, @@ -21,7 +22,7 @@ class LoadGizmo(api.Loader): label = "Load Gizmo" order = 0 icon = "dropbox" - color = style.colors.light + color = "white" node_color = "0x75338eff" def load(self, context, name, namespace, data): diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py index 240bfd467d..78d2625758 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -1,5 +1,6 @@ -from avalon import api, style, io +from avalon import api, io import nuke + from openpype.hosts.nuke.api.lib import ( maintained_selection, create_backdrop, @@ -22,7 +23,7 @@ class LoadGizmoInputProcess(api.Loader): label = "Load Gizmo - Input Process" order = 0 icon = "eye" - color = style.colors.alert + color = "#cc0000" node_color = "0x7533c1ff" def load(self, context, name, namespace, data): diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py index aa48b631c5..48bf0b889f 100644 --- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py +++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py @@ -1,5 +1,6 @@ import nuke -from avalon import api, style, io +from avalon import api, io + from openpype.hosts.nuke.api.lib import get_avalon_knob_data from openpype.hosts.nuke.api import ( containerise, @@ -17,7 +18,7 @@ class LinkAsGroup(api.Loader): label = "Load Precomp" order = 0 icon = "file" - color = style.colors.alert + color = "#cc0000" def load(self, context, name, namespace, data): # for k, v in context.items(): From 239badf4d32f9d1b2a8144957c9e47514562d833 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 15:24:40 +0100 Subject: [PATCH 39/67] added functions to get colors from style data --- openpype/style/__init__.py | 83 ++++++++++++++++++++++++++++++-------- openpype/style/data.json | 6 +++ 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/openpype/style/__init__.py b/openpype/style/__init__.py index ea88b342ee..d92e18c0cd 100644 --- a/openpype/style/__init__.py +++ b/openpype/style/__init__.py @@ -7,13 +7,30 @@ from openpype import resources from .color_defs import parse_color - -_STYLESHEET_CACHE = None -_FONT_IDS = None - current_dir = os.path.dirname(os.path.abspath(__file__)) +# Default colors +# - default color used in tool icons +_TOOLS_ICON_COLOR = "#ffffff" +# - entities icon color - use 'get_default_asset_icon_color' +_DEFAULT_ENTITY_ICON_COLOR = "#fb9c15" +# - disabled entitie +_DISABLED_ENTITY_ICON_ICON_COLOR = "#808080" +# - deprecated entity font color +_DEPRECATED_ENTITY_FONT_COLOR = "#666666" + + +class _Cache: + stylesheet = None + font_ids = None + + tools_icon_color = None + default_entity_icon_color = None + disabled_entity_icon_color = None + deprecated_entity_font_color = None + + def get_style_image_path(image_name): # All filenames are lowered image_name = image_name.lower() @@ -125,21 +142,19 @@ def _load_font(): """Load and register fonts into Qt application.""" from Qt import QtGui - global _FONT_IDS - # Check if font ids are still loaded - if _FONT_IDS is not None: - for font_id in tuple(_FONT_IDS): + if _Cache.font_ids is not None: + for font_id in tuple(_Cache.font_ids): font_families = QtGui.QFontDatabase.applicationFontFamilies( font_id ) # Reset font if font id is not available if not font_families: - _FONT_IDS = None + _Cache.font_ids = None break - if _FONT_IDS is None: - _FONT_IDS = [] + if _Cache.font_ids is None: + _Cache.font_ids = [] fonts_dirpath = os.path.join(current_dir, "fonts") font_dirs = [] font_dirs.append(os.path.join(fonts_dirpath, "Noto_Sans")) @@ -157,7 +172,7 @@ def _load_font(): continue full_path = os.path.join(font_dir, filename) font_id = QtGui.QFontDatabase.addApplicationFont(full_path) - _FONT_IDS.append(font_id) + _Cache.font_ids.append(font_id) font_families = QtGui.QFontDatabase.applicationFontFamilies( font_id ) @@ -167,11 +182,11 @@ def _load_font(): def load_stylesheet(): """Load and return OpenPype Qt stylesheet.""" - global _STYLESHEET_CACHE - if _STYLESHEET_CACHE is None: - _STYLESHEET_CACHE = _load_stylesheet() + + if _Cache.stylesheet is None: + _Cache.stylesheet = _load_stylesheet() _load_font() - return _STYLESHEET_CACHE + return _Cache.stylesheet def get_app_icon_path(): @@ -182,3 +197,39 @@ def get_app_icon_path(): def app_icon_path(): # Backwards compatibility return get_app_icon_path() + + +def get_default_tools_icon_color(): + if _Cache.tools_icon_color is None: + color_data = get_colors_data() + color = color_data.get("icon-tools") + _Cache.tools_icon_color = color or _TOOLS_ICON_COLOR + return _Cache.tools_icon_color + + +def get_default_entity_icon_color(): + if _Cache.default_entity_icon_color is None: + color_data = get_colors_data() + color = color_data.get("icon-entity-default") + _Cache.default_entity_icon_color = color or _DEFAULT_ENTITY_ICON_COLOR + return _Cache.default_entity_icon_color + + +def get_disabled_entity_icon_color(): + if _Cache.disabled_entity_icon_color is None: + color_data = get_colors_data() + color = color_data.get("icon-entity-disabled") + _Cache.disabled_entity_icon_color = ( + color or _DISABLED_ENTITY_ICON_ICON_COLOR + ) + return _Cache.disabled_entity_icon_color + + +def get_deprecated_entity_font_color(): + if _Cache.deprecated_entity_font_color is None: + color_data = get_colors_data() + color = color_data.get("font-entity-deprecated") + _Cache.deprecated_entity_font_color = ( + color or _DEPRECATED_ENTITY_FONT_COLOR + ) + return _Cache.deprecated_entity_font_color diff --git a/openpype/style/data.json b/openpype/style/data.json index b8ccef8bbd..2af23acd0d 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -56,6 +56,12 @@ "delete-btn-bg": "rgb(201, 54, 54)", "delete-btn-bg-disabled": "rgba(201, 54, 54, 64)", + "icon-tools": "#ffffff", + "icon-alert-tools": "#AA5050", + "icon-entity-default": "#fb9c15", + "icon-entity-disabled": "#808080", + "font-entity-deprecated": "#666666", + "tab-widget": { "bg": "#21252B", "bg-selected": "#434a56", From 8d81a91c1cce37c85f0e39f091f30013f2d2db34 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 15:24:56 +0100 Subject: [PATCH 40/67] use openpype style in delete old versions --- openpype/plugins/load/delete_old_versions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index e8612745fb..fb8be0ed33 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -8,9 +8,10 @@ import ftrack_api import qargparse from Qt import QtWidgets, QtCore -from avalon import api, style +from avalon import api from avalon.api import AvalonMongoDB import avalon.pipeline +from openpype import style from openpype.api import Anatomy From 9bbaf42a3bb10cb56360f6f9a223c8428f855ce3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 15:26:08 +0100 Subject: [PATCH 41/67] use functions for colors in tools --- openpype/plugins/load/copy_file.py | 5 ++-- openpype/tools/loader/model.py | 19 ++++++++------- openpype/tools/mayalookassigner/models.py | 13 ++++++++--- openpype/tools/sceneinventory/model.py | 7 ++++-- .../standalonepublish/widgets/model_asset.py | 23 +++++++++++++++---- .../widgets/model_tasks_template.py | 6 +++-- .../standalonepublish/widgets/widget_asset.py | 6 +++-- openpype/tools/utils/assets_widget.py | 13 +++++++---- openpype/tools/utils/lib.py | 13 +++++++---- openpype/tools/utils/tasks_widget.py | 14 +++++++---- openpype/tools/workfiles/model.py | 16 ++++++++++--- 11 files changed, 93 insertions(+), 42 deletions(-) diff --git a/openpype/plugins/load/copy_file.py b/openpype/plugins/load/copy_file.py index eaf5853035..bdcb4fec79 100644 --- a/openpype/plugins/load/copy_file.py +++ b/openpype/plugins/load/copy_file.py @@ -1,4 +1,5 @@ -from avalon import api, style +from avalon import api +from openpype.style import get_default_entity_icon_color class CopyFile(api.Loader): @@ -10,7 +11,7 @@ class CopyFile(api.Loader): label = "Copy File" order = 10 icon = "copy" - color = style.colors.default + color = get_default_entity_icon_color() def load(self, context, name=None, namespace=None, data=None): self.log.info("Added copy to clipboard: {0}".format(self.fname)) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index baee569239..1007355989 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -3,15 +3,13 @@ import re import math from uuid import uuid4 -from avalon import ( - style, - schema -) from Qt import QtCore, QtGui import qtawesome +from avalon import schema from avalon.lib import HeroVersionType +from openpype.style import get_default_entity_icon_color from openpype.tools.utils.models import TreeModel, Item from openpype.tools.utils import lib @@ -180,7 +178,10 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._sorter = None self._grouping = grouping self._icons = { - "subset": qtawesome.icon("fa.file-o", color=style.colors.default) + "subset": qtawesome.icon( + "fa.file-o", + color=get_default_entity_icon_color() + ) } self._items_by_id = {} @@ -1066,8 +1067,10 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self._docs = {} self._icons = lib.get_repre_icons() - self._icons["repre"] = qtawesome.icon("fa.file-o", - color=style.colors.default) + self._icons["repre"] = qtawesome.icon( + "fa.file-o", + color=get_default_entity_icon_color() + ) self._items_by_id = {} def set_version_ids(self, version_ids): @@ -1165,7 +1168,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): "remote_site_name": self.remote_site, "icon": qtawesome.icon( "fa.folder", - color=style.colors.default + color=get_default_entity_icon_color() ) }) self._items_by_id[item_id] = group_item diff --git a/openpype/tools/mayalookassigner/models.py b/openpype/tools/mayalookassigner/models.py index 386b7d7e1e..77a3c8a590 100644 --- a/openpype/tools/mayalookassigner/models.py +++ b/openpype/tools/mayalookassigner/models.py @@ -3,14 +3,19 @@ from collections import defaultdict from Qt import QtCore import qtawesome -from avalon.style import colors from openpype.tools.utils import models +from openpype.style import get_default_entity_icon_color class AssetModel(models.TreeModel): Columns = ["label"] + def __init__(self, *args, **kwargs): + super(AssetModel, self).__init__(*args, **kwargs) + + self._icon_color = get_default_entity_icon_color() + def add_items(self, items): """ Add items to model with needed data @@ -65,8 +70,10 @@ class AssetModel(models.TreeModel): node = index.internalPointer() icon = node.get("icon") if icon: - return qtawesome.icon("fa.{0}".format(icon), - color=colors.default) + return qtawesome.icon( + "fa.{0}".format(icon), + color=self._icon_color + ) return super(AssetModel, self).data(index, role) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index cba60be355..6ec3601705 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -6,8 +6,9 @@ from collections import defaultdict from Qt import QtCore, QtGui import qtawesome -from avalon import api, io, style, schema +from avalon import api, io, schema from avalon.lib import HeroVersionType +from openpype.style import get_default_entity_icon_color from openpype.tools.utils.models import TreeModel, Item from .lib import ( @@ -38,6 +39,8 @@ class InventoryModel(TreeModel): self._hierarchy_view = False + self._default_icon_color = get_default_entity_icon_color() + manager = ModulesManager() sync_server = manager.modules_by_name["sync_server"] self.sync_enabled = sync_server.enabled @@ -131,7 +134,7 @@ class InventoryModel(TreeModel): if role == QtCore.Qt.DecorationRole: if index.column() == 0: # Override color - color = item.get("color", style.colors.default) + color = item.get("color", self._default_icon_color) if item.get("isGroupNode"): # group-item return qtawesome.icon("fa.folder", color=color) if item.get("isNotSet"): diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py index 6d764eff9f..7d93e7a943 100644 --- a/openpype/tools/standalonepublish/widgets/model_asset.py +++ b/openpype/tools/standalonepublish/widgets/model_asset.py @@ -1,10 +1,15 @@ import logging import collections + from Qt import QtCore, QtGui import qtawesome -from . import TreeModel, Node -from avalon import style +from openpype.style import ( + get_default_entity_icon_color, + get_deprecated_entity_font_color, +) + +from . import TreeModel, Node log = logging.getLogger(__name__) @@ -49,6 +54,14 @@ class AssetModel(TreeModel): def __init__(self, dbcon, parent=None): super(AssetModel, self).__init__(parent=parent) self.dbcon = dbcon + + self._default_asset_icon_color = QtGui.QColor( + get_default_entity_icon_color() + ) + self._deprecated_asset_font_color = QtGui.QColor( + get_deprecated_entity_font_color() + ) + self.refresh() def _add_hierarchy(self, assets, parent=None, silos=None): @@ -163,7 +176,7 @@ class AssetModel(TreeModel): icon = data.get("icon", None) if icon is None and node.get("type") == "silo": icon = "database" - color = data.get("color", style.colors.default) + color = data.get("color", self._default_asset_icon_color) if icon is None: # Use default icons if no custom one is specified. @@ -188,8 +201,8 @@ class AssetModel(TreeModel): return if role == QtCore.Qt.ForegroundRole: # font color - if "deprecated" in node.get("tags", []): - return QtGui.QColor(style.colors.light).darker(250) + # if "deprecated" in node.get("tags", []): + return QtGui.QColor(self._deprecated_asset_font_color) if role == self.ObjectIdRole: return node.get("_id", None) diff --git a/openpype/tools/standalonepublish/widgets/model_tasks_template.py b/openpype/tools/standalonepublish/widgets/model_tasks_template.py index 1f36eaa39d..648f7ed479 100644 --- a/openpype/tools/standalonepublish/widgets/model_tasks_template.py +++ b/openpype/tools/standalonepublish/widgets/model_tasks_template.py @@ -1,7 +1,9 @@ from Qt import QtCore import qtawesome + +from openpype.style import get_default_entity_icon_color + from . import Node, TreeModel -from avalon import style class TasksTemplateModel(TreeModel): @@ -14,7 +16,7 @@ class TasksTemplateModel(TreeModel): self.selectable = selectable self.icon = qtawesome.icon( 'fa.calendar-check-o', - color=style.colors.default + color=get_default_entity_icon_color() ) def set_tasks(self, tasks): diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index d929f227f9..e6b74f8f82 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -4,7 +4,7 @@ import qtawesome from openpype.tools.utils import PlaceholderLineEdit -from avalon import style +from openpype.style import get_default_tools_icon_color from . import RecursiveSortFilterProxyModel, AssetModel from . import TasksTemplateModel, DeselectableTreeView @@ -165,7 +165,9 @@ class AssetWidget(QtWidgets.QWidget): # Header header = QtWidgets.QHBoxLayout() - icon = qtawesome.icon("fa.refresh", color=style.colors.light) + icon = qtawesome.icon( + "fa.refresh", color=get_default_tools_icon_color() + ) refresh = QtWidgets.QPushButton(icon, "") refresh.setToolTip("Refresh items") diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 4c77b81c0e..9beca69f12 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -5,9 +5,10 @@ import Qt from Qt import QtWidgets, QtCore, QtGui import qtawesome -from avalon import style - -from openpype.style import get_objected_colors +from openpype.style import ( + get_objected_colors, + get_default_tools_icon_color, +) from openpype.tools.flickcharm import FlickCharm from .views import ( @@ -589,7 +590,7 @@ class AssetsWidget(QtWidgets.QWidget): view.setModel(proxy) current_asset_icon = qtawesome.icon( - "fa.arrow-down", color=style.colors.light + "fa.arrow-down", color=get_default_tools_icon_color() ) current_asset_btn = QtWidgets.QPushButton(self) current_asset_btn.setIcon(current_asset_icon) @@ -597,7 +598,9 @@ class AssetsWidget(QtWidgets.QWidget): # Hide by default current_asset_btn.setVisible(False) - refresh_icon = qtawesome.icon("fa.refresh", color=style.colors.light) + refresh_icon = qtawesome.icon( + "fa.refresh", color=get_default_tools_icon_color() + ) refresh_btn = QtWidgets.QPushButton(self) refresh_btn.setIcon(refresh_icon) refresh_btn.setToolTip("Refresh items") diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 042ceaab88..829725dcf2 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -7,8 +7,8 @@ from Qt import QtWidgets, QtCore, QtGui import qtawesome import avalon.api -from avalon import style +from openpype.style import get_default_entity_icon_color from openpype.api import ( get_project_settings, Logger @@ -128,7 +128,7 @@ def get_qta_icon_by_name_and_color(icon_name, icon_color): def get_asset_icon(asset_doc, has_children=False): asset_data = asset_doc.get("data") or {} - icon_color = asset_data.get("color") or style.colors.default + icon_color = asset_data.get("color") or get_default_entity_icon_color() icon_name = asset_data.get("icon") if not icon_name: # Use default icons if no custom one is specified. @@ -149,7 +149,9 @@ def get_task_icon(): Icon should be defined by task type which is stored on project. """ - return get_qta_icon_by_name_and_color("fa.male", style.colors.default) + return get_qta_icon_by_name_and_color( + "fa.male", get_default_entity_icon_color() + ) def schedule(func, time, channel="default"): @@ -412,6 +414,7 @@ class GroupsConfig: def __init__(self, dbcon): self.dbcon = dbcon self.groups = {} + self._default_group_color = get_default_entity_icon_color() @classmethod def default_group_config(cls): @@ -419,7 +422,7 @@ class GroupsConfig: cls._default_group_config = { "icon": qtawesome.icon( "fa.object-group", - color=style.colors.default + color=get_default_entity_icon_color() ), "order": 0 } @@ -453,7 +456,7 @@ class GroupsConfig: for config in group_configs: name = config["name"] icon = "fa." + config.get("icon", "object-group") - color = config.get("color", style.colors.default) + color = config.get("color", self._default_group_color) order = float(config.get("order", 0)) self.groups[name] = { diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index 7619f59974..2c92b7228a 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -1,7 +1,10 @@ from Qt import QtWidgets, QtCore, QtGui import qtawesome -from avalon import style +from openpype.style import ( + get_default_entity_icon_color, + get_disabled_entity_icon_color, +) from .views import DeselectableTreeView @@ -21,13 +24,14 @@ class TasksModel(QtGui.QStandardItemModel): self.setHeaderData( 0, QtCore.Qt.Horizontal, "Tasks", QtCore.Qt.DisplayRole ) + default_color = get_default_entity_icon_color() + self._default_color = default_color self._default_icon = qtawesome.icon( - "fa.male", - color=style.colors.default + "fa.male", color=default_color ) self._no_tasks_icon = qtawesome.icon( "fa.exclamation-circle", - color=style.colors.mid + color=get_disabled_entity_icon_color() ) self._cached_icons = {} self._project_task_types = {} @@ -62,7 +66,7 @@ class TasksModel(QtGui.QStandardItemModel): try: icon = qtawesome.icon( "fa.{}".format(icon_name), - color=style.colors.default + color=self._default_color ) except Exception: diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py index b3cf5063e7..e9184842fc 100644 --- a/openpype/tools/workfiles/model.py +++ b/openpype/tools/workfiles/model.py @@ -4,7 +4,11 @@ import logging from Qt import QtCore import qtawesome -from avalon import style +from openpype.style import ( + get_default_entity_icon_color, + get_disabled_entity_icon_color, +) + from openpype.tools.utils.models import TreeModel, Item log = logging.getLogger(__name__) @@ -25,7 +29,10 @@ class FilesModel(TreeModel): self._root = None self._file_extensions = file_extensions self._icons = { - "file": qtawesome.icon("fa.file-o", color=style.colors.default) + "file": qtawesome.icon( + "fa.file-o", + color=get_default_entity_icon_color() + ) } def set_root(self, root): @@ -64,7 +71,10 @@ class FilesModel(TreeModel): "date": None, "filepath": None, "enabled": False, - "icon": qtawesome.icon("fa.times", color=style.colors.mid) + "icon": qtawesome.icon( + "fa.times", + color=get_disabled_entity_icon_color() + ) }) self.add_child(item) self.endResetModel() From 7c63ef68df3a10d31655dd9d8a0b3a8de21c1d3a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 15:45:03 +0100 Subject: [PATCH 42/67] change asset colorchange entity icon color --- openpype/style/data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/style/data.json b/openpype/style/data.json index 2af23acd0d..a76a77015b 100644 --- a/openpype/style/data.json +++ b/openpype/style/data.json @@ -58,7 +58,7 @@ "icon-tools": "#ffffff", "icon-alert-tools": "#AA5050", - "icon-entity-default": "#fb9c15", + "icon-entity-default": "#bfccd6", "icon-entity-disabled": "#808080", "font-entity-deprecated": "#666666", From bf3b9407cb8c8b05abde6a2df2145b4f86198a07 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 16:18:23 +0100 Subject: [PATCH 43/67] remove avalon style from maya look assigner --- openpype/tools/mayalookassigner/__init__.py | 4 +- openpype/tools/mayalookassigner/app.py | 55 ++++++++++++--------- openpype/tools/mayalookassigner/views.py | 3 -- openpype/tools/mayalookassigner/widgets.py | 36 +++++++------- openpype/tools/utils/host_tools.py | 6 +-- 5 files changed, 55 insertions(+), 49 deletions(-) diff --git a/openpype/tools/mayalookassigner/__init__.py b/openpype/tools/mayalookassigner/__init__.py index 616a3e94d0..5e40777741 100644 --- a/openpype/tools/mayalookassigner/__init__.py +++ b/openpype/tools/mayalookassigner/__init__.py @@ -1,9 +1,9 @@ from .app import ( - App, + MayaLookAssignerWindow, show ) __all__ = [ - "App", + "MayaLookAssignerWindow", "show"] diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 31bb455f95..da9f06f3f0 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -4,10 +4,10 @@ import logging from Qt import QtWidgets, QtCore -from openpype.hosts.maya.api.lib import assign_look_by_version - -from avalon import style, io +from avalon import io +from openpype import style from openpype.tools.utils.lib import qt_app_context +from openpype.hosts.maya.api.lib import assign_look_by_version from maya import cmds # old api for MFileIO @@ -28,10 +28,10 @@ module = sys.modules[__name__] module.window = None -class App(QtWidgets.QWidget): +class MayaLookAssignerWindow(QtWidgets.QWidget): def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent=parent) + super(MayaLookAssignerWindow, self).__init__(parent=parent) self.log = logging.getLogger(__name__) @@ -56,30 +56,41 @@ class App(QtWidgets.QWidget): def setup_ui(self): """Build the UI""" + main_splitter = QtWidgets.QSplitter(self) + # Assets (left) - asset_outliner = AssetOutliner() + asset_outliner = AssetOutliner(main_splitter) # Looks (right) - looks_widget = QtWidgets.QWidget() - looks_layout = QtWidgets.QVBoxLayout(looks_widget) + looks_widget = QtWidgets.QWidget(main_splitter) - look_outliner = LookOutliner() # Database look overview + look_outliner = LookOutliner(looks_widget) # Database look overview - assign_selected = QtWidgets.QCheckBox("Assign to selected only") + assign_selected = QtWidgets.QCheckBox( + "Assign to selected only", looks_widget + ) assign_selected.setToolTip("Whether to assign only to selected nodes " "or to the full asset") - remove_unused_btn = QtWidgets.QPushButton("Remove Unused Looks") + remove_unused_btn = QtWidgets.QPushButton( + "Remove Unused Looks", looks_widget + ) + looks_layout = QtWidgets.QVBoxLayout(looks_widget) looks_layout.addWidget(look_outliner) looks_layout.addWidget(assign_selected) looks_layout.addWidget(remove_unused_btn) + main_splitter.addWidget(asset_outliner) + main_splitter.addWidget(looks_widget) + main_splitter.setSizes([350, 200]) + # Footer - status = QtWidgets.QStatusBar() + status = QtWidgets.QStatusBar(self) status.setSizeGripEnabled(False) status.setFixedHeight(25) - warn_layer = QtWidgets.QLabel("Current Layer is not " - "defaultRenderLayer") + warn_layer = QtWidgets.QLabel( + "Current Layer is not defaultRenderLayer", self + ) warn_layer.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) warn_layer.setStyleSheet("color: #DD5555; font-weight: bold;") warn_layer.setFixedHeight(25) @@ -92,11 +103,6 @@ class App(QtWidgets.QWidget): # Build up widgets main_layout = QtWidgets.QVBoxLayout(self) main_layout.setSpacing(0) - main_splitter = QtWidgets.QSplitter() - main_splitter.setStyleSheet("QSplitter{ border: 0px; }") - main_splitter.addWidget(asset_outliner) - main_splitter.addWidget(looks_widget) - main_splitter.setSizes([350, 200]) main_layout.addWidget(main_splitter) main_layout.addLayout(footer) @@ -124,6 +130,8 @@ class App(QtWidgets.QWidget): self.remove_unused = remove_unused_btn self.assign_selected = assign_selected + self._first_show = True + def setup_connections(self): """Connect interactive widgets with actions""" if self._connections_set_up: @@ -147,11 +155,14 @@ class App(QtWidgets.QWidget): def showEvent(self, event): self.setup_connections() - super(App, self).showEvent(event) + super(MayaLookAssignerWindow, self).showEvent(event) + if self._first_show: + self._first_show = False + self.setStyleSheet(style.load_stylesheet()) def closeEvent(self, event): self.remove_connection() - super(App, self).closeEvent(event) + super(MayaLookAssignerWindow, self).closeEvent(event) def _on_renderlayer_switch(self, *args): """Callback that updates on Maya renderlayer switch""" @@ -267,7 +278,7 @@ def show(): if widget.objectName() == "MayaWindow") with qt_app_context(): - window = App(parent=mainwindow) + window = MayaLookAssignerWindow(parent=mainwindow) window.setStyleSheet(style.load_stylesheet()) window.show() diff --git a/openpype/tools/mayalookassigner/views.py b/openpype/tools/mayalookassigner/views.py index 993023bb45..8e676ebc7f 100644 --- a/openpype/tools/mayalookassigner/views.py +++ b/openpype/tools/mayalookassigner/views.py @@ -1,9 +1,6 @@ from Qt import QtWidgets, QtCore -DEFAULT_COLOR = "#fb9c15" - - class View(QtWidgets.QTreeView): data_changed = QtCore.Signal() diff --git a/openpype/tools/mayalookassigner/widgets.py b/openpype/tools/mayalookassigner/widgets.py index e546ee705d..10e573342a 100644 --- a/openpype/tools/mayalookassigner/widgets.py +++ b/openpype/tools/mayalookassigner/widgets.py @@ -14,7 +14,7 @@ from .models import ( LookModel ) from . import commands -from . import views +from .views import View from maya import cmds @@ -24,25 +24,28 @@ class AssetOutliner(QtWidgets.QWidget): selection_changed = QtCore.Signal() def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) + super(AssetOutliner, self).__init__(parent) - layout = QtWidgets.QVBoxLayout() - - title = QtWidgets.QLabel("Assets") + title = QtWidgets.QLabel("Assets", self) title.setAlignment(QtCore.Qt.AlignCenter) title.setStyleSheet("font-weight: bold; font-size: 12px") model = AssetModel() - view = views.View() + view = View(self) view.setModel(model) view.customContextMenuRequested.connect(self.right_mouse_menu) view.setSortingEnabled(False) view.setHeaderHidden(True) view.setIndentation(10) - from_all_asset_btn = QtWidgets.QPushButton("Get All Assets") - from_selection_btn = QtWidgets.QPushButton("Get Assets From Selection") + from_all_asset_btn = QtWidgets.QPushButton( + "Get All Assets", self + ) + from_selection_btn = QtWidgets.QPushButton( + "Get Assets From Selection", self + ) + layout = QtWidgets.QVBoxLayout(self) layout.addWidget(title) layout.addWidget(from_all_asset_btn) layout.addWidget(from_selection_btn) @@ -58,8 +61,6 @@ class AssetOutliner(QtWidgets.QWidget): self.view = view self.model = model - self.setLayout(layout) - self.log = logging.getLogger(__name__) def clear(self): @@ -188,15 +189,10 @@ class LookOutliner(QtWidgets.QWidget): menu_apply_action = QtCore.Signal() def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) - - # look manager layout - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(10) + super(LookOutliner, self).__init__(parent) # Looks from database - title = QtWidgets.QLabel("Looks") + title = QtWidgets.QLabel("Looks", self) title.setAlignment(QtCore.Qt.AlignCenter) title.setStyleSheet("font-weight: bold; font-size: 12px") title.setAlignment(QtCore.Qt.AlignCenter) @@ -207,13 +203,17 @@ class LookOutliner(QtWidgets.QWidget): proxy = QtCore.QSortFilterProxyModel() proxy.setSourceModel(model) - view = views.View() + view = View(self) view.setModel(proxy) view.setMinimumHeight(180) view.setToolTip("Use right mouse button menu for direct actions") view.customContextMenuRequested.connect(self.right_mouse_menu) view.sortByColumn(0, QtCore.Qt.AscendingOrder) + # look manager layout + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(10) layout.addWidget(title) layout.addWidget(view) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 6ce9e818d9..2d9733ec94 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -224,20 +224,18 @@ class HostToolsHelper: def get_look_assigner_tool(self, parent): """Create, cache and return look assigner tool window.""" if self._look_assigner_tool is None: - import mayalookassigner + from openpype.tools.mayalookassigner import MayaLookAssignerWindow - mayalookassigner_window = mayalookassigner.App(parent) + mayalookassigner_window = MayaLookAssignerWindow(parent) self._look_assigner_tool = mayalookassigner_window return self._look_assigner_tool def show_look_assigner(self, parent=None): """Look manager is Maya specific tool for look management.""" - from avalon import style with qt_app_context(): look_assigner_tool = self.get_look_assigner_tool(parent) look_assigner_tool.show() - look_assigner_tool.setStyleSheet(style.load_stylesheet()) def get_experimental_tools_dialog(self, parent=None): """Dialog of experimental tools. From cd1e764da6299d328fe5d9895d4b6552f48fdc2e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 16:19:08 +0100 Subject: [PATCH 44/67] cleanup --- .../project_manager/project_manager/multiselection_combobox.py | 2 +- openpype/tools/utils/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/multiselection_combobox.py b/openpype/tools/project_manager/project_manager/multiselection_combobox.py index 890567de6d..f776831298 100644 --- a/openpype/tools/project_manager/project_manager/multiselection_combobox.py +++ b/openpype/tools/project_manager/project_manager/multiselection_combobox.py @@ -1,4 +1,4 @@ -from Qt import QtCore, QtGui, QtWidgets +from Qt import QtCore, QtWidgets class ComboItemDelegate(QtWidgets.QStyledItemDelegate): diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index 6ab9e75b52..ea1133c442 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -42,6 +42,7 @@ __all__ = ( "set_style_property", "DynamicQThread", "qt_app_context", + "get_asset_icon", "RecursiveSortFilterProxyModel", ) From 4f8129acfae2061211f00c4d8061dc8bb8626199 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 16:40:05 +0100 Subject: [PATCH 45/67] move spinner svg into openpype --- openpype/resources/images/spinner-200.svg | 6 ++++++ openpype/tools/utils/views.py | 9 +++------ 2 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 openpype/resources/images/spinner-200.svg diff --git a/openpype/resources/images/spinner-200.svg b/openpype/resources/images/spinner-200.svg new file mode 100644 index 0000000000..73d8ae2890 --- /dev/null +++ b/openpype/resources/images/spinner-200.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/openpype/tools/utils/views.py b/openpype/tools/utils/views.py index 97aaf622a4..a2f1f15b95 100644 --- a/openpype/tools/utils/views.py +++ b/openpype/tools/utils/views.py @@ -1,5 +1,5 @@ import os -from avalon import style +from openpype.resources import get_image_path from Qt import QtWidgets, QtCore, QtGui, QtSvg @@ -24,11 +24,8 @@ class TreeViewSpinner(QtWidgets.QTreeView): def __init__(self, parent=None): super(TreeViewSpinner, self).__init__(parent=parent) - loading_image_path = os.path.join( - os.path.dirname(os.path.abspath(style.__file__)), - "svg", - "spinner-200.svg" - ) + loading_image_path = get_image_path("spinner-200.svg") + self.spinner = QtSvg.QSvgRenderer(loading_image_path) self.is_loading = False From e1dd07c936c65a4e34c68135425e0ffd54ab94db Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 16:40:32 +0100 Subject: [PATCH 46/67] remove backwards compatibility of 'application' context function --- openpype/tools/utils/lib.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 829725dcf2..3ad2d12883 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -92,10 +92,6 @@ def qt_app_context(): yield app -# Backwards compatibility -application = qt_app_context - - class SharedObjects: jobs = {} icons = {} From 2b062e98a0a3076d56d3f91756570db67ea17e0c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 16:51:37 +0100 Subject: [PATCH 47/67] added icon option for projects --- openpype/tools/launcher/models.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 9036c9cbd5..08bf43a451 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -14,7 +14,10 @@ from openpype.lib.applications import ( CUSTOM_LAUNCH_APP_GROUPS, ApplicationManager ) -from openpype.tools.utils.lib import DynamicQThread +from openpype.tools.utils.lib import ( + DynamicQThread, + get_qta_icon_by_name_and_color, +) from openpype.tools.utils.assets_widget import ( AssetModel, ASSET_NAME_ROLE @@ -400,6 +403,7 @@ class LauncherModel(QtCore.QObject): self._dbcon = dbcon # Available project names self._project_names = set() + self._project_icons_by_name = {} # Context data self._asset_docs = [] @@ -460,6 +464,9 @@ class LauncherModel(QtCore.QObject): """Available project names.""" return self._project_names + def get_icon_for_project(self, project_name): + return self._project_icons_by_name.get(project_name) + @property def asset_filter_data_by_id(self): """Prepared filter data by asset id.""" @@ -516,9 +523,15 @@ class LauncherModel(QtCore.QObject): """Refresh projects.""" current_project = self.project_name project_names = set() + project_icons_by_name = {} for project_doc in self._dbcon.projects(only_active=True): - project_names.add(project_doc["name"]) + project_name = project_doc["name"] + project_names.add(project_name) + project_icons_by_name[project_name] = ( + project_doc.get("data", {}).get("icon") + ) + self._project_icons_by_name = project_icons_by_name self._project_names = project_names self.projects_refreshed.emit() if ( @@ -718,7 +731,6 @@ class AssetRecursiveSortFilterModel(QtCore.QSortFilterProxyModel): self._assignee_filter = self._launcher_model.assignee_filters self.invalidateFilter() - """Filters to the regex if any of the children matches allow parent""" def filterAcceptsRow(self, row, parent): if ( not self._name_filter @@ -863,7 +875,14 @@ class ProjectModel(QtGui.QStandardItemModel): for row in reversed(sorted(row_counts.keys())): items = [] for project_name in row_counts[row]: - item = QtGui.QStandardItem(self.project_icon, project_name) + icon_name = self._launcher_model.get_icon_for_project( + project_name + ) + icon = get_qta_icon_by_name_and_color(icon_name, "white") + if not icon: + icon = self.project_icon + + item = QtGui.QStandardItem(icon, project_name) items.append(item) self.invisibleRootItem().insertRows(row, items) From b48460d579e275dae09248168ae3748e2eb77716 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 17:17:33 +0100 Subject: [PATCH 48/67] added function for getting a project icon --- openpype/tools/launcher/models.py | 24 ++++++---------- openpype/tools/utils/lib.py | 46 +++++++++++++++++++++++-------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 08bf43a451..8dbc45aadb 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -16,7 +16,7 @@ from openpype.lib.applications import ( ) from openpype.tools.utils.lib import ( DynamicQThread, - get_qta_icon_by_name_and_color, + get_project_icon, ) from openpype.tools.utils.assets_widget import ( AssetModel, @@ -403,7 +403,7 @@ class LauncherModel(QtCore.QObject): self._dbcon = dbcon # Available project names self._project_names = set() - self._project_icons_by_name = {} + self._project_docs_by_name = {} # Context data self._asset_docs = [] @@ -464,8 +464,8 @@ class LauncherModel(QtCore.QObject): """Available project names.""" return self._project_names - def get_icon_for_project(self, project_name): - return self._project_icons_by_name.get(project_name) + def get_project_doc(self, project_name): + return self._project_docs_by_name.get(project_name) @property def asset_filter_data_by_id(self): @@ -523,15 +523,13 @@ class LauncherModel(QtCore.QObject): """Refresh projects.""" current_project = self.project_name project_names = set() - project_icons_by_name = {} + project_docs_by_name = {} for project_doc in self._dbcon.projects(only_active=True): project_name = project_doc["name"] project_names.add(project_name) - project_icons_by_name[project_name] = ( - project_doc.get("data", {}).get("icon") - ) + project_docs_by_name[project_name] = project_doc - self._project_icons_by_name = project_icons_by_name + self._project_docs_by_name = project_docs_by_name self._project_names = project_names self.projects_refreshed.emit() if ( @@ -830,7 +828,6 @@ class ProjectModel(QtGui.QStandardItemModel): super(ProjectModel, self).__init__(parent=parent) self._launcher_model = launcher_model - self.project_icon = qtawesome.icon("fa.map", color="white") self._project_names = set() launcher_model.projects_refreshed.connect(self._on_refresh) @@ -875,13 +872,10 @@ class ProjectModel(QtGui.QStandardItemModel): for row in reversed(sorted(row_counts.keys())): items = [] for project_name in row_counts[row]: - icon_name = self._launcher_model.get_icon_for_project( + project_doc = self._launcher_model.get_project_doc( project_name ) - icon = get_qta_icon_by_name_and_color(icon_name, "white") - if not icon: - icon = self.project_icon - + icon = get_project_icon(project_doc) item = QtGui.QStandardItem(icon, project_name) items.append(item) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 3ad2d12883..4754a85bf1 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -122,18 +122,42 @@ def get_qta_icon_by_name_and_color(icon_name, icon_color): return icon +def get_project_icon(project_doc): + if project_doc: + icon_name = project_doc.get("data", {}).get("icon") + icon = get_qta_icon_by_name_and_color(icon_name, "white") + if icon: + return icon + + return get_qta_icon_by_name_and_color( + "fa.map", get_default_entity_icon_color() + ) + + +def get_asset_icon_name(asset_doc, has_children=True): + if asset_doc: + asset_data = asset_doc.get("data") or {} + icon_name = asset_data.get("icon") + if icon_name: + return icon_name + + if has_children: + return "folder" + return "folder-o" + + +def get_asset_icon_color(asset_doc): + if asset_doc: + asset_data = asset_doc.get("data") or {} + icon_color = asset_data.get("color") + if icon_color: + return icon_color + return get_default_entity_icon_color() + + def get_asset_icon(asset_doc, has_children=False): - asset_data = asset_doc.get("data") or {} - icon_color = asset_data.get("color") or get_default_entity_icon_color() - icon_name = asset_data.get("icon") - if not icon_name: - # Use default icons if no custom one is specified. - # If it has children show a full folder, otherwise - # show an open folder - if has_children: - icon_name = "folder" - else: - icon_name = "folder-o" + icon_name = get_asset_icon_name(asset_doc, has_children) + icon_color = get_asset_icon_color(asset_doc) return get_qta_icon_by_name_and_color(icon_name, icon_color) From 8f998f06e88fa410c1603b40616911f83a3ac7b1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 14 Mar 2022 17:24:57 +0100 Subject: [PATCH 49/67] use openpype style on scene inventory error dialog --- openpype/tools/sceneinventory/view.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 32c1883de6..fb93faefd6 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -5,9 +5,10 @@ from functools import partial from Qt import QtWidgets, QtCore import qtawesome -from avalon import io, api, style +from avalon import io, api from avalon.lib import HeroVersionType +from openpype import style from openpype.modules import ModulesManager from openpype.tools.utils.lib import ( get_progress_for_repre, From 16a24d03daaf0eec01fb7121e48e40c6f0f1fa1f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 09:56:58 +0100 Subject: [PATCH 50/67] removed unnecessary style set --- openpype/tools/mayalookassigner/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index da9f06f3f0..0e633a21e3 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -279,7 +279,6 @@ def show(): with qt_app_context(): window = MayaLookAssignerWindow(parent=mainwindow) - window.setStyleSheet(style.load_stylesheet()) window.show() module.window = window From c5b3098acd35c439628d12750d521dfe5b36e598 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 10:24:12 +0100 Subject: [PATCH 51/67] fix function typo --- openpype/hosts/houdini/api/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 7d4e58efb7..eb1bdafbb0 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -13,7 +13,7 @@ from avalon.lib import find_submodule from openpype.pipeline import ( LegacyCreator, - register_loader_plugin_path, + register_loader_plugins_path, ) import openpype.hosts.houdini from openpype.hosts.houdini.api import lib @@ -53,7 +53,7 @@ def install(): pyblish.api.register_host("hpython") pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugin_path(LOAD_PATH) + register_loader_plugins_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info("Installing callbacks ... ") From 96f55916dd3cd000aad6901f842653123313285f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 10:26:07 +0100 Subject: [PATCH 52/67] remove constants --- openpype/style/__init__.py | 55 +++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/openpype/style/__init__.py b/openpype/style/__init__.py index d92e18c0cd..b2a1a4ce6c 100644 --- a/openpype/style/__init__.py +++ b/openpype/style/__init__.py @@ -10,17 +10,6 @@ from .color_defs import parse_color current_dir = os.path.dirname(os.path.abspath(__file__)) -# Default colors -# - default color used in tool icons -_TOOLS_ICON_COLOR = "#ffffff" -# - entities icon color - use 'get_default_asset_icon_color' -_DEFAULT_ENTITY_ICON_COLOR = "#fb9c15" -# - disabled entitie -_DISABLED_ENTITY_ICON_ICON_COLOR = "#808080" -# - deprecated entity font color -_DEPRECATED_ENTITY_FONT_COLOR = "#666666" - - class _Cache: stylesheet = None font_ids = None @@ -200,36 +189,60 @@ def app_icon_path(): def get_default_tools_icon_color(): + """Default color used in tool icons. + + Color must be possible to parse using QColor. + + Returns: + str: Color as a string. + """ if _Cache.tools_icon_color is None: color_data = get_colors_data() - color = color_data.get("icon-tools") - _Cache.tools_icon_color = color or _TOOLS_ICON_COLOR + _Cache.tools_icon_color = color_data["icon-tools"] return _Cache.tools_icon_color def get_default_entity_icon_color(): + """Default color of entities icons. + + Color must be possible to parse using QColor. + + Returns: + str: Color as a string. + """ if _Cache.default_entity_icon_color is None: color_data = get_colors_data() - color = color_data.get("icon-entity-default") - _Cache.default_entity_icon_color = color or _DEFAULT_ENTITY_ICON_COLOR + _Cache.default_entity_icon_color = color_data["icon-entity-default"] return _Cache.default_entity_icon_color def get_disabled_entity_icon_color(): + """Default color of entities icons. + + TODO: Find more suitable function name. + + Color must be possible to parse using QColor. + + Returns: + str: Color as a string. + """ if _Cache.disabled_entity_icon_color is None: color_data = get_colors_data() - color = color_data.get("icon-entity-disabled") - _Cache.disabled_entity_icon_color = ( - color or _DISABLED_ENTITY_ICON_ICON_COLOR - ) + _Cache.disabled_entity_icon_color = color_data["icon-entity-disabled"] return _Cache.disabled_entity_icon_color def get_deprecated_entity_font_color(): + """Font color for deprecated entities. + + Color must be possible to parse using QColor. + + Returns: + str: Color as a string. + """ if _Cache.deprecated_entity_font_color is None: color_data = get_colors_data() - color = color_data.get("font-entity-deprecated") _Cache.deprecated_entity_font_color = ( - color or _DEPRECATED_ENTITY_FONT_COLOR + color_data["font-entity-deprecated"] ) return _Cache.deprecated_entity_font_color From 58d31a11b9c89eef71d784947b19d9c9d78a8eae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 10:49:51 +0100 Subject: [PATCH 53/67] implemented function to receive task icon --- openpype/tools/launcher/models.py | 5 ++ openpype/tools/utils/lib.py | 39 +++++++++++--- openpype/tools/utils/tasks_widget.py | 76 +++++++--------------------- 3 files changed, 56 insertions(+), 64 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 8dbc45aadb..85d553fca4 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -705,6 +705,11 @@ class LauncherTaskModel(TasksModel): self._launcher_model = launcher_model super(LauncherTaskModel, self).__init__(*args, **kwargs) + def _refresh_project_doc(self): + self._project_doc = self._launcher_model.get_project_doc( + self._launcher_model.project_name + ) + def set_asset_id(self, asset_id): asset_doc = None if self._context_is_valid(): diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 4754a85bf1..00cec20b2a 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -162,16 +162,43 @@ def get_asset_icon(asset_doc, has_children=False): return get_qta_icon_by_name_and_color(icon_name, icon_color) -def get_task_icon(): - """Get icon for a task. +def get_default_task_icon(color=None): + if color is None: + color = get_default_entity_icon_color() + return get_qta_icon_by_name_and_color("fa.male", color) - TODO: Get task icon based on data in database. + +def get_task_icon(project_doc, asset_doc, task_name): + """Get icon for a task. Icon should be defined by task type which is stored on project. """ - return get_qta_icon_by_name_and_color( - "fa.male", get_default_entity_icon_color() - ) + + color = get_default_entity_icon_color() + + tasks_info = asset_doc.get("data", {}).get("tasks") or {} + task_info = tasks_info.get(task_name) or {} + task_icon = task_info.get("icon") + if task_icon: + icon = get_qta_icon_by_name_and_color(task_icon, color) + if icon is not None: + return icon + + task_type = task_info.get("type") + if "config" not in project_doc: + print(10*"*") + print(project_doc) + task_types = {} + else: + task_types = project_doc["config"].get("tasks") or {} + + task_type_info = task_types.get(task_type) or {} + task_type_icon = task_type_info.get("icon") + if task_type_icon: + icon = get_qta_icon_by_name_and_color(task_icon, color) + if icon is not None: + return icon + return get_default_task_icon(color) def schedule(func, time, channel="default"): diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index 2c92b7228a..eab183d5f3 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -1,10 +1,8 @@ from Qt import QtWidgets, QtCore, QtGui import qtawesome -from openpype.style import ( - get_default_entity_icon_color, - get_disabled_entity_icon_color, -) +from openpype.style import get_disabled_entity_icon_color +from openpype.tools.utils.lib import get_task_icon from .views import DeselectableTreeView @@ -24,54 +22,35 @@ class TasksModel(QtGui.QStandardItemModel): self.setHeaderData( 0, QtCore.Qt.Horizontal, "Tasks", QtCore.Qt.DisplayRole ) - default_color = get_default_entity_icon_color() - self._default_color = default_color - self._default_icon = qtawesome.icon( - "fa.male", color=default_color - ) + self._no_tasks_icon = qtawesome.icon( "fa.exclamation-circle", color=get_disabled_entity_icon_color() ) self._cached_icons = {} - self._project_task_types = {} + self._project_doc = {} self._empty_tasks_item = None self._last_asset_id = None self._loaded_project_name = None def _context_is_valid(self): - if self.dbcon.Session.get("AVALON_PROJECT"): + if self._get_current_project(): return True return False def refresh(self): - self._refresh_task_types() + self._refresh_project_doc() self.set_asset_id(self._last_asset_id) - def _refresh_task_types(self): + def _refresh_project_doc(self): # Get the project configured icons from database - task_types = {} + project_doc = {} if self._context_is_valid(): - project = self.dbcon.find_one( - {"type": "project"}, - {"config.tasks"} - ) - task_types = project["config"].get("tasks") or task_types - self._project_task_types = task_types + project_doc = self.dbcon.find_one({"type": "project"}) - def _try_get_awesome_icon(self, icon_name): - icon = None - if icon_name: - try: - icon = qtawesome.icon( - "fa.{}".format(icon_name), - color=self._default_color - ) - - except Exception: - pass - return icon + self._loaded_project_name = self._get_current_project() + self._project_doc = project_doc def headerData(self, section, orientation, role=None): if role is None: @@ -86,28 +65,8 @@ class TasksModel(QtGui.QStandardItemModel): return super(TasksModel, self).headerData(section, orientation, role) - def _get_icon(self, task_icon, task_type_icon): - if task_icon in self._cached_icons: - return self._cached_icons[task_icon] - - icon = self._try_get_awesome_icon(task_icon) - if icon is not None: - self._cached_icons[task_icon] = icon - return icon - - if task_type_icon in self._cached_icons: - icon = self._cached_icons[task_type_icon] - self._cached_icons[task_icon] = icon - return icon - - icon = self._try_get_awesome_icon(task_type_icon) - if icon is None: - icon = self._default_icon - - self._cached_icons[task_icon] = icon - self._cached_icons[task_type_icon] = icon - - return icon + def _get_current_project(self): + return self.dbcon.Session.get("AVALON_PROJECT") def set_asset_id(self, asset_id): asset_doc = None @@ -132,6 +91,9 @@ class TasksModel(QtGui.QStandardItemModel): Arguments: asset_doc (dict): Asset document from MongoDB. """ + if self._loaded_project_name != self._get_current_project(): + self._refresh_project_doc() + asset_tasks = {} self._last_asset_id = None if asset_doc: @@ -142,13 +104,11 @@ class TasksModel(QtGui.QStandardItemModel): root_item.removeRows(0, root_item.rowCount()) items = [] + for task_name, task_info in asset_tasks.items(): - task_icon = task_info.get("icon") task_type = task_info.get("type") task_order = task_info.get("order") - task_type_info = self._project_task_types.get(task_type) or {} - task_type_icon = task_type_info.get("icon") - icon = self._get_icon(task_icon, task_type_icon) + icon = get_task_icon(self._project_doc, asset_doc, task_name) task_assignees = set() assignees_data = task_info.get("assignees") or [] From 8a0005e93dd4eec57be0e92f3cf1741bc88e260c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 10:50:00 +0100 Subject: [PATCH 54/67] use default task icon in publisher --- openpype/tools/publisher/widgets/tasks_widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/widgets/tasks_widget.py b/openpype/tools/publisher/widgets/tasks_widget.py index 8a913b7114..aa239f6334 100644 --- a/openpype/tools/publisher/widgets/tasks_widget.py +++ b/openpype/tools/publisher/widgets/tasks_widget.py @@ -1,7 +1,7 @@ from Qt import QtCore, QtGui from openpype.tools.utils.tasks_widget import TasksWidget, TASK_NAME_ROLE -from openpype.tools.utils.lib import get_task_icon +from openpype.tools.utils.lib import get_default_task_icon class TasksModel(QtGui.QStandardItemModel): @@ -120,7 +120,7 @@ class TasksModel(QtGui.QStandardItemModel): item = QtGui.QStandardItem(task_name) item.setData(task_name, TASK_NAME_ROLE) if task_name: - item.setData(get_task_icon(), QtCore.Qt.DecorationRole) + item.setData(get_default_task_icon(), QtCore.Qt.DecorationRole) self._items_by_name[task_name] = item new_items.append(item) From 001f096f4e7861d8065304af521d848d417265cd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 10:55:27 +0100 Subject: [PATCH 55/67] fix deprecated font color in standalone publisher --- openpype/tools/standalonepublish/widgets/model_asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py index 7d93e7a943..a7316a2aa7 100644 --- a/openpype/tools/standalonepublish/widgets/model_asset.py +++ b/openpype/tools/standalonepublish/widgets/model_asset.py @@ -201,8 +201,8 @@ class AssetModel(TreeModel): return if role == QtCore.Qt.ForegroundRole: # font color - # if "deprecated" in node.get("tags", []): - return QtGui.QColor(self._deprecated_asset_font_color) + if "deprecated" in node.get("tags", []): + return QtGui.QColor(self._deprecated_asset_font_color) if role == self.ObjectIdRole: return node.get("_id", None) From 10333be88310781deffe118e276201c13ea5a4ae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 10:55:40 +0100 Subject: [PATCH 56/67] remove asset document check --- openpype/tools/utils/assets_widget.py | 2 +- openpype/tools/utils/lib.py | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 9beca69f12..3d4efcdd4d 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -513,7 +513,7 @@ class AssetModel(QtGui.QStandardItemModel): item.setData(asset_label, ASSET_LABEL_ROLE) has_children = item.rowCount() > 0 - icon = get_asset_icon(asset_data, has_children) + icon = get_asset_icon(asset_doc, has_children) item.setData(icon, QtCore.Qt.DecorationRole) def _threaded_fetch(self): diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 00cec20b2a..d66b636b2a 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -135,11 +135,9 @@ def get_project_icon(project_doc): def get_asset_icon_name(asset_doc, has_children=True): - if asset_doc: - asset_data = asset_doc.get("data") or {} - icon_name = asset_data.get("icon") - if icon_name: - return icon_name + icon_name = asset_doc["data"].get("icon") + if icon_name: + return icon_name if has_children: return "folder" @@ -147,11 +145,9 @@ def get_asset_icon_name(asset_doc, has_children=True): def get_asset_icon_color(asset_doc): - if asset_doc: - asset_data = asset_doc.get("data") or {} - icon_color = asset_data.get("color") - if icon_color: - return icon_color + icon_color = asset_doc["data"].get("color") + if icon_color: + return icon_color return get_default_entity_icon_color() From 43239740119e2456b8d7a26a28f3a4eaab05af47 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 11:12:39 +0100 Subject: [PATCH 57/67] remove debug prints --- openpype/tools/utils/lib.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index d66b636b2a..93b156bef8 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -181,12 +181,7 @@ def get_task_icon(project_doc, asset_doc, task_name): return icon task_type = task_info.get("type") - if "config" not in project_doc: - print(10*"*") - print(project_doc) - task_types = {} - else: - task_types = project_doc["config"].get("tasks") or {} + task_types = project_doc["config"]["tasks"] task_type_info = task_types.get(task_type) or {} task_type_icon = task_type_info.get("icon") From 5b835d87e7707fd625cba05c7e8e587d766c75fa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 11:40:59 +0100 Subject: [PATCH 58/67] renamed 'load_representation' to 'load_container' --- openpype/hosts/blender/plugins/load/load_layout_json.py | 4 ++-- openpype/hosts/maya/api/lib.py | 4 ++-- openpype/hosts/maya/api/setdress.py | 4 ++-- openpype/hosts/nuke/plugins/publish/validate_read_legacy.py | 4 ++-- openpype/hosts/unreal/plugins/load/load_layout.py | 4 ++-- openpype/lib/avalon_context.py | 4 ++-- openpype/pipeline/__init__.py | 4 ++-- openpype/pipeline/load/__init__.py | 4 ++-- openpype/pipeline/load/utils.py | 2 +- openpype/tools/mayalookassigner/vray_proxies.py | 4 ++-- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py index 499d2c49f3..0693937fec 100644 --- a/openpype/hosts/blender/plugins/load/load_layout_json.py +++ b/openpype/hosts/blender/plugins/load/load_layout_json.py @@ -10,7 +10,7 @@ import bpy from openpype.pipeline import ( discover_loader_plugins, remove_container, - load_representation, + load_container, get_representation_path, loaders_from_representation, ) @@ -108,7 +108,7 @@ class JsonLayoutLoader(plugin.AssetLoader): # at this time it will not return anything. The assets will be # loaded in the next Blender cycle, so we use the options to # set the transform, parent and assign the action, if there is one. - load_representation( + load_container( loader, reference, namespace=instance_name, diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 94efbb7a07..9f97eef2f1 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -25,7 +25,7 @@ from openpype.pipeline import ( discover_loader_plugins, loaders_from_representation, get_representation_path, - load_representation, + load_container, ) from .commands import reset_frame_range @@ -1594,7 +1594,7 @@ def assign_look_by_version(nodes, version_id): # Reference the look file with maintained_selection(): - container_node = load_representation(Loader, look_representation) + container_node = load_container(Loader, look_representation) # Get container members shader_nodes = get_container_members(container_node) diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index 74ee292eb2..96a9700b88 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -12,7 +12,7 @@ from avalon import io from openpype.pipeline import ( discover_loader_plugins, loaders_from_representation, - load_representation, + load_container, update_container, remove_container, get_representation_path, @@ -189,7 +189,7 @@ def _add(instance, representation_id, loaders, namespace, root="|"): instance['loader'], instance) raise RuntimeError("Loader is missing.") - container = load_representation( + container = load_container( Loader, representation_id, namespace=instance['namespace'] diff --git a/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py index 39fe011d85..2bf1ff81f8 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py +++ b/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py @@ -8,7 +8,7 @@ from bson.objectid import ObjectId from openpype.pipeline import ( discover_loader_plugins, - load_representation, + load_container, ) @@ -59,7 +59,7 @@ class RepairReadLegacyAction(pyblish.api.Action): loader_plugin = Loader - load_representation( + load_container( Loader=loader_plugin, representation=ObjectId(data["representation"]) ) diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py index b987a32a61..19ee179d20 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout.py +++ b/openpype/hosts/unreal/plugins/load/load_layout.py @@ -15,7 +15,7 @@ from avalon.pipeline import AVALON_CONTAINER_ID from openpype.pipeline import ( discover_loader_plugins, loaders_from_representation, - load_representation, + load_container, get_representation_path, ) from openpype.hosts.unreal.api import plugin @@ -258,7 +258,7 @@ class LayoutLoader(plugin.Loader): "asset_dir": asset_dir } - assets = load_representation( + assets = load_container( loader, reference, namespace=instance_name, diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index d7f17d8eed..c88e72c46a 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -1394,7 +1394,7 @@ class BuildWorkfile: """ from openpype.pipeline import ( IncompatibleLoaderError, - load_representation, + load_container, ) loaded_containers = [] @@ -1458,7 +1458,7 @@ class BuildWorkfile: if not loader: continue try: - container = load_representation( + container = load_container( loader, repre["_id"], name=subset_name diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index d582ef1d07..3ff3638a23 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -24,7 +24,7 @@ from .load import ( register_loader_plugins_path, deregister_loader_plugin, - load_representation, + load_container, remove_container, update_container, switch_container, @@ -68,7 +68,7 @@ __all__ = ( "register_loader_plugins_path", "deregister_loader_plugin", - "load_representation", + "load_container", "remove_container", "update_container", "switch_container", diff --git a/openpype/pipeline/load/__init__.py b/openpype/pipeline/load/__init__.py index 2af15e8705..eac303c10c 100644 --- a/openpype/pipeline/load/__init__.py +++ b/openpype/pipeline/load/__init__.py @@ -10,7 +10,7 @@ from .utils import ( load_with_subset_context, load_with_subset_contexts, - load_representation, + load_container, remove_container, update_container, switch_container, @@ -51,7 +51,7 @@ __all__ = ( "load_with_subset_context", "load_with_subset_contexts", - "load_representation", + "load_container", "remove_container", "update_container", "switch_container", diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index 4ef0f099d7..ae47cb9ce9 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -333,7 +333,7 @@ def load_with_subset_contexts( return loader.load(subset_contexts, name, namespace, options) -def load_representation( +def load_container( Loader, representation, namespace=None, name=None, options=None, **kwargs ): """Use Loader to load a representation. diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index 3179ba1445..6a9347449a 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -13,7 +13,7 @@ from maya import cmds from avalon import io, api from openpype.pipeline import ( - load_representation, + load_container, loaders_from_representation, discover_loader_plugins, get_representation_path, @@ -208,7 +208,7 @@ def load_look(version_id): # Reference the look file with lib.maintained_selection(): - container_node = load_representation(loader, look_representation) + container_node = load_container(loader, look_representation) # Get container members shader_nodes = lib.get_container_members(container_node) From 4f7d99babeee9a5a309b009e805877d70feec8a6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 15 Mar 2022 14:15:35 +0100 Subject: [PATCH 59/67] remove plural from de/register_loader_plugins_path --- openpype/__init__.py | 10 +++++----- openpype/hosts/aftereffects/api/pipeline.py | 8 ++++---- openpype/hosts/blender/api/pipeline.py | 8 ++++---- openpype/hosts/flame/api/pipeline.py | 8 ++++---- openpype/hosts/fusion/api/pipeline.py | 8 ++++---- openpype/hosts/harmony/api/pipeline.py | 8 ++++---- openpype/hosts/hiero/api/pipeline.py | 8 ++++---- openpype/hosts/houdini/api/pipeline.py | 4 ++-- openpype/hosts/maya/api/pipeline.py | 8 ++++---- openpype/hosts/nuke/api/pipeline.py | 8 ++++---- openpype/hosts/photoshop/api/pipeline.py | 8 ++++---- openpype/hosts/resolve/api/pipeline.py | 8 ++++---- openpype/hosts/tvpaint/api/pipeline.py | 8 ++++---- openpype/hosts/unreal/api/pipeline.py | 8 ++++---- openpype/pipeline/__init__.py | 8 ++++---- openpype/pipeline/load/__init__.py | 8 ++++---- openpype/pipeline/load/plugins.py | 4 ++-- 17 files changed, 65 insertions(+), 65 deletions(-) diff --git a/openpype/__init__.py b/openpype/__init__.py index 0df1b7270f..99629a4257 100644 --- a/openpype/__init__.py +++ b/openpype/__init__.py @@ -77,7 +77,7 @@ def install(): from openpype.modules import load_modules from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, + register_loader_plugin_path, ) from avalon import pipeline @@ -94,7 +94,7 @@ def install(): log.info("Registering global plug-ins..") pyblish.register_plugin_path(PUBLISH_PATH) pyblish.register_discovery_filter(filter_pyblish_plugins) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) project_name = os.environ.get("AVALON_PROJECT") @@ -122,7 +122,7 @@ def install(): continue pyblish.register_plugin_path(path) - register_loader_plugins_path(path) + register_loader_plugin_path(path) avalon.register_plugin_path(LegacyCreator, path) avalon.register_plugin_path(avalon.InventoryAction, path) @@ -142,12 +142,12 @@ def _on_task_change(): @import_wrapper def uninstall(): """Uninstall Pype from Avalon.""" - from openpype.pipeline import deregister_loader_plugins_path + from openpype.pipeline import deregister_loader_plugin_path log.info("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) pyblish.deregister_discovery_filter(filter_pyblish_plugins) - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) log.info("Global plug-ins unregistred") # restore original discover diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index 8961599149..681f1c51a7 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -11,8 +11,8 @@ from openpype import lib from openpype.api import Logger from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) import openpype.hosts.aftereffects from openpype.lib import register_event_callback @@ -71,7 +71,7 @@ def install(): pyblish.api.register_host("aftereffects") pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info(PUBLISH_PATH) @@ -84,7 +84,7 @@ def install(): def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 64fb135d89..07a7509dd7 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -16,8 +16,8 @@ from avalon.pipeline import AVALON_CONTAINER_ID from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) from openpype.api import Logger from openpype.lib import ( @@ -54,7 +54,7 @@ def install(): pyblish.api.register_host("blender") pyblish.api.register_plugin_path(str(PUBLISH_PATH)) - register_loader_plugins_path(str(LOAD_PATH)) + register_loader_plugin_path(str(LOAD_PATH)) avalon.api.register_plugin_path(LegacyCreator, str(CREATE_PATH)) lib.append_user_scripts() @@ -76,7 +76,7 @@ def uninstall(): pyblish.api.deregister_host("blender") pyblish.api.deregister_plugin_path(str(PUBLISH_PATH)) - deregister_loader_plugins_path(str(LOAD_PATH)) + deregister_loader_plugin_path(str(LOAD_PATH)) avalon.api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH)) if not IS_HEADLESS: diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 6a045214c3..930c6abe29 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -9,8 +9,8 @@ from pyblish import api as pyblish from openpype.api import Logger from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) from .lib import ( set_segment_data_marker, @@ -37,7 +37,7 @@ def install(): pyblish.register_host("flame") pyblish.register_plugin_path(PUBLISH_PATH) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) log.info("OpenPype Flame plug-ins registred ...") @@ -52,7 +52,7 @@ def uninstall(): log.info("Deregistering Flame plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH) avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py index 3f5da7fcc7..92e54ad6f5 100644 --- a/openpype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -13,8 +13,8 @@ from avalon.pipeline import AVALON_CONTAINER_ID from openpype.api import Logger from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) import openpype.hosts.fusion @@ -67,7 +67,7 @@ def install(): pyblish.api.register_plugin_path(PUBLISH_PATH) log.info("Registering Fusion plug-ins..") - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) @@ -91,7 +91,7 @@ def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) log.info("Deregistering Fusion plug-ins..") - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.deregister_plugin_path( avalon.api.InventoryAction, INVENTORY_PATH diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index b9d2e78bce..f967da15ca 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -12,8 +12,8 @@ from openpype import lib from openpype.lib import register_event_callback from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) import openpype.hosts.harmony import openpype.hosts.harmony.api as harmony @@ -184,7 +184,7 @@ def install(): pyblish.api.register_host("harmony") pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info(PUBLISH_PATH) @@ -198,7 +198,7 @@ def install(): def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index f27b7a4f81..eff126c0b6 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -11,8 +11,8 @@ from pyblish import api as pyblish from openpype.api import Logger from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) from openpype.tools.utils import host_tools from . import lib, menu, events @@ -49,7 +49,7 @@ def install(): log.info("Registering Hiero plug-ins..") pyblish.register_host("hiero") pyblish.register_plugin_path(PUBLISH_PATH) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) @@ -71,7 +71,7 @@ def uninstall(): log.info("Deregistering Hiero plug-ins..") pyblish.deregister_host("hiero") pyblish.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH) # register callback for switching publishable diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index eb1bdafbb0..7d4e58efb7 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -13,7 +13,7 @@ from avalon.lib import find_submodule from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, + register_loader_plugin_path, ) import openpype.hosts.houdini from openpype.hosts.houdini.api import lib @@ -53,7 +53,7 @@ def install(): pyblish.api.register_host("hpython") pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info("Installing callbacks ... ") diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index ae8b36f9d3..5cdc3ff4fd 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -22,8 +22,8 @@ from openpype.lib import ( from openpype.lib.path_tools import HostDirmap from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) from openpype.hosts.maya.lib import copy_workspace_mel from . import menu, lib @@ -57,7 +57,7 @@ def install(): pyblish.api.register_host("mayapy") pyblish.api.register_host("maya") - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) log.info(PUBLISH_PATH) @@ -186,7 +186,7 @@ def uninstall(): pyblish.api.deregister_host("mayapy") pyblish.api.deregister_host("maya") - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.deregister_plugin_path( avalon.api.InventoryAction, INVENTORY_PATH diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index cecd129eac..fd2e16b8d3 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -17,8 +17,8 @@ from openpype.api import ( from openpype.lib import register_event_callback from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) from openpype.tools.utils import host_tools @@ -103,7 +103,7 @@ def install(): log.info("Registering Nuke plug-ins..") pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) @@ -129,7 +129,7 @@ def uninstall(): log.info("Deregistering Nuke plug-ins..") pyblish.deregister_host("nuke") pyblish.api.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) pyblish.api.deregister_callback( diff --git a/openpype/hosts/photoshop/api/pipeline.py b/openpype/hosts/photoshop/api/pipeline.py index 85155f45d6..e814e1ca4d 100644 --- a/openpype/hosts/photoshop/api/pipeline.py +++ b/openpype/hosts/photoshop/api/pipeline.py @@ -9,8 +9,8 @@ from openpype.api import Logger from openpype.lib import register_event_callback from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) import openpype.hosts.photoshop @@ -72,7 +72,7 @@ def install(): pyblish.api.register_host("photoshop") pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) log.info(PUBLISH_PATH) @@ -85,7 +85,7 @@ def install(): def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py index 829794dd41..fa309e3503 100644 --- a/openpype/hosts/resolve/api/pipeline.py +++ b/openpype/hosts/resolve/api/pipeline.py @@ -11,8 +11,8 @@ from pyblish import api as pyblish from openpype.api import Logger from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) from . import lib from . import PLUGINS_DIR @@ -46,7 +46,7 @@ def install(): pyblish.register_plugin_path(PUBLISH_PATH) log.info("Registering DaVinci Resovle plug-ins..") - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.register_plugin_path(LegacyCreator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) @@ -71,7 +71,7 @@ def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) log.info("Deregistering DaVinci Resovle plug-ins..") - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH) avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 46981851f4..46c9d3a1dd 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -17,8 +17,8 @@ from openpype.api import get_current_project_settings from openpype.lib import register_event_callback from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) from .lib import ( @@ -81,7 +81,7 @@ def install(): pyblish.api.register_host("tvpaint") pyblish.api.register_plugin_path(PUBLISH_PATH) - register_loader_plugins_path(LOAD_PATH) + register_loader_plugin_path(LOAD_PATH) avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH) registered_callbacks = ( @@ -103,7 +103,7 @@ def uninstall(): log.info("OpenPype - Uninstalling TVPaint integration") pyblish.api.deregister_host("tvpaint") pyblish.api.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugins_path(LOAD_PATH) + deregister_loader_plugin_path(LOAD_PATH) avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH) diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index 7100ff3a83..9ec11b942d 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -9,8 +9,8 @@ from avalon import api from openpype.pipeline import ( LegacyCreator, - register_loader_plugins_path, - deregister_loader_plugins_path, + register_loader_plugin_path, + deregister_loader_plugin_path, ) from openpype.tools.utils import host_tools import openpype.hosts.unreal @@ -48,7 +48,7 @@ def install(): print("-=" * 40) logger.info("installing OpenPype for Unreal") pyblish.api.register_plugin_path(str(PUBLISH_PATH)) - register_loader_plugins_path(str(LOAD_PATH)) + register_loader_plugin_path(str(LOAD_PATH)) api.register_plugin_path(LegacyCreator, str(CREATE_PATH)) _register_callbacks() _register_events() @@ -57,7 +57,7 @@ def install(): def uninstall(): """Uninstall Unreal configuration for Avalon.""" pyblish.api.deregister_plugin_path(str(PUBLISH_PATH)) - deregister_loader_plugins_path(str(LOAD_PATH)) + deregister_loader_plugin_path(str(LOAD_PATH)) api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH)) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index 3ff3638a23..e204eea239 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -20,8 +20,8 @@ from .load import ( discover_loader_plugins, register_loader_plugin, - deregister_loader_plugins_path, - register_loader_plugins_path, + deregister_loader_plugin_path, + register_loader_plugin_path, deregister_loader_plugin, load_container, @@ -64,8 +64,8 @@ __all__ = ( "discover_loader_plugins", "register_loader_plugin", - "deregister_loader_plugins_path", - "register_loader_plugins_path", + "deregister_loader_plugin_path", + "register_loader_plugin_path", "deregister_loader_plugin", "load_container", diff --git a/openpype/pipeline/load/__init__.py b/openpype/pipeline/load/__init__.py index eac303c10c..6e7612d4c1 100644 --- a/openpype/pipeline/load/__init__.py +++ b/openpype/pipeline/load/__init__.py @@ -32,8 +32,8 @@ from .plugins import ( discover_loader_plugins, register_loader_plugin, - deregister_loader_plugins_path, - register_loader_plugins_path, + deregister_loader_plugin_path, + register_loader_plugin_path, deregister_loader_plugin, ) @@ -72,7 +72,7 @@ __all__ = ( "discover_loader_plugins", "register_loader_plugin", - "deregister_loader_plugins_path", - "register_loader_plugins_path", + "deregister_loader_plugin_path", + "register_loader_plugin_path", "deregister_loader_plugin", ) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index 5648236739..601ad3b258 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -113,13 +113,13 @@ def register_loader_plugin(plugin): return avalon.api.register_plugin(LoaderPlugin, plugin) -def deregister_loader_plugins_path(path): +def deregister_loader_plugin_path(path): import avalon.api avalon.api.deregister_plugin_path(LoaderPlugin, path) -def register_loader_plugins_path(path): +def register_loader_plugin_path(path): import avalon.api return avalon.api.register_plugin_path(LoaderPlugin, path) From 372d686024ffb53be947441be48dd014e5608feb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Mar 2022 14:24:36 +0100 Subject: [PATCH 60/67] Fix - Harmony creator issue Creator failed with 'str' object does not support item assignment --- openpype/hosts/harmony/api/TB_sceneOpened.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js index 5a3fe9ce82..6a403fa65e 100644 --- a/openpype/hosts/harmony/api/TB_sceneOpened.js +++ b/openpype/hosts/harmony/api/TB_sceneOpened.js @@ -272,8 +272,8 @@ function Client() { app.avalonClient.send( { - 'module': 'avalon.api', - 'method': 'emit', + 'module': 'openpype.lib', + 'method': 'emit_event', 'args': ['application.launched'] }, false); }; From 0f67d46ae0c7899f33b165600898e6743b1782e1 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 16 Mar 2022 03:37:11 +0000 Subject: [PATCH 61/67] [Automated] Bump version --- CHANGELOG.md | 17 ++++++++++++++++- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5acb161bf9..7790894b7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,23 @@ # Changelog +## [3.9.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.0...HEAD) + +**🐛 Bug fixes** + +- Harmony - fixed creator issue [\#2891](https://github.com/pypeclub/OpenPype/pull/2891) +- General: Remove forgotten use of avalon Creator [\#2885](https://github.com/pypeclub/OpenPype/pull/2885) +- General: Avoid circular import [\#2884](https://github.com/pypeclub/OpenPype/pull/2884) +- Fixes for attaching loaded containers \(\#2837\) [\#2874](https://github.com/pypeclub/OpenPype/pull/2874) + +**🔀 Refactored code** + +- General: Reduce style usage to OpenPype repository [\#2889](https://github.com/pypeclub/OpenPype/pull/2889) + ## [3.9.0](https://github.com/pypeclub/OpenPype/tree/3.9.0) (2022-03-14) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...3.9.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.0-nightly.9...3.9.0) **Deprecated:** diff --git a/openpype/version.py b/openpype/version.py index d2182ac7da..17e514642d 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.9.0" +__version__ = "3.9.1-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 681702560a..128d1cd615 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.9.0" # OpenPype +version = "3.9.1-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From b0a8809cbfceaa58a11171315d9579479885f02f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Mar 2022 10:19:22 +0100 Subject: [PATCH 62/67] dnxhd is adding bitrate in case of dnxhd profile --- openpype/lib/transcoding.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 6181ff6d13..6bab6a8160 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -705,6 +705,12 @@ def _ffmpeg_dnxhd_codec_args(stream_data, source_ffmpeg_cmd): profile = stream_data.get("profile") or "" # Lower profile and replace space with underscore cleaned_profile = profile.lower().replace(" ", "_") + + # TODO validate this statement + # Looks like using 'dnxhd' profile must have set bit rate and in that case + # should be used bitrate from source. + # - related attributes 'bit_rate_defined', 'bit_rate_must_be_defined' + bit_rate_must_be_defined = True dnx_profiles = { "dnxhd", "dnxhr_lb", @@ -714,6 +720,8 @@ def _ffmpeg_dnxhd_codec_args(stream_data, source_ffmpeg_cmd): "dnxhr_444" } if cleaned_profile in dnx_profiles: + if cleaned_profile != "dnxhd": + bit_rate_must_be_defined = False output.extend(["-profile:v", cleaned_profile]) pix_fmt = stream_data.get("pix_fmt") @@ -721,15 +729,28 @@ def _ffmpeg_dnxhd_codec_args(stream_data, source_ffmpeg_cmd): output.extend(["-pix_fmt", pix_fmt]) # Use arguments from source if are available source arguments + bit_rate_defined = False if source_ffmpeg_cmd: - copy_args = ( - "-b:v", "-vb", - ) + # Define bitrate arguments + bit_rate_args = ("-b:v", "-vb",) + # Seprate the two variables in case something else should be copied + # from source command + copy_args = [] + copy_args.extend(bit_rate_args) + args = source_ffmpeg_cmd.split(" ") for idx, arg in enumerate(args): if arg in copy_args: + if arg in bit_rate_args: + bit_rate_defined = True output.extend([arg, args[idx + 1]]) + # Add bitrate if needed + if bit_rate_must_be_defined and not bit_rate_defined: + src_bit_rate = stream_data.get("bit_rate") + if src_bit_rate: + output.extend(["-b:v", src_bit_rate]) + output.extend(["-g", "1"]) return output From fedde965c1ab77093b915231db34fad2831e4a0f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Mar 2022 10:19:38 +0100 Subject: [PATCH 63/67] pass source ffmpeg argument if available in extract review slate --- openpype/plugins/publish/extract_review_slate.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py index 460d546340..505ae75169 100644 --- a/openpype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -360,11 +360,14 @@ class ExtractReviewSlate(openpype.api.Extractor): ) return codec_args + source_ffmpeg_cmd = repre.get("ffmpeg_cmd") codec_args.extend( - get_ffmpeg_format_args(ffprobe_data) + get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd) ) codec_args.extend( - get_ffmpeg_codec_args(ffprobe_data, logger=self.log) + get_ffmpeg_codec_args( + ffprobe_data, source_ffmpeg_cmd, logger=self.log + ) ) return codec_args From 256286c55fb22dc1bfad6032c4d10b75be1ce274 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Mar 2022 10:37:51 +0100 Subject: [PATCH 64/67] fix tooltip of buttons in scene inventory --- openpype/tools/sceneinventory/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py index 83e4435015..b40fbb69e4 100644 --- a/openpype/tools/sceneinventory/window.py +++ b/openpype/tools/sceneinventory/window.py @@ -61,7 +61,7 @@ class SceneInventoryWindow(QtWidgets.QDialog): icon = qtawesome.icon("fa.refresh", color="white") refresh_button = QtWidgets.QPushButton(self) - update_all_button.setToolTip("Refresh") + refresh_button.setToolTip("Refresh") refresh_button.setIcon(icon) control_layout = QtWidgets.QHBoxLayout() From f130e30a2cd6188a265513663c3548268760c9de Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Mar 2022 10:45:11 +0100 Subject: [PATCH 65/67] fix missing import of 'get_repres_contexts' --- openpype/pipeline/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py index e204eea239..26970e4edc 100644 --- a/openpype/pipeline/__init__.py +++ b/openpype/pipeline/__init__.py @@ -31,6 +31,7 @@ from .load import ( loaders_from_representation, get_representation_path, + get_repres_contexts, ) from .publish import ( @@ -75,6 +76,7 @@ __all__ = ( "loaders_from_representation", "get_representation_path", + "get_repres_contexts", # --- Publish --- "PublishValidationError", From 8b3d32c5b26733cf2da44cedc164f478f8045fca Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 16 Mar 2022 15:42:49 +0100 Subject: [PATCH 66/67] global: fixing order of output resolution flow --- openpype/plugins/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index f046194c0d..cbe1924408 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -1188,8 +1188,8 @@ class ExtractReview(pyblish.api.InstancePlugin): # NOTE Setting only one of `width` or `heigth` is not allowed # - settings value can't have None but has value of 0 - output_width = output_width or output_def.get("width") or None - output_height = output_height or output_def.get("height") or None + output_width = output_def.get("width") or output_width or None + output_height = output_def.get("height") or output_height or None # Overscal color overscan_color_value = "black" From 2563a7ce607db3e2eddd83a70832a02b6e6065b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 16 Mar 2022 16:36:46 +0100 Subject: [PATCH 67/67] Update openpype/hosts/flame/plugins/publish/extract_subset_resources.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/extract_subset_resources.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 5c3aed9672..3b1466925f 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -218,16 +218,10 @@ class ExtractSubsetResources(openpype.api.Extractor): # imagesequence as list if ( # first check if path in files is not mov extension - next( - # iter all paths in files - # return only .mov positive test - iter([ - f for f in files - if ".mov" in os.path.splitext(f)[-1] - ]), - # if nothing return default - None - ) + [ + f for f in files + if os.path.splitext(f)[-1] == ".mov" + ] # then try if thumbnail is not in unique name or unique_name == "thumbnail" ):