From c14b8323dbc40cc4bf41712c52642e75ae391546 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Fri, 6 May 2022 17:06:32 +0200 Subject: [PATCH 01/57] Add menu quad in hiero --- .../defaults/project_settings/hiero.json | 32 +++++++++++++++++++ .../projects_schema/schema_project_hiero.json | 4 +++ .../common/scriptsmenu/launchfornuke.py | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 1dff3aac51..eb75aff6c0 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,4 +1,36 @@ { + "ext_mapping": { + "model": "hrox", + "mayaAscii": "hrox", + "camera": "hrox", + "rig": "hrox", + "workfile": "hrox", + "yetiRig": "hrox" + }, + "heiro-dirmap": { + "enabled": false, + "paths": { + "source-path": [], + "destination-path": [] + } + }, + "scriptsmenu": { + "name": "OpenPype Tools", + "definition": [ + { + "type": "action", + "command": "import openpype.hosts.hiero.api.commands as hiero; hiero.edit_shader_definitions()", + "sourcetype": "python", + "title": "Edit shader name definitions", + "tooltip": "Edit shader name definitions used in validation and renaming.", + "tags": [ + "pipeline", + "shader" + ] + } + ] + }, + "create": { "CreateShotClip": { "hierarchy": "{folder}/{sequence}", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index f717eff7dd..39721c641a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -206,6 +206,10 @@ { "type": "schema", "name": "schema_publish_gui_filter" + }, + { + "type": "schema", + "name": "schema_maya_scriptsmenu" } ] } diff --git a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py index 72302a79a6..4f65a8e3ae 100644 --- a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py +++ b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py @@ -6,7 +6,7 @@ def _nuke_main_window(): """Return Nuke's main window""" for obj in QtWidgets.QApplication.topLevelWidgets(): if (obj.inherits('QMainWindow') and - obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): + obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): return obj raise RuntimeError('Could not find Nuke MainWindow instance') From 174c04a7f26dbe3c41c8c459222f97135fb4268d Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Fri, 6 May 2022 17:16:12 +0200 Subject: [PATCH 02/57] menu hiero --- .../common/scriptsmenu/launchforhiero.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 openpype/vendor/python/common/scriptsmenu/launchforhiero.py diff --git a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py new file mode 100644 index 0000000000..8c3aa35126 --- /dev/null +++ b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py @@ -0,0 +1,84 @@ +import logging + +import scriptsmenu +from .vendor.Qt import QtWidgets + +log = logging.getLogger(__name__) + + +def _hiero_main_window(): + """Return Nuke's main window""" + for obj in QtWidgets.QApplication.topLevelWidgets(): + if (obj.inherits('QMainWindow') and + obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): + return obj + raise RuntimeError('Could not find HieroWindow instance') + + +def _hiero_main_menubar(): + """Retrieve the main menubar of the Nuke window""" + hiero_window = _hiero_main_window() + menubar = [i for i in hiero_window.children() if isinstance( + i, + QtWidgets.QMenuBar + )] + + assert len(menubar) == 1, "Error, could not find menu bar!" + return menubar[0] + + +def find_scripts_menu(title, parent): + """ + Check if the menu exists with the given title in the parent + + Args: + title (str): the title name of the scripts menu + + parent (QtWidgets.QMenuBar): the menubar to check + + Returns: + QtWidgets.QMenu or None + + """ + + menu = None + search = [i for i in parent.children() if + isinstance(i, scriptsmenu.ScriptsMenu) + and i.title() == title] + if search: + assert len(search) < 2, ("Multiple instances of menu '{}' " + "in menu bar".format(title)) + menu = search[0] + + return menu + + +def main(title="Scripts", parent=None, objectName=None): + """Build the main scripts menu in Maya + + Args: + title (str): name of the menu in the application + + parent (QtWidgets.QtMenuBar): the parent object for the menu + + objectName (str): custom objectName for scripts menu + + Returns: + scriptsmenu.ScriptsMenu instance + + """ + hieromainbar = parent or _hiero_main_menubar() + try: + # check menu already exists + menu = find_scripts_menu(title, hieromainbar) + if not menu: + log.info("Attempting to build menu ...") + object_name = objectName or title.lower() + menu = scriptsmenu.ScriptsMenu(title=title, + parent=hieromainbar, + objectName=object_name) + except Exception as e: + log.error(e) + return + + return menu From 3ea514f029085653a104b37dcae8b6d2cd6bf9cc Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Mon, 23 May 2022 15:11:56 +0200 Subject: [PATCH 03/57] add script menu --- openpype/hosts/hiero/api/menu.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index e262abec00..d4e62c9e8a 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -9,6 +9,7 @@ from openpype.pipeline import legacy_io from openpype.tools.utils import host_tools from . import tags +from openpype.settings import get_project_settings log = Logger.get_logger(__name__) @@ -41,6 +42,7 @@ def menu_install(): Installing menu into Hiero """ + print("YOLOOOOOOOOOOOOOOOOOO") from Qt import QtGui from . import ( publish, launch_workfiles_app, reload_config, @@ -138,3 +140,32 @@ def menu_install(): exeprimental_action.triggered.connect( lambda: host_tools.show_experimental_tools_dialog(parent=main_window) ) + + +def add_scripts_menu(): + try: + from scriptsmenu import launchforhiero + except ImportError: + log.warning( + "Skipping studio.menu install, because " + "'scriptsmenu' module seems unavailable." + ) + return + + # load configuration of custom menu + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + config = project_settings["hiero"]["scriptsmenu"]["definition"] + _menu = project_settings["hiero"]["scriptsmenu"]["name"] + + if not config: + log.warning("Skipping studio menu, no definition found.") + return + + # run the launcher for Maya menu + studio_menu = launchforhiero.main(title=_menu.title()) + + # apply configuration + studio_menu.build_from_configuration(studio_menu, config) + + +add_scripts_menu() From 4c9cd18f1cce331d4e434c531775450cab5b0b71 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Mon, 23 May 2022 15:13:36 +0200 Subject: [PATCH 04/57] clen print --- openpype/hosts/hiero/api/menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index d4e62c9e8a..9e999da2f6 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -42,7 +42,7 @@ def menu_install(): Installing menu into Hiero """ - print("YOLOOOOOOOOOOOOOOOOOO") + from Qt import QtGui from . import ( publish, launch_workfiles_app, reload_config, From 0e3607805925eaa1fc7ad9bc0e17f0144081a71d Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Mon, 23 May 2022 15:47:29 +0200 Subject: [PATCH 05/57] nothing change --- openpype/settings/defaults/project_settings/hiero.json | 2 +- openpype/vendor/python/common/scriptsmenu/launchforhiero.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index eb75aff6c0..cec4bca8fd 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -7,7 +7,7 @@ "workfile": "hrox", "yetiRig": "hrox" }, - "heiro-dirmap": { + "hiero-dirmap": { "enabled": false, "paths": { "source-path": [], diff --git a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py index 8c3aa35126..3f8e726083 100644 --- a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py +++ b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py @@ -7,6 +7,7 @@ log = logging.getLogger(__name__) def _hiero_main_window(): + print("YEAAAAAAAAAAAAAAAAAAAH") """Return Nuke's main window""" for obj in QtWidgets.QApplication.topLevelWidgets(): if (obj.inherits('QMainWindow') and From 6ee781c8a6b4701f6166a877eda4e43b9a76e454 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 17:00:53 +0200 Subject: [PATCH 06/57] change launcher for api folder --- openpype/hosts/hiero/api/launchforhiero.py | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 openpype/hosts/hiero/api/launchforhiero.py diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py new file mode 100644 index 0000000000..3f8e726083 --- /dev/null +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -0,0 +1,85 @@ +import logging + +import scriptsmenu +from .vendor.Qt import QtWidgets + +log = logging.getLogger(__name__) + + +def _hiero_main_window(): + print("YEAAAAAAAAAAAAAAAAAAAH") + """Return Nuke's main window""" + for obj in QtWidgets.QApplication.topLevelWidgets(): + if (obj.inherits('QMainWindow') and + obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): + return obj + raise RuntimeError('Could not find HieroWindow instance') + + +def _hiero_main_menubar(): + """Retrieve the main menubar of the Nuke window""" + hiero_window = _hiero_main_window() + menubar = [i for i in hiero_window.children() if isinstance( + i, + QtWidgets.QMenuBar + )] + + assert len(menubar) == 1, "Error, could not find menu bar!" + return menubar[0] + + +def find_scripts_menu(title, parent): + """ + Check if the menu exists with the given title in the parent + + Args: + title (str): the title name of the scripts menu + + parent (QtWidgets.QMenuBar): the menubar to check + + Returns: + QtWidgets.QMenu or None + + """ + + menu = None + search = [i for i in parent.children() if + isinstance(i, scriptsmenu.ScriptsMenu) + and i.title() == title] + if search: + assert len(search) < 2, ("Multiple instances of menu '{}' " + "in menu bar".format(title)) + menu = search[0] + + return menu + + +def main(title="Scripts", parent=None, objectName=None): + """Build the main scripts menu in Maya + + Args: + title (str): name of the menu in the application + + parent (QtWidgets.QtMenuBar): the parent object for the menu + + objectName (str): custom objectName for scripts menu + + Returns: + scriptsmenu.ScriptsMenu instance + + """ + hieromainbar = parent or _hiero_main_menubar() + try: + # check menu already exists + menu = find_scripts_menu(title, hieromainbar) + if not menu: + log.info("Attempting to build menu ...") + object_name = objectName or title.lower() + menu = scriptsmenu.ScriptsMenu(title=title, + parent=hieromainbar, + objectName=object_name) + except Exception as e: + log.error(e) + return + + return menu From 35a2c7dd13ddd3f0d780c60c0e492de676e92dcc Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 17:27:33 +0200 Subject: [PATCH 07/57] revert vendor files --- .../common/scriptsmenu/launchforhiero.py | 85 ------------------- 1 file changed, 85 deletions(-) delete mode 100644 openpype/vendor/python/common/scriptsmenu/launchforhiero.py diff --git a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py b/openpype/vendor/python/common/scriptsmenu/launchforhiero.py deleted file mode 100644 index 3f8e726083..0000000000 --- a/openpype/vendor/python/common/scriptsmenu/launchforhiero.py +++ /dev/null @@ -1,85 +0,0 @@ -import logging - -import scriptsmenu -from .vendor.Qt import QtWidgets - -log = logging.getLogger(__name__) - - -def _hiero_main_window(): - print("YEAAAAAAAAAAAAAAAAAAAH") - """Return Nuke's main window""" - for obj in QtWidgets.QApplication.topLevelWidgets(): - if (obj.inherits('QMainWindow') and - obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): - return obj - raise RuntimeError('Could not find HieroWindow instance') - - -def _hiero_main_menubar(): - """Retrieve the main menubar of the Nuke window""" - hiero_window = _hiero_main_window() - menubar = [i for i in hiero_window.children() if isinstance( - i, - QtWidgets.QMenuBar - )] - - assert len(menubar) == 1, "Error, could not find menu bar!" - return menubar[0] - - -def find_scripts_menu(title, parent): - """ - Check if the menu exists with the given title in the parent - - Args: - title (str): the title name of the scripts menu - - parent (QtWidgets.QMenuBar): the menubar to check - - Returns: - QtWidgets.QMenu or None - - """ - - menu = None - search = [i for i in parent.children() if - isinstance(i, scriptsmenu.ScriptsMenu) - and i.title() == title] - if search: - assert len(search) < 2, ("Multiple instances of menu '{}' " - "in menu bar".format(title)) - menu = search[0] - - return menu - - -def main(title="Scripts", parent=None, objectName=None): - """Build the main scripts menu in Maya - - Args: - title (str): name of the menu in the application - - parent (QtWidgets.QtMenuBar): the parent object for the menu - - objectName (str): custom objectName for scripts menu - - Returns: - scriptsmenu.ScriptsMenu instance - - """ - hieromainbar = parent or _hiero_main_menubar() - try: - # check menu already exists - menu = find_scripts_menu(title, hieromainbar) - if not menu: - log.info("Attempting to build menu ...") - object_name = objectName or title.lower() - menu = scriptsmenu.ScriptsMenu(title=title, - parent=hieromainbar, - objectName=object_name) - except Exception as e: - log.error(e) - return - - return menu From 4cf1edc3c39603be582d4e3a5013687d24718bf2 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 17:31:00 +0200 Subject: [PATCH 08/57] revert vendor files --- openpype/vendor/python/common/scriptsmenu/launchfornuke.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py index 4f65a8e3ae..72302a79a6 100644 --- a/openpype/vendor/python/common/scriptsmenu/launchfornuke.py +++ b/openpype/vendor/python/common/scriptsmenu/launchfornuke.py @@ -6,7 +6,7 @@ def _nuke_main_window(): """Return Nuke's main window""" for obj in QtWidgets.QApplication.topLevelWidgets(): if (obj.inherits('QMainWindow') and - obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): + obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): return obj raise RuntimeError('Could not find Nuke MainWindow instance') From 5b83675a9b0684517d5bf0c0e5b47365bb1e2e67 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 17:54:18 +0200 Subject: [PATCH 09/57] change import in launch script --- openpype/hosts/hiero/api/launchforhiero.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py index 3f8e726083..3d9328dc34 100644 --- a/openpype/hosts/hiero/api/launchforhiero.py +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -1,14 +1,13 @@ import logging import scriptsmenu -from .vendor.Qt import QtWidgets +from .api.Qt import QtWidgets log = logging.getLogger(__name__) def _hiero_main_window(): - print("YEAAAAAAAAAAAAAAAAAAAH") - """Return Nuke's main window""" + """Return Hiero's main window""" for obj in QtWidgets.QApplication.topLevelWidgets(): if (obj.inherits('QMainWindow') and obj.metaObject().className() == 'Foundry::UI::DockMainWindow'): @@ -17,7 +16,7 @@ def _hiero_main_window(): def _hiero_main_menubar(): - """Retrieve the main menubar of the Nuke window""" + """Retrieve the main menubar of the Hiero window""" hiero_window = _hiero_main_window() menubar = [i for i in hiero_window.children() if isinstance( i, @@ -55,7 +54,7 @@ def find_scripts_menu(title, parent): def main(title="Scripts", parent=None, objectName=None): - """Build the main scripts menu in Maya + """Build the main scripts menu in Hiero Args: title (str): name of the menu in the application From 6cd20e110f6613b1f8c246eb0b489852f3d9507e Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Tue, 7 Jun 2022 18:03:05 +0200 Subject: [PATCH 10/57] change path launch script --- openpype/hosts/hiero/api/launchforhiero.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py index 3d9328dc34..8b63b47c10 100644 --- a/openpype/hosts/hiero/api/launchforhiero.py +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -1,7 +1,7 @@ import logging import scriptsmenu -from .api.Qt import QtWidgets +from .openpype.vendor.python.common.scriptsmenu.vendor.Qt import QtWidgets log = logging.getLogger(__name__) From 6741b9e6336eb4a1b5d1fc837f021c4d45942b5d Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Wed, 8 Jun 2022 17:33:10 +0200 Subject: [PATCH 11/57] modif launch menu hiero --- openpype/hosts/hiero/api/launchforhiero.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py index 8b63b47c10..5c230eb9fe 100644 --- a/openpype/hosts/hiero/api/launchforhiero.py +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -1,7 +1,8 @@ import logging -import scriptsmenu -from .openpype.vendor.python.common.scriptsmenu.vendor.Qt import QtWidgets +from openpype.vendor.python.common.scriptsmenu import scriptsmenu +from openpype.vendor.python.common.scriptsmenu.vendor.Qt import QtWidgets + log = logging.getLogger(__name__) From 373524b9dfd40592be4904694681c2ab508b5653 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Wed, 8 Jun 2022 17:33:40 +0200 Subject: [PATCH 12/57] modif menu hiero --- openpype/hosts/hiero/api/menu.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index 9e999da2f6..412f08272e 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -144,8 +144,9 @@ def menu_install(): def add_scripts_menu(): try: - from scriptsmenu import launchforhiero + from . import launchforhiero except ImportError: + log.warning( "Skipping studio.menu install, because " "'scriptsmenu' module seems unavailable." From 1eb3a55cc3610e7f12b1fa28e61deb942ee57cf4 Mon Sep 17 00:00:00 2001 From: Ophelie Abbonato Date: Wed, 15 Jun 2022 09:58:56 +0200 Subject: [PATCH 13/57] change name string --- .../entities/schemas/projects_schema/schema_project_hiero.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index 39721c641a..3108d2197e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -209,7 +209,7 @@ }, { "type": "schema", - "name": "schema_maya_scriptsmenu" + "name": "schema_scriptsmenu" } ] } From 05e709ec065e7b7fe422e49225f5c2866028162b Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 28 Jun 2022 16:41:13 +0200 Subject: [PATCH 14/57] create default action in custom menu redirecting to openpype hiero doc --- openpype/settings/defaults/project_settings/hiero.json | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index cec4bca8fd..3b0127c24a 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -19,14 +19,10 @@ "definition": [ { "type": "action", - "command": "import openpype.hosts.hiero.api.commands as hiero; hiero.edit_shader_definitions()", "sourcetype": "python", - "title": "Edit shader name definitions", - "tooltip": "Edit shader name definitions used in validation and renaming.", - "tags": [ - "pipeline", - "shader" - ] + "title": "OpenPype Docs", + "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_hiero')", + "tooltip": "Open the OpenPype Hiero user doc page" } ] }, From 397a3f7fc4ebfe02ca033f775208301432e9db38 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 28 Jun 2022 17:21:09 +0200 Subject: [PATCH 15/57] add docs for hiero custom menu --- website/docs/admin_hosts_hiero.md | 9 +++++++++ website/docs/assets/hiero-admin_scriptsmenu.png | Bin 0 -> 23368 bytes website/sidebars.js | 1 + 3 files changed, 10 insertions(+) create mode 100644 website/docs/admin_hosts_hiero.md create mode 100644 website/docs/assets/hiero-admin_scriptsmenu.png diff --git a/website/docs/admin_hosts_hiero.md b/website/docs/admin_hosts_hiero.md new file mode 100644 index 0000000000..b75d8dee7d --- /dev/null +++ b/website/docs/admin_hosts_hiero.md @@ -0,0 +1,9 @@ +--- +id: admin_hosts_hiero +title: Hiero +sidebar_label: Hiero +--- + +## Custom Menu +You can add your custom tools menu into Hiero by extending definitions in **Hiero -> Scripts Menu Definition**. +![Custom menu definition](assets/hiero-admin_scriptsmenu.png) diff --git a/website/docs/assets/hiero-admin_scriptsmenu.png b/website/docs/assets/hiero-admin_scriptsmenu.png new file mode 100644 index 0000000000000000000000000000000000000000..6de136a434e19f78fe871be147a8e5580f3af552 GIT binary patch literal 23368 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 J3MGrM{{f4=o! Date: Tue, 28 Jun 2022 17:26:54 +0200 Subject: [PATCH 16/57] refactor imports --- openpype/hosts/hiero/api/launchforhiero.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/hiero/api/launchforhiero.py b/openpype/hosts/hiero/api/launchforhiero.py index 5c230eb9fe..5f7dbe23c9 100644 --- a/openpype/hosts/hiero/api/launchforhiero.py +++ b/openpype/hosts/hiero/api/launchforhiero.py @@ -1,7 +1,7 @@ import logging -from openpype.vendor.python.common.scriptsmenu import scriptsmenu -from openpype.vendor.python.common.scriptsmenu.vendor.Qt import QtWidgets +from scriptsmenu import scriptsmenu +from Qt import QtWidgets log = logging.getLogger(__name__) From 6d894e8ef0291a93a25719c8de45cc79090544c8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 15:00:17 +0200 Subject: [PATCH 17/57] Reduce code duplication - merge animation + pointcache extractor logic --- .../maya/plugins/publish/extract_animation.py | 111 ------------------ .../plugins/publish/extract_pointcache.py | 38 ++++-- 2 files changed, 30 insertions(+), 119 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/extract_animation.py diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py deleted file mode 100644 index b0beb5968e..0000000000 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ /dev/null @@ -1,111 +0,0 @@ -import os - -from maya import cmds - -import openpype.api -from openpype.hosts.maya.api.lib import ( - extract_alembic, - suspended_refresh, - maintained_selection, - iter_visible_in_frame_range -) - - -class ExtractAnimation(openpype.api.Extractor): - """Produce an alembic of just point positions and normals. - - Positions and normals, uvs, creases are preserved, but nothing more, - for plain and predictable point caches. - - Plugin can run locally or remotely (on a farm - if instance is marked with - "farm" it will be skipped in local processing, but processed on farm) - """ - - label = "Extract Animation" - hosts = ["maya"] - families = ["animation"] - targets = ["local", "remote"] - - def process(self, instance): - if instance.data.get("farm"): - self.log.debug("Should be processed on farm, skipping.") - return - - # Collect the out set nodes - out_sets = [node for node in instance if node.endswith("out_SET")] - if len(out_sets) != 1: - raise RuntimeError("Couldn't find exactly one out_SET: " - "{0}".format(out_sets)) - out_set = out_sets[0] - roots = cmds.sets(out_set, query=True) - - # Include all descendants - nodes = roots + cmds.listRelatives(roots, - allDescendents=True, - fullPath=True) or [] - - # Collect the start and end including handles - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] - - self.log.info("Extracting animation..") - dirname = self.staging_dir(instance) - - parent_dir = self.staging_dir(instance) - filename = "{name}.abc".format(**instance.data) - path = os.path.join(parent_dir, filename) - - options = { - "step": instance.data.get("step", 1.0) or 1.0, - "attr": ["cbId"], - "writeVisibility": True, - "writeCreases": True, - "uvWrite": True, - "selection": True, - "worldSpace": instance.data.get("worldSpace", True), - "writeColorSets": instance.data.get("writeColorSets", False), - "writeFaceSets": instance.data.get("writeFaceSets", False) - } - - if not instance.data.get("includeParentHierarchy", True): - # Set the root nodes if we don't want to include parents - # The roots are to be considered the ones that are the actual - # direct members of the set - options["root"] = roots - - if int(cmds.about(version=True)) >= 2017: - # Since Maya 2017 alembic supports multiple uv sets - write them. - options["writeUVSets"] = True - - if instance.data.get("visibleOnly", False): - # If we only want to include nodes that are visible in the frame - # range then we need to do our own check. Alembic's `visibleOnly` - # flag does not filter out those that are only hidden on some - # frames as it counts "animated" or "connected" visibilities as - # if it's always visible. - nodes = list(iter_visible_in_frame_range(nodes, - start=start, - end=end)) - - with suspended_refresh(): - with maintained_selection(): - cmds.select(nodes, noExpand=True) - extract_alembic(file=path, - startFrame=float(start), - endFrame=float(end), - **options) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, - "stagingDir": dirname, - } - instance.data["representations"].append(representation) - - instance.context.data["cleanupFullPaths"].append(path) - - self.log.info("Extracted {} to {}".format(instance, dirname)) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 7aa3aaee2a..a7ba5b3745 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -33,7 +33,7 @@ class ExtractAlembic(openpype.api.Extractor): self.log.debug("Should be processed on farm, skipping.") return - nodes = instance[:] + nodes, roots = self.get_members_and_roots(instance) # Collect the start and end including handles start = float(instance.data.get("frameStartHandle", 1)) @@ -46,10 +46,6 @@ class ExtractAlembic(openpype.api.Extractor): attr_prefixes = instance.data.get("attrPrefix", "").split(";") attr_prefixes = [value for value in attr_prefixes if value.strip()] - # Get extra export arguments - writeColorSets = instance.data.get("writeColorSets", False) - writeFaceSets = instance.data.get("writeFaceSets", False) - self.log.info("Extracting pointcache..") dirname = self.staging_dir(instance) @@ -63,8 +59,8 @@ class ExtractAlembic(openpype.api.Extractor): "attrPrefix": attr_prefixes, "writeVisibility": True, "writeCreases": True, - "writeColorSets": writeColorSets, - "writeFaceSets": writeFaceSets, + "writeColorSets": instance.data.get("writeColorSets", False), + "writeFaceSets": instance.data.get("writeFaceSets", False), "uvWrite": True, "selection": True, "worldSpace": instance.data.get("worldSpace", True) @@ -74,7 +70,7 @@ class ExtractAlembic(openpype.api.Extractor): # Set the root nodes if we don't want to include parents # The roots are to be considered the ones that are the actual # direct members of the set - options["root"] = instance.data.get("setMembers") + options["root"] = roots if int(cmds.about(version=True)) >= 2017: # Since Maya 2017 alembic supports multiple uv sets - write them. @@ -112,3 +108,29 @@ class ExtractAlembic(openpype.api.Extractor): instance.context.data["cleanupFullPaths"].append(path) self.log.info("Extracted {} to {}".format(instance, dirname)) + + def get_members_and_roots(self, instance): + return instance[:], instance.data.get("setMembers") + + +class ExtractAnimation(ExtractAlembic): + label = "Extract Animation" + families = ["animation"] + + def get_members_and_roots(self, instance): + + # Collect the out set nodes + out_sets = [node for node in instance if node.endswith("out_SET")] + if len(out_sets) != 1: + raise RuntimeError("Couldn't find exactly one out_SET: " + "{0}".format(out_sets)) + out_set = out_sets[0] + roots = cmds.sets(out_set, query=True) + + # Include all descendants + nodes = roots + cmds.listRelatives(roots, + allDescendents=True, + fullPath=True) or [] + + return nodes, roots + From a9462ac4ed89db38f0895d6ce14190c44ee77cb4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jun 2022 22:20:02 +0200 Subject: [PATCH 18/57] Nuke: fixing metadata slate TC difference --- .../nuke/plugins/publish/extract_slate_frame.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 6d930d358d..6997180c9f 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -152,6 +152,7 @@ class ExtractSlateFrame(openpype.api.Extractor): self.log.debug("__ first_frame: {}".format(first_frame)) self.log.debug("__ slate_first_frame: {}".format(slate_first_frame)) + above_slate_node = slate_node.dependencies().pop() # fallback if files does not exists if self._check_frames_exists(instance): # Read node @@ -164,8 +165,16 @@ class ExtractSlateFrame(openpype.api.Extractor): r_node["colorspace"].setValue(instance.data["colorspace"]) previous_node = r_node temporary_nodes = [previous_node] + + # adding copy metadata node for correct frame metadata + cm_node = nuke.createNode("CopyMetaData") + cm_node.setInput(0, previous_node) + cm_node.setInput(1, above_slate_node) + previous_node = cm_node + temporary_nodes.append(cm_node) + else: - previous_node = slate_node.dependencies().pop() + previous_node = above_slate_node temporary_nodes = [] # only create colorspace baking if toggled on @@ -221,8 +230,8 @@ class ExtractSlateFrame(openpype.api.Extractor): write_node.name(), int(slate_first_frame), int(slate_first_frame)) # Clean up - for node in temporary_nodes: - nuke.delete(node) + # for node in temporary_nodes: + # nuke.delete(node) def _render_slate_to_sequence(self, instance): # set slate frame From 7dc7bde29334a0debe738af6ef6f07d374f40bcf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:48:33 +0200 Subject: [PATCH 19/57] subset documents can be queried based on combination of asset id and subset names --- openpype/client/entities.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 28cd994254..5e242ea180 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -381,6 +381,7 @@ def get_subsets( subset_ids=None, subset_names=None, asset_ids=None, + names_by_asset_ids=None, archived=False, fields=None ): @@ -396,6 +397,9 @@ def get_subsets( Filter ignored if 'None' is passed. asset_ids (list[str|ObjectId]): Asset ids under which should look for the subsets. Filter ignored if 'None' is passed. + names_by_asset_ids (dict[ObjectId, list[str]]): Complex filtering + using asset ids and list of subset names under the asset. + archived (bool): Look for archived subsets too. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -429,6 +433,18 @@ def get_subsets( return [] query_filter["name"] = {"$in": list(subset_names)} + if names_by_asset_ids is not None: + or_query = [] + for asset_id, names in names_by_asset_ids.items(): + if asset_id and names: + or_query.append({ + "parent": _convert_id(asset_id), + "name": {"$in": list(names)} + }) + if not or_query: + return [] + query_filter["$or"] = or_query + conn = _get_project_connection(project_name) return conn.find(query_filter, _prepare_fields(fields)) From de4147ef93c2c45631b72664d067d7c070887f48 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:49:06 +0200 Subject: [PATCH 20/57] get last version does not do double query if only _id, name or parent are requested --- openpype/client/entities.py | 50 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 5e242ea180..aed465c46f 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -755,7 +755,10 @@ def get_last_versions(project_name, subset_ids, fields=None): """Latest versions for entered subset_ids. Args: + project_name (str): Name of project where to look for queried entities. subset_ids (list): List of subset ids. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. Returns: dict[ObjectId, int]: Key is subset id and value is last version name. @@ -765,7 +768,34 @@ def get_last_versions(project_name, subset_ids, fields=None): if not subset_ids: return {} - _pipeline = [ + if fields is not None: + fields = list(fields) + if not fields: + return {} + + # Avoid double query if only name and _id are requested + name_needed = False + limit_query = False + if fields: + fields_s = set(fields) + if "name" in fields_s: + name_needed = True + fields_s.remove("name") + + for field in ("_id", "parent"): + if field in fields_s: + fields_s.remove(field) + limit_query = len(fields_s) == 0 + + group_item = { + "_id": "$parent", + "_version_id": {"$last": "$_id"} + } + # Add name if name is needed (only for limit query) + if name_needed: + group_item["name"] = {"$last": "$name"} + + aggregation_pipeline = [ # Find all versions of those subsets {"$match": { "type": "version", @@ -774,16 +804,24 @@ def get_last_versions(project_name, subset_ids, fields=None): # Sorting versions all together {"$sort": {"name": 1}}, # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"} - }} + {"$group": group_item} ] conn = _get_project_connection(project_name) + aggregate_result = conn.aggregate(aggregation_pipeline) + if limit_query: + output = {} + for item in aggregate_result: + subset_id = item["_id"] + item_data = {"_id": item["_version_id"], "parent": subset_id} + if name_needed: + item_data["name"] = item["name"] + output[subset_id] = item_data + return output + version_ids = [ doc["_version_id"] - for doc in conn.aggregate(_pipeline) + for doc in aggregate_result ] fields = _prepare_fields(fields, ["parent"]) From f5cceb3e056e71c2204df75b1eb6b87598f161a1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:49:39 +0200 Subject: [PATCH 21/57] use query functions in delete old versions --- openpype/plugins/load/delete_old_versions.py | 37 +++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 7465f53855..039893aa54 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -8,6 +8,7 @@ import ftrack_api import qargparse from Qt import QtWidgets, QtCore +from openpype.client import get_versions, get_representations from openpype import style from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype.lib import StringTemplate @@ -197,18 +198,10 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): def get_data(self, context, versions_count): subset = context["subset"] asset = context["asset"] - anatomy = Anatomy(context["project"]["name"]) + project_name = context["project"]["name"] + anatomy = Anatomy(project_name) - self.dbcon = AvalonMongoDB() - self.dbcon.Session["AVALON_PROJECT"] = context["project"]["name"] - self.dbcon.install() - - versions = list( - self.dbcon.find({ - "type": "version", - "parent": {"$in": [subset["_id"]]} - }) - ) + versions = list(get_versions(project_name, subset_ids=[subset["_id"]])) versions_by_parent = collections.defaultdict(list) for ent in versions: @@ -267,10 +260,9 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): print(msg) return - repres = list(self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids} - })) + repres = list(get_representations( + project_name, version_ids=version_ids + )) self.log.debug( "Collected representations to remove ({})".format(len(repres)) @@ -329,7 +321,7 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): return data - def main(self, data, remove_publish_folder): + def main(self, project_name, data, remove_publish_folder): # Size of files. size = 0 if not data: @@ -366,9 +358,11 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): )) if mongo_changes_bulk: - self.dbcon.bulk_write(mongo_changes_bulk) - - self.dbcon.uninstall() + dbcon = AvalonMongoDB() + dbcon.Session["AVALON_PROJECT"] = project_name + dbcon.install() + dbcon.bulk_write(mongo_changes_bulk) + dbcon.uninstall() # Set attribute `is_published` to `False` on ftrack AssetVersions session = ftrack_api.Session() @@ -422,7 +416,8 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): if not data: continue - size += self.main(data, remove_publish_folder) + project_name = context["project"]["name"] + size += self.main(project_name, data, remove_publish_folder) print("Progressing {}/{}".format(count + 1, len(contexts))) msg = "Total size of files: " + self.sizeof_fmt(size) @@ -448,7 +443,7 @@ class CalculateOldVersions(DeleteOldVersions): ) ] - def main(self, data, remove_publish_folder): + def main(self, project_name, data, remove_publish_folder): size = 0 if not data: From bc2f8387fe5eb31f70aa7c39fd3cb67848d23cbf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:50:06 +0200 Subject: [PATCH 22/57] delivery is using guery functions --- openpype/plugins/load/delivery.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 0361ab2be5..7585ea4c59 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -3,8 +3,9 @@ from collections import defaultdict from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_representations from openpype.lib import config -from openpype.pipeline import load, AvalonMongoDB, Anatomy +from openpype.pipeline import load, Anatomy from openpype import resources, style from openpype.lib.delivery import ( @@ -68,17 +69,13 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): self.setStyleSheet(style.load_stylesheet()) - project = contexts[0]["project"]["name"] - self.anatomy = Anatomy(project) + project_name = contexts[0]["project"]["name"] + self.anatomy = Anatomy(project_name) self._representations = None self.log = log self.currently_uploaded = 0 - self.dbcon = AvalonMongoDB() - self.dbcon.Session["AVALON_PROJECT"] = project - self.dbcon.install() - - self._set_representations(contexts) + self._set_representations(project_name, contexts) dropdown = QtWidgets.QComboBox() self.templates = self._get_templates(self.anatomy) @@ -238,13 +235,12 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): return templates - def _set_representations(self, contexts): + def _set_representations(self, project_name, contexts): version_ids = [context["version"]["_id"] for context in contexts] - repres = list(self.dbcon.find({ - "type": "representation", - "parent": {"$in": version_ids} - })) + repres = list(get_representations( + project_name, version_ids=version_ids + )) self._representations = repres From 3f3ae1fd7dabd3908543ec0fb7d8ab66b0e8f9a3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 10:58:44 +0200 Subject: [PATCH 23/57] use query functions in collect anatomy instance data --- .../publish/collect_anatomy_instance_data.py | 78 ++++++------------- 1 file changed, 24 insertions(+), 54 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 6a6ea170b5..c75534cf83 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -27,6 +27,11 @@ import collections import pyblish.api +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions +) from openpype.pipeline import legacy_io @@ -44,13 +49,14 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): def process(self, context): self.log.info("Collecting anatomy data for all instances.") - self.fill_missing_asset_docs(context) - self.fill_latest_versions(context) + project_name = legacy_io.active_project() + self.fill_missing_asset_docs(context, project_name) + self.fill_latest_versions(context, project_name) self.fill_anatomy_data(context) self.log.info("Anatomy Data collection finished.") - def fill_missing_asset_docs(self, context): + def fill_missing_asset_docs(self, context, project_name): self.log.debug("Qeurying asset documents for instances.") context_asset_doc = context.data.get("assetEntity") @@ -84,10 +90,8 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): self.log.debug("Querying asset documents with names: {}".format( ", ".join(["\"{}\"".format(name) for name in asset_names]) )) - asset_docs = legacy_io.find({ - "type": "asset", - "name": {"$in": asset_names} - }) + + asset_docs = get_assets(project_name, asset_names=asset_names) asset_docs_by_name = { asset_doc["name"]: asset_doc for asset_doc in asset_docs @@ -111,7 +115,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): "Not found asset documents with names \"{}\"." ).format(joined_asset_names)) - def fill_latest_versions(self, context): + def fill_latest_versions(self, context, project_name): """Try to find latest version for each instance's subset. Key "latestVersion" is always set to latest version or `None`. @@ -126,7 +130,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): self.log.debug("Qeurying latest versions for instances.") hierarchy = {} - subset_filters = [] + names_by_asset_ids = collections.defaultdict(set) for instance in context: # Make sure `"latestVersion"` key is set latest_version = instance.data.get("latestVersion") @@ -147,67 +151,33 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): if subset_name not in hierarchy[asset_id]: hierarchy[asset_id][subset_name] = [] hierarchy[asset_id][subset_name].append(instance) - subset_filters.append({ - "parent": asset_id, - "name": subset_name - }) + names_by_asset_ids[asset_id].add(subset_name) subset_docs = [] - if subset_filters: - subset_docs = list(legacy_io.find({ - "type": "subset", - "$or": subset_filters - })) + if names_by_asset_ids: + subset_docs = list(get_subsets( + project_name, names_by_asset_ids=names_by_asset_ids + )) subset_ids = [ subset_doc["_id"] for subset_doc in subset_docs ] - last_version_by_subset_id = self._query_last_versions(subset_ids) + last_version_docs_by_subset_id = get_last_versions( + project_name, subset_ids, fields=["name"] + ) for subset_doc in subset_docs: subset_id = subset_doc["_id"] - last_version = last_version_by_subset_id.get(subset_id) - if last_version is None: + last_version_doc = last_version_docs_by_subset_id.get(subset_id) + if last_version_docs_by_subset_id is None: continue asset_id = subset_doc["parent"] subset_name = subset_doc["name"] _instances = hierarchy[asset_id][subset_name] for _instance in _instances: - _instance.data["latestVersion"] = last_version - - def _query_last_versions(self, subset_ids): - """Retrieve all latest versions for entered subset_ids. - - Args: - subset_ids (list): List of subset ids with type `ObjectId`. - - Returns: - dict: Key is subset id and value is last version name. - """ - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": subset_ids} - }}, - # Sorting versions all together - {"$sort": {"name": 1}}, - # Group them by "parent", but only take the last - {"$group": { - "_id": "$parent", - "_version_id": {"$last": "$_id"}, - "name": {"$last": "$name"} - }} - ] - - last_version_by_subset_id = {} - for doc in legacy_io.aggregate(_pipeline): - subset_id = doc["_id"] - last_version_by_subset_id[subset_id] = doc["name"] - - return last_version_by_subset_id + _instance.data["latestVersion"] = last_version_doc["name"] def fill_anatomy_data(self, context): self.log.debug("Storing anatomy data to instance data.") From d1e7ae25d4e97abd2734ecf5e86d0ce89f6a09e5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:35:44 +0200 Subject: [PATCH 24/57] added function to receive archived representations --- openpype/client/__init__.py | 2 + openpype/client/entities.py | 146 +++++++++++++++++++++++++++--------- 2 files changed, 113 insertions(+), 35 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e3b4ef5132..6094b4e0ab 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -29,6 +29,7 @@ from .entities import ( get_representations, get_representation_parents, get_representations_parents, + get_archived_representations, get_thumbnail, get_thumbnails, @@ -66,6 +67,7 @@ __all__ = ( "get_representations", "get_representation_parents", "get_representations_parents", + "get_archived_representations", "get_thumbnail", "get_thumbnails", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index aed465c46f..9d425810b9 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -918,7 +918,7 @@ def get_representation_by_id(project_name, representation_id, fields=None): if not representation_id: return None - repre_types = ["representation", "archived_representations"] + repre_types = ["representation", "archived_representation"] query_filter = { "type": {"$in": repre_types} } @@ -962,43 +962,26 @@ def get_representation_by_name( return conn.find_one(query_filter, _prepare_fields(fields)) -def get_representations( +def _get_representations( project_name, - representation_ids=None, - representation_names=None, - version_ids=None, - extensions=None, - names_by_version_ids=None, - archived=False, - fields=None + representation_ids, + representation_names, + version_ids, + extensions, + names_by_version_ids, + standard, + archived, + fields ): - """Representaion entities data from one project filtered by filters. - - Filters are additive (all conditions must pass to return subset). - - Args: - project_name (str): Name of project where to look for queried entities. - representation_ids (list[str|ObjectId]): Representation ids used as - filter. Filter ignored if 'None' is passed. - representation_names (list[str]): Representations names used as filter. - Filter ignored if 'None' is passed. - version_ids (list[str]): Subset ids used as parent filter. Filter - ignored if 'None' is passed. - extensions (list[str]): Filter by extension of main representation - file (without dot). - names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering - using version ids and list of names under the version. - archived (bool): Output will also contain archived representations. - fields (list[str]): Fields that should be returned. All fields are - returned if 'None' is passed. - - Returns: - Cursor: Iterable cursor yielding all matching representations. - """ - - repre_types = ["representation"] + repre_types = [] + if standard: + repre_types.append("representation") if archived: - repre_types.append("archived_representations") + repre_types.append("archived_representation") + + if not repre_types: + return [] + if len(repre_types) == 1: query_filter = {"type": repre_types[0]} else: @@ -1043,6 +1026,99 @@ def get_representations( return conn.find(query_filter, _prepare_fields(fields)) +def get_representations( + project_name, + representation_ids=None, + representation_names=None, + version_ids=None, + extensions=None, + names_by_version_ids=None, + archived=False, + standard=True, + fields=None +): + """Representaion entities data from one project filtered by filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + representation_ids (list[str|ObjectId]): Representation ids used as + filter. Filter ignored if 'None' is passed. + representation_names (list[str]): Representations names used as filter. + Filter ignored if 'None' is passed. + version_ids (list[str]): Subset ids used as parent filter. Filter + ignored if 'None' is passed. + extensions (list[str]): Filter by extension of main representation + file (without dot). + names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering + using version ids and list of names under the version. + archived (bool): Output will also contain archived representations. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching representations. + """ + + return _get_representations( + project_name=project_name, + representation_ids=representation_ids, + representation_names=representation_names, + version_ids=version_ids, + extensions=extensions, + names_by_version_ids=names_by_version_ids, + standard=True, + archived=archived, + fields=fields + ) + + +def get_archived_representations( + project_name, + representation_ids=None, + representation_names=None, + version_ids=None, + extensions=None, + names_by_version_ids=None, + fields=None +): + """Archived representaion entities data from project with applied filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + representation_ids (list[str|ObjectId]): Representation ids used as + filter. Filter ignored if 'None' is passed. + representation_names (list[str]): Representations names used as filter. + Filter ignored if 'None' is passed. + version_ids (list[str]): Subset ids used as parent filter. Filter + ignored if 'None' is passed. + extensions (list[str]): Filter by extension of main representation + file (without dot). + names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering + using version ids and list of names under the version. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching representations. + """ + + return _get_representations( + project_name=project_name, + representation_ids=representation_ids, + representation_names=representation_names, + version_ids=version_ids, + extensions=extensions, + names_by_version_ids=names_by_version_ids, + standard=False, + archived=True, + fields=fields + ) + + def get_representations_parents(project_name, representations): """Prepare parents of representation entities. From 12d23347fc20409900d9ac032852765e38b6a842 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:45:52 +0200 Subject: [PATCH 25/57] integrate hero version use query functions --- .../plugins/publish/integrate_hero_version.py | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index a706b653c4..5f97a9bd41 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -8,6 +8,12 @@ from bson.objectid import ObjectId from pymongo import InsertOne, ReplaceOne import pyblish.api +from openpype.client import ( + get_version_by_id, + get_hero_version_by_subset_id, + get_archived_representations, + get_representations, +) from openpype.lib import ( create_hard_link, filter_profiles @@ -85,9 +91,13 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): hero_template )) - self.integrate_instance(instance, template_key, hero_template) + self.integrate_instance( + instance, project_name, template_key, hero_template + ) - def integrate_instance(self, instance, template_key, hero_template): + def integrate_instance( + self, instance, project_name, template_key, hero_template + ): anatomy = instance.context.data["anatomy"] published_repres = instance.data["published_representations"] hero_publish_dir = self.get_publish_dir(instance, template_key) @@ -118,8 +128,8 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): "Published version entity was not sent in representation data." " Querying entity from database." )) - src_version_entity = ( - self.version_from_representations(published_repres) + src_version_entity = self.version_from_representations( + project_name, published_repres ) if not src_version_entity: @@ -170,8 +180,8 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): other_file_paths_mapping.append((file_path, dst_filepath)) # Current version - old_version, old_repres = ( - self.current_hero_ents(src_version_entity) + old_version, old_repres = self.current_hero_ents( + project_name, src_version_entity ) old_repres_by_name = { @@ -223,11 +233,11 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): if old_repres_by_name: old_repres_to_delete = old_repres_by_name - archived_repres = list(legacy_io.find({ + archived_repres = list(get_archived_representations( + project_name, # Check what is type of archived representation - "type": "archived_repsentation", - "parent": new_version_id - })) + version_ids=[new_version_id] + )) archived_repres_by_name = {} for repre in archived_repres: repre_name_low = repre["name"].lower() @@ -586,25 +596,23 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): shutil.copy(src_path, dst_path) - def version_from_representations(self, repres): + def version_from_representations(self, project_name, repres): for repre in repres: - version = legacy_io.find_one({"_id": repre["parent"]}) + version = get_version_by_id(project_name, repre["parent"]) if version: return version - def current_hero_ents(self, version): - hero_version = legacy_io.find_one({ - "parent": version["parent"], - "type": "hero_version" - }) + def current_hero_ents(self, project_name, version): + hero_version = get_hero_version_by_subset_id( + project_name, version["parent"] + ) if not hero_version: return (None, []) - hero_repres = list(legacy_io.find({ - "parent": hero_version["_id"], - "type": "representation" - })) + hero_repres = list(get_representations( + project_name, version_ids=[hero_version["_id"]] + )) return (hero_version, hero_repres) def _update_path(self, anatomy, path, src_file, dst_file): From f0f0a87c5d7e81c226dd22fba6589040cf62bfff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:47:01 +0200 Subject: [PATCH 26/57] global plugins are using query functions --- .../publish/collect_avalon_entities.py | 13 +++---- .../publish/collect_scene_loaded_versions.py | 33 +++++++++-------- .../publish/extract_hierarchy_avalon.py | 36 ++++++++++++------- .../plugins/publish/integrate_thumbnail.py | 3 +- .../publish/validate_editorial_asset_name.py | 7 ++-- 5 files changed, 53 insertions(+), 39 deletions(-) diff --git a/openpype/plugins/publish/collect_avalon_entities.py b/openpype/plugins/publish/collect_avalon_entities.py index 3e7843407f..6cd0d136e8 100644 --- a/openpype/plugins/publish/collect_avalon_entities.py +++ b/openpype/plugins/publish/collect_avalon_entities.py @@ -10,6 +10,7 @@ Provides: import pyblish.api +from openpype.client import get_project, get_asset_by_name from openpype.pipeline import legacy_io @@ -25,10 +26,7 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): asset_name = legacy_io.Session["AVALON_ASSET"] task_name = legacy_io.Session["AVALON_TASK"] - project_entity = legacy_io.find_one({ - "type": "project", - "name": project_name - }) + project_entity = get_project(project_name) assert project_entity, ( "Project '{0}' was not found." ).format(project_name) @@ -39,11 +37,8 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): if not asset_name: self.log.info("Context is not set. Can't collect global data.") return - asset_entity = legacy_io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) + + asset_entity = get_asset_by_name(project_name, asset_name) assert asset_entity, ( "No asset found by the name '{0}' in project '{1}'" ).format(asset_name, project_name) diff --git a/openpype/plugins/publish/collect_scene_loaded_versions.py b/openpype/plugins/publish/collect_scene_loaded_versions.py index bb34e3ce31..5ff2b46e3b 100644 --- a/openpype/plugins/publish/collect_scene_loaded_versions.py +++ b/openpype/plugins/publish/collect_scene_loaded_versions.py @@ -1,7 +1,6 @@ -from bson.objectid import ObjectId - import pyblish.api +from openpype.client import get_representations from openpype.pipeline import ( registered_host, legacy_io, @@ -39,23 +38,29 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin): return loaded_versions = [] - _containers = list(host.ls()) - _repr_ids = [ObjectId(c["representation"]) for c in _containers] - repre_docs = legacy_io.find( - {"_id": {"$in": _repr_ids}}, - projection={"_id": 1, "parent": 1} + containers = list(host.ls()) + repre_ids = { + container["representation"] + for container in containers + } + + project_name = legacy_io.active_project() + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["_id", "parent"] ) - version_by_repr = { - str(doc["_id"]): doc["parent"] + repre_doc_by_str_id = { + str(doc["_id"]): doc for doc in repre_docs } # QUESTION should we add same representation id when loaded multiple # times? - for con in _containers: + for con in containers: repre_id = con["representation"] - version_id = version_by_repr.get(repre_id) - if version_id is None: + repre_doc = repre_doc_by_str_id.get(repre_id) + if repre_doc is None: self.log.warning(( "Skipping container," " did not find representation document. {}" @@ -66,8 +71,8 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin): # may have more then one representation that are same version version = { "subsetName": con["name"], - "representation": ObjectId(repre_id), - "version": version_id, + "representation": repre_doc["_id"], + "version": repre_doc["parent"], } loaded_versions.append(version) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index 1f7ce839ed..8d447ba595 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -1,5 +1,11 @@ from copy import deepcopy import pyblish.api +from openpype.client import ( + get_project, + get_asset_by_id, + get_asset_by_name, + get_archived_assets +) from openpype.pipeline import legacy_io @@ -19,14 +25,14 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if not legacy_io.Session: legacy_io.install() + project_name = legacy_io.active_project() hierarchy_context = self._get_active_assets(context) self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) self.project = None - self.import_to_avalon(hierarchy_context) + self.import_to_avalon(project_name, hierarchy_context) - - def import_to_avalon(self, input_data, parent=None): + def import_to_avalon(self, project_name, input_data, parent=None): for name in input_data: self.log.info("input_data[name]: {}".format(input_data[name])) entity_data = input_data[name] @@ -62,7 +68,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): update_data = True # Process project if entity_type.lower() == "project": - entity = legacy_io.find_one({"type": "project"}) + entity = get_project(project_name) # TODO: should be in validator? assert (entity is not None), "Did not find project in DB" @@ -79,7 +85,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): ) # Else process assset else: - entity = legacy_io.find_one({"type": "asset", "name": name}) + entity = get_asset_by_name(project_name, name) if entity: # Do not override data, only update cur_entity_data = entity.get("data") or {} @@ -103,10 +109,10 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): # Skip updating data update_data = False - archived_entities = legacy_io.find({ - "type": "archived_asset", - "name": name - }) + archived_entities = get_archived_assets( + project_name, + asset_names=[name] + ) unarchive_entity = None for archived_entity in archived_entities: archived_parents = ( @@ -120,7 +126,9 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if unarchive_entity is None: # Create entity if doesn"t exist - entity = self.create_avalon_asset(name, data) + entity = self.create_avalon_asset( + project_name, name, data + ) else: # Unarchive if entity was archived entity = self.unarchive_entity(unarchive_entity, data) @@ -133,7 +141,9 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): ) if "childs" in entity_data: - self.import_to_avalon(entity_data["childs"], entity) + self.import_to_avalon( + project_name, entity_data["childs"], entity + ) def unarchive_entity(self, entity, data): # Unarchived asset should not use same data @@ -151,7 +161,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): ) return new_entity - def create_avalon_asset(self, name, data): + def create_avalon_asset(self, project_name, name, data): item = { "schema": "openpype:asset-3.0", "name": name, @@ -162,7 +172,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): self.log.debug("Creating asset: {}".format(item)) entity_id = legacy_io.insert_one(item).inserted_id - return legacy_io.find_one({"_id": entity_id}) + return get_asset_by_id(project_name, entity_id) def _get_active_assets(self, context): """ Returns only asset dictionary. diff --git a/openpype/plugins/publish/integrate_thumbnail.py b/openpype/plugins/publish/integrate_thumbnail.py index 5d6fc561ea..fd50858a91 100644 --- a/openpype/plugins/publish/integrate_thumbnail.py +++ b/openpype/plugins/publish/integrate_thumbnail.py @@ -8,6 +8,7 @@ import six import pyblish.api from bson.objectid import ObjectId +from openpype.client import get_version_by_id from openpype.pipeline import legacy_io @@ -70,7 +71,7 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): thumbnail_template = anatomy.templates["publish"]["thumbnail"] - version = legacy_io.find_one({"_id": thumb_repre["parent"]}) + version = get_version_by_id(project_name, thumb_repre["parent"]) if not version: raise AssertionError( "There does not exist version with id {}".format( diff --git a/openpype/plugins/publish/validate_editorial_asset_name.py b/openpype/plugins/publish/validate_editorial_asset_name.py index f9cdaebf0c..702e87b58d 100644 --- a/openpype/plugins/publish/validate_editorial_asset_name.py +++ b/openpype/plugins/publish/validate_editorial_asset_name.py @@ -3,6 +3,7 @@ from pprint import pformat import pyblish.api from openpype.pipeline import legacy_io +from openpype.client import get_assets class ValidateEditorialAssetName(pyblish.api.ContextPlugin): @@ -29,8 +30,10 @@ class ValidateEditorialAssetName(pyblish.api.ContextPlugin): if not legacy_io.Session: legacy_io.install() - db_assets = list(legacy_io.find( - {"type": "asset"}, {"name": 1, "data.parents": 1})) + project_name = legacy_io.active_project() + db_assets = list(get_assets( + project_name, fields=["name", "data.parents"] + )) self.log.debug("__ db_assets: {}".format(db_assets)) asset_db_docs = { From 646e9edd9b11fe2d6095d9c511e1e61c096b19c4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:49:28 +0200 Subject: [PATCH 27/57] integrate asset new is using query functions --- openpype/plugins/publish/integrate_new.py | 53 +++++++++++------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 4c14c17dae..c264f0fd59 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -16,6 +16,14 @@ from pymongo import DeleteOne, InsertOne import pyblish.api import openpype.api +from openpype.client import ( + get_asset_by_name, + get_subset_by_id, + get_version_by_id, + get_version_by_name, + get_representations, + get_archived_representations, +) from openpype.lib.profiles_filtering import filter_profiles from openpype.lib import ( prepare_template_data, @@ -201,6 +209,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): context = instance.context project_entity = instance.data["projectEntity"] + project_name = project_entity["name"] context_asset_name = None context_asset_doc = context.data.get("assetEntity") @@ -210,11 +219,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): asset_name = instance.data["asset"] asset_entity = instance.data.get("assetEntity") if not asset_entity or asset_entity["name"] != context_asset_name: - asset_entity = legacy_io.find_one({ - "type": "asset", - "name": asset_name, - "parent": project_entity["_id"] - }) + asset_entity = get_asset_by_name(project_name, asset_name) assert asset_entity, ( "No asset found by the name \"{0}\" in project \"{1}\"" ).format(asset_name, project_entity["name"]) @@ -270,7 +275,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "Establishing staging directory @ {0}".format(stagingdir) ) - subset = self.get_subset(asset_entity, instance) + subset = self.get_subset(project_name, asset_entity, instance) instance.data["subsetEntity"] = subset version_number = instance.data["version"] @@ -297,11 +302,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for _repre in repres ] - existing_version = legacy_io.find_one({ - 'type': 'version', - 'parent': subset["_id"], - 'name': version_number - }) + existing_version = get_version_by_name( + project_name, version_number, subset["_id"] + ) if existing_version is None: version_id = legacy_io.insert_one(version).inserted_id @@ -322,10 +325,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): version_id = existing_version['_id'] # Find representations of existing version and archive them - current_repres = list(legacy_io.find({ - "type": "representation", - "parent": version_id - })) + current_repres = list(get_representations( + project_name, version_ids=[version_id] + )) bulk_writes = [] for repre in current_repres: if append_repres: @@ -345,18 +347,17 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # bulk updates if bulk_writes: - project_name = legacy_io.Session["AVALON_PROJECT"] legacy_io.database[project_name].bulk_write( bulk_writes ) - version = legacy_io.find_one({"_id": version_id}) + version = get_version_by_id(project_name, version_id) instance.data["versionEntity"] = version - existing_repres = list(legacy_io.find({ - "parent": version_id, - "type": "archived_representation" - })) + existing_repres = list(get_archived_representations( + project_name, + version_ids=[version_id] + )) instance.data['version'] = version['name'] @@ -792,13 +793,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): create_hard_link(src, dst) - def get_subset(self, asset, instance): + def get_subset(self, project_name, asset, instance): subset_name = instance.data["subset"] - subset = legacy_io.find_one({ - "type": "subset", - "parent": asset["_id"], - "name": subset_name - }) + subset = get_subset_by_name(project_name, subset_name, asset["_id"]) if subset is None: self.log.info("Subset '%s' not found, creating ..." % subset_name) @@ -825,7 +822,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "parent": asset["_id"] }).inserted_id - subset = legacy_io.find_one({"_id": _id}) + subset = get_subset_by_id(project_name, _id) # QUESTION Why is changing of group and updating it's # families in 'get_subset'? From 26bdde12d0d81bf3c5bd60ec69659716a904d0e9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 1 Jul 2022 11:54:17 +0200 Subject: [PATCH 28/57] add missing import --- openpype/plugins/publish/integrate_new.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index c264f0fd59..f870220421 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -19,6 +19,7 @@ import openpype.api from openpype.client import ( get_asset_by_name, get_subset_by_id, + get_subset_by_name, get_version_by_id, get_version_by_name, get_representations, From 0537317fcf96dabb75679fbb9cd9b9f0d0d7ac8c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 1 Jul 2022 15:54:33 +0200 Subject: [PATCH 29/57] Shush the hound --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index a7ba5b3745..bcf96eb128 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -133,4 +133,3 @@ class ExtractAnimation(ExtractAlembic): fullPath=True) or [] return nodes, roots - From 45cc1ed9ae9e5ad1a309146d9c5ae1903a34b3cc Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Fri, 1 Jul 2022 16:37:01 +0200 Subject: [PATCH 30/57] bugfix: delete_old_version use settings to process ftrack logic. --- openpype/plugins/load/delete_old_versions.py | 65 ++++++++++---------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 7465f53855..f626775180 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -4,13 +4,14 @@ import uuid import clique from pymongo import UpdateOne -import ftrack_api +import importlib import qargparse from Qt import QtWidgets, QtCore from openpype import style from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype.lib import StringTemplate +from openpype.settings import get_system_settings class DeleteOldVersions(load.SubsetLoaderPlugin): @@ -370,37 +371,39 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): self.dbcon.uninstall() - # Set attribute `is_published` to `False` on ftrack AssetVersions - session = ftrack_api.Session() - query = ( - "AssetVersion where asset.parent.id is \"{}\"" - " and asset.name is \"{}\"" - " and version is \"{}\"" - ) - for v in data["versions"]: - try: - ftrack_version = session.query( - query.format( - data["asset"]["data"]["ftrackId"], - data["subset"]["name"], - v["name"] - ) - ).one() - except ftrack_api.exception.NoResultFoundError: - continue - - ftrack_version["is_published"] = False - - try: - session.commit() - - except Exception: - msg = ( - "Could not set `is_published` attribute to `False`" - " for selected AssetVersions." + if get_system_settings()["modules"]["ftrack"]["enabled"]: + # Set attribute `is_published` to `False` on ftrack AssetVersions + ftrack_api = importlib.import_module("ftrack_api") + session = ftrack_api.Session() + query = ( + "AssetVersion where asset.parent.id is \"{}\"" + " and asset.name is \"{}\"" + " and version is \"{}\"" ) - self.log.error(msg) - self.message(msg) + for v in data["versions"]: + try: + ftrack_version = session.query( + query.format( + data["asset"]["data"]["ftrackId"], + data["subset"]["name"], + v["name"] + ) + ).one() + except ftrack_api.exception.NoResultFoundError: + continue + + ftrack_version["is_published"] = False + + try: + session.commit() + + except Exception: + msg = ( + "Could not set `is_published` attribute to `False`" + " for selected AssetVersions." + ) + self.log.error(msg) + self.message(msg) return size From bcdcf5b6af6ebb65c4bf803a30c4ba2afffca5fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 2 Jul 2022 22:16:58 +0200 Subject: [PATCH 31/57] resolve: removing small blockers --- openpype/hosts/resolve/api/lib.py | 13 ++-- openpype/hosts/resolve/api/plugin.py | 2 +- .../plugins/create/create_shot_clip.py | 68 ++++++++++--------- .../hosts/resolve/plugins/load/load_clip.py | 2 +- .../plugins/publish/precollect_workfile.py | 3 +- 5 files changed, 45 insertions(+), 43 deletions(-) diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index c4717bd370..93ccdaf812 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -319,14 +319,13 @@ def get_current_timeline_items( selected_track_count = timeline.GetTrackCount(track_type) # loop all tracks and get items - _clips = dict() + _clips = {} for track_index in range(1, (int(selected_track_count) + 1)): _track_name = timeline.GetTrackName(track_type, track_index) # filter out all unmathed track names - if track_name: - if _track_name not in track_name: - continue + if track_name and _track_name not in track_name: + continue timeline_items = timeline.GetItemListInTrack( track_type, track_index) @@ -348,12 +347,8 @@ def get_current_timeline_items( "index": clip_index } ti_color = ti.GetClipColor() - if filter is True: - if selecting_color in ti_color: - selected_clips.append(data) - else: + if filter and selecting_color in ti_color or not filter: selected_clips.append(data) - return selected_clips diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 8e1436021c..49b478fb3b 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -506,7 +506,7 @@ class Creator(LegacyCreator): super(Creator, self).__init__(*args, **kwargs) from openpype.api import get_current_project_settings resolve_p_settings = get_current_project_settings().get("resolve") - self.presets = dict() + self.presets = {} if resolve_p_settings: self.presets = resolve_p_settings["create"].get( self.__class__.__name__, {}) diff --git a/openpype/hosts/resolve/plugins/create/create_shot_clip.py b/openpype/hosts/resolve/plugins/create/create_shot_clip.py index 62d5557a50..dbf10c5163 100644 --- a/openpype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/openpype/hosts/resolve/plugins/create/create_shot_clip.py @@ -116,12 +116,13 @@ class CreateShotClip(resolve.Creator): "order": 0}, "vSyncTrack": { "value": gui_tracks, # noqa - "type": "QComboBox", - "label": "Hero track", - "target": "ui", - "toolTip": "Select driving track name which should be mastering all others", # noqa - "order": 1} + "type": "QComboBox", + "label": "Hero track", + "target": "ui", + "toolTip": "Select driving track name which should be mastering all others", # noqa + "order": 1 } + } }, "publishSettings": { "type": "section", @@ -172,28 +173,31 @@ class CreateShotClip(resolve.Creator): "target": "ui", "order": 4, "value": { - "workfileFrameStart": { - "value": 1001, - "type": "QSpinBox", - "label": "Workfiles Start Frame", - "target": "tag", - "toolTip": "Set workfile starting frame number", # noqa - "order": 0}, - "handleStart": { - "value": 0, - "type": "QSpinBox", - "label": "Handle start (head)", - "target": "tag", - "toolTip": "Handle at start of clip", # noqa - "order": 1}, - "handleEnd": { - "value": 0, - "type": "QSpinBox", - "label": "Handle end (tail)", - "target": "tag", - "toolTip": "Handle at end of clip", # noqa - "order": 2}, - } + "workfileFrameStart": { + "value": 1001, + "type": "QSpinBox", + "label": "Workfiles Start Frame", + "target": "tag", + "toolTip": "Set workfile starting frame number", # noqa + "order": 0 + }, + "handleStart": { + "value": 0, + "type": "QSpinBox", + "label": "Handle start (head)", + "target": "tag", + "toolTip": "Handle at start of clip", # noqa + "order": 1 + }, + "handleEnd": { + "value": 0, + "type": "QSpinBox", + "label": "Handle end (tail)", + "target": "tag", + "toolTip": "Handle at end of clip", # noqa + "order": 2 + } + } } } @@ -229,8 +233,10 @@ class CreateShotClip(resolve.Creator): v_sync_track = widget.result["vSyncTrack"]["value"] # sort selected trackItems by - sorted_selected_track_items = list() - unsorted_selected_track_items = list() + sorted_selected_track_items = [] + unsorted_selected_track_items = [] + print("_____ selected ______") + print(self.selected) for track_item_data in self.selected: if track_item_data["track"]["name"] in v_sync_track: sorted_selected_track_items.append(track_item_data) @@ -253,10 +259,10 @@ class CreateShotClip(resolve.Creator): "sq_frame_start": sq_frame_start, "sq_markers": sq_markers } - + print(kwargs) for i, track_item_data in enumerate(sorted_selected_track_items): self.rename_index = i - + self.log.info(track_item_data) # convert track item to timeline media pool item track_item = resolve.PublishClip( self, track_item_data, **kwargs).convert() diff --git a/openpype/hosts/resolve/plugins/load/load_clip.py b/openpype/hosts/resolve/plugins/load/load_clip.py index cf88b14e81..567be2be87 100644 --- a/openpype/hosts/resolve/plugins/load/load_clip.py +++ b/openpype/hosts/resolve/plugins/load/load_clip.py @@ -19,7 +19,7 @@ class LoadClip(resolve.TimelineItemLoader): """ families = ["render2d", "source", "plate", "render", "review"] - representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264", ".mov"] + representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264", "mov"] label = "Load as clip" order = -10 diff --git a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py index a58f288770..53e67aee0e 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_workfile.py @@ -30,7 +30,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin): "asset": asset, "subset": "{}{}".format(asset, subset.capitalize()), "item": project, - "family": "workfile" + "family": "workfile", + "families": [] } # create instance with workfile From 4e4bf771724376bf6ff2587d77bb1ebb938d793c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 10:37:36 +0200 Subject: [PATCH 32/57] use direct import of ftrack_api --- openpype/plugins/load/delete_old_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index f626775180..622e6b41b7 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -4,7 +4,6 @@ import uuid import clique from pymongo import UpdateOne -import importlib import qargparse from Qt import QtWidgets, QtCore @@ -373,7 +372,8 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): if get_system_settings()["modules"]["ftrack"]["enabled"]: # Set attribute `is_published` to `False` on ftrack AssetVersions - ftrack_api = importlib.import_module("ftrack_api") + import ftrack_api + session = ftrack_api.Session() query = ( "AssetVersion where asset.parent.id is \"{}\"" From 5baedfc3a068a338603cc5e956e6446b655e9af0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 10:37:57 +0200 Subject: [PATCH 33/57] use ftrack module to determine if ftrack is enabled --- openpype/plugins/load/delete_old_versions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 622e6b41b7..2a0b7492bc 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -10,7 +10,7 @@ from Qt import QtWidgets, QtCore from openpype import style from openpype.pipeline import load, AvalonMongoDB, Anatomy from openpype.lib import StringTemplate -from openpype.settings import get_system_settings +from openpype.modules import ModulesManager class DeleteOldVersions(load.SubsetLoaderPlugin): @@ -370,7 +370,9 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): self.dbcon.uninstall() - if get_system_settings()["modules"]["ftrack"]["enabled"]: + modules_manager = ModulesManager() + ftrack_module = modules_manager.modules_by_name.get("ftrack") + if ftrack_module and ftrack_module.enabled: # Set attribute `is_published` to `False` on ftrack AssetVersions import ftrack_api From 409362e4ffeb2d269d0a418643764b965ca14d8a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 10:50:40 +0200 Subject: [PATCH 34/57] moveed the ftrack logic into separated method --- openpype/plugins/load/delete_old_versions.py | 103 ++++++++++++------- 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 2a0b7492bc..1f48e651b4 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -370,45 +370,76 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): self.dbcon.uninstall() - modules_manager = ModulesManager() - ftrack_module = modules_manager.modules_by_name.get("ftrack") - if ftrack_module and ftrack_module.enabled: - # Set attribute `is_published` to `False` on ftrack AssetVersions - import ftrack_api - - session = ftrack_api.Session() - query = ( - "AssetVersion where asset.parent.id is \"{}\"" - " and asset.name is \"{}\"" - " and version is \"{}\"" - ) - for v in data["versions"]: - try: - ftrack_version = session.query( - query.format( - data["asset"]["data"]["ftrackId"], - data["subset"]["name"], - v["name"] - ) - ).one() - except ftrack_api.exception.NoResultFoundError: - continue - - ftrack_version["is_published"] = False - - try: - session.commit() - - except Exception: - msg = ( - "Could not set `is_published` attribute to `False`" - " for selected AssetVersions." - ) - self.log.error(msg) - self.message(msg) + self._ftrack_delete_versions(data) return size + def _ftrack_delete_versions(self, data): + """Delete version on ftrack. + + Handling of ftrack logic in this plugin is not ideal. But in OP3 it is + almost impossible to solve the issue other way. + + Note: + Asset versions on ftrack are not deleted but marked as + "not published" which cause that they're invisible. + + Args: + data (dict): Data sent to subset loader with full context. + """ + + # First check for ftrack id on asset document + # - skip if ther is none + asset_ftrack_id = data["asset"]["data"].get("ftrackId") + if not asset_ftrack_id: + self.log.info(( + "Asset does not have filled ftrack id. Skipped delete" + " of ftrack version." + )) + return + + # Check if ftrack module is enabled + modules_manager = ModulesManager() + ftrack_module = modules_manager.modules_by_name.get("ftrack") + if not ftrack_module or not ftrack_module.enabled: + return + + import ftrack_api + + session = ftrack_api.Session() + subset_name = data["subset"]["name"] + versions = { + '"{}"'.format(version_doc["name"]) + for version_doc in data["versions"] + } + asset_versions = session.query( + ( + "select id, is_published from AssetVersion where" + " asset.parent.id is \"{}\"" + " and asset.name is \"{}\"" + " and version in (\"{}\")" + ).format( + asset_ftrack_id, + subset_name, + ",".join(versions) + ) + ) + + # Set attribute `is_published` to `False` on ftrack AssetVersions + for asset_version in asset_versions: + asset_version["is_published"] = False + + try: + session.commit() + + except Exception: + msg = ( + "Could not set `is_published` attribute to `False`" + " for selected AssetVersions." + ) + self.log.error(msg) + self.message(msg) + def load(self, contexts, name=None, namespace=None, options=None): try: size = 0 From 9f174cc92c4aeb869d4e847d7558e7cda18302ff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:11:34 +0200 Subject: [PATCH 35/57] fix query --- openpype/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 1f48e651b4..6b469357f7 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -417,7 +417,7 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): "select id, is_published from AssetVersion where" " asset.parent.id is \"{}\"" " and asset.name is \"{}\"" - " and version in (\"{}\")" + " and version in ({})" ).format( asset_ftrack_id, subset_name, From 408593bf14d4ae5c5cd022ddfd3c87969178a0c7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:12:46 +0200 Subject: [PATCH 36/57] define query result --- openpype/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py index 6b469357f7..0952cd9bd6 100644 --- a/openpype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -423,7 +423,7 @@ class DeleteOldVersions(load.SubsetLoaderPlugin): subset_name, ",".join(versions) ) - ) + ).all() # Set attribute `is_published` to `False` on ftrack AssetVersions for asset_version in asset_versions: From 01c48a3c194adf6e83421380d7bf002eca445dcd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:40:28 +0200 Subject: [PATCH 37/57] trigger 'openpype.project.prepare' event on project preparation --- .../ftrack/event_handlers_server/action_prepare_project.py | 5 +++++ .../ftrack/event_handlers_user/action_prepare_project.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 361aa98d16..50cadb7f09 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -1,4 +1,5 @@ import json +import copy from openpype.client import get_project from openpype.api import ProjectSettings @@ -400,6 +401,10 @@ class PrepareProjectServer(ServerAction): self.log.debug("- Key \"{}\" set to \"{}\"".format(key, value)) session.commit() + event_data = copy.deepcopy(in_data) + event_data["project_name"] = project_name + self.trigger_event("openpype.project.prepared", event_data) + return True diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index e9dc11de9f..9cac2f4386 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -1,4 +1,5 @@ import json +import copy from openpype.client import get_project from openpype.api import ProjectSettings @@ -433,6 +434,10 @@ class PrepareProjectLocal(BaseAction): self.process_identifier() ) self.trigger_action(trigger_identifier, event) + + event_data = copy.deepcopy(in_data) + event_data["project_name"] = project_name + self.trigger_event("openpype.project.prepared", event_data) return True From 91a9dcdbd491d7c4cc4b28a0abcbda3a1fd70a5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:42:00 +0200 Subject: [PATCH 38/57] Don't use dictionary as default value --- openpype/modules/ftrack/lib/ftrack_base_handler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/ftrack/lib/ftrack_base_handler.py index 2130abc20c..c0fad6aadc 100644 --- a/openpype/modules/ftrack/lib/ftrack_base_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_base_handler.py @@ -535,7 +535,7 @@ class BaseHandler(object): ) def trigger_event( - self, topic, event_data={}, session=None, source=None, + self, topic, event_data=None, session=None, source=None, event=None, on_error="ignore" ): if session is None: @@ -543,6 +543,9 @@ class BaseHandler(object): if not source and event: source = event.get("source") + + if event_data is None: + event_data = {} # Create and trigger event event = ftrack_api.event.base.Event( topic=topic, From b340bd532d6233c3fadf86295e98eb617412e066 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 11:47:06 +0200 Subject: [PATCH 39/57] trigger ftrack event 'openpype.project.created' on project creation --- .../event_handlers_server/action_prepare_project.py | 4 ++++ .../ftrack/event_handlers_user/action_prepare_project.py | 4 ++++ openpype/modules/ftrack/lib/avalon_sync.py | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py index 50cadb7f09..713a4d9aba 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_server/action_prepare_project.py @@ -374,6 +374,10 @@ class PrepareProjectServer(ServerAction): project_name, project_code )) create_project(project_name, project_code) + self.trigger_event( + "openpype.project.created", + {"project_name": project_name} + ) project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] diff --git a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index 9cac2f4386..e89595109e 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -400,6 +400,10 @@ class PrepareProjectLocal(BaseAction): project_name, project_code )) create_project(project_name, project_code) + self.trigger_event( + "openpype.project.created", + {"project_name": project_name} + ) project_settings = ProjectSettings(project_name) project_anatomy_settings = project_settings["project_anatomy"] diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 68b5c62c53..6161a2131e 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -443,6 +443,7 @@ class SyncEntitiesFactory: } self.create_list = [] + self.project_created = False self.unarchive_list = [] self.updates = collections.defaultdict(dict) @@ -2016,6 +2017,13 @@ class SyncEntitiesFactory: if len(self.create_list) > 0: self.dbcon.insert_many(self.create_list) + if self.project_created: + event = ftrack_api.event.base.Event( + topic="openpype.project.created", + data={"project_name": self.project_name} + ) + self.session.event_hub.publish(event) + self.session.commit() self.log.debug("* Processing entities for update") @@ -2214,6 +2222,7 @@ class SyncEntitiesFactory: self._avalon_ents_by_name[project_item["name"]] = str(new_id) self.create_list.append(project_item) + self.project_created = True # store mongo id to ftrack entity entity = self.entities_dict[self.ft_project_id]["entity"] From 606b057ab0d16e82e801aaaaddc1744be2256ae8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 12:25:32 +0200 Subject: [PATCH 40/57] trigger 'openpype.project.created' in sync actions because session avalon sync is not with event hub --- .../event_handlers_server/action_sync_to_avalon.py | 10 +++++++++- .../event_handlers_user/action_sync_to_avalon.py | 10 +++++++++- openpype/modules/ftrack/lib/avalon_sync.py | 7 ------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py index 58f79e8a2b..df9147bdf7 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py @@ -1,7 +1,8 @@ import time import sys import json -import traceback + +import ftrack_api from openpype_modules.ftrack.lib import ServerAction from openpype_modules.ftrack.lib.avalon_sync import SyncEntitiesFactory @@ -180,6 +181,13 @@ class SyncToAvalonServer(ServerAction): "* Total time: {}".format(time_7 - time_start) ) + if self.entities_factory.project_created: + event = ftrack_api.event.base.Event( + topic="openpype.project.created", + data={"project_name": project_name} + ) + self.session.event_hub.publish(event) + report = self.entities_factory.report() if report and report.get("items"): default_title = "Synchronization report ({}):".format( diff --git a/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py index cd2f371f38..e52a061471 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py @@ -1,7 +1,8 @@ import time import sys import json -import traceback + +import ftrack_api from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import SyncEntitiesFactory @@ -184,6 +185,13 @@ class SyncToAvalonLocal(BaseAction): "* Total time: {}".format(time_7 - time_start) ) + if self.entities_factory.project_created: + event = ftrack_api.event.base.Event( + topic="openpype.project.created", + data={"project_name": project_name} + ) + self.session.event_hub.publish(event) + report = self.entities_factory.report() if report and report.get("items"): default_title = "Synchronization report ({}):".format( diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 6161a2131e..f8883cefbd 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -2017,13 +2017,6 @@ class SyncEntitiesFactory: if len(self.create_list) > 0: self.dbcon.insert_many(self.create_list) - if self.project_created: - event = ftrack_api.event.base.Event( - topic="openpype.project.created", - data={"project_name": self.project_name} - ) - self.session.event_hub.publish(event) - self.session.commit() self.log.debug("* Processing entities for update") From 4781fc56b48b6f66dc33803c35ee7d9be56729ec Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 4 Jul 2022 12:50:39 +0200 Subject: [PATCH 41/57] remove the global scope add_scripts_menu call from menu.py and add it in the install function of pipeline.py --- openpype/hosts/hiero/api/menu.py | 5 +---- openpype/hosts/hiero/api/pipeline.py | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py index 412f08272e..541a1f1f92 100644 --- a/openpype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -162,11 +162,8 @@ def add_scripts_menu(): log.warning("Skipping studio menu, no definition found.") return - # run the launcher for Maya menu + # run the launcher for Hiero menu studio_menu = launchforhiero.main(title=_menu.title()) # apply configuration studio_menu.build_from_configuration(studio_menu, config) - - -add_scripts_menu() diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py index 9b628ec70b..b243a38b06 100644 --- a/openpype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -48,6 +48,7 @@ def install(): # install menu menu.menu_install() + menu.add_scripts_menu() # register hiero events events.register_hiero_events() From 6c658f7da3fdf9bf716d8adfaf17f69641e78d87 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 4 Jul 2022 14:24:07 +0200 Subject: [PATCH 42/57] fix default json project_settings by setting defaults through run_settings UI --- .../defaults/project_settings/hiero.json | 42 ++++++------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 3b0127c24a..e9e7199330 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,32 +1,4 @@ { - "ext_mapping": { - "model": "hrox", - "mayaAscii": "hrox", - "camera": "hrox", - "rig": "hrox", - "workfile": "hrox", - "yetiRig": "hrox" - }, - "hiero-dirmap": { - "enabled": false, - "paths": { - "source-path": [], - "destination-path": [] - } - }, - "scriptsmenu": { - "name": "OpenPype Tools", - "definition": [ - { - "type": "action", - "sourcetype": "python", - "title": "OpenPype Docs", - "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_hiero')", - "tooltip": "Open the OpenPype Hiero user doc page" - } - ] - }, - "create": { "CreateShotClip": { "hierarchy": "{folder}/{sequence}", @@ -79,5 +51,17 @@ ] } }, - "filters": {} + "filters": {}, + "scriptsmenu": { + "name": "OpenPype Tools", + "definition": [ + { + "type": "action", + "sourcetype": "python", + "title": "OpenPype Docs", + "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_hiero')", + "tooltip": "Open the OpenPype Hiero user doc page" + } + ] + } } \ No newline at end of file From 3612ceb9378053e124440661a89944d76c5a4d88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Jul 2022 19:12:52 +0200 Subject: [PATCH 43/57] fix query of current version --- openpype/pipeline/load/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index eca581ba37..2c1b2ea8ea 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -394,7 +394,7 @@ def update_container(container, version=-1): assert current_representation is not None, "This is a bug" current_version = get_version_by_id( - project_name, current_representation["_id"], fields=["parent"] + project_name, current_representation["parent"], fields=["parent"] ) if version == -1: new_version = get_last_version_by_subset_id( From 488f58a52ed9d518082e28d30e1687fc562cbce4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 5 Jul 2022 09:15:00 +0200 Subject: [PATCH 44/57] Resolve conflicts --- openpype/hosts/maya/plugins/publish/extract_animation.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/extract_animation.py diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py deleted file mode 100644 index e69de29bb2..0000000000 From deaeed3f0b5b7eb602efa0b23a4e34ec1774571a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Jul 2022 10:35:42 +0200 Subject: [PATCH 45/57] nuke: make it clean nodes again --- openpype/hosts/nuke/plugins/publish/extract_slate_frame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 6997180c9f..99ade4cf9b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -230,8 +230,8 @@ class ExtractSlateFrame(openpype.api.Extractor): write_node.name(), int(slate_first_frame), int(slate_first_frame)) # Clean up - # for node in temporary_nodes: - # nuke.delete(node) + for node in temporary_nodes: + nuke.delete(node) def _render_slate_to_sequence(self, instance): # set slate frame From b96cb503dafcbfed7adf144d863096adf2fd1e39 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Jul 2022 11:35:07 +0200 Subject: [PATCH 46/57] flame: fix loading with missing template part --- openpype/hosts/flame/plugins/load/load_clip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index e0a7297381..0b049214ff 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -2,7 +2,7 @@ import os import flame from pprint import pformat import openpype.hosts.flame.api as opfapi - +from openpype.lib import StringTemplate class LoadClip(opfapi.ClipLoader): """Load a subset to timeline as clip @@ -22,7 +22,7 @@ class LoadClip(opfapi.ClipLoader): # settings reel_group_name = "OpenPype_Reels" reel_name = "Loaded" - clip_name_template = "{asset}_{subset}_{output}" + clip_name_template = "{asset}_{subset}<_{output}>" def load(self, context, name, namespace, options): @@ -36,7 +36,7 @@ class LoadClip(opfapi.ClipLoader): version_data = version.get("data", {}) version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) - clip_name = self.clip_name_template.format( + clip_name = StringTemplate(self.clip_name_template).format( **context["representation"]["context"]) # TODO: settings in imageio From 110ed4d752cdf5a10086b8029dfae118d4ffca2b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Jul 2022 11:40:31 +0200 Subject: [PATCH 47/57] flame: fixing loading name template --- openpype/hosts/flame/plugins/load/load_clip.py | 2 +- openpype/hosts/flame/plugins/load/load_clip_batch.py | 7 ++++--- openpype/settings/defaults/project_settings/flame.json | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index 0b049214ff..b12f2f9690 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -37,7 +37,7 @@ class LoadClip(opfapi.ClipLoader): version_name = version.get("name", None) colorspace = version_data.get("colorspace", None) clip_name = StringTemplate(self.clip_name_template).format( - **context["representation"]["context"]) + context["representation"]["context"]) # TODO: settings in imageio # convert colorspace with ocio to flame mapping diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index 5de3226035..fb4a3dc6e9 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -2,6 +2,7 @@ import os import flame from pprint import pformat import openpype.hosts.flame.api as opfapi +from openpype.lib import StringTemplate class LoadClipBatch(opfapi.ClipLoader): @@ -21,7 +22,7 @@ class LoadClipBatch(opfapi.ClipLoader): # settings reel_name = "OP_LoadedReel" - clip_name_template = "{asset}_{subset}_{output}" + clip_name_template = "{asset}_{subset}<_{output}>" def load(self, context, name, namespace, options): @@ -39,8 +40,8 @@ class LoadClipBatch(opfapi.ClipLoader): if not context["representation"]["context"].get("output"): self.clip_name_template.replace("output", "representation") - clip_name = self.clip_name_template.format( - **context["representation"]["context"]) + clip_name = StringTemplate(self.clip_name_template).format( + context["representation"]["context"]) # TODO: settings in imageio # convert colorspace with ocio to flame mapping diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index a7836b9c1f..bfdc58d9ee 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -98,7 +98,7 @@ ], "reel_group_name": "OpenPype_Reels", "reel_name": "Loaded", - "clip_name_template": "{asset}_{subset}_{output}" + "clip_name_template": "{asset}_{subset}<_{output}>" }, "LoadClipBatch": { "enabled": true, @@ -121,7 +121,7 @@ "exr16fpdwaa" ], "reel_name": "OP_LoadedReel", - "clip_name_template": "{asset}_{subset}_{output}" + "clip_name_template": "{asset}_{subset}<_{output}>" } } } \ No newline at end of file From 6e3fd1122bc9614d124b8bc49edac70c121bb39c Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Tue, 5 Jul 2022 17:13:11 +0200 Subject: [PATCH 48/57] Kitsu bugfix update assets attributes --- .../plugins/publish/collect_kitsu_entities.py | 18 ++++--- openpype/modules/kitsu/utils/sync_service.py | 32 +++++++----- .../modules/kitsu/utils/update_op_with_zou.py | 52 ++++++++++++------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 84c400bde9..d28ded06c7 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -32,11 +32,17 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): context.data["kitsu_project"] = kitsu_project self.log.debug("Collect kitsu project: {}".format(kitsu_project)) - kitsu_asset = gazu.asset.get_asset(zou_asset_data["id"]) - if not kitsu_asset: - raise AssertionError("Asset not found in kitsu!") - context.data["kitsu_asset"] = kitsu_asset - self.log.debug("Collect kitsu asset: {}".format(kitsu_asset)) + entity_type = zou_asset_data["type"] + if entity_type == "Shot": + kitsu_entity = gazu.shot.get_shot(zou_asset_data["id"]) + else: + kitsu_entity = gazu.asset.get_asset(zou_asset_data["id"]) + + if not kitsu_entity: + raise AssertionError(f"{entity_type} not found in kitsu!") + + context.data["kitsu_entity"] = kitsu_entity + self.log.debug(f"Collect kitsu {entity_type}: {kitsu_entity}") if zou_task_data: kitsu_task = gazu.task.get_task(zou_task_data["id"]) @@ -57,7 +63,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): ) kitsu_task = gazu.task.get_task_by_name( - kitsu_asset, kitsu_task_type + kitsu_entity, kitsu_task_type ) if not kitsu_task: raise AssertionError("Task not found in kitsu!") diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 6c003942f8..577050c5af 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -165,10 +165,12 @@ class Listener: zou_ids_and_asset_docs[asset["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets( + update_op_result = update_op_assets( self.dbcon, project_doc, [asset], zou_ids_and_asset_docs - )[0] - self.dbcon.update_one({"_id": asset_doc_id}, asset_update) + ) + if update_op_result: + asset_doc_id, asset_update = update_op_result[0] + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_asset(self, data): """Delete asset of OP DB.""" @@ -212,10 +214,12 @@ class Listener: zou_ids_and_asset_docs[episode["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets( + update_op_result = update_op_assets( self.dbcon, project_doc, [episode], zou_ids_and_asset_docs - )[0] - self.dbcon.update_one({"_id": asset_doc_id}, asset_update) + ) + if update_op_result: + asset_doc_id, asset_update = update_op_result[0] + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_episode(self, data): """Delete shot of OP DB.""" @@ -260,10 +264,12 @@ class Listener: zou_ids_and_asset_docs[sequence["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets( + update_op_result = update_op_assets( self.dbcon, project_doc, [sequence], zou_ids_and_asset_docs - )[0] - self.dbcon.update_one({"_id": asset_doc_id}, asset_update) + ) + if update_op_result: + asset_doc_id, asset_update = update_op_result[0] + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_sequence(self, data): """Delete sequence of OP DB.""" @@ -308,10 +314,12 @@ class Listener: zou_ids_and_asset_docs[shot["project_id"]] = project_doc # Update - asset_doc_id, asset_update = update_op_assets( + update_op_result = update_op_assets( self.dbcon, project_doc, [shot], zou_ids_and_asset_docs - )[0] - self.dbcon.update_one({"_id": asset_doc_id}, asset_update) + ) + if update_op_result: + asset_doc_id, asset_update = update_op_result[0] + self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_shot(self, data): """Delete shot of OP DB.""" diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index cd98c0d204..f8ee6688d2 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -82,22 +82,37 @@ def update_op_assets( item_data["zou"] = item # == Asset settings == - # Frame in, fallback on 0 - frame_in = int(item_data.get("frame_in") or 0) + # Frame in, fallback to project's value or default value (1001) + # TODO: get default from settings/project_anatomy/attributes.json + try: + frame_in = int( + item_data.pop( + "frame_in", project_doc["data"].get("frameStart") + ) + ) + except (TypeError, ValueError): + frame_in = 1001 item_data["frameStart"] = frame_in - item_data.pop("frame_in", None) - # Frame out, fallback on frame_in + duration - frames_duration = int(item.get("nb_frames") or 1) - frame_out = ( - item_data["frame_out"] - if item_data.get("frame_out") - else frame_in + frames_duration - ) - item_data["frameEnd"] = int(frame_out) - item_data.pop("frame_out", None) - # Fps, fallback to project's value when entity fps is deleted - if not item_data.get("fps") and item_doc["data"].get("fps"): - item_data["fps"] = project_doc["data"]["fps"] + # Frames duration, fallback on 0 + try: + frames_duration = int(item_data.pop("nb_frames", 0)) + except (TypeError, ValueError): + frames_duration = 0 + # Frame out, fallback on frame_in + duration or project's value or 1001 + frame_out = item_data.pop("frame_out", None) + if not frame_out: + frame_out = frame_in + frames_duration + try: + frame_out = int(frame_out) + except (TypeError, ValueError): + frame_out = 1001 + item_data["frameEnd"] = frame_out + # Fps, fallback to project's value or default value (25.0) + try: + fps = float(item_data.get("fps", project_doc["data"].get("fps"))) + except (TypeError, ValueError): + fps = 25.0 + item_data["fps"] = fps # Tasks tasks_list = [] @@ -106,7 +121,6 @@ def update_op_assets( tasks_list = all_tasks_for_asset(item) elif item_type == "Shot": tasks_list = all_tasks_for_shot(item) - # TODO frame in and out item_data["tasks"] = { t["task_type_name"]: {"type": t["task_type_name"]} for t in tasks_list @@ -229,9 +243,9 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: project_data.update( { "code": project_code, - "fps": project["fps"], - "resolutionWidth": project["resolution"].split("x")[0], - "resolutionHeight": project["resolution"].split("x")[1], + "fps": float(project["fps"]), + "resolutionWidth": int(project["resolution"].split("x")[0]), + "resolutionHeight": int(project["resolution"].split("x")[1]), "zou_id": project["id"], } ) From 4ddebd51790b994048667edbbf150b557823a360 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Wed, 6 Jul 2022 14:49:08 +0200 Subject: [PATCH 49/57] Kitsu update tasks with zou key --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index f8ee6688d2..de74b0c677 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -122,7 +122,7 @@ def update_op_assets( elif item_type == "Shot": tasks_list = all_tasks_for_shot(item) item_data["tasks"] = { - t["task_type_name"]: {"type": t["task_type_name"]} + t["task_type_name"]: {"type": t["task_type_name"], "zou": t} for t in tasks_list } From d9d8f7c315b6592d305cd8b4d045a0def6f49644 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 7 Jul 2022 15:37:21 +0200 Subject: [PATCH 50/57] fix add missing project name in args --- openpype/pipeline/load/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py index 2c1b2ea8ea..2c213aff6f 100644 --- a/openpype/pipeline/load/utils.py +++ b/openpype/pipeline/load/utils.py @@ -208,10 +208,12 @@ def get_representation_context(representation): assert representation is not None, "This is a bug" - if not isinstance(representation, dict): - representation = get_representation_by_id(representation) - project_name = legacy_io.active_project() + if not isinstance(representation, dict): + representation = get_representation_by_id( + project_name, representation + ) + version, subset, asset, project = get_representation_parents( project_name, representation ) From 5ad1f8ba7e110a8679df6581069c3a00035130df Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Jul 2022 15:45:57 +0200 Subject: [PATCH 51/57] thumbnails --- .../{extract_jpeg_exr.py => extract_thumbnail.py} | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) rename openpype/plugins/publish/{extract_jpeg_exr.py => extract_thumbnail.py} (92%) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_thumbnail.py similarity index 92% rename from openpype/plugins/publish/extract_jpeg_exr.py rename to openpype/plugins/publish/extract_thumbnail.py index 42c4cbe062..7a438ca701 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -71,18 +71,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not is_oiio_supported(): thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa else: - # Check if the file can be read by OIIO - oiio_tool_path = get_oiio_tools_path() - args = [ - oiio_tool_path, "--info", "-i", full_output_path - ] - returncode = execute(args, silent=True) # If the input can read by OIIO then use OIIO method for # conversion otherwise use ffmpeg - if returncode == 0: - self.log.info("Input can be read by OIIO, converting with oiiotool now.") # noqa - thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa - else: + self.log.info("Trying to convert with OIIO") # noqa + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + + if not thumbnail_created: self.log.info("Converting with FFMPEG because input can't be read by OIIO.") # noqa thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa From 0665e5b75963036189fa8dfdae8f91af7e32c174 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:14:07 +0200 Subject: [PATCH 52/57] pass create context after settings --- openpype/pipeline/create/context.py | 2 +- openpype/pipeline/create/creator_plugins.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 12cd9bbc68..9f387ec676 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -873,9 +873,9 @@ class CreateContext: continue creator = creator_class( - self, system_settings, project_settings, + self, self.headless ) creators[creator_identifier] = creator diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 8006d4f4f8..1d09116fb6 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -70,7 +70,7 @@ class BaseCreator: host_name = None def __init__( - self, create_context, system_settings, project_settings, headless=False + self, system_settings, project_settings, create_context, headless=False ): # Reference to CreateContext self.create_context = create_context From 2e8e4f8f54fb026fffbff30e45532056e900f5a5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:30:49 +0200 Subject: [PATCH 53/57] pass project settings before system settings --- openpype/pipeline/create/context.py | 2 +- openpype/pipeline/create/creator_plugins.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9f387ec676..260856286b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -873,8 +873,8 @@ class CreateContext: continue creator = creator_class( - system_settings, project_settings, + system_settings, self, self.headless ) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 1d09116fb6..41847ac322 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -70,7 +70,7 @@ class BaseCreator: host_name = None def __init__( - self, system_settings, project_settings, create_context, headless=False + self, project_settings, system_settings, create_context, headless=False ): # Reference to CreateContext self.create_context = create_context From 56764f638fe9283d33a057d7ff22dfb1c055e992 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:34:08 +0200 Subject: [PATCH 54/57] modified after effects creator init to use new args order --- .../hosts/aftereffects/plugins/create/create_render.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 215c148f37..1019709dd6 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -17,11 +17,8 @@ class RenderCreator(Creator): create_allow_context_change = True - def __init__( - self, create_context, system_settings, project_settings, headless=False - ): - super(RenderCreator, self).__init__(create_context, system_settings, - project_settings, headless) + def __init__(self, project_settings, *args, **kwargs): + super(RenderCreator, self).__init__(project_settings, *args, **kwargs) self._default_variants = (project_settings["aftereffects"] ["create"] ["RenderCreator"] From 4e81bd27a7759d2d4aa5aa4741dcf2881a8c1650 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 10:49:35 +0200 Subject: [PATCH 55/57] create plugins have access to project name --- openpype/pipeline/create/context.py | 4 ++++ openpype/pipeline/create/creator_plugins.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 260856286b..9d870a2234 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -748,6 +748,10 @@ class CreateContext: def host_name(self): return os.environ["AVALON_APP"] + @property + def project_name(self): + return self.dbcon.active_project() + @property def log(self): """Dynamic access to logger.""" diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 41847ac322..b888feb606 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -92,6 +92,12 @@ class BaseCreator: """Family that plugin represents.""" pass + @property + def project_name(self): + """Family that plugin represents.""" + + self.create_context.project_name + @property def log(self): if self._log is None: From bde50a36297ec2c7bc31b3ba64757008d7070ac4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:15:09 +0200 Subject: [PATCH 56/57] use project_name attribute --- openpype/pipeline/create/context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 9d870a2234..7758a660d3 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -843,9 +843,8 @@ class CreateContext: self.plugins_with_defs = plugins_with_defs # Prepare settings - project_name = self.dbcon.Session["AVALON_PROJECT"] system_settings = get_system_settings() - project_settings = get_project_settings(project_name) + project_settings = get_project_settings(self.project_name) # Discover and prepare creators creators = {} From 85485f26d8f7d3b387f67f26e31d9ff075ad817d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 17:15:38 +0200 Subject: [PATCH 57/57] fix return of value --- openpype/pipeline/create/creator_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index b888feb606..4c7b9f8d94 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -96,7 +96,7 @@ class BaseCreator: def project_name(self): """Family that plugin represents.""" - self.create_context.project_name + return self.create_context.project_name @property def log(self):