From bf39fc72a550ce8ac6fc8d4c07bc2359ec9cdfa8 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 19 Jan 2022 18:31:24 -0800 Subject: [PATCH 001/195] Update submodules. --- openpype/modules/default_modules/ftrack/python2_vendor/arrow | 2 +- .../default_modules/ftrack/python2_vendor/ftrack-python-api | 2 +- repos/avalon-core | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/arrow b/openpype/modules/default_modules/ftrack/python2_vendor/arrow index b746fedf72..4c4689c6d9 160000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/arrow +++ b/openpype/modules/default_modules/ftrack/python2_vendor/arrow @@ -1 +1 @@ -Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 +Subproject commit 4c4689c6d97ed2b1f37a67b96c561266c66ee088 diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api index d277f474ab..ddf943a5dc 160000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api +++ b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api @@ -1 +1 @@ -Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e +Subproject commit ddf943a5dcc44d2cecf29896d2075f5198b699aa diff --git a/repos/avalon-core b/repos/avalon-core index ffe9e910f1..047fe1f5bb 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit ffe9e910f1f382e222d457d8e4a8426c41ed43ae +Subproject commit 047fe1f5bba425b8b41c2197961925c1e2ec5fdf From 48fd45ed1aedfba8ac8afdc1f947970c2d76dc85 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Mon, 24 Jan 2022 14:42:58 -0800 Subject: [PATCH 002/195] Revert "Update submodules." This reverts commit bf39fc72a550ce8ac6fc8d4c07bc2359ec9cdfa8. --- openpype/modules/default_modules/ftrack/python2_vendor/arrow | 2 +- .../default_modules/ftrack/python2_vendor/ftrack-python-api | 2 +- repos/avalon-core | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/arrow b/openpype/modules/default_modules/ftrack/python2_vendor/arrow index 4c4689c6d9..b746fedf72 160000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/arrow +++ b/openpype/modules/default_modules/ftrack/python2_vendor/arrow @@ -1 +1 @@ -Subproject commit 4c4689c6d97ed2b1f37a67b96c561266c66ee088 +Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api index ddf943a5dc..d277f474ab 160000 --- a/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api +++ b/openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api @@ -1 +1 @@ -Subproject commit ddf943a5dcc44d2cecf29896d2075f5198b699aa +Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e diff --git a/repos/avalon-core b/repos/avalon-core index 047fe1f5bb..ffe9e910f1 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 047fe1f5bba425b8b41c2197961925c1e2ec5fdf +Subproject commit ffe9e910f1f382e222d457d8e4a8426c41ed43ae From c5aa315c30413cc4e78cf113bff159a00f51164d Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Tue, 29 Mar 2022 11:30:00 -0700 Subject: [PATCH 003/195] Add toggle button for Loaders' family filter widget. --- openpype/tools/libraryloader/app.py | 11 +++++++++-- openpype/tools/loader/app.py | 9 ++++++++- openpype/tools/loader/widgets.py | 6 ++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index b73b415128..6825b3c975 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -84,11 +84,15 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families_filter_view = FamilyListView( dbcon, self.family_config_cache, left_side_splitter ) + families_toggle_button = QtWidgets.QPushButton("Toggle All") + left_side_splitter.addWidget(projects_combobox) left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) - left_side_splitter.setStretchFactor(1, 65) - left_side_splitter.setStretchFactor(2, 35) + left_side_splitter.addWidget(families_toggle_button) + left_side_splitter.setStretchFactor(0, 65) + left_side_splitter.setStretchFactor(1, 30) + left_side_splitter.setStretchFactor(2, 5) # --- Middle part --- # Subsets widget @@ -163,6 +167,9 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families_filter_view.active_changed.connect( self._on_family_filter_change ) + families_toggle_button.clicked.connect( + families_filter_view.toggle_all + ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) subsets_widget.active_changed.connect(self.on_subsetschanged) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 923a1fabdb..68a59e1ccc 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -67,10 +67,14 @@ class LoaderWindow(QtWidgets.QDialog): families_filter_view = FamilyListView( io, self.family_config_cache, left_side_splitter ) + families_toggle_button = QtWidgets.QPushButton("Toggle All") + left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) + left_side_splitter.addWidget(families_toggle_button) left_side_splitter.setStretchFactor(0, 65) - left_side_splitter.setStretchFactor(1, 35) + left_side_splitter.setStretchFactor(1, 30) + left_side_splitter.setStretchFactor(2, 5) # --- Middle part --- # Subsets widget @@ -147,6 +151,9 @@ class LoaderWindow(QtWidgets.QDialog): families_filter_view.active_changed.connect( self._on_family_filter_change ) + families_toggle_button.clicked.connect( + families_filter_view.toggle_all + ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) subsets_widget.active_changed.connect(self.on_subsetschanged) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..b5f1df1e36 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1070,6 +1070,12 @@ class FamilyListView(QtWidgets.QListView): def set_all_checked(self): self._set_checkstates(True, self._get_all_indexes()) + def toggle_all(self): + if self.get_enabled_families(): + self.set_all_unchecked() + else: + self.set_all_checked() + def _get_all_indexes(self): indexes = [] model = self._family_model From ea84c18d2f1d6a1c11fca506512a4ce7e164a114 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Tue, 29 Mar 2022 11:53:13 -0700 Subject: [PATCH 004/195] Fix left splitter indexing. --- openpype/tools/libraryloader/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 6825b3c975..59f4aa4d7a 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -90,9 +90,9 @@ class LibraryLoaderWindow(QtWidgets.QDialog): left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) left_side_splitter.addWidget(families_toggle_button) - left_side_splitter.setStretchFactor(0, 65) - left_side_splitter.setStretchFactor(1, 30) - left_side_splitter.setStretchFactor(2, 5) + left_side_splitter.setStretchFactor(1, 65) + left_side_splitter.setStretchFactor(2, 30) + left_side_splitter.setStretchFactor(3, 5) # --- Middle part --- # Subsets widget From d636a4144dd4cfff4517dd6b39f6d9524160ed0d Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 17 May 2022 12:35:06 +0300 Subject: [PATCH 005/195] Replace plugin name with more descriptive one. --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- openpype/settings/defaults/project_settings/global.json | 2 +- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index ae29f8b95b..84cdd186dc 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -15,7 +15,7 @@ from openpype.lib import ( import shutil -class ExtractJpegEXR(pyblish.api.InstancePlugin): +class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" label = "Extract Jpeg EXR" diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 7b223798f1..cedd0eed99 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -33,7 +33,7 @@ "enabled": false, "profiles": [] }, - "ExtractJpegEXR": { + "ExtractThumbnail": { "enabled": true, "ffmpeg_args": { "input": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 061874e31c..a3cbf0cfcd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -126,8 +126,8 @@ "type": "dict", "collapsible": true, "checkbox_key": "enabled", - "key": "ExtractJpegEXR", - "label": "ExtractJpegEXR", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", "is_group": true, "children": [ { From 78c70819156fc0ad6896619abc8b4d2cc017e589 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 17 May 2022 12:38:04 +0300 Subject: [PATCH 006/195] Change label name. --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 84cdd186dc..11dfca8eb2 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -18,7 +18,7 @@ import shutil class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" - label = "Extract Jpeg EXR" + label = "Extract Thumbnail" order = pyblish.api.ExtractorOrder families = [ "imagesequence", "render", "render2d", From 55cdecd95137a197664c9ed6307d746d2d7e95b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:24:00 +0200 Subject: [PATCH 007/195] Implement support for Redshift Proxy export in Houdini Tested with Houdini 19.0.589 + Redshift 3.5.01 --- openpype/hosts/houdini/api/lib.py | 2 + .../plugins/create/create_redshift_proxy.py | 49 +++++++++++++++++++ .../houdini/plugins/publish/collect_frames.py | 2 +- .../plugins/publish/collect_output_node.py | 3 ++ .../plugins/publish/extract_redshift_proxy.py | 48 ++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_redshift_proxy.py create mode 100644 openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 603519069a..96ca019f8f 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -130,6 +130,8 @@ def get_output_parameter(node): elif node_type == "arnold": if node.evalParm("ar_ass_export_enable"): return node.parm("ar_ass_file") + elif node_type == "Redshift_Proxy_Output": + return node.parm("RS_archive_file") raise TypeError("Node type '%s' not supported" % node_type) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py new file mode 100644 index 0000000000..52c81240fa --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py @@ -0,0 +1,49 @@ +import hou +from openpype.hosts.houdini.api import plugin + + +class CreateRedshiftProxy(plugin.Creator): + """Redshift Proxy""" + + label = "Redshift Proxy" + family = "redshiftproxy" + icon = "magic" + + def __init__(self, *args, **kwargs): + super(CreateRedshiftProxy, self).__init__(*args, **kwargs) + + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) + + # Redshift provides a `Redshift_Proxy_Output` node type which shows + # a limited set of parameters by default and is set to extract a + # Redshift Proxy. However when "imprinting" extra parameters needed + # for OpenPype it starts showing all its parameters again. It's unclear + # why this happens. + # TODO: Somehow enforce so that it only shows the original limited + # attributes of the Redshift_Proxy_Output node type + self.data.update({"node_type": "Redshift_Proxy_Output"}) + + def _process(self, instance): + """Creator main entry point. + + Args: + instance (hou.Node): Created Houdini instance. + + """ + parms = { + "RS_archive_file": '$HIP/pyblish/`chs("subset")`.$F4.rs', + } + + if self.nodes: + node = self.nodes[0] + path = node.path() + parms["RS_archive_sopPath"] = path + + instance.setParms(parms) + + # Lock some Avalon attributes + to_lock = ["family", "id"] + for name in to_lock: + parm = instance.parm(name) + parm.lock(True) diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index fac40b4d2b..9bd43d8a09 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -20,7 +20,7 @@ class CollectFrames(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder label = "Collect Frames" - families = ["vdbcache", "imagesequence", "ass"] + families = ["vdbcache", "imagesequence", "ass", "redshiftproxy"] def process(self, instance): diff --git a/openpype/hosts/houdini/plugins/publish/collect_output_node.py b/openpype/hosts/houdini/plugins/publish/collect_output_node.py index 938ee81cc3..0130c0a8da 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_output_node.py +++ b/openpype/hosts/houdini/plugins/publish/collect_output_node.py @@ -12,6 +12,7 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): "imagesequence", "usd", "usdrender", + "redshiftproxy" ] hosts = ["houdini"] @@ -54,6 +55,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): else: out_node = node.parm("loppath").evalAsNode() + elif node_type == "Redshift_Proxy_Output": + out_node = node.parm("RS_archive_sopPath").evalAsNode() else: raise ValueError( "ROP node type '%s' is" " not supported." % node_type diff --git a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py new file mode 100644 index 0000000000..eb7e0d5677 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py @@ -0,0 +1,48 @@ +import os + +import pyblish.api +import openpype.api +from openpype.hosts.houdini.api.lib import render_rop + + +class ExtractRedshiftProxy(openpype.api.Extractor): + + order = pyblish.api.ExtractorOrder + 0.1 + label = "Extract Redshift Proxy" + families = ["redshiftproxy"] + hosts = ["houdini"] + + def process(self, instance): + + ropnode = instance[0] + + # Get the filename from the filename parameter + # `.evalParm(parameter)` will make sure all tokens are resolved + output = ropnode.evalParm("RS_archive_file") + staging_dir = os.path.normpath(os.path.dirname(output)) + instance.data["stagingDir"] = staging_dir + file_name = os.path.basename(output) + + self.log.info("Writing Redshift Proxy '%s' to '%s'" % (file_name, + staging_dir)) + + render_rop(ropnode) + + output = instance.data["frames"] + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + "name": "rs", + "ext": "rs", + "files": output, + "stagingDir": staging_dir, + } + + # A single frame may also be rendered without start/end frame. + if "frameStart" in instance.data and "frameEnd" in instance.data: + representation["frameStart"] = instance.data["frameStart"] + representation["frameEnd"] = instance.data["frameEnd"] + + instance.data["representations"].append(representation) \ No newline at end of file From be1453b4e7971ad3f20f87de0167865b38746340 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:27:40 +0200 Subject: [PATCH 008/195] Fix new line --- .../hosts/houdini/plugins/publish/extract_redshift_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py index eb7e0d5677..c754d60c59 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/publish/extract_redshift_proxy.py @@ -45,4 +45,4 @@ class ExtractRedshiftProxy(openpype.api.Extractor): representation["frameStart"] = instance.data["frameStart"] representation["frameEnd"] = instance.data["frameEnd"] - instance.data["representations"].append(representation) \ No newline at end of file + instance.data["representations"].append(representation) From 0f84e7d92a661f99e347bedb110928f014b37eec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:28:40 +0200 Subject: [PATCH 009/195] Remove unused import --- openpype/hosts/houdini/plugins/create/create_redshift_proxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py index 52c81240fa..da4d80bf2b 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_proxy.py @@ -1,4 +1,3 @@ -import hou from openpype.hosts.houdini.api import plugin From 606ef6415d229e7ee29f2776749fc33b288e0dd8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:42:55 +0200 Subject: [PATCH 010/195] Fix popping of `handles` --- openpype/hosts/maya/plugins/create/create_yeti_cache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_yeti_cache.py b/openpype/hosts/maya/plugins/create/create_yeti_cache.py index 86e13b95b2..e8c3203f21 100644 --- a/openpype/hosts/maya/plugins/create/create_yeti_cache.py +++ b/openpype/hosts/maya/plugins/create/create_yeti_cache.py @@ -22,7 +22,8 @@ class CreateYetiCache(plugin.Creator): # Add animation data without step and handles anim_data = lib.collect_animation_data() anim_data.pop("step") - anim_data.pop("handles") + anim_data.pop("handleStart") + anim_data.pop("handleEnd") self.data.update(anim_data) # Add samples From e4d54aaa7a8304b426b67b707565451ce5e5599d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:44:27 +0200 Subject: [PATCH 011/195] Fix invalid refactored usage --- openpype/hosts/maya/plugins/publish/extract_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index d12567a55a..6b5054a198 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -167,7 +167,7 @@ class ExtractYetiRig(openpype.api.Extractor): resources = instance.data.get("resources", {}) with disconnect_plugs(settings, members): with yetigraph_attribute_values(resources_dir, resources): - with maya.attribute_values(attr_value): + with lib.attribute_values(attr_value): cmds.select(nodes, noExpand=True) cmds.file(maya_path, force=True, From 0f97c9f3d388a8973dce13e68fb9d263c0441b48 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:50:41 +0200 Subject: [PATCH 012/195] Time values are required for exporting without errors --- openpype/hosts/maya/plugins/publish/extract_yeti_cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index 0d85708789..b0a60b77f4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -29,9 +29,9 @@ class ExtractYetiCache(openpype.api.Extractor): data_file = os.path.join(dirname, "yeti.fursettings") # Collect information for writing cache - start_frame = instance.data.get("frameStartHandle") - end_frame = instance.data.get("frameEndHandle") - preroll = instance.data.get("preroll") + start_frame = instance.data["frameStartHandle"] + end_frame = instance.data["frameEndHandle"] + preroll = instance.data["preroll"] if preroll > 0: start_frame -= preroll From 6fb9bb1558401273e2437e3e50a18877c296851e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:54:41 +0200 Subject: [PATCH 013/195] Allow empty input_SET --- openpype/hosts/maya/plugins/publish/extract_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index 6b5054a198..f981c4fe50 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -157,7 +157,7 @@ class ExtractYetiRig(openpype.api.Extractor): input_set = next(i for i in instance if i == "input_SET") # Get all items - set_members = cmds.sets(input_set, query=True) + set_members = cmds.sets(input_set, query=True) or [] set_members += cmds.listRelatives(set_members, allDescendents=True, fullPath=True) or [] From 2a2dbd243408f4eed8863fd86967a39080b4931d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 17 May 2022 17:56:45 +0200 Subject: [PATCH 014/195] Force required frame values for a single frame cache extract for `yetiRig` family. --- .../hosts/maya/plugins/publish/collect_yeti_rig.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py b/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py index 029432223b..bc15edd9e0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py @@ -43,11 +43,12 @@ class CollectYetiRig(pyblish.api.InstancePlugin): instance.data["resources"] = yeti_resources - # Force frame range for export - instance.data["frameStart"] = cmds.playbackOptions( - query=True, animationStartTime=True) - instance.data["frameEnd"] = cmds.playbackOptions( - query=True, animationStartTime=True) + # Force frame range for yeti cache export for the rig + start = cmds.playbackOptions(query=True, animationStartTime=True) + for key in ["frameStart", "frameEnd", + "frameStartHandle", "frameEndHandle"]: + instance.data[key] = start + instance.data["preroll"] = 0 def collect_input_connections(self, instance): """Collect the inputs for all nodes in the input_SET""" From 8f7428dd95d7fe461151821f901fa3059ff5309a Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 18 May 2022 15:07:53 +0300 Subject: [PATCH 015/195] Add OIIO --- openpype/plugins/publish/extract_jpeg_exr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 11dfca8eb2..2d3ad1e8a8 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -3,6 +3,7 @@ import os import pyblish.api from openpype.lib import ( get_ffmpeg_tool_path, + get_oiio_tools_path, run_subprocess, path_to_subprocess_arg, @@ -29,6 +30,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # presetable attribute ffmpeg_args = None + oiio_args = None def process(self, instance): self.log.info("subset {}".format(instance.data['subset'])) @@ -119,7 +121,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) - + # run subprocess self.log.debug("{}".format(subprocess_command)) try: # temporary until oiiotool is supported cross platform From a64b3f5b7696534b3384a21143be9842397da482 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 18 May 2022 16:31:14 -0700 Subject: [PATCH 016/195] Revert "Add toggle button for Loaders' family filter widget." This reverts commit c5aa315c30413cc4e78cf113bff159a00f51164d. --- openpype/tools/libraryloader/app.py | 9 +-------- openpype/tools/loader/app.py | 9 +-------- openpype/tools/loader/widgets.py | 6 ------ 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 99dfce36e3..7fda6bd6f9 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -82,15 +82,11 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families_filter_view = FamilyListView( dbcon, self.family_config_cache, left_side_splitter ) - families_toggle_button = QtWidgets.QPushButton("Toggle All") - left_side_splitter.addWidget(projects_combobox) left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) - left_side_splitter.addWidget(families_toggle_button) left_side_splitter.setStretchFactor(1, 65) - left_side_splitter.setStretchFactor(2, 30) - left_side_splitter.setStretchFactor(3, 5) + left_side_splitter.setStretchFactor(2, 35) # --- Middle part --- # Subsets widget @@ -165,9 +161,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families_filter_view.active_changed.connect( self._on_family_filter_change ) - families_toggle_button.clicked.connect( - families_filter_view.toggle_all - ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) subsets_widget.active_changed.connect(self.on_subsetschanged) diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index 57baeae061..bb589c199d 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -71,14 +71,10 @@ class LoaderWindow(QtWidgets.QDialog): families_filter_view = FamilyListView( legacy_io, self.family_config_cache, left_side_splitter ) - families_toggle_button = QtWidgets.QPushButton("Toggle All") - left_side_splitter.addWidget(assets_widget) left_side_splitter.addWidget(families_filter_view) - left_side_splitter.addWidget(families_toggle_button) left_side_splitter.setStretchFactor(0, 65) - left_side_splitter.setStretchFactor(1, 30) - left_side_splitter.setStretchFactor(2, 5) + left_side_splitter.setStretchFactor(1, 35) # --- Middle part --- # Subsets widget @@ -159,9 +155,6 @@ class LoaderWindow(QtWidgets.QDialog): families_filter_view.active_changed.connect( self._on_family_filter_change ) - families_toggle_button.clicked.connect( - families_filter_view.toggle_all - ) assets_widget.selection_changed.connect(self.on_assetschanged) assets_widget.refresh_triggered.connect(self.on_assetschanged) subsets_widget.active_changed.connect(self.on_subsetschanged) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index b5f1df1e36..42fb62b632 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1070,12 +1070,6 @@ class FamilyListView(QtWidgets.QListView): def set_all_checked(self): self._set_checkstates(True, self._get_all_indexes()) - def toggle_all(self): - if self.get_enabled_families(): - self.set_all_unchecked() - else: - self.set_all_checked() - def _get_all_indexes(self): indexes = [] model = self._family_model From feb07912c50728054a5078c23a870f445969f947 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 May 2022 18:27:50 +0200 Subject: [PATCH 017/195] Fix yeti publish and load for caches --- .../maya/plugins/load/load_yeti_cache.py | 244 +++++++++--------- .../plugins/publish/extract_yeti_cache.py | 38 +-- .../maya/plugins/publish/extract_yeti_rig.py | 4 +- 3 files changed, 146 insertions(+), 140 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index fb903785ae..9752188551 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -1,15 +1,13 @@ import os import json import re -import glob from collections import defaultdict -from pprint import pprint +import clique from maya import cmds from openpype.api import get_project_settings from openpype.pipeline import ( - legacy_io, load, get_representation_path ) @@ -17,7 +15,15 @@ from openpype.hosts.maya.api import lib from openpype.hosts.maya.api.pipeline import containerise +def set_attribute(node, attr, value): + """Wrapper of set attribute which ignores None values""" + if value is None: + return + lib.set_attribute(node, attr, value) + + class YetiCacheLoader(load.LoaderPlugin): + """Load Yeti Cache with one or more Yeti nodes""" families = ["yeticache", "yetiRig"] representations = ["fur"] @@ -28,6 +34,16 @@ class YetiCacheLoader(load.LoaderPlugin): color = "orange" def load(self, context, name=None, namespace=None, data=None): + """Loads a .fursettings file defining how to load .fur sequences + + A single yeticache or yetiRig can have more than a single pgYetiMaya + nodes and thus load more than a single yeti.fur sequence. + + The .fursettings file defines what the node names should be and also + what "cbId" attribute they should receive to match the original source + and allow published looks to also work for Yeti rigs and its caches. + + """ try: family = context["representation"]["context"]["family"] @@ -43,22 +59,11 @@ class YetiCacheLoader(load.LoaderPlugin): if not cmds.pluginInfo("pgYetiMaya", query=True, loaded=True): cmds.loadPlugin("pgYetiMaya", quiet=True) - # Get JSON - fbase = re.search(r'^(.+)\.(\d+|#+)\.fur', self.fname) - if not fbase: - raise RuntimeError('Cannot determine fursettings file path') - settings_fname = "{}.fursettings".format(fbase.group(1)) - with open(settings_fname, "r") as fp: - fursettings = json.load(fp) - - # Check if resources map exists - # Get node name from JSON - if "nodes" not in fursettings: - raise RuntimeError("Encountered invalid data, expect 'nodes' in " - "fursettings.") - - node_data = fursettings["nodes"] - nodes = self.create_nodes(namespace, node_data) + # Create Yeti cache nodes according to settings + settings = self.read_settings(self.fname) + nodes = [] + for node in settings["nodes"]: + nodes.extend(self.create_node(namespace, node)) group_name = "{}:{}".format(namespace, name) group_node = cmds.group(nodes, name=group_name) @@ -111,28 +116,14 @@ class YetiCacheLoader(load.LoaderPlugin): def update(self, container, representation): - legacy_io.install() namespace = container["namespace"] container_node = container["objectName"] - fur_settings = legacy_io.find_one( - {"parent": representation["parent"], "name": "fursettings"} - ) - - pprint({"parent": representation["parent"], "name": "fursettings"}) - pprint(fur_settings) - assert fur_settings is not None, ( - "cannot find fursettings representation" - ) - - settings_fname = get_representation_path(fur_settings) path = get_representation_path(representation) - # Get all node data - with open(settings_fname, "r") as fp: - settings = json.load(fp) + settings = self.read_settings(path) # Collect scene information of asset - set_members = cmds.sets(container["objectName"], query=True) + set_members = lib.get_container_members(container) container_root = lib.get_container_transforms(container, members=set_members, root=True) @@ -147,7 +138,7 @@ class YetiCacheLoader(load.LoaderPlugin): # Re-assemble metadata with cbId as keys meta_data_lookup = {n["cbId"]: n for n in settings["nodes"]} - # Compare look ups and get the nodes which ar not relevant any more + # Delete nodes by "cbId" that are not in the updated version to_delete_lookup = {cb_id for cb_id in scene_lookup.keys() if cb_id not in meta_data_lookup} if to_delete_lookup: @@ -163,25 +154,18 @@ class YetiCacheLoader(load.LoaderPlugin): fullPath=True) or [] to_remove.extend(shapes + transforms) - # Remove id from look uop + # Remove id from lookup scene_lookup.pop(_id, None) cmds.delete(to_remove) - # replace frame in filename with %04d - RE_frame = re.compile(r"(\d+)(\.fur)$") - file_name = re.sub(RE_frame, r"%04d\g<2>", os.path.basename(path)) - for cb_id, data in meta_data_lookup.items(): - - # Update cache file name - data["attrs"]["cacheFileName"] = os.path.join( - os.path.dirname(path), file_name) + for cb_id, node_settings in meta_data_lookup.items(): if cb_id not in scene_lookup: - + # Create new nodes self.log.info("Creating new nodes ..") - new_nodes = self.create_nodes(namespace, [data]) + new_nodes = self.create_node(namespace, node_settings) cmds.sets(new_nodes, addElement=container_node) cmds.parent(new_nodes, container_root) @@ -218,14 +202,8 @@ class YetiCacheLoader(load.LoaderPlugin): children=True) yeti_node = yeti_nodes[0] - for attr, value in data["attrs"].items(): - # handle empty attribute strings. Those are reported - # as None, so their type is NoneType and this is not - # supported on attributes in Maya. We change it to - # empty string. - if value is None: - value = "" - lib.set_attribute(attr, value, yeti_node) + for attr, value in node_settings["attrs"].items(): + set_attribute(attr, value, yeti_node) cmds.setAttr("{}.representation".format(container_node), str(representation["_id"]), @@ -235,7 +213,6 @@ class YetiCacheLoader(load.LoaderPlugin): self.update(container, representation) # helper functions - def create_namespace(self, asset): """Create a unique namespace Args: @@ -253,100 +230,129 @@ class YetiCacheLoader(load.LoaderPlugin): return namespace - def validate_cache(self, filename, pattern="%04d"): - """Check if the cache has more than 1 frame + def get_cache_node_filepath(self, root, node_name): + """Get the cache file path for one of the yeti nodes. - All caches with more than 1 frame need to be called with `%04d` - If the cache has only one frame we return that file name as we assume + All caches with more than 1 frame need cache file name set with `%04d` + If the cache has only one frame we return the file name as we assume it is a snapshot. + This expects the files to be named after the "node name" through + exports with in Yeti. + Args: - filename(str) - pattern(str) + root(str): Folder containing cache files to search in. + node_name(str): Node name to search cache files for Returns: - str + str: Cache file path value needed for cacheFileName attribute """ - glob_pattern = filename.replace(pattern, "*") + name = node_name.replace(":", "_") + pattern = r"^({name})(\.[0-4]+)?(\.fur)$".format(name=re.escape(name)) - escaped = re.escape(filename) - re_pattern = escaped.replace(pattern, "-?[0-9]+") - - files = glob.glob(glob_pattern) - files = [str(f) for f in files if re.match(re_pattern, f)] + files = [fname for fname in os.listdir(root) if re.match(pattern, + fname)] + if not files: + self.log.error("Could not find cache files for '{}' " + "with pattern {}".format(node_name, pattern)) + return if len(files) == 1: - return files[0] - elif len(files) == 0: - self.log.error("Could not find cache files for '%s'" % filename) + # Single file + return os.path.join(root, files[0]) - return filename + # Get filename for the sequence with padding + collections, remainder = clique.assemble(files) + assert not remainder, "This is a bug" + assert len(collections) == 1, "This is a bug" + collection = collections[0] - def create_nodes(self, namespace, settings): + # Assume padding from the first frame since clique returns 0 if the + # sequence contains no files padded with a zero at the start (e.g. + # a sequence starting at 1001) + padding = len(str(collection.indexes[0])) + + fname = "{head}%0{padding}d{tail}".format(collection.head, + padding, + collection.tail) + + return os.path.join(root, fname) + + def create_node(self, namespace, node_settings): """Create nodes with the correct namespace and settings Args: namespace(str): namespace - settings(list): list of dictionaries + node_settings(dict): Single "nodes" entry from .fursettings file. Returns: - list + list: Created nodes """ - nodes = [] - for node_settings in settings: - # Create pgYetiMaya node - original_node = node_settings["name"] - node_name = "{}:{}".format(namespace, original_node) - yeti_node = cmds.createNode("pgYetiMaya", name=node_name) + # Get original names and ids + orig_transform_name = node_settings["transform"]["name"] + orig_shape_name = node_settings["name"] - # Create transform node - transform_node = node_name.rstrip("Shape") + # Add namespace + transform_name = "{}:{}".format(namespace, orig_transform_name) + shape_name = "{}:{}".format(namespace, orig_shape_name) - lib.set_id(transform_node, node_settings["transform"]["cbId"]) - lib.set_id(yeti_node, node_settings["cbId"]) + # Create pgYetiMaya node + transform_node = cmds.createNode("transform", + name=transform_name) + yeti_node = cmds.createNode("pgYetiMaya", + name=shape_name, + parent=transform_node) - nodes.extend([transform_node, yeti_node]) + lib.set_id(transform_node, node_settings["transform"]["cbId"]) + lib.set_id(yeti_node, node_settings["cbId"]) - # Ensure the node has no namespace identifiers - attributes = node_settings["attrs"] + nodes.extend([transform_node, yeti_node]) - # Check if cache file name is stored + # Update attributes with defaults + attributes = node_settings["attrs"] + attributes.update({ + "viewportDensity": 0.1, + "verbosity": 2, + "fileMode": 1, - # get number of # in path and convert it to C prinf format - # like %04d expected by Yeti - fbase = re.search(r'^(.+)\.(\d+|#+)\.fur', self.fname) - if not fbase: - raise RuntimeError('Cannot determine file path') - padding = len(fbase.group(2)) - if "cacheFileName" not in attributes: - cache = "{}.%0{}d.fur".format(fbase.group(1), padding) + # Fix render stats, like Yeti's own + # ../scripts/pgYetiNode.mel script + "visibleInReflections": True, + "visibleInRefractions": True + }) - self.validate_cache(cache) - attributes["cacheFileName"] = cache + # Apply attributes to pgYetiMaya node + for attr, value in attributes.items(): + set_attribute(attr, value, yeti_node) - # Update attributes with requirements - attributes.update({"viewportDensity": 0.1, - "verbosity": 2, - "fileMode": 1}) - - # Apply attributes to pgYetiMaya node - for attr, value in attributes.items(): - if value is None: - continue - lib.set_attribute(attr, value, yeti_node) - - # Fix for : YETI-6 - # Fixes the render stats (this is literally taken from Perigrene's - # ../scripts/pgYetiNode.mel script) - cmds.setAttr("{}.visibleInReflections".format(yeti_node), True) - cmds.setAttr("{}.visibleInRefractions".format(yeti_node), True) - - # Connect to the time node - cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) + # Connect to the time node + cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) return nodes + + def read_settings(self, path): + """Read .fursettings file and compute some additional attributes""" + + with open(path, "r") as fp: + fur_settings = json.load(fp) + + if "nodes" not in fur_settings: + raise RuntimeError("Encountered invalid data, " + "expected 'nodes' in fursettings.") + + # Compute the cache file name values we want to set for the nodes + root = os.path.dirname(path) + for node in fur_settings["nodes"]: + cache_filename = self.get_cache_node_filepath( + root=root, node_name=node["name"]) + + attrs = node.get("attrs", {}) # allow 'attrs' to not exist + attrs["cacheFileName"] = cache_filename + node["attrs"] = attrs + + return fur_settings diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index b0a60b77f4..cf6db00e9a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -25,9 +25,6 @@ class ExtractYetiCache(openpype.api.Extractor): # Define extract output file path dirname = self.staging_dir(instance) - # Yeti related staging dirs - data_file = os.path.join(dirname, "yeti.fursettings") - # Collect information for writing cache start_frame = instance.data["frameStartHandle"] end_frame = instance.data["frameEndHandle"] @@ -57,32 +54,35 @@ class ExtractYetiCache(openpype.api.Extractor): cache_files = [x for x in os.listdir(dirname) if x.endswith(".fur")] self.log.info("Writing metadata file") - settings = instance.data.get("fursettings", None) - if settings is not None: - with open(data_file, "w") as fp: - json.dump(settings, fp, ensure_ascii=False) + settings = instance.data["fursettings"] + fursettings_path = os.path.join(dirname, "yeti.fursettings") + with open(fursettings_path, "w") as fp: + json.dump(settings, fp, ensure_ascii=False) # build representations if "representations" not in instance.data: instance.data["representations"] = [] self.log.info("cache files: {}".format(cache_files[0])) - instance.data["representations"].append( - { - 'name': 'fur', - 'ext': 'fur', - 'files': cache_files[0] if len(cache_files) == 1 else cache_files, - 'stagingDir': dirname, - 'frameStart': int(start_frame), - 'frameEnd': int(end_frame) - } - ) + + # Workaround: We do not explicitly register these files with the + # representation solely so that we can write multiple sequences + # a single Subset without renaming - it's a bit of a hack + # TODO: Implement better way to manage this sort of integration + if 'transfers' not in instance.data: + instance.data['transfers'] = [] + + publish_dir = instance.data["publishDir"] + for cache_filename in cache_files: + src = os.path.join(dirname, cache_filename) + dst = os.path.join(publish_dir, os.path.basename(cache_filename)) + instance.data['transfers'].append([src, dst]) instance.data["representations"].append( { - 'name': 'fursettings', + 'name': 'fur', 'ext': 'fursettings', - 'files': os.path.basename(data_file), + 'files': os.path.basename(fursettings_path), 'stagingDir': dirname } ) diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index f981c4fe50..6e21bffa4e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -124,8 +124,8 @@ class ExtractYetiRig(openpype.api.Extractor): settings_path = os.path.join(dirname, "yeti.rigsettings") # Yeti related staging dirs - maya_path = os.path.join( - dirname, "yeti_rig.{}".format(self.scene_type)) + maya_path = os.path.join(dirname, + "yeti_rig.{}".format(self.scene_type)) self.log.info("Writing metadata file") From 87f7fa5470ed5fb74433633810bf379d92a3b58e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 May 2022 18:32:02 +0200 Subject: [PATCH 018/195] Format name correctly for sequences on load --- openpype/hosts/maya/plugins/load/load_yeti_cache.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index 9752188551..8435ba2493 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -269,15 +269,8 @@ class YetiCacheLoader(load.LoaderPlugin): assert len(collections) == 1, "This is a bug" collection = collections[0] - # Assume padding from the first frame since clique returns 0 if the - # sequence contains no files padded with a zero at the start (e.g. - # a sequence starting at 1001) - padding = len(str(collection.indexes[0])) - - fname = "{head}%0{padding}d{tail}".format(collection.head, - padding, - collection.tail) - + # Formats name as {head}%d{tail} like cache.%04d.fur + fname = collection.format("{head}{padding}{tail}") return os.path.join(root, fname) def create_node(self, namespace, node_settings): From 8453f9710f1fb305d917723eeee4cfea5c007062 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 06:27:52 +0300 Subject: [PATCH 019/195] Add profiles handling in schema aand for extractor --- openpype/plugins/publish/extract_jpeg_exr.py | 27 ++++++++++++- .../defaults/project_settings/deadline.json | 2 +- .../defaults/project_settings/global.json | 12 +++++- .../schemas/schema_global_publish.json | 38 +++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 2d3ad1e8a8..c0363caf5b 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,10 +1,15 @@ import os import pyblish.api +from openpype.pipeline import ( + legacy_io, + KnownPublishError +) from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, + filter_profiles, run_subprocess, path_to_subprocess_arg, @@ -33,6 +38,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_args = None def process(self, instance): + task_name = instance.data.get("task", legacy_io.Session["AVALON_TASK"]) + host_name = legacy_io.Session["AVALON_APP"] + family = instance.data["family"] + filtering_criteria = { + "hosts": host_name, + "families": family, + "tasks": task_name + } + profile = filter_profiles(self.profiles, filtering_criteria, + logger=self.log) + if not profile: + return + + oiio_path = get_oiio_tools_path() + # Raise an exception when oiiotool is not available + + if not os.path.exists(oiio_path): + KnownPublishError( + "OpenImageIO tool is not available on this machine." + ) self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -121,7 +146,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) - + # run subprocess self.log.debug("{}".format(subprocess_command)) try: # temporary until oiiotool is supported cross platform diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index f0b2a7e555..5c5a14bf21 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index cedd0eed99..4ad56d8086 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -40,7 +40,17 @@ "-apply_trc gamma22" ], "output": [] - } + }, + "thumbnail_extraction_profiles": [ + { + "families": [ + "" + ], + "hosts": [], + "task_types": [], + "tasks": [] + } + ] }, "ExtractReview": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index a3cbf0cfcd..4149c99348 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -152,6 +152,44 @@ "label": "FFmpeg output arguments" } ] + }, + { + "type": "list", + "key": "thumbnail_extraction_profiles", + "label": "Thumbnail Extraction profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "label", + "label": "" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "tasks", + "label": "Task names", + "type": "list", + "object_type": "text" + } + ] + } } ] }, From 0040f02ef71bd6156139c184170041d94f146a40 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 06:58:22 +0300 Subject: [PATCH 020/195] Style fix --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c0363caf5b..0ad39aa720 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -53,7 +53,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_path = get_oiio_tools_path() # Raise an exception when oiiotool is not available - + if not os.path.exists(oiio_path): KnownPublishError( "OpenImageIO tool is not available on this machine." From 8e0b0156cad9fdf8d2c439bb4dc4078f50d468df Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 12:24:20 +0300 Subject: [PATCH 021/195] Fix comment typo --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index adb9bb2c3a..ee9a0f08de 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -533,7 +533,7 @@ def convert_input_paths_for_ffmpeg( output_dir, logger=None ): - """Contert source file to format supported in ffmpeg. + """Convert source file to format supported in ffmpeg. Currently can convert only exrs. The input filepaths should be files with same type. Information about input is loaded only from first found From 043ba41de7ba619be04b98e321deaa16487407d5 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 25 May 2022 12:59:53 +0300 Subject: [PATCH 022/195] Remove whitespace --- openpype/plugins/publish/extract_jpeg_exr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 0ad39aa720..070e9e1e08 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -144,7 +144,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # output file jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) # run subprocess From c50cab558c80664e332d9831798d2754df1b89e1 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 02:22:33 +0300 Subject: [PATCH 023/195] initial refactoring --- openpype/plugins/publish/extract_jpeg_exr.py | 191 +++++++++++-------- 1 file changed, 107 insertions(+), 84 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 070e9e1e08..52a678e9ef 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -8,6 +8,7 @@ from openpype.pipeline import ( from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, + is_oiio_supported, filter_profiles, run_subprocess, @@ -89,99 +90,121 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - - do_convert = should_convert_for_ffmpeg(full_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - convert_dir = None - if do_convert: - convert_dir = get_transcode_temp_directory() - filename = os.path.basename(full_input_path) - convert_input_paths_for_ffmpeg( - [full_input_path], - convert_dir, - self.log - ) - full_input_path = os.path.join(convert_dir, filename) - - filename = os.path.splitext(input_file)[0] - if not filename.endswith('.'): - filename += "." - jpeg_file = filename + "jpg" - full_output_path = os.path.join(stagingdir, jpeg_file) - - self.log.info("output {}".format(full_output_path)) - - ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - ffmpeg_args = self.ffmpeg_args or {} - - jpeg_items = [] - jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) - # override file if already exists - jpeg_items.append("-y") - # use same input args like with mov - jpeg_items.extend(ffmpeg_args.get("input") or []) - # input file - jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) - )) - # output arguments from presets - jpeg_items.extend(ffmpeg_args.get("output") or []) - - # If its a movie file, we just want one frame. + # If it's a movie file, use ffmpeg if repre["ext"] == "mov": + do_convert = should_convert_for_ffmpeg(full_input_path) + # If result is None the requirement of conversion can't be + # determined + if do_convert is None: + self.log.info(( + "Can't determine if representation requires conversion." + " Skipped." + )) + continue + + # Do conversion if needed + # - change staging dir of source representation + # - must be set back after output definitions processing + convert_dir = None + if do_convert: + convert_dir = get_transcode_temp_directory() + filename = os.path.basename(full_input_path) + convert_input_paths_for_ffmpeg( + [full_input_path], + convert_dir, + self.log + ) + full_input_path = os.path.join(convert_dir, filename) + + filename = os.path.splitext(input_file)[0] + if not filename.endswith('.'): + filename += "." + jpeg_file = filename + "jpg" + full_output_path = os.path.join(stagingdir, jpeg_file) + + self.log.info("output {}".format(full_output_path)) + + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + ffmpeg_args = self.ffmpeg_args or {} + + jpeg_items = [] + jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) + # override file if already exists + jpeg_items.append("-y") + # use same input args like with mov + jpeg_items.extend(ffmpeg_args.get("input") or []) + # input file + jpeg_items.append("-i {}".format( + path_to_subprocess_arg(full_input_path) + )) + # output arguments from presets + jpeg_items.extend(ffmpeg_args.get("output") or []) + # we just want one frame from movie files jpeg_items.append("-vframes 1") - # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) + # output file + jpeg_items.append(path_to_subprocess_arg(full_output_path)) + subprocess_command = " ".join(jpeg_items) - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!!!" + # run subprocess + self.log.debug("{}".format(subprocess_command)) + try: # temporary until oiiotool is supported cross platform + run_subprocess( + subprocess_command, shell=True, logger=self.log ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise + except RuntimeError as exp: + if "Compression" in str(exp): + self.log.debug( + "Unsupported compression on input files. Skipping!" + ) + return + self.log.warning("Conversion crashed", exc_info=True) + raise - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) - # Cleanup temp folder - if convert_dir is not None and os.path.exists(convert_dir): - shutil.rmtree(convert_dir) + # Cleanup temp folder + if convert_dir is not None and os.path.exists(convert_dir): + shutil.rmtree(convert_dir) - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which representation - # will be thumbnail created - break + elif repre["ext"] == "exr": + oiio_support = is_oiio_supported() + + if oiio_support: + oiio_tool_path = get_oiio_tools_path() + args = [oiio_tool_path] + + ext = os.path.splitext(input_path)[1][1:] + if ext in self.movie_extensions: + args.extend(["--subimage", str(int(input_frame))]) + else: + args.extend(["--frames", str(int(input_frame))]) + + if ext == "exr": + args.extend(["--powc", "0.45,0.45,0.45,1.0"]) + + args.extend([input_path, "-o", output_path]) + output = openpype.api.run_subprocess(args) + + failed_output = "oiiotool produced no output." + if failed_output in output: + raise ValueError( + "oiiotool processing failed. Args: {}".format(args) + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which representation + # will be thumbnail created + break def _get_filtered_repres(self, instance): filtered_repres = [] From d992935468c488fdd4fe783cd7ce797d6f27ca50 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 02:31:56 +0300 Subject: [PATCH 024/195] name probesize and duration to max_int --- openpype/plugins/publish/extract_jpeg_exr.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 52a678e9ef..066272fd01 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,4 +1,5 @@ import os +from urllib.parse import MAX_CACHE_SIZE import pyblish.api from openpype.pipeline import ( @@ -131,6 +132,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) # override file if already exists jpeg_items.append("-y") + # flag for large file sizes + max_int = 2147483647 + jpeg_items.append("-analyzeduration {}".format(max_int)) + jpeg_items.append("-probesize {}".format(max_int)) # use same input args like with mov jpeg_items.extend(ffmpeg_args.get("input") or []) # input file From cc4efe7638340e0ab64857f6e00b676cfa4bb77e Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 06:02:00 +0300 Subject: [PATCH 025/195] style fix --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 066272fd01..52457ebb31 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -185,7 +185,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): elif repre["ext"] == "exr": oiio_support = is_oiio_supported() - + if oiio_support: oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] From 70c5366134ccafb066c40097907b958496392190 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 26 May 2022 07:25:09 +0300 Subject: [PATCH 026/195] Additional oiiotool conversion refactoring, some style fixes --- openpype/plugins/publish/extract_jpeg_exr.py | 123 +++++++++++-------- 1 file changed, 69 insertions(+), 54 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 52457ebb31..bb3b80b7ef 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -91,38 +91,41 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - # If it's a movie file, use ffmpeg + + + # Presumably the following is not needed since we are being + # explicit + # TODO: Test cases, cleanup. + # do_convert = should_convert_for_ffmpeg(full_input_path) + # # If result is None the requirement of conversion can't be + # # determined + # if do_convert is None: + # self.log.info(( + # "Can't determine if representation requires conversion." # noqa + # " Skipped." + # )) + # continue + # + # # Do conversion if needed + # # - change staging dir of source representation + # # - must be set back after output definitions processing + # convert_dir = None + # if do_convert: + # convert_dir = get_transcode_temp_directory() + # filename = os.path.basename(full_input_path) + # convert_input_paths_for_ffmpeg( + # [full_input_path], + # convert_dir, + # self.log + # ) + # full_input_path = os.path.join(convert_dir, filename) + filename = os.path.splitext(input_file)[0] + if not filename.endswith('.'): + filename += "." + jpeg_file = filename + "jpg" + full_output_path = os.path.join(stagingdir, jpeg_file) + # If it's a movie file, use ffmpeg if repre["ext"] == "mov": - do_convert = should_convert_for_ffmpeg(full_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - convert_dir = None - if do_convert: - convert_dir = get_transcode_temp_directory() - filename = os.path.basename(full_input_path) - convert_input_paths_for_ffmpeg( - [full_input_path], - convert_dir, - self.log - ) - full_input_path = os.path.join(convert_dir, filename) - - filename = os.path.splitext(input_file)[0] - if not filename.endswith('.'): - filename += "." - jpeg_file = filename + "jpg" - full_output_path = os.path.join(stagingdir, jpeg_file) - self.log.info("output {}".format(full_output_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") @@ -178,37 +181,49 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # adding representation self.log.debug("Adding: {}".format(new_repre)) instance.data["representations"].append(new_repre) - - # Cleanup temp folder - if convert_dir is not None and os.path.exists(convert_dir): - shutil.rmtree(convert_dir) - elif repre["ext"] == "exr": oiio_support = is_oiio_supported() - if oiio_support: + # TODO: Add resolution checking, possibly check for other + # places where things may break oiio_tool_path = get_oiio_tools_path() + full_output_path = os.path.join(stagingdir, jpeg_file) args = [oiio_tool_path] + oiio_cmd = [oiio_tool_path, + full_input_path, "-o", + full_output_path + ] + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + run_subprocess(subprocess_exr, logger=self.log) - ext = os.path.splitext(input_path)[1][1:] - if ext in self.movie_extensions: - args.extend(["--subimage", str(int(input_frame))]) - else: - args.extend(["--frames", str(int(input_frame))]) - - if ext == "exr": - args.extend(["--powc", "0.45,0.45,0.45,1.0"]) - - args.extend([input_path, "-o", output_path]) - output = openpype.api.run_subprocess(args) - + # raise error if there is no ouptput + if not os.path.exists(full_input_path): + self.log.error( + ("File {} was not converted " + "by oiio tool!").format(full_input_path)) + raise AssertionError("OIIO tool conversion failed") failed_output = "oiiotool produced no output." + output = run_subprocess(args) if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args) - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which representation - # will be thumbnail created + raise ValueError( + "oiiotool processing failed. Args: {}".format(args)) + + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) + + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which repre + # will be thumbnail created break def _get_filtered_repres(self, instance): From 20b7b5dca4c44cff55538bc7b9a13705edab7206 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 27 May 2022 12:39:39 +0300 Subject: [PATCH 027/195] Style fixes, remove unused imports --- openpype/plugins/publish/extract_jpeg_exr.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index bb3b80b7ef..771ffc0aed 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,8 +1,7 @@ import os -from urllib.parse import MAX_CACHE_SIZE import pyblish.api -from openpype.pipeline import ( +from openpype.pipeline import ( legacy_io, KnownPublishError ) @@ -14,14 +13,8 @@ from openpype.lib import ( filter_profiles, run_subprocess, path_to_subprocess_arg, - - get_transcode_temp_directory, - convert_input_paths_for_ffmpeg, - should_convert_for_ffmpeg ) -import shutil - class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" @@ -91,8 +84,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - - # Presumably the following is not needed since we are being # explicit # TODO: Test cases, cleanup. @@ -149,7 +140,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.extend(ffmpeg_args.get("output") or []) # we just want one frame from movie files jpeg_items.append("-vframes 1") - # output file jpeg_items.append(path_to_subprocess_arg(full_output_path)) subprocess_command = " ".join(jpeg_items) @@ -207,8 +197,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): output = run_subprocess(args) if failed_output in output: raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) - + "oiiotool processing failed. Args: {}".format(args)) # noqa new_repre = { "name": "thumbnail", "ext": "jpg", From 9afdf50bdc43ddbc014abd0ce4143d1755e79495 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 30 May 2022 15:18:51 +0300 Subject: [PATCH 028/195] initial conversion refactor --- openpype/plugins/publish/extract_jpeg_exr.py | 153 +++++++++++++------ 1 file changed, 106 insertions(+), 47 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 771ffc0aed..88fa627183 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -73,17 +73,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres = self._get_filtered_repres(instance) for repre in filtered_repres: - repre_files = repre["files"] - if not isinstance(repre_files, (list, tuple)): - input_file = repre_files - else: - file_index = int(float(len(repre_files)) * 0.5) - input_file = repre_files[file_index] - - stagingdir = os.path.normpath(repre["stagingDir"]) - - full_input_path = os.path.join(stagingdir, input_file) - self.log.info("input {}".format(full_input_path)) # Presumably the following is not needed since we are being # explicit # TODO: Test cases, cleanup. @@ -110,6 +99,17 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # self.log # ) # full_input_path = os.path.join(convert_dir, filename) + repre_files = repre["files"] + if not isinstance(repre_files, (list, tuple)): + input_file = repre_files + else: + file_index = int(float(len(repre_files)) * 0.5) + input_file = repre_files[file_index] + + stagingdir = os.path.normpath(repre["stagingDir"]) + + full_input_path = os.path.join(stagingdir, input_file) + self.log.info("input {}".format(full_input_path)) filename = os.path.splitext(input_file)[0] if not filename.endswith('.'): filename += "." @@ -178,42 +178,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # places where things may break oiio_tool_path = get_oiio_tools_path() full_output_path = os.path.join(stagingdir, jpeg_file) - args = [oiio_tool_path] - oiio_cmd = [oiio_tool_path, - full_input_path, "-o", - full_output_path - ] - subprocess_exr = " ".join(oiio_cmd) - self.log.info(f"running: {subprocess_exr}") - run_subprocess(subprocess_exr, logger=self.log) - - # raise error if there is no ouptput - if not os.path.exists(full_input_path): - self.log.error( - ("File {} was not converted " - "by oiio tool!").format(full_input_path)) - raise AssertionError("OIIO tool conversion failed") - failed_output = "oiiotool produced no output." - output = run_subprocess(args) - if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) # noqa - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which repre - # will be thumbnail created - break + def _get_filtered_repres(self, instance): filtered_repres = [] @@ -233,3 +198,97 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres + + + def create_thumbnail_oiio(self, src_path, dst_path): + args = [oiio_tool_path] + oiio_cmd = [oiio_tool_path, + full_input_path, "-o", + full_output_path + ] + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + run_subprocess(subprocess_exr, logger=self.log) + + # raise error if there is no ouptput + if not os.path.exists(full_input_path): + self.log.error( + ("File {} was not converted " + "by oiio tool!").format(full_input_path)) + raise AssertionError("OIIO tool conversion failed") + failed_output = "oiiotool produced no output." + output = run_subprocess(args) + if failed_output in output: + raise ValueError( + "oiiotool processing failed. Args: {}".format(args)) # noqa + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) + + # Create only one representation with name 'thumbnail' + # TODO maybe handle way how to decide from which repre + # will be thumbnail created + + def create_thumbnail_ffmpeg(self, src_path, dst_path): + self.log.info("output {}".format(full_output_path)) + + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + ffmpeg_args = self.ffmpeg_args or {} + + jpeg_items = [] + jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) + # override file if already exists + jpeg_items.append("-y") + # flag for large file sizes + max_int = 2147483647 + jpeg_items.append("-analyzeduration {}".format(max_int)) + jpeg_items.append("-probesize {}".format(max_int)) + # use same input args like with mov + jpeg_items.extend(ffmpeg_args.get("input") or []) + # input file + jpeg_items.append("-i {}".format( + path_to_subprocess_arg(full_input_path) + )) + # output arguments from presets + jpeg_items.extend(ffmpeg_args.get("output") or []) + # we just want one frame from movie files + jpeg_items.append("-vframes 1") + # output file + jpeg_items.append(path_to_subprocess_arg(full_output_path)) + subprocess_command = " ".join(jpeg_items) + + # run subprocess + self.log.debug("{}".format(subprocess_command)) + try: # temporary until oiiotool is supported cross platform + run_subprocess( + subprocess_command, shell=True, logger=self.log + ) + except RuntimeError as exp: + if "Compression" in str(exp): + self.log.debug( + "Unsupported compression on input files. Skipping!" + ) + return + self.log.warning("Conversion crashed", exc_info=True) + raise + + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) \ No newline at end of file From b6e6b7ae69f92aa540a3af146249d8ba0db4fd93 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Tue, 31 May 2022 15:17:52 +0300 Subject: [PATCH 029/195] refactor cleanup --- openpype/plugins/publish/extract_jpeg_exr.py | 32 ++++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 88fa627183..8211c1a255 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -46,13 +46,9 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not profile: return - oiio_path = get_oiio_tools_path() + # Raise an exception when oiiotool is not available - if not os.path.exists(oiio_path): - KnownPublishError( - "OpenImageIO tool is not available on this machine." - ) self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -167,18 +163,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "thumbnail": True, "tags": ["thumbnail"] } - # adding representation self.log.debug("Adding: {}".format(new_repre)) instance.data["representations"].append(new_repre) - elif repre["ext"] == "exr": - oiio_support = is_oiio_supported() - if oiio_support: - # TODO: Add resolution checking, possibly check for other - # places where things may break - oiio_tool_path = get_oiio_tools_path() - full_output_path = os.path.join(stagingdir, jpeg_file) - + def _get_filtered_repres(self, instance): filtered_repres = [] @@ -199,22 +187,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - - def create_thumbnail_oiio(self, src_path, dst_path): + def create_thumbnail_oiio(self, instance, src_path, dst_path): + oiio_path = get_oiio_tools_path() + if not os.path.exists(oiio_path): + KnownPublishError( + "OpenImageIO tool is not available on this machine." + ) args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, - full_input_path, "-o", - full_output_path + src_path, "-o", + dst_path ] subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) # raise error if there is no ouptput - if not os.path.exists(full_input_path): + if not os.path.exists(src_path): self.log.error( ("File {} was not converted " - "by oiio tool!").format(full_input_path)) + "by oiio tool!").format(src_path)) raise AssertionError("OIIO tool conversion failed") failed_output = "oiiotool produced no output." output = run_subprocess(args) From ca9c06df18bd7ff16ee7bae5ba7f4ef51ac383f7 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:03:05 +0300 Subject: [PATCH 030/195] continue refactor --- openpype/plugins/publish/extract_jpeg_exr.py | 104 +++++++------------ 1 file changed, 37 insertions(+), 67 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 8211c1a255..4ffacf2600 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -13,6 +13,8 @@ from openpype.lib import ( filter_profiles, run_subprocess, path_to_subprocess_arg, + + execute, ) @@ -46,9 +48,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not profile: return - - # Raise an exception when oiiotool is not available - self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. @@ -67,7 +66,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres = self._get_filtered_repres(instance) - for repre in filtered_repres: # Presumably the following is not needed since we are being # explicit @@ -112,61 +110,42 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_file = filename + "jpg" full_output_path = os.path.join(stagingdir, jpeg_file) # If it's a movie file, use ffmpeg - if repre["ext"] == "mov": - self.log.info("output {}".format(full_output_path)) - ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - ffmpeg_args = self.ffmpeg_args or {} + thumbnail_created = False + # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool + # isn't available) + if not is_oiio_supported(): + thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + else: + # Check if the file can be read by OIIO + args = [ + oiiotool_path, "--info", "-i", src_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: + + thumbnail_created = self.create_thumbnail_oiio(src_path, dst_path) + else: + thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) - jpeg_items = [] - jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) - # override file if already exists - jpeg_items.append("-y") - # flag for large file sizes - max_int = 2147483647 - jpeg_items.append("-analyzeduration {}".format(max_int)) - jpeg_items.append("-probesize {}".format(max_int)) - # use same input args like with mov - jpeg_items.extend(ffmpeg_args.get("input") or []) - # input file - jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) - )) - # output arguments from presets - jpeg_items.extend(ffmpeg_args.get("output") or []) - # we just want one frame from movie files - jpeg_items.append("-vframes 1") - # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) - subprocess_command = " ".join(jpeg_items) + # Skip the rest of the process if the thumbnail wasn't created + if not thumbnail_created: - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!" - ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise + return - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) def _get_filtered_repres(self, instance): filtered_repres = [] @@ -188,11 +167,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres def create_thumbnail_oiio(self, instance, src_path, dst_path): - oiio_path = get_oiio_tools_path() - if not os.path.exists(oiio_path): - KnownPublishError( - "OpenImageIO tool is not available on this machine." - ) + oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, src_path, "-o", @@ -202,12 +177,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) - # raise error if there is no ouptput - if not os.path.exists(src_path): - self.log.error( - ("File {} was not converted " - "by oiio tool!").format(src_path)) - raise AssertionError("OIIO tool conversion failed") + # raise an error if there's no output failed_output = "oiiotool produced no output." output = run_subprocess(args) if failed_output in output: @@ -230,7 +200,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # will be thumbnail created def create_thumbnail_ffmpeg(self, src_path, dst_path): - self.log.info("output {}".format(full_output_path)) + self.log.info("output {}".format(dst_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} From ebfef2a1b869c07eee950952bc38b97717281854 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:11:26 +0300 Subject: [PATCH 031/195] Cleanup --- openpype/plugins/publish/extract_jpeg_exr.py | 32 ++++++-------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 4ffacf2600..15f90262dc 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -115,19 +115,20 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool # isn't available) if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) else: # Check if the file can be read by OIIO args = [ - oiiotool_path, "--info", "-i", src_path + 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 the input can read by OIIO then use OIIO method for + # conversion otherwise use ffmpeg if returncode == 0: - thumbnail_created = self.create_thumbnail_oiio(src_path, dst_path) + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: - thumbnail_created = self.create_thumbnail_ffmpeg(src_path, dst_path) + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created if not thumbnail_created: @@ -166,7 +167,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - def create_thumbnail_oiio(self, instance, src_path, dst_path): + def create_thumbnail_oiio(self, src_path, dst_path): oiio_tool_path = get_oiio_tools_path() args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, @@ -183,21 +184,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if failed_output in output: raise ValueError( "oiiotool processing failed. Args: {}".format(args)) # noqa - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which repre - # will be thumbnail created def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("output {}".format(dst_path)) @@ -217,14 +203,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.extend(ffmpeg_args.get("input") or []) # input file jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) + path_to_subprocess_arg(src_path) )) # output arguments from presets jpeg_items.extend(ffmpeg_args.get("output") or []) # we just want one frame from movie files jpeg_items.append("-vframes 1") # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) + jpeg_items.append(path_to_subprocess_arg(src_path)) subprocess_command = " ".join(jpeg_items) # run subprocess From b720d46489cc88a2b08cb11964fad2b4d91af8e5 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 08:31:20 +0300 Subject: [PATCH 032/195] continue refactoring --- openpype/plugins/publish/extract_jpeg_exr.py | 116 +++++-------------- 1 file changed, 29 insertions(+), 87 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 15f90262dc..8dc06bfe79 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -67,32 +67,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres = self._get_filtered_repres(instance) for repre in filtered_repres: - # Presumably the following is not needed since we are being - # explicit - # TODO: Test cases, cleanup. - # do_convert = should_convert_for_ffmpeg(full_input_path) - # # If result is None the requirement of conversion can't be - # # determined - # if do_convert is None: - # self.log.info(( - # "Can't determine if representation requires conversion." # noqa - # " Skipped." - # )) - # continue - # - # # Do conversion if needed - # # - change staging dir of source representation - # # - must be set back after output definitions processing - # convert_dir = None - # if do_convert: - # convert_dir = get_transcode_temp_directory() - # filename = os.path.basename(full_input_path) - # convert_input_paths_for_ffmpeg( - # [full_input_path], - # convert_dir, - # self.log - # ) - # full_input_path = os.path.join(convert_dir, filename) repre_files = repre["files"] if not isinstance(repre_files, (list, tuple)): input_file = repre_files @@ -111,38 +85,38 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_output_path = os.path.join(stagingdir, jpeg_file) # If it's a movie file, use ffmpeg - thumbnail_created = False - # Try to use FFMPEG if OIIO is not supported (for cases when oiiotool - # isn't available) - if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) - else: - # Check if the file can be read by OIIO - 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: - - thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + thumbnail_created = False + # Try to use FFMPEG if OIIO is not supported (for cases when + # oiiotool isn't available) + if not is_oiio_supported(): + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) else: - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa + # Check if the file can be read by OIIO + 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: + + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + else: + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa - # Skip the rest of the process if the thumbnail wasn't created - if not thumbnail_created: + # Skip the rest of the process if the thumbnail wasn't created + if not thumbnail_created: - return + return - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + new_repre = { + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } # adding representation self.log.debug("Adding: {}".format(new_repre)) @@ -169,7 +143,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def create_thumbnail_oiio(self, src_path, dst_path): oiio_tool_path = get_oiio_tools_path() - args = [oiio_tool_path] oiio_cmd = [oiio_tool_path, src_path, "-o", dst_path @@ -178,13 +151,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info(f"running: {subprocess_exr}") run_subprocess(subprocess_exr, logger=self.log) - # raise an error if there's no output - failed_output = "oiiotool produced no output." - output = run_subprocess(args) - if failed_output in output: - raise ValueError( - "oiiotool processing failed. Args: {}".format(args)) # noqa - def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("output {}".format(dst_path)) @@ -213,30 +179,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(src_path)) subprocess_command = " ".join(jpeg_items) - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( + run_subprocess( subprocess_command, shell=True, logger=self.log ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!" - ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise - - new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) \ No newline at end of file From 84c073dd3c45515a055d36a8eee29bfe3cd749cd Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:19:45 +0300 Subject: [PATCH 033/195] Further clean up and refactor. --- openpype/plugins/publish/extract_jpeg_exr.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 8dc06bfe79..a5a62c04ee 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -3,7 +3,6 @@ import os import pyblish.api from openpype.pipeline import ( legacy_io, - KnownPublishError ) from openpype.lib import ( get_ffmpeg_tool_path, @@ -83,15 +82,15 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filename += "." jpeg_file = filename + "jpg" full_output_path = os.path.join(stagingdir, jpeg_file) - # If it's a movie file, use ffmpeg thumbnail_created = False # Try to use FFMPEG if OIIO is not supported (for cases when # oiiotool isn't available) if not is_oiio_supported(): - thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) + 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 ] @@ -99,9 +98,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # 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.") thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: + self.log.info("converting with") thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created @@ -142,6 +142,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres def create_thumbnail_oiio(self, src_path, dst_path): + self.log.info("outputting {}".format(dst_path)) oiio_tool_path = get_oiio_tools_path() oiio_cmd = [oiio_tool_path, src_path, "-o", @@ -152,7 +153,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): run_subprocess(subprocess_exr, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): - self.log.info("output {}".format(dst_path)) + self.log.info("outputting {}".format(dst_path)) ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} From 3b26f78335057810cd8cd9d23f1de3546524211a Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:23:52 +0300 Subject: [PATCH 034/195] Revert "Add profiles handling in schema aand for extractor" This reverts commit 8453f9710f1fb305d917723eeee4cfea5c007062. --- openpype/plugins/publish/extract_jpeg_exr.py | 17 --------- .../defaults/project_settings/deadline.json | 2 +- .../defaults/project_settings/global.json | 12 +----- .../schemas/schema_global_publish.json | 38 ------------------- repos/avalon-core | 1 + 5 files changed, 3 insertions(+), 67 deletions(-) create mode 160000 repos/avalon-core diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index a5a62c04ee..4a1fea2043 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -1,15 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - legacy_io, -) from openpype.lib import ( get_ffmpeg_tool_path, get_oiio_tools_path, is_oiio_supported, - filter_profiles, run_subprocess, path_to_subprocess_arg, @@ -34,19 +30,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): oiio_args = None def process(self, instance): - task_name = instance.data.get("task", legacy_io.Session["AVALON_TASK"]) - host_name = legacy_io.Session["AVALON_APP"] - family = instance.data["family"] - filtering_criteria = { - "hosts": host_name, - "families": family, - "tasks": task_name - } - profile = filter_profiles(self.profiles, filtering_criteria, - logger=self.log) - if not profile: - return - self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5c5a14bf21..f0b2a7e555 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} \ No newline at end of file +} diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 4ad56d8086..cedd0eed99 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -40,17 +40,7 @@ "-apply_trc gamma22" ], "output": [] - }, - "thumbnail_extraction_profiles": [ - { - "families": [ - "" - ], - "hosts": [], - "task_types": [], - "tasks": [] - } - ] + } }, "ExtractReview": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 4149c99348..a3cbf0cfcd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -152,44 +152,6 @@ "label": "FFmpeg output arguments" } ] - }, - { - "type": "list", - "key": "thumbnail_extraction_profiles", - "label": "Thumbnail Extraction profiles", - "use_label_wrap": true, - "object_type": { - "type": "dict", - "children": [ - { - "type": "label", - "label": "" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, - { - "type": "hosts-enum", - "key": "hosts", - "label": "Hosts", - "multiselection": true - }, - { - "key": "task_types", - "label": "Task types", - "type": "task-types-enum" - }, - { - "key": "tasks", - "label": "Task names", - "type": "list", - "object_type": "text" - } - ] - } } ] }, diff --git a/repos/avalon-core b/repos/avalon-core new file mode 160000 index 0000000000..2fa14cea6f --- /dev/null +++ b/repos/avalon-core @@ -0,0 +1 @@ +Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb From c4468664970bd93bbd074d7424042d4adee20c8e Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Wed, 1 Jun 2022 10:25:56 +0300 Subject: [PATCH 035/195] remove unused variable --- openpype/plugins/publish/extract_jpeg_exr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 4a1fea2043..daf7430a32 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -27,7 +27,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # presetable attribute ffmpeg_args = None - oiio_args = None def process(self, instance): self.log.info("subset {}".format(instance.data['subset'])) From 85748589ef4cdbda6ad65c59c3130ad2901dc0b4 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 2 Jun 2022 11:18:02 +0200 Subject: [PATCH 036/195] :bug: skip empty attributes --- openpype/hosts/maya/plugins/publish/collect_look.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 323bede761..9b6d1d999c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -601,6 +601,9 @@ class CollectLook(pyblish.api.InstancePlugin): source, computed_source)) + if not source: + self.log.info("source is empty, skipping...") + continue # We replace backslashes with forward slashes because V-Ray # can't handle the UDIM files with the backslashes in the # paths as the computed patterns From 73b492c1fff78f4ece0e63dfc9bb0c83189ebe47 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 2 Jun 2022 15:53:40 +0200 Subject: [PATCH 037/195] Flame: adding `OPENPYPE_TEMP_DIR` to extractor --- .../publish/extract_subset_resources.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 0bad3f7cfc..f3239af23e 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -1,5 +1,6 @@ import os import re +import tempfile from pprint import pformat from copy import deepcopy @@ -420,3 +421,28 @@ class ExtractSubsetResources(openpype.api.Extractor): "Path `{}` is containing more that one clip".format(path) ) return clips[0] + + def staging_dir(self, instance): + """Provide a temporary directory in which to store extracted files + + Upon calling this method the staging directory is stored inside + the instance.data['stagingDir'] + """ + staging_dir = instance.data.get('stagingDir', None) + openpype_temp_dir = os.getenv("OPENPYPE_TEMP_DIR") + + if not staging_dir: + if openpype_temp_dir and os.path.exists(openpype_temp_dir): + staging_dir = os.path.normpath( + tempfile.mkdtemp( + prefix="pyblish_tmp_", + dir=openpype_temp_dir + ) + ) + else: + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data['stagingDir'] = staging_dir + + return staging_dir From 2339e41b4c32afcbd4eb5d1a6d8ea2cadd58f057 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 10:19:22 +0200 Subject: [PATCH 038/195] Fix - removed unnecessary initialization --- openpype/modules/sync_server/sync_server_module.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 5a1d8467ec..bd75172cc6 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1026,9 +1026,6 @@ class SyncServerModule(OpenPypeModule, ITrayModule): """ self.server_init() - from .tray.app import SyncServerWindow - self.widget = SyncServerWindow(self) - def server_init(self): """Actual initialization of Sync Server.""" # import only in tray or Python3, because of Python2 hosts From 688400ad7b7065f6db5322dd480bfe352cc2f62d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 10:20:33 +0200 Subject: [PATCH 039/195] Fix - removed unnecessary first query setSortable forces refresh itself --- openpype/modules/sync_server/tray/models.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 7241cc3472..d3d36cdfd4 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -523,10 +523,6 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self.query = self.get_query() self.default_query = list(self.get_query()) - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) - self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) @@ -1003,9 +999,6 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel): self.sort_criteria = self.DEFAULT_SORT self.query = self.get_query() - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) From 81a51696804915888daeeb42cf0b1d5f4c92e538 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 11:08:52 +0200 Subject: [PATCH 040/195] Fix - faster loop logic --- openpype/modules/sync_server/sync_server.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 22eed01ef3..808d703616 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -282,10 +282,9 @@ class SyncServerThread(threading.Thread): import time start_time = None self.module.set_sync_project_settings() # clean cache - for collection, preset in self.module.sync_project_settings.\ - items(): - if collection not in self.module.get_enabled_projects(): - continue + enabled_projects = self.module.get_enabled_projects() + for collection in enabled_projects: + preset = self.module.sync_project_settings["collection"] start_time = time.time() local_site, remote_site = self._working_sites(collection) From 3c285d859a454107278470be43d00c205a0233e1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 12:34:21 +0200 Subject: [PATCH 041/195] Fix - fixes Do creation of settings only in Thread as it is expensive operation. --- openpype/modules/sync_server/sync_server.py | 6 +++--- openpype/modules/sync_server/sync_server_module.py | 12 +++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index 808d703616..356a75f99d 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -280,13 +280,13 @@ class SyncServerThread(threading.Thread): while self.is_running and not self.module.is_paused(): try: import time - start_time = None + start_time = time.time() self.module.set_sync_project_settings() # clean cache + collection = None enabled_projects = self.module.get_enabled_projects() for collection in enabled_projects: - preset = self.module.sync_project_settings["collection"] + preset = self.module.sync_project_settings[collection] - start_time = time.time() local_site, remote_site = self._working_sites(collection) if not all([local_site, remote_site]): continue diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index bd75172cc6..7f541d52e3 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -1029,15 +1029,10 @@ class SyncServerModule(OpenPypeModule, ITrayModule): def server_init(self): """Actual initialization of Sync Server.""" # import only in tray or Python3, because of Python2 hosts - from .sync_server import SyncServerThread - if not self.enabled: return - enabled_projects = self.get_enabled_projects() - if not enabled_projects: - self.enabled = False - return + from .sync_server import SyncServerThread self.lock = threading.Lock() @@ -1057,7 +1052,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): self.server_start() def server_start(self): - if self.sync_project_settings and self.enabled: + if self.enabled: self.sync_server_thread.start() else: log.info("No presets or active providers. " + @@ -1848,6 +1843,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): Returns: (int): in seconds """ + if not project_name: + return 60 + ld = self.sync_project_settings[project_name]["config"]["loop_delay"] return int(ld) From 96115fac6ed2addf4908e0aacaf9d3d2dc77ad12 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 12:36:18 +0200 Subject: [PATCH 042/195] Fix - remove reset of Settings Let only thread do it, expensive operation potentially. --- openpype/modules/sync_server/tray/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index d3d36cdfd4..6b309312a2 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -378,7 +378,6 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): project (str): name of project """ self._project = project - self.sync_server.set_sync_project_settings() # project might have been deactivated in the meantime if not self.sync_server.get_sync_project_setting(project): return From d86c71c15bc00d31c5459b3d75a28fcd70813696 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 13:23:51 +0200 Subject: [PATCH 043/195] Fix - added disabled icon to Site Queue --- .../sync_server/resources/disabled.png | Bin 0 -> 2368 bytes .../modules/sync_server/sync_server_module.py | 17 +++++++-- openpype/modules/sync_server/tray/widgets.py | 34 ++++++++++++------ openpype/tools/loader/model.py | 2 +- openpype/tools/loader/widgets.py | 14 ++++---- 5 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 openpype/modules/sync_server/resources/disabled.png diff --git a/openpype/modules/sync_server/resources/disabled.png b/openpype/modules/sync_server/resources/disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e036d7ef6a12a353551ddfc3e8a4e099c6506dd7 GIT binary patch literal 2368 zcmb_e2~ZPP7>*VZ@L-&JptjTPQmu-cJ+dU(EfJBRpoYW5gAP_+c3;R!k`38Kf-}}? zwXI5VMkdyC#wymMmU>MOoz_|qi=#}bPFwG)^{8UedNAtX^d*EyJ6_XiW;Xlwz3+Yh z_y6yEZ+B5{_PAbggW@zAO|Ps>^CWb~f>%rw`aiDhoQrO;-pqVOqv>52yt)LEdJooU zqW4I)Y3ek~L|%~HS|G}HsP((O2(8hir24%;D26I#heeXdgtzS7hhvgx!lx!%D2q28 zI;G6nK4_htZ4+h}3!I3jj>1y>JR)#I6<~h1%cJmq6CUE_Q9Ia8;#dfx7Mt+2U_fk| zB^OJVeGoHfX+oeV3Nvt8gB>^o2Q?C7D266!illV}Me!`nGe#`@!%;Y&=-?-r$A*KU zCll^eRWDDHWo2dBGM!fT6_GT@aU{i%3_~CUp_F@6;3qsvVh4j6DuPe)s*>!%f{ef} zm#QWlNo~);?TyfSlyIF;#YjK!lC+iz<`e>oLIme6^|?a9MS+Aa=!PCuL0CG1^*Uu$ zR-Ez^s3XJw2!Lv9u|#4#R*Tym38APNvrro0f;<+j*vh?-oCFoQ)F;4Vms*6^VM4mJ1 zj4Z7;5PE}7AoLEFC4imd2)&rhGLY37jSzOsH_Jk4aP@=p+joK}3y9;9GCg2)z;1L9 zMnOjtw1IUH00NGHc88Is05H-t*TI(Klh9@auFkAMRU%?E>h(HEB|8YwkjxMQU?BmF zMuDJcgWdpjlwPpwf{qWFh|iQ1w7livJ!FL?;g(CnLcZaF5Zn(YTnHKgig>tNdQ3f@ zD{^>#nG+(?rx>I?x*|K>?)0MW}YqG$8pT@d}vrbm?~5ZlLWtKmaz`NH81=2)md} z6Oa-OqR3KugC3c-T~lZ2oeWDOZ?_xvzeo>r3r^rEg6MQ5@&BI8^ZD|r$voP9|6ej> z@JwxY82NAWg~mFazi1&tUFc8`HY2YS^bmO=K@X~v54~=fpA$+nnyw?W%xSjFgQo+j z#eMVo9yr_BJaq3H#T88puo3~=xOSm)L;sV~h?_|<%bVg0x0!8G`w!({>(>Q-YgxB! z%J%I$UL4#lf8x%DXhHvr-BEVwdh=@wr!=0UYf~D(ZvFPbWlovfz4eut>i7f0h9vAT z%&&?5NS#2I?>9}a9upWexqfqQ=Yx5RN8LXLcoToso*=KRZtk&Q z&-%~urkI+qbbn6@46Hwo+3)9lQ#GJ;-sqg^1M3H0+R{6U-4gFVPS%~lzS4hr!Rj@d zG9~8vPf5#f^h~jKyS(D#Yi(zaFa2|0emyt6KIg=|yGJ)BXH;HvT{=GCudQiAk4gvc ztP#3@;D4*2bj{?v`*)WGKCjzt)fzKOJj$!Lf4$iH@b&rhb?>P{^Szl%i7(!deS7aM zzNT(;%PvkkVpGA2;=^|)aOd+1AhR#AZTa1q=XN!UtG86&n!S1Qi4(cwUglj+>*}kw z_eWoQv&fQq?|bERwfrnM z>C9Ms=!$h|YexhgCj3!b@GvemCN?%ErmBVMcW6QF3v+glZdz;el5Owqy%~SyllUg~ T){1$-Z=9@*Y;(<+!ufv#nIl6P literal 0 HcmV?d00001 diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 7f541d52e3..698b296a52 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -926,9 +926,22 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return enabled_projects - def is_project_enabled(self, project_name): + def is_project_enabled(self, project_name, single=False): + """Checks if 'project_name' is enabled for syncing. + 'get_sync_project_setting' is potentially expensive operation (pulls + settings for all projects if cached version is not available), using + project_settings for specific project should be faster. + Args: + project_name (str) + single (bool): use 'get_project_settings' method + """ if self.enabled: - project_settings = self.get_sync_project_setting(project_name) + if single: + project_settings = get_project_settings(project_name) + project_settings = \ + self._parse_sync_settings_from_settings(project_settings) + else: + project_settings = self.get_sync_project_setting(project_name) if project_settings and project_settings.get("enabled"): return True return False diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 6aae9562cf..18de4d311d 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -122,11 +122,13 @@ class SyncProjectListWidget(QtWidgets.QWidget): self._model_reset = False selected_item = None - for project_name in self.sync_server.sync_project_settings.\ - keys(): + sync_settings = self.sync_server.sync_project_settings + for project_name in sync_settings.keys(): if self.sync_server.is_paused() or \ self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") + elif not sync_settings["enabled"]: + icon = self._get_icon("disabled") else: icon = self._get_icon("synced") @@ -578,10 +580,11 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): ) def __init__(self, sync_server, project=None, parent=None): + import time + log.info("SyncRepresentationSummaryWidget start") super(SyncRepresentationSummaryWidget, self).__init__(parent) self.sync_server = sync_server - self._selected_ids = set() # keep last selected _id txt_filter = QtWidgets.QLineEdit() @@ -600,8 +603,11 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] + start_time = time.time() model = SyncRepresentationSummaryModel(sync_server, headers, project, parent=self) + log.info("SyncRepresentationSummaryModel:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setModel(model) table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) table_view.setSelectionMode( @@ -625,7 +631,8 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): column = table_view.model().get_header_index("priority") priority_delegate = delegates.PriorityDelegate(self) table_view.setItemDelegateForColumn(column, priority_delegate) - + log.info("SyncRepresentationSummaryWidget.2:: {}".format(time.time() - start_time)) + start_time = time.time() layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -633,27 +640,35 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): self.table_view = table_view self.model = model - + log.info("SyncRepresentationSummaryWidget.3:: {}".format(time.time() - start_time)) + start_time = time.time() horizontal_header = HorizontalHeader(self) - + log.info("SyncRepresentationSummaryWidget.4:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setHorizontalHeader(horizontal_header) + log.info("SyncRepresentationSummaryWidget.4.1:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.setSortingEnabled(True) - + log.info("SyncRepresentationSummaryWidget.5:: {}".format(time.time() - start_time)) + start_time = time.time() for column_name, width in self.default_widths: idx = model.get_header_index(column_name) table_view.setColumnWidth(idx, width) - + log.info("SyncRepresentationSummaryWidget.6:: {}".format(time.time() - start_time)) + start_time = time.time() table_view.doubleClicked.connect(self._double_clicked) self.txt_filter.textChanged.connect(lambda: model.set_word_filter( self.txt_filter.text())) table_view.customContextMenuRequested.connect(self._on_context_menu) - + log.info("SyncRepresentationSummaryWidget.7:: {}".format(time.time() - start_time)) + start_time = time.time() model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) model.modelReset.connect(self._set_selection) self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) + log.info("SyncRepresentationSummaryWidget.end:: {}".format(time.time() - start_time)) def _prepare_menu(self, local_progress, remote_progress, is_multi, can_edit, status=None): @@ -963,7 +978,6 @@ class HorizontalHeader(QtWidgets.QHeaderView): super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent) self._parent = parent self.checked_values = {} - self.setModel(self._parent.model) self.setSectionsClickable(True) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6f39c428be..e6bef0a33a 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -89,7 +89,7 @@ class BaseRepresentationModel(object): self._last_manager_cache = now_time sync_server = self._modules_manager.modules_by_name["sync_server"] - if sync_server.is_project_enabled(project_name): + if sync_server.is_project_enabled(project_name, single=True): active_site = sync_server.get_active_site(project_name) active_provider = sync_server.get_provider_for_site( project_name, active_site) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..5764085b6a 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -356,9 +356,10 @@ class SubsetWidget(QtWidgets.QWidget): enabled = False if project_name: self.model.reset_sync_server(project_name) - if self.model.sync_server: - enabled_proj = self.model.sync_server.get_enabled_projects() - enabled = project_name in enabled_proj + sync_server = self.model.sync_server + if sync_server: + enabled = sync_server.is_project_enabled(project_name, + single=True) lib.change_visibility(self.model, self.view, "repre_info", enabled) @@ -1228,9 +1229,10 @@ class RepresentationWidget(QtWidgets.QWidget): enabled = False if project_name: self.model.reset_sync_server(project_name) - if self.model.sync_server: - enabled_proj = self.model.sync_server.get_enabled_projects() - enabled = project_name in enabled_proj + sync_server = self.model.sync_server + if sync_server: + enabled = sync_server.is_project_enabled(project_name, + single=True) self.sync_server_enabled = enabled lib.change_visibility(self.model, self.tree_view, From 60bef01f6cd9a4f1233fcebf655254d52a8e3f04 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 3 Jun 2022 14:18:58 +0200 Subject: [PATCH 044/195] Fix - removed unwanted logs --- openpype/modules/sync_server/tray/widgets.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 18de4d311d..049a3f0127 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -580,8 +580,6 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): ) def __init__(self, sync_server, project=None, parent=None): - import time - log.info("SyncRepresentationSummaryWidget start") super(SyncRepresentationSummaryWidget, self).__init__(parent) self.sync_server = sync_server @@ -603,11 +601,8 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): table_view = QtWidgets.QTableView() headers = [item[0] for item in self.default_widths] - start_time = time.time() model = SyncRepresentationSummaryModel(sync_server, headers, project, parent=self) - log.info("SyncRepresentationSummaryModel:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setModel(model) table_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) table_view.setSelectionMode( @@ -631,8 +626,6 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): column = table_view.model().get_header_index("priority") priority_delegate = delegates.PriorityDelegate(self) table_view.setItemDelegateForColumn(column, priority_delegate) - log.info("SyncRepresentationSummaryWidget.2:: {}".format(time.time() - start_time)) - start_time = time.time() layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) @@ -640,35 +633,22 @@ class SyncRepresentationSummaryWidget(_SyncRepresentationWidget): self.table_view = table_view self.model = model - log.info("SyncRepresentationSummaryWidget.3:: {}".format(time.time() - start_time)) - start_time = time.time() horizontal_header = HorizontalHeader(self) - log.info("SyncRepresentationSummaryWidget.4:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setHorizontalHeader(horizontal_header) - log.info("SyncRepresentationSummaryWidget.4.1:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.setSortingEnabled(True) - log.info("SyncRepresentationSummaryWidget.5:: {}".format(time.time() - start_time)) - start_time = time.time() for column_name, width in self.default_widths: idx = model.get_header_index(column_name) table_view.setColumnWidth(idx, width) - log.info("SyncRepresentationSummaryWidget.6:: {}".format(time.time() - start_time)) - start_time = time.time() table_view.doubleClicked.connect(self._double_clicked) self.txt_filter.textChanged.connect(lambda: model.set_word_filter( self.txt_filter.text())) table_view.customContextMenuRequested.connect(self._on_context_menu) - log.info("SyncRepresentationSummaryWidget.7:: {}".format(time.time() - start_time)) - start_time = time.time() model.refresh_started.connect(self._save_scrollbar) model.refresh_finished.connect(self._set_scrollbar) model.modelReset.connect(self._set_selection) self.selection_model = self.table_view.selectionModel() self.selection_model.selectionChanged.connect(self._selection_changed) - log.info("SyncRepresentationSummaryWidget.end:: {}".format(time.time() - start_time)) def _prepare_menu(self, local_progress, remote_progress, is_multi, can_edit, status=None): From 5526435e41554a685864218fd601dd276cfc0cdd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 4 Jun 2022 21:07:49 +0200 Subject: [PATCH 045/195] initial commit of client entities functions --- openpype/client/__init__.py | 59 +++ openpype/client/entities.py | 897 ++++++++++++++++++++++++++++++++++++ 2 files changed, 956 insertions(+) create mode 100644 openpype/client/__init__.py create mode 100644 openpype/client/entities.py diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py new file mode 100644 index 0000000000..861f828e67 --- /dev/null +++ b/openpype/client/__init__.py @@ -0,0 +1,59 @@ +from .entities import ( + get_projects, + get_project, + + get_asset, + get_assets, + get_asset_ids_with_subsets, + + get_subset, + get_subsets, + get_subset_families, + + get_version_by_name, + get_version, + get_versions, + get_last_versions, + get_last_version_for_subset, + get_hero_version, + get_hero_versions, + get_version_links, + + get_representation, + get_representation_by_name, + get_representations, + get_representations_parents, + + get_thumbnail, + get_thumbnail_id_from_source, +) + +__all__ = ( + "get_projects", + "get_project", + + "get_asset", + "get_assets", + "get_asset_ids_with_subsets", + + "get_subset", + "get_subsets", + "get_subset_families", + + "get_version_by_name", + "get_version", + "get_versions", + "get_last_versions", + "get_last_version_for_subset", + "get_hero_version", + "get_hero_versions", + "get_version_links", + + "get_representation", + "get_representation_by_name", + "get_representations", + "get_representations_parents", + + "get_thubmnail", + "get_thumbnail_id_from_source", +) diff --git a/openpype/client/entities.py b/openpype/client/entities.py new file mode 100644 index 0000000000..4b52f8cf2d --- /dev/null +++ b/openpype/client/entities.py @@ -0,0 +1,897 @@ +"""Unclear if these will have public functions like these. + +Goal is that most of functions here are called on (or with) an object +that has project name as a context (e.g. on 'ProjectEntity'?). + ++ We will need more specific functions doing wery specific queires really fast. +""" + +import os +import collections + +import six +from bson.objectid import ObjectId + +from openpype.lib.mongo import OpenPypeMongoConnection + + +def _get_project_connection(project_name=None): + db_name = os.environ.get("AVALON_DB") or "avalon" + mongodb = OpenPypeMongoConnection.get_mongo_client()[db_name] + if project_name: + return mongodb[project_name] + return mongodb + + +def _prepare_fields(fields): + if not fields: + return None + + output = { + field: True + for field in fields + } + if "_id" not in output: + output["_id"] = True + return output + + +def _convert_id(in_id): + if isinstance(in_id, six.string_types): + return ObjectId(in_id) + return in_id + + +def _convert_ids(in_ids): + _output = set() + for in_id in in_ids: + if in_id is not None: + _output.add(_convert_id(in_id)) + return list(_output) + + +def get_projects(active=True, inactive=False, fields=None): + mongodb = _get_project_connection() + for project_name in mongodb.collection_names(): + if project_name in ("system.indexes",): + continue + project_doc = get_project( + project_name, active=active, inactive=inactive, fields=fields + ) + if project_doc is not None: + yield project_doc + + +def get_project(project_name, active=True, inactive=False, fields=None): + # Skip if both are disabled + if not active and not inactive: + return None + + query_filter = {"type": "project"} + # Keep query untouched if both should be available + if active and inactive: + pass + + # Add filter to keep only active + elif active: + query_filter["$or"] = [ + {"data.active": {"$exists": False}}, + {"data.active": True}, + ] + + # Add filter to keep only inactive + elif inactive: + query_filter["$or"] = [ + {"data.active": {"$exists": False}}, + {"data.active": False}, + ] + + conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_asset(project_name, asset_name=None, asset_id=None, fields=None): + query_filter = {"type": "asset"} + has_filter = False + if asset_name: + has_filter = True + query_filter["name"] = asset_name + + if asset_id: + has_filter = True + query_filter["_id"] = _convert_id(asset_id) + + # Avoid random asset quqery + if not has_filter: + return None + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_assets( + project_name, asset_ids=None, asset_names=None, archived=False, fields=None +): + asset_types = ["asset"] + if archived: + asset_types.append("archived_asset") + + if len(asset_types) == 1: + query_filter = {"type": asset_types[0]} + else: + query_filter = {"type": {"$in": asset_types}} + + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + query_filter["_id"] = {"$in": asset_ids} + + if asset_names is not None: + if not asset_names: + return [] + query_filter["name"] = {"$in": list(asset_names)} + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_asset_ids_with_subsets(project_name, asset_ids=None): + subset_query = { + "type": "subset" + } + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + subset_query["parent"] = {"$in": asset_ids} + + conn = _get_project_connection(project_name) + result = conn.aggregate([ + { + "$match": subset_query + }, + { + "$group": { + "_id": "$parent", + "count": {"$sum": 1} + } + } + ]) + asset_ids_with_subsets = [] + for item in result: + asset_id = item["_id"] + count = item["count"] + if count > 0: + asset_ids_with_subsets.append(asset_id) + return asset_ids_with_subsets + + +def get_subset( + project_name, + subset_id=None, + subset_name=None, + asset_id=None, + fields=None +): + """Single subset document by subset id or name and parent id. + + When subset id is defined it is not needed to add any other arguments but + subset name filter must be always combined with asset id (or subset id). + + Question: + This could be split into more functions? + + Args: + project_name (str): Name of project where to look for subset. + subset_id (ObjectId): Id of subset which should be found. + subset_name (str): Name of asset. Must be combined with 'asset_id' or + 'subset_id' arguments otherwise result is 'None'. + asset_id (ObjectId): Id of parent asset. Must be combined with + 'subset_name' or 'subset_id'. + fields (list): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If subset with specified filters was not found. + Dict: Subset document which can be reduced to specified 'fields'. + """ + + query_filters = {"type": "subset"} + has_valid_filters = False + if subset_id is not None: + query_filters["_id"] = _convert_id(asset_id) + has_valid_filters = True + + if subset_name is not None: + if asset_id is not None: + has_valid_filters = True + query_filters["name"] = subset_name + + if asset_id is not None: + query_filters["parent"] = _convert_id(asset_id) + + if not has_valid_filters: + return None + + conn = _get_project_connection(project_name) + return conn.find_one(query_filters, _prepare_fields(fields)) + + +def get_subsets( + project_name, + asset_ids=None, + subset_ids=None, + subset_names=None, + archived=False, + fields=None +): + subset_types = ["subset"] + if archived: + subset_types.append("archived_subset") + + if len(subset_types) == 1: + query_filter = {"type": subset_types[0]} + else: + query_filter = {"type": {"$in": subset_types}} + + if asset_ids is not None: + asset_ids = _convert_ids(asset_ids) + if not asset_ids: + return [] + query_filter["parent"] = {"$in": asset_ids} + + if subset_ids is not None: + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return [] + query_filter["_id"] = {"$in": subset_ids} + + if subset_names is not None: + if not subset_names: + return [] + query_filter["name"] = {"$in": list(subset_names)} + + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_subset_families(project_name, subset_ids=None): + subset_filter = { + "type": "subset" + } + if subset_ids is not None: + if not subset_ids: + return set() + subset_filter["_id"] = {"$in": list(subset_ids)} + + conn = _get_project_connection(project_name) + result = list(conn.aggregate([ + {"$match": subset_filter}, + {"$project": { + "family": {"$arrayElemAt": ["$data.families", 0]} + }}, + {"$group": { + "_id": "family_group", + "families": {"$addToSet": "$family"} + }} + ])) + if result: + return set(result[0]["families"]) + return set() + + +def get_version_by_name(project_name, subset_id, version, fields=None): + conn = _get_project_connection(project_name) + query_filter = { + "type": "version", + "parent": _convert_id(subset_id), + "name": version + } + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_version(project_name, version_id, fields=None): + if not version_id: + return None + conn = _get_project_connection(project_name) + query_filter = { + "type": {"$in": ["version", "hero_version"]}, + "_id": _convert_id(version_id) + } + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def _get_versions( + project_name, + subset_ids=None, + version_ids=None, + standard=True, + hero=False, + fields=None +): + version_types = [] + if standard: + version_types.append("version") + + if hero: + version_types.append("hero_version") + + if not version_types: + return [] + elif len(version_types) == 1: + query_filter = {"type": version_types[0]} + else: + query_filter = {"type": {"$in": version_types}} + + if subset_ids is not None: + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return [] + query_filter["parent"] = {"$in": subset_ids} + + if version_ids is not None: + version_ids = _convert_ids(version_ids) + if not version_ids: + return [] + query_filter["_id"] = {"$in": version_ids} + + conn = _get_project_connection(project_name) + + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_versions( + project_name, + subset_ids=None, + version_ids=None, + hero=False, + fields=None +): + return _get_versions( + project_name, + subset_ids, + version_ids, + standard=True, + hero=hero, + fields=fields + ) + + +def get_hero_version( + project_name, + subset_id=None, + version_id=None, + fields=None +): + if not subset_id and not version_id: + return None + + subset_ids = None + if subset_id is not None: + subset_ids = [subset_id] + + version_ids = None + if version_id is not None: + version_ids = [version_id] + + versions = list(_get_versions( + project_name, + subset_ids=subset_ids, + version_ids=version_ids, + standard=False, + hero=True, + fields=fields + )) + if versions: + return versions[0] + return None + + +def get_hero_versions( + project_name, + subset_ids=None, + version_ids=None, + fields=None +): + return _get_versions( + project_name, + subset_ids, + version_ids, + standard=False, + hero=True, + fields=fields + ) + + +def get_version_links(project_name, version_id, fields=None): + conn = _get_project_connection(project_name) + # Does make sense to look for hero versions? + query_filter = { + "type": "version", + "data.inputLinks.input": _convert_id(version_id) + } + return conn.find(query_filter, _prepare_fields(fields)) + + +def get_last_versions(project_name, subset_ids, fields=None): + """Retrieve all latest versions for entered subset_ids. + + Args: + subset_ids (list): List of subset ids. + + Returns: + dict: Key is subset id and value is last version name. + """ + + subset_ids = _convert_ids(subset_ids) + if not subset_ids: + return {} + + _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"} + }} + ] + + conn = _get_project_connection(project_name) + version_ids = [ + doc["_version_id"] + for doc in conn.aggregate(_pipeline) + ] + version_docs = get_versions( + project_name, version_ids=version_ids, fields=fields + ) + + return { + version_doc["parent"]: version_doc + for version_doc in version_docs + } + + +def get_last_version_for_subset( + project_name, subset_id=None, subset_name=None, asset_id=None, fields=None +): + subset_doc = get_subset( + project_name, + subset_id=subset_id, + subset_name=subset_name, + asset_id=asset_id, + fields=["_id"] + ) + if not subset_doc: + return None + subset_id = subset_doc["_id"] + last_versions = get_last_versions( + project_name, subset_ids=[subset_id], fields=fields + ) + return last_versions.get(subset_id) + + +def get_representation( + project_name, + representation_id=None, + representation_name=None, + version_id=None, + fields=None +): + if not representation_id: + if not representation_name or not version_id: + return None + + repre_types = ["representation", "archived_representations"] + query_filter = { + "type": {"$in": repre_types} + } + if representation_id is not None: + query_filter["_id"] = _convert_id(representation_id) + + if representation_name is not None: + query_filter["name"] = representation_name + + if version_id is not None: + query_filter["parent"] = version_id + + conn = _get_project_connection(project_name) + + return conn.find_one(query_filter, _prepare_fields(fields)) + + +def get_representation_by_name( + project_name, representation_name, version_id, fields=None +): + if not version_id or not representation_name: + return None + repre_types = ["representation", "archived_representations"] + query_filter = { + "type": {"$in": repre_types}, + "name": representation_name, + "parent": _convert_id(version_id) + } + + conn = _get_project_connection(project_name) + + return conn.find_one(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, + check_site_name=False, + archived=False, + fields=None +): + repre_types = ["representation"] + if archived: + repre_types.append("archived_representations") + if len(repre_types) == 1: + query_filter = {"type": repre_types[0]} + else: + query_filter = {"type": {"$in": repre_types}} + + if check_site_name: + query_filter["files.site.name"] = {"$exists": True} + + if representation_ids is not None: + representation_ids = _convert_ids(representation_ids) + if not representation_ids: + return [] + query_filter["_id"] = {"$in": representation_ids} + + if representation_names is not None: + if not representation_names: + return [] + query_filter["name"] = {"$in": list(representation_names)} + + if version_ids is not None: + version_ids = _convert_ids(version_ids) + if not version_ids: + return [] + query_filter["parent"] = {"$in": version_ids} + + if extensions is not None: + if not extensions: + return [] + query_filter["context.ext"] = {"$in": list(extensions)} + + if names_by_version_ids is not None: + or_query = [] + for version_id, names in names_by_version_ids.items(): + if version_id and names: + or_query.append({ + "parent": _convert_id(version_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)) + + +def get_representations_parents(project_name, representations): + repres_by_version_id = collections.defaultdict(list) + versions_by_version_id = {} + versions_by_subset_id = collections.defaultdict(list) + subsets_by_subset_id = {} + subsets_by_asset_id = collections.defaultdict(list) + for representation in representations: + repre_id = representation["_id"] + version_id = representation["parent"] + repres_by_version_id[version_id].append(representation) + + versions = get_versions( + project_name, version_ids=repres_by_version_id.keys() + ) + for version in versions: + version_id = version["_id"] + subset_id = version["parent"] + versions_by_version_id[version_id] = version + versions_by_subset_id[subset_id].append(version) + + subsets = get_subsets( + project_name, subset_ids=versions_by_subset_id.keys() + ) + for subset in subsets: + subset_id = subset["_id"] + asset_id = subset["parent"] + subsets_by_subset_id[subset_id] = subset + subsets_by_asset_id[asset_id].append(subset) + + assets = get_assets(project_name, asset_ids=subsets_by_asset_id.keys()) + assets_by_id = { + asset["_id"]: asset + for asset in assets + } + + project = get_project(project_name) + + output = {} + for version_id, representations in repres_by_version_id.items(): + asset = None + subset = None + version = versions_by_version_id.get(version_id) + if version: + subset_id = version["parent"] + subset = subsets_by_subset_id.get(subset_id) + if subset: + asset_id = subset["parent"] + asset = assets_by_id.get(asset_id) + + for representation in representations: + repre_id = representation["_id"] + output[repre_id] = (version, subset, asset, project) + return output + + +def get_thumbnail_id_from_source(project_name, src_type, src_id): + if not src_type or not src_id: + return None + + query_filter = {"_id": _convert_id(src_id)} + + conn = _get_project_connection(project_name) + src_doc = conn.find_one(query_filter, {"data.thumbnail_id"}) + if src_doc: + return src_doc.get("data", {}).get("thumbnail_id") + return None + + +def get_thumbnail(project_name, thumbnail_id, fields=None): + if not thumbnail_id: + return None + query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + +""" +Custom data storage: +- Webpublisher - jobs +- Ftrack - events + +openpype/tools/assetlinks/widgets.py +- SimpleLinkView + Query: + - get_versions + - get_subsets + - get_assets + - get_version_links + +openpype/tools/creator/window.py +- CreatorWindow + Query: + - get_asset + - get_subsets + +openpype/tools/launcher/models.py +- LauncherModel + Query: + - get_project + - get_assets + +openpype/tools/libraryloader/app.py +- LibraryLoaderWindow + Query: + - get_project + +openpype/tools/loader/app.py +- LoaderWindow + Query: + - get_project +- show + Query: + - get_projects + +openpype/tools/loader/model.py +- SubsetsModel + Query: + - get_assets + - get_subsets + - get_last_versions + - get_versions + - get_hero_versions + - get_version_by_name +- RepresentationModel + Query: + - get_representations + - sync server specific queries (separated into multiple functions?) + - NOT REPLACED + +openpype/tools/loader/widgets.py +- FamilyModel + Query: + - get_subset_families +- VersionTextEdit + Query: + - get_subset + - get_version +- SubsetWidget + Query: + - get_subsets + - get_representations +- RepresentationWidget + Query: + - get_subsets + - get_versions + - get_representations +- ThumbnailWidget + Query: + - get_thumbnail_id_from_source + - get_thumbnail + +openpype/tools/mayalookassigner/app.py +- MayaLookAssignerWindow + Query: + - get_last_version_for_subset + +openpype/tools/mayalookassigner/commands.py +- create_items_from_nodes + Query: + - get_asset + +openpype/tools/mayalookassigner/vray_proxies.py +- get_look_relationships + Query: + - get_representation_by_name +- load_look + Query: + - get_representation_by_name +- vrayproxy_assign_look + Query: + - get_last_version_for_subset + +openpype/tools/project_manager/project_manager/model.py +- HierarchyModel + Query: + - get_asset_ids_with_subsets + - get_project + - get_assets + +openpype/tools/project_manager/project_manager/view.py +- ProjectDocCache + Query: + - get_project + +openpype/tools/project_manager/project_manager/widgets.py +- CreateProjectDialog + Query: + - get_projects + +openpype/tools/publisher/widgets/create_dialog.py +- CreateDialog + Query: + - get_asset + - get_subsets + +openpype/tools/publisher/control.py +- AssetDocsCache + Query: + - get_assets + +openpype/tools/sceneinventory/model.py +- InventoryModel + Query: + - get_asset + - get_subset + - get_version + - get_last_version_for_subset + - get_representation + +openpype/tools/sceneinventory/switch_dialog.py +- SwitchAssetDialog + Query: + - get_asset + - get_assets + - get_subset + - get_subsets + - get_versions + - get_hero_versions + - get_last_versions + - get_representations + +openpype/tools/sceneinventory/view.py +- SceneInventoryView + Query: + - get_version + - get_versions + - get_hero_versions + - get_representation + - get_representations + +openpype/tools/standalonepublish/widgets/model_asset.py +- AssetModel + Query: + - get_assets + +openpype/tools/standalonepublish/widgets/widget_asset.py +- AssetWidget + Query: + - get_project + - get_asset + +openpype/tools/standalonepublish/widgets/widget_family.py +- FamilyWidget + Query: + - get_asset + - get_subset + - get_subsets + - get_last_version_for_subset + +openpype/tools/standalonepublish/app.py +- Window + Query: + - get_asset + +openpype/tools/texture_copy/app.py +- TextureCopy + Query: + - get_project + - get_asset + +openpype/tools/workfiles/files_widget.py +- FilesWidget + Query: + - get_asset + +openpype/tools/workfiles/model.py +- PublishFilesModel + Query: + - get_subsets + - get_versions + - get_representations + +openpype/tools/workfiles/save_as_dialog.py +- build_workfile_data + Query: + - get_project + - get_asset + +openpype/tools/workfiles/window.py +- Window + Query: + - get_asset + +openpype/tools/utils/assets_widget.py +- AssetModel + Query: + - get_project + - get_assets + +openpype/tools/utils/delegates.py +- VersionDelegate + Query: + - get_versions + - get_hero_versions + +openpype/tools/utils/lib.py +- GroupsConfig + Query: + - get_project +- FamilyConfigCache + Query: + - get_asset + +openpype/tools/utils/tasks_widget.py +- TasksModel + Query: + - get_project + - get_asset +""" From 7109d932ec2ed70a2042d2df7563de368293a88e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 4 Jun 2022 21:08:42 +0200 Subject: [PATCH 046/195] replace queries in tools --- openpype/tools/assetlinks/widgets.py | 159 +++-- openpype/tools/creator/window.py | 21 +- openpype/tools/launcher/models.py | 21 +- openpype/tools/libraryloader/app.py | 57 +- openpype/tools/loader/app.py | 17 +- openpype/tools/loader/model.py | 289 +++++---- openpype/tools/loader/widgets.py | 231 ++++--- openpype/tools/mayalookassigner/app.py | 21 +- openpype/tools/mayalookassigner/commands.py | 18 +- .../tools/mayalookassigner/vray_proxies.py | 77 +-- .../project_manager/project_manager/model.py | 42 +- .../project_manager/project_manager/view.py | 9 +- .../project_manager/widgets.py | 13 +- openpype/tools/publisher/control.py | 9 +- .../tools/publisher/widgets/create_dialog.py | 22 +- openpype/tools/sceneinventory/model.py | 38 +- .../tools/sceneinventory/switch_dialog.py | 569 ++++++++---------- openpype/tools/sceneinventory/view.py | 113 ++-- openpype/tools/standalonepublish/app.py | 18 +- .../standalonepublish/widgets/model_asset.py | 12 +- .../standalonepublish/widgets/widget_asset.py | 27 +- .../widgets/widget_family.py | 81 ++- openpype/tools/texture_copy/app.py | 28 +- openpype/tools/utils/assets_widget.py | 19 +- openpype/tools/utils/delegates.py | 40 +- openpype/tools/utils/lib.py | 14 +- openpype/tools/utils/tasks_widget.py | 13 +- openpype/tools/workfiles/files_widget.py | 5 +- openpype/tools/workfiles/model.py | 48 +- openpype/tools/workfiles/save_as_dialog.py | 30 +- openpype/tools/workfiles/window.py | 23 +- 31 files changed, 1010 insertions(+), 1074 deletions(-) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 22e8848a60..5ce2a835ef 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -1,10 +1,16 @@ +import collections +from openpype.client import ( + get_versions, + get_subsets, + get_assets, + get_version_links, +) from Qt import QtWidgets class SimpleLinkView(QtWidgets.QWidget): - - def __init__(self, dbcon, parent=None): + def __init__(self, dbcon, parent): super(SimpleLinkView, self).__init__(parent=parent) self.dbcon = dbcon @@ -24,6 +30,11 @@ class SimpleLinkView(QtWidgets.QWidget): self._in_view = in_view self._out_view = out_view + self._version_doc_to_process = None + + @property + def project_name(self): + return self.dbcon.current_project() def clear(self): self._in_view.clear() @@ -31,60 +42,114 @@ class SimpleLinkView(QtWidgets.QWidget): def set_version(self, version_doc): self.clear() - if not version_doc or not self.isVisible(): - return + self._version_doc_to_process = version_doc + if version_doc and self.isVisible(): + self._fill_values() - # inputs - # + def showEvent(self, event): + super(SimpleLinkView, self).showEvent(event) + self._fill_values() + + def _fill_values(self): + if self._version_doc_to_process is None: + return + version_doc = self._version_doc_to_process + self._version_doc_to_process = None + self._fill_inputs(version_doc) + self._fill_outputs(version_doc) + + def _fill_inputs(self, version_doc): + version_ids = set() for link in version_doc["data"].get("inputLinks", []): # Backwards compatibility for "input" key used as "id" if "id" not in link: link_id = link["input"] else: link_id = link["id"] - version = self.dbcon.find_one( - {"_id": link_id, "type": "version"}, - projection={"name": 1, "parent": 1} - ) - if not version: - continue - subset = self.dbcon.find_one( - {"_id": version["parent"], "type": "subset"}, - projection={"name": 1, "parent": 1} - ) - if not subset: - continue - asset = self.dbcon.find_one( - {"_id": subset["parent"], "type": "asset"}, - projection={"name": 1} - ) + version_ids.add(link_id) - self._in_view.addItem("{asset} {subset} v{version:0>3}".format( - asset=asset["name"], - subset=subset["name"], - version=version["name"], + version_docs = list(get_versions( + self.project_name, + version_ids=version_ids, + fields=["name", "parent"] + )) + + subset_docs = [] + versions_by_subset_id = collections.defaultdict(list) + if versions_by_subset_id: + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + + subset_docs = list(get_subsets( + self.project_name, + subset_ids=versions_by_subset_id.keys(), + fields=["_id", "name", "parent"] )) - # outputs - # - outputs = self.dbcon.find( - {"type": "version", "data.inputLinks.input": version_doc["_id"]}, - projection={"name": 1, "parent": 1} - ) - for version in outputs or []: - subset = self.dbcon.find_one( - {"_id": version["parent"], "type": "subset"}, - projection={"name": 1, "parent": 1} - ) - if not subset: - continue - asset = self.dbcon.find_one( - {"_id": subset["parent"], "type": "asset"}, - projection={"name": 1} - ) + asset_docs = [] + subsets_by_asset_id = collections.defaultdict(list) + if subset_docs: + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subsets_by_asset_id[asset_id].append(subset_doc) - self._out_view.addItem("{asset} {subset} v{version:0>3}".format( - asset=asset["name"], - subset=subset["name"], - version=version["name"], + asset_docs = list(get_assets( + self.project_name, + asset_ids=subsets_by_asset_id.keys(), + fields=["_id", "name"] )) + + for asset_doc in asset_docs: + asset_id = asset_doc["_id"] + for subset_doc in subsets_by_asset_id[asset_id]: + subset_id = subset_doc["_id"] + for version_doc in versions_by_subset_id[subset_id]: + self._in_view.addItem("{} {} v{:0>3}".format( + asset_doc["name"], + subset_doc["name"], + version_doc["name"], + )) + + def _fill_outputs(self, version_doc): + version_docs = list(get_version_links( + self.project_name, + version_doc["_id"], + fields=["name", "parent"] + )) + subset_docs = [] + versions_by_subset_id = collections.defaultdict(list) + if versions_by_subset_id: + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + + subset_docs = list(get_subsets( + self.project_name, + subset_ids=versions_by_subset_id.keys(), + fields=["_id", "name", "parent"] + )) + + asset_docs = [] + subsets_by_asset_id = collections.defaultdict(list) + if subset_docs: + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subsets_by_asset_id[asset_id].append(subset_doc) + + asset_docs = list(get_assets( + self.project_name, + asset_ids=subsets_by_asset_id.keys(), + fields=["_id", "name"] + )) + + for asset_doc in asset_docs: + asset_id = asset_doc["_id"] + for subset_doc in subsets_by_asset_id[asset_id]: + subset_id = subset_doc["_id"] + for version_doc in versions_by_subset_id[subset_id]: + self._out_view.addItem("{} {} v{:0>3}".format( + asset_doc["name"], + subset_doc["name"], + version_doc["name"], + )) diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py index e0c329fb78..a85f47a060 100644 --- a/openpype/tools/creator/window.py +++ b/openpype/tools/creator/window.py @@ -4,6 +4,7 @@ import re from Qt import QtWidgets, QtCore +from openpype.client import get_asset, get_subsets from openpype import style from openpype.api import get_current_project_settings from openpype.tools.utils.lib import qt_app_context @@ -215,12 +216,12 @@ class CreatorWindow(QtWidgets.QDialog): self._set_valid_state(False) return + project_name = legacy_io.active_project() asset_doc = None if creator_plugin: # Get the asset from the database which match with the name - asset_doc = legacy_io.find_one( - {"name": asset_name, "type": "asset"}, - projection={"_id": 1} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin @@ -235,7 +236,6 @@ class CreatorWindow(QtWidgets.QDialog): self._set_valid_state(False) return - project_name = legacy_io.Session["AVALON_PROJECT"] asset_id = asset_doc["_id"] task_name = legacy_io.Session["AVALON_TASK"] @@ -269,14 +269,13 @@ class CreatorWindow(QtWidgets.QDialog): self._subset_name_input.setText(subset_name) # Get all subsets of the current asset - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) + existing_subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } existing_subset_names_low = set( _name.lower() for _name in existing_subset_names diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 13567e7916..307f591d96 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -9,6 +9,10 @@ import appdirs from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_assets, +) from openpype.lib import JSONSettingRegistry from openpype.lib.applications import ( CUSTOM_LAUNCH_APP_GROUPS, @@ -81,13 +85,11 @@ class ActionModel(QtGui.QStandardItemModel): def get_application_actions(self): actions = [] - if not self.dbcon.Session.get("AVALON_PROJECT"): + if not self.dbcon.current_project(): return actions - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"config.apps": True} - ) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name, fields=["config.apps"]) if not project_doc: return actions @@ -448,7 +450,7 @@ class LauncherModel(QtCore.QObject): @property def project_name(self): """Current project name.""" - return self._dbcon.Session.get("AVALON_PROJECT") + return self._dbcon.current_project() @property def refreshing_assets(self): @@ -649,10 +651,9 @@ class LauncherModel(QtCore.QObject): self._asset_refresh_thread = None def _refresh_assets(self): - asset_docs = list(self._dbcon.find( - {"type": "asset"}, - self._asset_projection - )) + asset_docs = get_assets( + self._last_project_name, fields=list(self._asset_projection.keys()) + ) if not self._refreshing_assets: return self._refreshing_assets = False diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py index 7fda6bd6f9..5f4d10d796 100644 --- a/openpype/tools/libraryloader/app.py +++ b/openpype/tools/libraryloader/app.py @@ -3,6 +3,7 @@ import sys from Qt import QtWidgets, QtCore, QtGui from openpype import style +from openpype.client import get_project from openpype.pipeline import AvalonMongoDB from openpype.tools.utils import lib as tools_lib from openpype.tools.loader.widgets import ( @@ -303,14 +304,26 @@ class LibraryLoaderWindow(QtWidgets.QDialog): families = self._subsets_widget.get_subsets_families() self._families_filter_view.set_enabled_families(families) - def set_context(self, context, refresh=True): - self.echo("Setting context: {}".format(context)) - lib.schedule( - lambda: self._set_context(context, refresh=refresh), - 50, channel="mongo" - ) - # ------------------------------ + def set_context(self, context, refresh=True): + """Set the selection in the interface using a context. + The context must contain `asset` data by name. + + Args: + context (dict): The context to apply. + Returns: + None + """ + + asset_name = context.get("asset", None) + if asset_name is None: + return + + if refresh: + self._refresh_assets() + + self._assets_widget.select_asset_by_name(asset_name) + def _on_family_filter_change(self, families): self._subsets_widget.set_family_filters(families) @@ -323,10 +336,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): """Load assets from database""" if self.current_project is not None: # Ensure a project is loaded - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"type": 1} - ) + project_doc = get_project(self.current_project, fields=["_id"]) assert project_doc, "This is a bug" self._families_filter_view.set_enabled_families(set()) @@ -371,7 +381,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog): # Clear the version information on asset change self._version_info_widget.set_version(None) - self._thumbnail_widget.set_thumbnail(asset_ids) + self._thumbnail_widget.set_thumbnail("asset", asset_ids) self.data["state"]["assetIds"] = asset_ids @@ -426,34 +436,17 @@ class LibraryLoaderWindow(QtWidgets.QDialog): version_doc["_id"] for version_doc in version_docs ] + src_type = "version" if not thumbnail_src_ids: + src_type = "asset" thumbnail_src_ids = self._assets_widget.get_selected_asset_ids() - self._thumbnail_widget.set_thumbnail(thumbnail_src_ids) + self._thumbnail_widget.set_thumbnail(src_type, thumbnail_src_ids) version_ids = [doc["_id"] for doc in version_docs or []] if self._repres_widget: self._repres_widget.set_version_ids(version_ids) - def _set_context(self, context, refresh=True): - """Set the selection in the interface using a context. - The context must contain `asset` data by name. - - Args: - context (dict): The context to apply. - Returns: - None - """ - - asset_name = context.get("asset", None) - if asset_name is None: - return - - if refresh: - self._refresh_assets() - - self._assets_widget.select_asset_by_name(asset_name) - def _on_message_timeout(self): self._message_label.setText("") diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py index bb589c199d..1917f23c60 100644 --- a/openpype/tools/loader/app.py +++ b/openpype/tools/loader/app.py @@ -3,6 +3,7 @@ import traceback from Qt import QtWidgets, QtCore +from openpype.client import get_projects, get_project from openpype import style from openpype.lib import register_event_callback from openpype.pipeline import ( @@ -39,7 +40,7 @@ class LoaderWindow(QtWidgets.QDialog): def __init__(self, parent=None): super(LoaderWindow, self).__init__(parent) title = "Asset Loader 2.1" - project_name = legacy_io.Session.get("AVALON_PROJECT") + project_name = legacy_io.active_project() if project_name: title += " - {}".format(project_name) self.setWindowTitle(title) @@ -274,8 +275,9 @@ class LoaderWindow(QtWidgets.QDialog): """Load assets from database""" # Ensure a project is loaded - project = legacy_io.find_one({"type": "project"}, {"type": 1}) - assert project, "Project was not found! This is a bug" + project_name = legacy_io.active_project() + project_doc = get_project(project_name, fields=["_id"]) + assert project_doc, "Project was not found! This is a bug" self._assets_widget.refresh() self._assets_widget.setFocus() @@ -314,7 +316,7 @@ class LoaderWindow(QtWidgets.QDialog): ) # Clear the version information on asset change - self._thumbnail_widget.set_thumbnail(asset_ids) + self._thumbnail_widget.set_thumbnail("asset", asset_ids) self._version_info_widget.set_version(None) self.data["state"]["assetIds"] = asset_ids @@ -371,10 +373,12 @@ class LoaderWindow(QtWidgets.QDialog): version_doc["_id"] for version_doc in version_docs ] + source_type = "version" if not thumbnail_src_ids: + source_type = "asset" thumbnail_src_ids = self._assets_widget.get_selected_asset_ids() - self._thumbnail_widget.set_thumbnail(thumbnail_src_ids) + self._thumbnail_widget.set_thumbnail(source_type, thumbnail_src_ids) if self._repres_widget is not None: version_ids = [doc["_id"] for doc in version_docs] @@ -576,8 +580,7 @@ def show(debug=False, parent=None, use_context=False): legacy_io.install() any_project = next( - project for project in legacy_io.projects() - if project.get("active", True) is not False + project for project in get_projects(fields=["name"]) ) legacy_io.Session["AVALON_PROJECT"] = any_project["name"] diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6f39c428be..e8e0480d9c 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -7,6 +7,15 @@ from uuid import uuid4 from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions, + get_versions, + get_hero_versions, + get_version_by_name, + get_representations +) from openpype.pipeline import ( HeroVersionType, schema, @@ -56,7 +65,7 @@ class BaseRepresentationModel(object): remote_site = remote_provider = None if not project_name: - project_name = self.dbcon.Session.get("AVALON_PROJECT") + project_name = self.dbcon.active_project() else: self.dbcon.Session["AVALON_PROJECT"] = project_name @@ -254,57 +263,61 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # because it also updates the information in other columns if index.column() == self.columns_index["version"]: item = index.internalPointer() - parent = item["_id"] + subset_id = item["_id"] if isinstance(value, HeroVersionType): - versions = list(self.dbcon.find({ - "type": {"$in": ["version", "hero_version"]}, - "parent": parent - }, sort=[("name", -1)])) - - version = None - last_version = None - for __version in versions: - if __version["type"] == "hero_version": - version = __version - elif last_version is None: - last_version = __version - - if version is not None and last_version is not None: - break - - _version = None - for __version in versions: - if __version["_id"] == version["version_id"]: - _version = __version - break - - version["data"] = _version["data"] - version["name"] = _version["name"] - version["is_from_latest"] = ( - last_version["_id"] == _version["_id"] - ) + version_doc = self._get_hero_version(subset_id) else: - version = self.dbcon.find_one({ - "name": value, - "type": "version", - "parent": parent - }) + project_name = self.dbcon.active_project() + version_doc = get_version_by_name( + project_name, subset_id, value + ) # update availability on active site when version changes - if self.sync_server.enabled and version: - query = self._repre_per_version_pipeline([version["_id"]], - self.active_site, - self.remote_site) + if self.sync_server.enabled and version_doc: + query = self._repre_per_version_pipeline( + [version_doc["_id"]], + self.active_site, + self.remote_site + ) docs = list(self.dbcon.aggregate(query)) if docs: repre = docs.pop() - version["data"].update(self._get_repre_dict(repre)) + version_doc["data"].update(self._get_repre_dict(repre)) - self.set_version(index, version) + self.set_version(index, version_doc) return super(SubsetsModel, self).setData(index, value, role) + def _get_hero_version(self, subset_id): + project_name = self.dbcon.active_project() + version_docs = get_versions( + project_name, subset_ids=[subset_id], hero=True + ) + standard_versions = [] + hero_version_doc = None + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version_doc = version_doc + continue + standard_versions.append(version_doc) + + src_version_id = hero_version_doc["version_id"] + src_version = None + is_from_latest = True + for version_doc in reversed(sorted( + standard_versions, key=lambda item: item["name"] + )): + if version_doc["_id"] == src_version_id: + src_version = version_doc + break + is_from_latest = False + + hero_version_doc["data"] = src_version["data"] + hero_version_doc["name"] = src_version["name"] + hero_version_doc["is_from_latest"] = is_from_latest + return hero_version_doc + def set_version(self, index, version): """Update the version data of the given index. @@ -391,26 +404,25 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): item["repre_info"] = repre_info def _fetch(self): - asset_docs = self.dbcon.find( - { - "type": "asset", - "_id": {"$in": self._asset_ids} - }, - self.asset_doc_projection + project_name = self.dbcon.active_project() + asset_docs = get_assets( + project_name, + asset_ids=self._asset_ids, + fields=self.asset_doc_projection.keys() ) + asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs } subset_docs_by_id = {} - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": {"$in": self._asset_ids} - }, - self.subset_doc_projection + subset_docs = get_subsets( + project_name, + asset_ids=self._asset_ids, + fields=self.subset_doc_projection.keys() ) + subset_families = set() for subset_doc in subset_docs: if self._doc_fetching_stop: @@ -423,37 +435,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): subset_docs_by_id[subset_doc["_id"]] = subset_doc subset_ids = list(subset_docs_by_id.keys()) - _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"}, - "type": {"$last": "$type"}, - "data": {"$last": "$data"}, - "locations": {"$last": "$locations"}, - "schema": {"$last": "$schema"} - }} - ] - last_versions_by_subset_id = dict() - for doc in self.dbcon.aggregate(_pipeline): - if self._doc_fetching_stop: - return - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc + last_versions_by_subset_id = get_last_versions( + project_name, + subset_ids, + fields=["_id", "parent", "name", "type", "data", "schema"] + ) - hero_versions = self.dbcon.find({ - "type": "hero_version", - "parent": {"$in": subset_ids} - }) + hero_versions = get_hero_versions(project_name, subset_ids=subset_ids) missing_versions = [] for hero_version in hero_versions: version_id = hero_version["version_id"] @@ -462,10 +450,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): missing_versions_by_id = {} if missing_versions: - missing_version_docs = self.dbcon.find({ - "type": "version", - "_id": {"$in": missing_versions} - }) + missing_version_docs = get_versions( + project_name, version_ids=missing_versions + ) missing_versions_by_id = { missing_version_doc["_id"]: missing_version_doc for missing_version_doc in missing_version_docs @@ -488,23 +475,16 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version - self._doc_payload = { - "asset_docs_by_id": asset_docs_by_id, - "subset_docs_by_id": subset_docs_by_id, - "subset_families": subset_families, - "last_versions_by_subset_id": last_versions_by_subset_id - } - + repre_info = {} if self.sync_server.enabled: version_ids = set() for _subset_id, doc in last_versions_by_subset_id.items(): version_ids.add(doc["_id"]) - query = self._repre_per_version_pipeline(list(version_ids), - self.active_site, - self.remote_site) + query = self._repre_per_version_pipeline( + list(version_ids), self.active_site, self.remote_site + ) - repre_info = {} for doc in self.dbcon.aggregate(query): if self._doc_fetching_stop: return @@ -512,7 +492,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): doc["remote_provider"] = self.remote_provider repre_info[doc["_id"]] = doc - self._doc_payload["repre_info_by_version_id"] = repre_info + self._doc_payload = { + "asset_docs_by_id": asset_docs_by_id, + "subset_docs_by_id": subset_docs_by_id, + "subset_families": subset_families, + "last_versions_by_subset_id": last_versions_by_subset_id, + "repre_info_by_version_id": repre_info + } self.doc_fetched.emit() @@ -1062,6 +1048,16 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): "remote_site": "Remote" } + repre_projection = { + "_id": 1, + "name": 1, + "context.subset": 1, + "context.asset": 1, + "context.version": 1, + "context.representation": 1, + 'files.sites': 1 + } + def __init__(self, dbcon, header, version_ids): super(RepresentationModel, self).__init__() self.dbcon = dbcon @@ -1073,22 +1069,22 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): sync_server = active_site = remote_site = None active_provider = remote_provider = None - project = dbcon.Session["AVALON_PROJECT"] - if project: + project_name = dbcon.current_project() + if project_name: sync_server = manager.modules_by_name["sync_server"] - active_site = sync_server.get_active_site(project) - remote_site = sync_server.get_remote_site(project) + active_site = sync_server.get_active_site(project_name) + remote_site = sync_server.get_remote_site(project_name) # TODO refactor - active_provider = \ - sync_server.get_provider_for_site(project, - active_site) + active_provider = sync_server.get_provider_for_site( + project_name, active_site + ) if active_site == 'studio': active_provider = 'studio' - remote_provider = \ - sync_server.get_provider_for_site(project, - remote_site) + remote_provider = sync_server.get_provider_for_site( + project_name, remote_site + ) if remote_site == 'studio': remote_provider = 'studio' @@ -1127,8 +1123,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): if index.column() == self.Columns.index("name"): if item.get("isMerged"): return item["icon"] - else: - return self._icons["repre"] + return self._icons["repre"] active_index = self.Columns.index("active_site") remote_index = self.Columns.index("remote_site") @@ -1144,12 +1139,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): # site added, sync in progress progress_str = "not avail." if progress >= 0: - # progress == 0 for isMerged is unavailable if progress == 0 and item.get("isMerged"): progress_str = "not avail." else: - progress_str = "{}% {}".format(int(progress * 100), - label) + progress_str = "{}% {}".format( + int(progress * 100), label + ) return progress_str @@ -1192,7 +1187,6 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): if len(self.version_ids) > 1: group = repre_groups.get(doc["name"]) if not group: - group_item = Item() item_id = str(uuid4()) group_item.update({ @@ -1213,9 +1207,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): repre_groups_items[doc["name"]] = 0 group = group_item - progress = lib.get_progress_for_repre(doc, - self.active_site, - self.remote_site) + progress = lib.get_progress_for_repre( + doc, self.active_site, self.remote_site + ) active_site_icon = self._icons.get(self.active_provider) remote_site_icon = self._icons.get(self.remote_provider) @@ -1248,9 +1242,9 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): 'remote_site_progress': progress[self.remote_site] } if group: - group = self._sum_group_progress(doc["name"], group, - current_progress, - repre_groups_items) + group = self._sum_group_progress( + doc["name"], group, current_progress, repre_groups_items + ) self.add_child(item, group) @@ -1269,47 +1263,40 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return self._items_by_id.get(item_id) def refresh(self): - docs = [] - session_project = self.dbcon.Session['AVALON_PROJECT'] - if not session_project: + project_name = self.dbcon.current_project() + if not project_name: return + repre_docs = [] if self.version_ids: # Simple find here for now, expected to receive lower number of # representations and logic could be in Python - docs = list(self.dbcon.find( - {"type": "representation", "parent": {"$in": self.version_ids}, - "files.sites.name": {"$exists": 1}}, self.projection())) - self._docs = docs + repre_docs = list(get_representations( + project_name, + version_ids=self.version_ids, + check_site_name=True, + fields=self.repre_projection.keys() + )) + + self._docs = repre_docs self.doc_fetched.emit() - @classmethod - def projection(cls): - return { - "_id": 1, - "name": 1, - "context.subset": 1, - "context.asset": 1, - "context.version": 1, - "context.representation": 1, - 'files.sites': 1 - } + def _sum_group_progress( + self, repre_name, group, current_item_progress, repre_groups_items + ): + """Update final group progress - def _sum_group_progress(self, repre_name, group, current_item_progress, - repre_groups_items): - """ - Update final group progress - Called after every item in group is added + Called after every item in group is added - Args: - repre_name(string) - group(dict): info about group of selected items - current_item_progress(dict): {'active_site_progress': XX, - 'remote_site_progress': YY} - repre_groups_items(dict) - Returns: - (dict): updated group info + Args: + repre_name(string) + group(dict): info about group of selected items + current_item_progress(dict): {'active_site_progress': XX, + 'remote_site_progress': YY} + repre_groups_items(dict) + Returns: + (dict): updated group info """ repre_groups_items[repre_name] += 1 diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 42fb62b632..6c7acc593d 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -7,6 +7,16 @@ import collections from Qt import QtWidgets, QtCore, QtGui +from openpype.client import ( + get_subset_families, + get_subset, + get_subsets, + get_version, + get_versions, + get_representations, + get_thumbnail_id_from_source, + get_thumbnail, +) from openpype.api import Anatomy from openpype.pipeline import HeroVersionType from openpype.pipeline.thumbnail import get_thumbnail_binary @@ -237,8 +247,7 @@ class SubsetWidget(QtWidgets.QWidget): self.model = model self.view = view - actual_project = dbcon.Session["AVALON_PROJECT"] - self.on_project_change(actual_project) + self.on_project_change(dbcon.current_project()) view.customContextMenuRequested.connect(self.on_context_menu) @@ -302,33 +311,23 @@ class SubsetWidget(QtWidgets.QWidget): item["version_document"] ) - subset_docs = list(self.dbcon.find( - { - "_id": {"$in": list(version_docs_by_subset_id.keys())}, - "type": "subset" - }, - { - "schema": 1, - "data.families": 1 - } + project_name = self.dbcon.active_project() + subset_docs = list(get_subsets( + project_name, + subset_ids=version_docs_by_subset_id.keys(), + fields=["schema", "data.families"] )) subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } version_ids = list(version_docs_by_id.keys()) - repre_docs = self.dbcon.find( - # Query all representations for selected versions at once - { - "type": "representation", - "parent": {"$in": version_ids} - }, - # Query only name and parent from representation - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=version_ids, + fields=["name", "parent"] ) + repre_docs_by_version_id = { version_id: [] for version_id in version_ids @@ -566,28 +565,42 @@ class SubsetWidget(QtWidgets.QWidget): # same representation available # Trigger - repre_ids = [] + project_name = self.dbcon.active_project() + subset_names_by_version_id = collections.defaultdict(set) for item in items: - representation = self.dbcon.find_one( - { - "type": "representation", - "name": representation_name, - "parent": item["version_document"]["_id"] - }, - {"_id": 1} - ) - if not representation: - self.echo("Subset '{}' has no representation '{}'".format( - item["subset"], representation_name - )) - continue - repre_ids.append(representation["_id"]) + version_id = item["version_document"]["_id"] + subset_names_by_version_id[version_id].add(item["subset"]) + + version_ids = set(subset_names_by_version_id.keys()) + repre_docs = get_representations( + project_name, + representation_names=[representation_name], + version_ids=version_ids, + fields=["_id", "parent"] + ) + + repre_ids = [] + for repre_doc in repre_docs: + repre_ids.append(repre_doc["_id"]) + + version_id = repre_doc["parent"] + if version_id not in version_ids: + version_ids.remove(version_id) + + for version_id in version_ids: + joined_subset_names = ", ".join([ + '"{}"'.format(subset) + for subset in subset_names_by_version_id[version_id] + ]) + self.echo("Subsets {} don't have representation '{}'".format( + joined_subset_names, representation_name + )) # get contexts only for selected menu option repre_contexts = get_repres_contexts(repre_ids, self.dbcon) - options = lib.get_options(action, loader, self, - list(repre_contexts.values())) - + options = lib.get_options( + action, loader, self, list(repre_contexts.values()) + ) error_info = _load_representations_by_loader( loader, repre_contexts, options=options ) @@ -661,27 +674,21 @@ class VersionTextEdit(QtWidgets.QTextEdit): print("Querying..") + project_name = self.dbcon.active_project() if not version_doc: - version_doc = self.dbcon.find_one({ - "_id": version_id, - "type": {"$in": ["version", "hero_version"]} - }) + version_doc = get_version(project_name, version_id=version_id) assert version_doc, "Not a valid version id" if version_doc["type"] == "hero_version": - _version_doc = self.dbcon.find_one({ - "_id": version_doc["version_id"], - "type": "version" - }) + _version_doc = get_version( + project_name, version_id=version_doc["version_id"] + ) version_doc["data"] = _version_doc["data"] version_doc["name"] = HeroVersionType( _version_doc["name"] ) - subset = self.dbcon.find_one({ - "_id": version_doc["parent"], - "type": "subset" - }) + subset = get_subset(project_name, subset_id=version_doc["parent"]) assert subset, "No valid subset parent for version" # Define readable creation timestamp @@ -752,7 +759,7 @@ class VersionTextEdit(QtWidgets.QTextEdit): if not source: return - project_name = self.dbcon.Session["AVALON_PROJECT"] + project_name = self.dbcon.current_project() if self._anatomy is None or self._anatomy.project_name != project_name: self._anatomy = Anatomy(project_name) @@ -833,24 +840,19 @@ class ThumbnailWidget(QtWidgets.QLabel): QtCore.Qt.SmoothTransformation ) - def set_thumbnail(self, doc_id=None): - if not doc_id: + def set_thumbnail(self, src_type, doc_ids): + if not doc_ids: self.set_pixmap() return - if isinstance(doc_id, (list, tuple)): - if len(doc_id) < 1: - self.set_pixmap() - return - doc_id = doc_id[0] + src_id = doc_ids[0] - doc = self.dbcon.find_one( - {"_id": doc_id}, - {"data.thumbnail_id"} + project_name = self.dbcon.active_project() + thumbnail_id = get_thumbnail_id_from_source( + project_name, + src_type, + src_id, ) - thumbnail_id = None - if doc: - thumbnail_id = doc.get("data", {}).get("thumbnail_id") if thumbnail_id == self.current_thumb_id: if self.current_thumbnail is None: self.set_pixmap() @@ -861,9 +863,7 @@ class ThumbnailWidget(QtWidgets.QLabel): self.set_pixmap() return - thumbnail_ent = self.dbcon.find_one( - {"type": "thumbnail", "_id": thumbnail_id} - ) + thumbnail_ent = get_thumbnail(project_name, thumbnail_id) if not thumbnail_ent: return @@ -917,21 +917,9 @@ class FamilyModel(QtGui.QStandardItemModel): def refresh(self): families = set() - if self.dbcon.Session.get("AVALON_PROJECT"): - result = list(self.dbcon.aggregate([ - {"$match": { - "type": "subset" - }}, - {"$project": { - "family": {"$arrayElemAt": ["$data.families", 0]} - }}, - {"$group": { - "_id": "family_group", - "families": {"$addToSet": "$family"} - }} - ])) - if result: - families = set(result[0]["families"]) + project_name = self.dbcon.current_project() + if project_name: + families = get_subset_families(project_name) root_item = self.invisibleRootItem() @@ -1213,8 +1201,8 @@ class RepresentationWidget(QtWidgets.QWidget): self.proxy_model = proxy_model self.sync_server_enabled = False - actual_project = dbcon.Session["AVALON_PROJECT"] - self.on_project_change(actual_project) + + self.on_project_change(dbcon.current_project()) self.model.refresh() @@ -1243,23 +1231,18 @@ class RepresentationWidget(QtWidgets.QWidget): for item in items: repre_ids.append(item["_id"]) - repre_docs = list(self.dbcon.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - { - "name": 1, - "parent": 1 - } - )) + project_name = self.dbcon.actual_project() + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["name", "parent"] + ) + version_ids = [ repre_doc["parent"] for repre_doc in repre_docs ] - version_docs = self.dbcon.find({ - "_id": {"$in": version_ids} - }) + version_docs = get_versions(project_name, version_ids=version_ids) version_docs_by_id = {} version_docs_by_subset_id = collections.defaultdict(list) @@ -1269,15 +1252,10 @@ class RepresentationWidget(QtWidgets.QWidget): version_docs_by_id[version_id] = version_doc version_docs_by_subset_id[subset_id].append(version_doc) - subset_docs = list(self.dbcon.find( - { - "_id": {"$in": list(version_docs_by_subset_id.keys())}, - "type": "subset" - }, - { - "schema": 1, - "data.families": 1 - } + subset_docs = list(get_subsets( + project_name, + subset_ids=version_docs_by_subset_id.keys(), + fields=["schema", "data.families"] )) subset_docs_by_id = { subset_doc["_id"]: subset_doc @@ -1446,13 +1424,12 @@ class RepresentationWidget(QtWidgets.QWidget): self._process_action(items, menu, point) def _process_action(self, items, menu, point): - """ - Show the context action menu and process selected + """Show the context action menu and process selected - Args: - items(dict): menu items - menu(OptionalMenu) - point(PointIndex) + Args: + items(dict): menu items + menu(OptionalMenu) + point(PointIndex) """ global_point = self.tree_view.mapToGlobal(point) action = menu.exec_(global_point) @@ -1468,21 +1445,23 @@ class RepresentationWidget(QtWidgets.QWidget): data_by_repre_id = {} selected_side = action_representation.get("selected_side") + is_sync_loader = tools_lib.is_sync_loader(loader) for item in items: - if tools_lib.is_sync_loader(loader): - site_name = "{}_site_name".format(selected_side) - data = { - "_id": item.get("_id"), - "site_name": item.get(site_name), - "project_name": self.dbcon.Session["AVALON_PROJECT"] - } + item_id = item.get("_id") + repre_ids.append(item_id) + if not is_sync_loader: + continue - if not data["site_name"]: - continue + site_name = "{}_site_name".format(selected_side) + data_site_name = item.get(site_name) + if not data_site_name: + continue - data_by_repre_id[data["_id"]] = data - - repre_ids.append(item.get("_id")) + data_by_repre_id[item_id] = { + "_id": item_id, + "site_name": data_site_name, + "project_name": self.dbcon.active_project() + } repre_contexts = get_repres_contexts(repre_ids, self.dbcon) options = lib.get_options(action, loader, self, diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 1b6cad77a8..427edf8245 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -4,6 +4,7 @@ import logging from Qt import QtWidgets, QtCore +from openpype.client import get_last_version_for_subset from openpype import style from openpype.pipeline import legacy_io from openpype.tools.utils.lib import qt_app_context @@ -211,6 +212,7 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): selection = self.assign_selected.isChecked() asset_nodes = self.asset_outliner.get_nodes(selection=selection) + project_name = legacy_io.active_project() start = time.time() for i, (asset, item) in enumerate(asset_nodes.items()): @@ -222,23 +224,20 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): assign_look = next((subset for subset in item["looks"] if subset["name"] in looks), None) if not assign_look: - self.echo("{} No matching selected " - "look for {}".format(prefix, asset)) + self.echo( + "{} No matching selected look for {}".format(prefix, asset) + ) continue # Get the latest version of this asset's look subset - version = legacy_io.find_one( - { - "type": "version", - "parent": assign_look["_id"] - }, - sort=[("name", -1)] + version = get_last_version_for_subset( + project_name, subset_id=assign_look["_id"], fields=["_id"] ) subset_name = assign_look["name"] - self.echo("{} Assigning {} to {}\t".format(prefix, - subset_name, - asset)) + self.echo("{} Assigning {} to {}\t".format( + prefix, subset_name, asset + )) nodes = item["nodes"] if cmds.pluginInfo('vrayformaya', query=True, loaded=True): diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index d41d8ca5a2..a4fc1fab70 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -2,9 +2,9 @@ from collections import defaultdict import logging import os -from bson.objectid import ObjectId import maya.cmds as cmds +from openpype.client import get_asset from openpype.pipeline import ( legacy_io, remove_container, @@ -159,11 +159,9 @@ def create_items_from_nodes(nodes): log.warning("No id hashes") return asset_view_items + project_name = legacy_io.active_project() for _id, id_nodes in id_hashes.items(): - asset = legacy_io.find_one( - {"_id": ObjectId(_id)}, - projection={"name": True} - ) + asset = get_asset(project_name, asset_id=_id, fields=["name"]) # Skip if asset id is not found if not asset: @@ -180,10 +178,12 @@ def create_items_from_nodes(nodes): namespace = get_namespace_from_node(node) namespaces.add(namespace) - asset_view_items.append({"label": asset["name"], - "asset": asset, - "looks": looks, - "namespaces": namespaces}) + asset_view_items.append({ + "label": asset["name"], + "asset": asset, + "looks": looks, + "namespaces": namespaces + }) return asset_view_items diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index 3523b24bf3..b2ba21f944 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -6,11 +6,14 @@ import logging import json import six -from bson.objectid import ObjectId import alembic.Abc from maya import cmds +from openpype.client import ( + get_representation_by_name, + get_last_version_for_subset, +) from openpype.pipeline import ( legacy_io, load_container, @@ -155,13 +158,12 @@ def get_look_relationships(version_id): Returns: dict: Dictionary of relations. - """ - json_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "json" - }) + + project_name = legacy_io.active_project() + json_representation = get_representation_by_name( + project_name, representation_name="json", version_id=version_id + ) # Load relationships shader_relation = get_representation_path(json_representation) @@ -184,12 +186,12 @@ def load_look(version_id): list of shader nodes. """ + + project_name = legacy_io.active_project() # Get representations of shader file and relationships - look_representation = legacy_io.find_one({ - "type": "representation", - "parent": version_id, - "name": "ma" - }) + look_representation = get_representation_by_name( + project_name, representation_name="ma", version_id=version_id + ) # See if representation is already loaded, if so reuse it. host = registered_host() @@ -220,42 +222,6 @@ def load_look(version_id): return shader_nodes -def get_latest_version(asset_id, subset): - # type: (str, str) -> dict - """Get latest version of subset. - - Args: - asset_id (str): Asset ID - subset (str): Subset name. - - Returns: - Latest version - - Throws: - RuntimeError: When subset or version doesn't exist. - - """ - subset = legacy_io.find_one({ - "name": subset, - "parent": ObjectId(asset_id), - "type": "subset" - }) - if not subset: - raise RuntimeError("Subset does not exist: %s" % subset) - - version = legacy_io.find_one( - { - "type": "version", - "parent": subset["_id"] - }, - sort=[("name", -1)] - ) - if not version: - raise RuntimeError("Version does not exist.") - - return version - - def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): # type: (str, str) -> None """Assign look to vray proxy. @@ -281,13 +247,20 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): asset_id = node_id.split(":", 1)[0] node_ids_by_asset_id[asset_id].add(node_id) + project_name = legacy_io.active_project() for asset_id, node_ids in node_ids_by_asset_id.items(): # Get latest look version - try: - version = get_latest_version(asset_id, subset=subset) - except RuntimeError as exc: - print(exc) + version = get_last_version_for_subset( + project_name, + subset_name=subset, + asset_id=asset_id, + fields=["_id"] + ) + if not version: + print("Didn't find last version for subset name {}".format( + subset + )) continue relationships = get_look_relationships(version["_id"]) diff --git a/openpype/tools/project_manager/project_manager/model.py b/openpype/tools/project_manager/project_manager/model.py index 223cfa629d..c5bde5aaec 100644 --- a/openpype/tools/project_manager/project_manager/model.py +++ b/openpype/tools/project_manager/project_manager/model.py @@ -7,6 +7,11 @@ from pymongo import UpdateOne, DeleteOne from Qt import QtCore, QtGui +from openpype.client import ( + get_project, + get_assets, + get_asset_ids_with_subsets, +) from openpype.lib import ( CURRENT_DOC_SCHEMAS, PypeLogger, @@ -255,10 +260,11 @@ class HierarchyModel(QtCore.QAbstractItemModel): return # Find project'd document - project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"}, - ProjectItem.query_projection + project_doc = get_project( + project_name, + fields=list(ProjectItem.query_projection.keys()) ) + # Skip if project document does not exist # - this shouldn't happen using only UI elements if not project_doc: @@ -269,9 +275,8 @@ class HierarchyModel(QtCore.QAbstractItemModel): self.add_item(project_item) # Query all assets of the project - asset_docs = self.dbcon.database[project_name].find( - {"type": "asset"}, - AssetItem.query_projection + asset_docs = get_assets( + project_name, fields=AssetItem.query_projection.keys() ) asset_docs_by_id = { asset_doc["_id"]: asset_doc @@ -282,31 +287,16 @@ class HierarchyModel(QtCore.QAbstractItemModel): # if asset item can be modified (name and hierarchy change) # - the same must be applied to all it's parents asset_ids = list(asset_docs_by_id.keys()) - result = [] + asset_ids_with_subsets = [] if asset_ids: - result = self.dbcon.database[project_name].aggregate([ - { - "$match": { - "type": "subset", - "parent": {"$in": asset_ids} - } - }, - { - "$group": { - "_id": "$parent", - "count": {"$sum": 1} - } - } - ]) + asset_ids_with_subsets = get_asset_ids_with_subsets( + project_name, asset_ids=asset_ids + ) asset_modifiable = { - asset_id: True + asset_id: asset_id not in asset_ids_with_subsets for asset_id in asset_docs_by_id.keys() } - for item in result: - asset_id = item["_id"] - count = item["count"] - asset_modifiable[asset_id] = count < 1 # Store assets by their visual parent to be able create their hierarchy asset_docs_by_parent_id = collections.defaultdict(list) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 4b5bc36aeb..e9d2874c54 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -3,6 +3,7 @@ from queue import Queue from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_project from .delegates import ( NumberDelegate, NameDelegate, @@ -47,12 +48,8 @@ class ProjectDocCache: def set_project(self, project_name): self.project_doc = None - if not project_name: - return - - self.project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"} - ) + if project_name: + self.project_doc = get_project(project_name) class ToolsCache: diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index dc75b30bd7..371d1ba2ef 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,5 +1,6 @@ import re +from openpype.client import get_projects from .constants import ( NAME_ALLOWED_SYMBOLS, NAME_REGEX @@ -272,15 +273,9 @@ class CreateProjectDialog(QtWidgets.QDialog): def _get_existing_projects(self): project_names = set() project_codes = set() - for project_name in self.dbcon.database.collection_names(): - # Each collection will have exactly one project document - project_doc = self.dbcon.database[project_name].find_one( - {"type": "project"}, - {"name": 1, "data.code": 1} - ) - if not project_doc: - continue - + for project_doc in get_projects( + inactive=True, fields=["name", "data.code"] + ): project_name = project_doc.get("name") if not project_name: continue diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 2973d6a5bb..915fb7f32e 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -13,6 +13,7 @@ except Exception: import pyblish.api +from openpype.client import get_assets from openpype.pipeline import ( PublishValidationError, registered_host, @@ -116,10 +117,10 @@ class AssetDocsCache: def _query(self): if self._asset_docs is None: - asset_docs = list(self.dbcon.find( - {"type": "asset"}, - self.projection - )) + project_name = self.dbcon.active_project() + asset_docs = get_assets( + project_name, fields=self.projection.keys() + ) task_names_by_asset_name = {} for asset_doc in asset_docs: asset_name = asset_doc["name"] diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index 9e357f3a56..d579831b21 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -9,6 +9,8 @@ try: except Exception: commonmark = None from Qt import QtWidgets, QtCore, QtGui + +from openpype.client import get_asset, get_subsets from openpype.lib import TaskNotSetError from openpype.pipeline.create import ( CreatorError, @@ -647,21 +649,19 @@ class CreateDialog(QtWidgets.QDialog): if asset_name is None: return - asset_doc = self.dbcon.find_one({ - "type": "asset", - "name": asset_name - }) + project_name = self.dbcon.active_project() + asset_doc = get_asset(project_name, asset_name=asset_name) self._asset_doc = asset_doc if asset_doc: - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"name": 1} + asset_id = asset_doc["_id"] + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - self._subset_names = set(subset_docs.distinct("name")) + self._subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } if not asset_doc: self.subset_name_input.setText("< Asset is not set >") diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 8d72020c98..9cf69ed650 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -5,8 +5,14 @@ from collections import defaultdict from Qt import QtCore, QtGui import qtawesome -from bson.objectid import ObjectId +from openpype.client import ( + get_asset, + get_subset, + get_version, + get_last_version_for_subset, + get_representation, +) from openpype.pipeline import ( legacy_io, schema, @@ -55,7 +61,7 @@ class InventoryModel(TreeModel): if not self.sync_enabled: return - project_name = legacy_io.Session["AVALON_PROJECT"] + project_name = legacy_io.current_project() active_site = sync_server.get_active_site(project_name) remote_site = sync_server.get_remote_site(project_name) @@ -291,6 +297,9 @@ class InventoryModel(TreeModel): node.Item: root node which has children added based on the data """ + # NOTE: @iLLiCiTiT this need refactor + project_name = legacy_io.active_project() + self.beginResetModel() # Group by representation @@ -304,32 +313,36 @@ class InventoryModel(TreeModel): for repre_id, group_dict in sorted(grouped.items()): group_items = group_dict["items"] # Get parenthood per group - representation = legacy_io.find_one({"_id": ObjectId(repre_id)}) + representation = get_representation( + project_name, representation_id=repre_id + ) if not representation: not_found["representation"].append(group_items) not_found_ids.append(repre_id) continue - version = legacy_io.find_one({"_id": representation["parent"]}) + version = get_version( + project_name, version_id=representation["parent"] + ) if not version: not_found["version"].append(group_items) not_found_ids.append(repre_id) continue elif version["type"] == "hero_version": - _version = legacy_io.find_one({ - "_id": version["version_id"] - }) + _version = get_version( + project_name, version_id=version["version_id"] + ) version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] - subset = legacy_io.find_one({"_id": version["parent"]}) + subset = get_subset(project_name, subset_id=version["parent"]) if not subset: not_found["subset"].append(group_items) not_found_ids.append(repre_id) continue - asset = legacy_io.find_one({"_id": subset["parent"]}) + asset = get_asset(project_name, asset_id=subset["parent"]) if not asset: not_found["asset"].append(group_items) not_found_ids.append(repre_id) @@ -390,10 +403,9 @@ class InventoryModel(TreeModel): # Store the highest available version so the model can know # whether current version is currently up-to-date. - highest_version = legacy_io.find_one({ - "type": "version", - "parent": version["parent"] - }, sort=[("name", -1)]) + highest_version = get_last_version_for_subset( + project_name, subset_id=version["parent"] + ) # create the group header group_node = Item() diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index b2d770330f..b940c66a56 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -4,6 +4,16 @@ from Qt import QtWidgets, QtCore import qtawesome from bson.objectid import ObjectId +from openpype.client import ( + get_asset, + get_assets, + get_subset, + get_subsets, + get_versions, + get_hero_versions, + get_last_versions, + get_representations, +) from openpype.pipeline import legacy_io from openpype.pipeline.load import ( discover_loader_plugins, @@ -144,6 +154,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): self._prepare_content_data() self.refresh(True) + def active_project(self): + return legacy_io.active_project() + def _prepare_content_data(self): repre_ids = set() content_loaders = set() @@ -151,10 +164,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_ids.add(ObjectId(item["representation"])) content_loaders.add(item["loader"]) - repres = list(legacy_io.find({ - "type": {"$in": ["representation", "archived_representation"]}, - "_id": {"$in": list(repre_ids)} - })) + project_name = self.active_project() + repres = get_representations( + project_name, + representation_ids=repre_ids, + archived=True + ) repres_by_id = {repre["_id"]: repre for repre in repres} # stash context values, works only for single representation @@ -179,10 +194,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): content_repres[repre_id] = repres_by_id[repre_id] version_ids.append(repre["parent"]) - versions = legacy_io.find({ - "type": {"$in": ["version", "hero_version"]}, - "_id": {"$in": list(set(version_ids))} - }) + versions = get_versions( + project_name, + version_ids=set(version_ids), + hero=True + ) content_versions = {} hero_version_ids = set() for version in versions: @@ -198,10 +214,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): else: subset_ids.append(content_versions[version_id]["parent"]) - subsets = legacy_io.find({ - "type": {"$in": ["subset", "archived_subset"]}, - "_id": {"$in": subset_ids} - }) + subsets = get_subsets( + project_name, subset_ids=subset_ids, archived=True + ) subsets_by_id = {sub["_id"]: sub for sub in subsets} asset_ids = [] @@ -220,10 +235,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_ids.append(subset["parent"]) content_subsets[subset_id] = subset - assets = legacy_io.find({ - "type": {"$in": ["asset", "archived_asset"]}, - "_id": {"$in": list(asset_ids)} - }) + assets = get_assets(project_name, asset_ids=asset_ids, archived=True) assets_by_id = {asset["_id"]: asset for asset in assets} missing_assets = [] @@ -472,9 +484,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): # Prepare asset document if asset is selected asset_doc = None if selected_asset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": True} + asset_doc = get_asset( + self.active_project(), + asset_name=selected_asset, + fields=["_id"] ) if not asset_doc: return [] @@ -523,38 +536,35 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _get_current_output_repre_ids_xxx( self, asset_doc, selected_subset, selected_repre ): - subset_doc = legacy_io.find_one( - { - "type": "subset", - "name": selected_subset, - "parent": asset_doc["_id"] - }, - {"_id": True} + project_name = self.active_project() + subset_doc = get_subset( + project_name, + subset_name=selected_subset, + asset_id=asset_doc["_id"], + fields=["_id"] ) + subset_id = subset_doc["_id"] last_versions_by_subset_id = self.find_last_versions([subset_id]) version_doc = last_versions_by_subset_id.get(subset_id) if not version_doc: return [] - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": version_doc["_id"], - "name": selected_repre - }, - {"_id": True} + repre_docs = get_representations( + project_name, + version_ids=[version_doc["_id"]], + representation_names=[selected_repre], + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xxo(self, asset_doc, selected_subset): - subset_doc = legacy_io.find_one( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": selected_subset - }, - {"_id": True} + project_name = self.active_project() + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) if not subset_doc: return [] @@ -563,41 +573,51 @@ class SwitchAssetDialog(QtWidgets.QDialog): for repre_doc in self.content_repres.values(): repre_names.add(repre_doc["name"]) - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": subset_doc["_id"], - "name": {"$in": list(repre_names)} - }, - {"_id": True} + # TODO where to take version ids? + version_ids = [] + repre_docs = get_representations( + project_name, + representation_names=repre_names, + version_ids=version_ids, + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xox(self, asset_doc, selected_repre): - susbet_names = set() + subset_names = set() for subset_doc in self.content_subsets.values(): - susbet_names.add(subset_doc["name"]) + subset_names.add(subset_doc["name"]) - subset_docs = legacy_io.find( - { - "type": "subset", - "name": {"$in": list(susbet_names)}, - "parent": asset_doc["_id"] - }, - {"_id": True} + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=subset_names, + fields=["_id", "name"] ) - subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": subset_ids}, - "name": selected_repre - }, - {"_id": True} + subset_name_by_id = { + subset_doc["_id"]: subset_doc["name"] + for subset_doc in subset_docs + } + subset_ids = list(subset_name_by_id.keys()) + last_versions_by_subset_id = self.find_last_versions(subset_ids) + last_version_id_by_subset_name = {} + for subset_id, last_version in last_versions_by_subset_id.items(): + subset_name = subset_name_by_id[subset_id] + last_version_id_by_subset_name[subset_name] = ( + last_version["_id"] + ) + + repre_docs = get_representations( + project_name, + version_ids=last_version_id_by_subset_name.values(), + representation_names=[selected_repre], + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_xoo(self, asset_doc): + project_name = self.active_project() repres_by_subset_name = collections.defaultdict(set) for repre_doc in self.content_repres.values(): repre_name = repre_doc["name"] @@ -606,13 +626,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_name = subset_doc["name"] repres_by_subset_name[subset_name].add(repre_name) - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": {"$in": list(repres_by_subset_name.keys())} - }, - {"_id": True, "name": True} + subset_docs = list(get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=repres_by_subset_name.keys(), + fields=["_id", "name"] )) subset_name_by_id = { subset_doc["_id"]: subset_doc["name"] @@ -627,60 +645,59 @@ class SwitchAssetDialog(QtWidgets.QDialog): last_version["_id"] ) - repre_or_query = [] + repre_names_by_version_id = {} for subset_name, repre_names in repres_by_subset_name.items(): version_id = last_version_id_by_subset_name.get(subset_name) # This should not happen but why to crash? - if version_id is None: - continue - repre_or_query.append({ - "parent": version_id, - "name": {"$in": list(repre_names)} - }) - repre_docs = legacy_io.find( - {"$or": repre_or_query}, - {"_id": True} + if version_id is not None: + repre_names_by_version_id[version_id] = list(repre_names) + + repre_docs = get_representations( + project_name, + names_by_version_ids=repre_names_by_version_id, + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oxx( self, selected_subset, selected_repre ): - subset_docs = list(legacy_io.find({ - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - })) + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id"] + ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] last_versions_by_subset_id = self.find_last_versions(subset_ids) last_version_ids = [ last_version["_id"] for last_version in last_versions_by_subset_id.values() ] - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": last_version_ids}, - "name": selected_repre - }) - + repre_docs = get_representations( + project_name, + version_ids=last_version_ids, + representation_names=[selected_repre], + fields=["_id"] + ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oxo(self, selected_subset): - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": True, "parent": True} - )) - if not subset_docs: - return list() - + project_name = self.active_project() + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "parent"] + ) subset_docs_by_id = { subset_doc["_id"]: subset_doc for subset_doc in subset_docs } + if not subset_docs: + return list() + last_versions_by_subset_id = self.find_last_versions( subset_docs_by_id.keys() ) @@ -702,56 +719,44 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_id = asset_doc["_id"] repre_names_by_asset_id[asset_id].add(repre_name) - repre_or_query = [] + repre_names_by_version_id = {} for last_version_id, subset_id in subset_id_by_version_id.items(): subset_doc = subset_docs_by_id[subset_id] asset_id = subset_doc["parent"] repre_names = repre_names_by_asset_id.get(asset_id) if not repre_names: continue - repre_or_query.append({ - "parent": last_version_id, - "name": {"$in": list(repre_names)} - }) - repre_docs = legacy_io.find( - { - "type": "representation", - "$or": repre_or_query - }, - {"_id": True} - ) + repre_names_by_version_id[last_version_id] = repre_names + repre_docs = get_representations( + project_name, + names_by_version_ids=repre_names_by_version_id, + fields=["_id"] + ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_current_output_repre_ids_oox(self, selected_repre): - repre_docs = legacy_io.find( - { - "name": selected_repre, - "parent": {"$in": list(self.content_versions.keys())} - }, - {"_id": True} + project_name = self.active_project() + repre_docs = get_representations( + project_name, + representation_names=[selected_repre], + version_ids=self.content_versions.keys(), + fields=["_id"] ) return [repre_doc["_id"] for repre_doc in repre_docs] def _get_asset_box_values(self): - asset_docs = legacy_io.find( - {"type": "asset"}, - {"_id": 1, "name": 1} - ) + project_name = self.active_project() + asset_docs = get_assets(project_name, fields=["_id", "name"]) asset_names_by_id = { asset_doc["_id"]: asset_doc["name"] for asset_doc in asset_docs } - subsets = legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(asset_names_by_id.keys())} - }, - { - "parent": 1 - } + subsets = get_subsets( + project_name, + asset_ids=asset_names_by_id.keys(), + fields=["parent"] ) - filtered_assets = [] for subset in subsets: asset_name = asset_names_by_id[subset["parent"]] @@ -760,25 +765,20 @@ class SwitchAssetDialog(QtWidgets.QDialog): return sorted(filtered_assets) def _get_subset_box_values(self): + project_name = self.active_project() selected_asset = self._assets_box.get_valid_value() if selected_asset: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": selected_asset - }) + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] + ) asset_ids = [asset_doc["_id"]] else: asset_ids = list(self.content_assets.keys()) - subsets = legacy_io.find( - { - "type": "subset", - "parent": {"$in": asset_ids} - }, - { - "parent": 1, - "name": 1 - } + subsets = get_subsets( + project_name, + asset_ids=asset_ids, + fields=["parent", "name"] ) subset_names_by_parent_id = collections.defaultdict(set) @@ -800,6 +800,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _representations_box_values(self): # NOTE hero versions are not used because it is expected that # hero version has same representations as latests + project_name = self.active_project() selected_asset = self._assets_box.currentText() selected_subset = self._subsets_box.currentText() @@ -807,16 +808,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [ ] [ ] [?] if not selected_asset and not selected_subset: # Find all representations of selection's subsets - possible_repres = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(self.content_versions.keys())} - }, - { - "parent": 1, - "name": 1 - } - )) + possible_repres = get_representations( + project_name, + version_ids=self.content_versions.keys(), + fields=["parent", "name"] + ) possible_repres_by_parent = collections.defaultdict(set) for repre in possible_repres: @@ -836,29 +832,23 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [?] if selected_asset and selected_subset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_doc = legacy_io.find_one( - { - "type": "subset", - "name": selected_subset, - "parent": asset_doc["_id"] - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) + subset_id = subset_doc["_id"] last_versions_by_subset_id = self.find_last_versions([subset_id]) version_doc = last_versions_by_subset_id.get(subset_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": version_doc["_id"] - }, - { - "name": 1 - } + repre_docs = get_representations( + project_name, + version_ids=[version_doc["_id"]], + fields=["name"] ) return [ repre_doc["name"] @@ -868,9 +858,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] # If asset only is selected if selected_asset: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) if not asset_doc: return list() @@ -879,13 +868,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_names = set() for subset_doc in self.content_subsets.values(): subset_names.add(subset_doc["name"]) - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": {"$in": list(subset_names)} - }, - {"_id": 1} + + subset_docs = get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + subset_names=subset_names, + fields=["_id"] ) subset_ids = [ subset_doc["_id"] @@ -903,15 +891,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): if not subset_id_by_version_id: return list() - repre_docs = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = list(get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] )) if not repre_docs: return list() @@ -933,13 +916,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): return list(available_repres) # [ ] [x] [?] - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": 1, "parent": 1} + subset_docs = list(get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "parent"] )) if not subset_docs: return list() @@ -960,16 +941,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): if not subset_id_by_version_id: return list() - repre_docs = list(legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } - )) + repre_docs = list( + get_representations( + project_name, + subset_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] + ) + ) if not repre_docs: return list() @@ -1016,14 +994,14 @@ class SwitchAssetDialog(QtWidgets.QDialog): return # [x] [ ] [?] - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + project_name = self.active_project() + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_docs = legacy_io.find( - {"type": "subset", "parent": asset_doc["_id"]}, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_doc["_id"]], fields=["name"] ) + subset_names = set( subset_doc["name"] for subset_doc in subset_docs @@ -1035,27 +1013,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): break def find_last_versions(self, subset_ids): - _pipeline = [ - # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": list(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"}, - "type": {"$last": "$type"} - }} - ] - last_versions_by_subset_id = dict() - for doc in legacy_io.aggregate(_pipeline): - doc["parent"] = doc["_id"] - doc["_id"] = doc.pop("_version_id") - last_versions_by_subset_id[doc["parent"]] = doc - return last_versions_by_subset_id + project_name = self.active_project() + return get_last_versions( + project_name, + subset_ids=subset_ids, + fields=["_id", "parent", "type"] + ) def _is_repre_ok(self, validation_state): selected_asset = self._assets_box.get_valid_value() @@ -1078,33 +1041,28 @@ class SwitchAssetDialog(QtWidgets.QDialog): return # [x] [x] [ ] + project_name = self.active_project() if selected_asset is not None and selected_subset is not None: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_doc = legacy_io.find_one( - { - "type": "subset", - "parent": asset_doc["_id"], - "name": selected_subset - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + asset_id=asset_doc["_id"], + subset_name=selected_subset, + fields=["_id"] ) - last_versions_by_subset_id = self.find_last_versions( - [subset_doc["_id"]] - ) - last_version = last_versions_by_subset_id.get(subset_doc["_id"]) + subset_id = subset_doc["_id"] + last_versions_by_subset_id = self.find_last_versions([subset_id]) + last_version = last_versions_by_subset_id.get(subset_id) if not last_version: validation_state.repre_ok = False return - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": last_version["_id"] - }, - {"name": 1} + repre_docs = get_representations( + project_name, + version_ids=[last_version["_id"]], + fields=["name"] ) repre_names = set( @@ -1119,16 +1077,13 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [ ] if selected_asset is not None: - asset_doc = legacy_io.find_one( - {"type": "asset", "name": selected_asset}, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=selected_asset, fields=["_id"] ) - subset_docs = list(legacy_io.find( - { - "type": "subset", - "parent": asset_doc["_id"] - }, - {"_id": 1, "name": 1} + subset_docs = list(get_subsets( + project_name, + asset_ids=[asset_doc["_id"]], + fields=["_id", "name"] )) subset_name_by_id = {} @@ -1145,15 +1100,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_id = last_version["_id"] subset_id_by_version_id[version_id] = subset_id - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + subset_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] ) repres_by_subset_name = {} for repre_doc in repre_docs: @@ -1176,15 +1126,12 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [ ] [x] [ ] # Subset documents - subset_docs = legacy_io.find( - { - "type": "subset", - "parent": {"$in": list(self.content_assets.keys())}, - "name": selected_subset - }, - {"_id": 1, "name": 1, "parent": 1} + subset_docs = get_subsets( + project_name, + asset_ids=self.content_assets.keys(), + subset_names=[selected_subset], + fields=["_id", "name", "parent"] ) - subset_docs_by_id = {} for subset_doc in subset_docs: subset_docs_by_id[subset_doc["_id"]] = subset_doc @@ -1197,15 +1144,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): version_id = last_version["_id"] subset_id_by_version_id[version_id] = subset_id - repre_docs = legacy_io.find( - { - "type": "representation", - "parent": {"$in": list(subset_id_by_version_id.keys())} - }, - { - "name": 1, - "parent": 1 - } + repre_docs = get_representations( + project_name, + version_ids=subset_id_by_version_id.keys(), + fields=["name", "parent"] ) repres_by_asset_id = {} for repre_doc in repre_docs: @@ -1245,11 +1187,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): selected_subset = self._subsets_box.get_valid_value() selected_representation = self._representations_box.get_valid_value() + project_name = self.active_project() if selected_asset: - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": selected_asset - }) + asset_doc = get_asset(project_name, asset_name=selected_asset) asset_docs_by_id = {asset_doc["_id"]: asset_doc} else: asset_docs_by_id = self.content_assets @@ -1259,16 +1199,15 @@ class SwitchAssetDialog(QtWidgets.QDialog): for asset_doc in asset_docs_by_id.values() } - asset_ids = list(asset_docs_by_id.keys()) - - subset_query = { - "type": "subset", - "parent": {"$in": asset_ids} - } + subset_names = None if selected_subset: - subset_query["name"] = selected_subset + subset_names = [selected_subset] - subset_docs = list(legacy_io.find(subset_query)) + subset_docs = list(get_subsets( + project_name, + subset_names=subset_names, + asset_ids=asset_docs_by_id.keys() + )) subset_ids = [] subset_docs_by_parent_and_name = collections.defaultdict(dict) for subset in subset_docs: @@ -1278,15 +1217,14 @@ class SwitchAssetDialog(QtWidgets.QDialog): subset_docs_by_parent_and_name[parent_id][name] = subset # versions - version_docs = list(legacy_io.find({ - "type": "version", - "parent": {"$in": subset_ids} - }, sort=[("name", -1)])) + _version_docs = get_versions(project_name, subset_ids=subset_ids) + version_docs = list(reversed( + sorted(_version_docs, key=lambda item: item["name"]) + )) - hero_version_docs = list(legacy_io.find({ - "type": "hero_version", - "parent": {"$in": subset_ids} - })) + hero_version_docs = list(get_hero_versions( + project_name, subset_ids=subset_ids + )) version_ids = list() @@ -1303,10 +1241,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): parent_id = hero_version_doc["parent"] hero_version_docs_by_parent_id[parent_id] = hero_version_doc - repre_docs = legacy_io.find({ - "type": "representation", - "parent": {"$in": version_ids} - }) + repre_docs = get_representations(project_name, version_ids=version_ids) repre_docs_by_parent_id_by_name = collections.defaultdict(dict) for repre_doc in repre_docs: parent_id = repre_doc["parent"] diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 448e3f4e6f..6a95ccb57b 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -6,6 +6,13 @@ from Qt import QtWidgets, QtCore import qtawesome from bson.objectid import ObjectId +from openpype.client import ( + get_version, + get_versions, + get_hero_versions, + get_representation, + get_representations, +) from openpype import style from openpype.pipeline import ( legacy_io, @@ -83,12 +90,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if item_id not in repre_ids: repre_ids.append(item_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - {"parent": 1} + project_name = legacy_io.active_project() + repre_docs = get_representations( + project_name, representaion_ids=repre_ids, fields=["parent"] ) version_ids = [] @@ -97,10 +101,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if version_id not in version_ids: version_ids.append(version_id) - loaded_versions = legacy_io.find({ - "_id": {"$in": version_ids}, - "type": {"$in": ["version", "hero_version"]} - }) + loaded_versions = get_versions( + project_name, version_ids=version_ids, hero=True + ) loaded_hero_versions = [] versions_by_parent_id = collections.defaultdict(list) @@ -114,10 +117,9 @@ class SceneInventoryView(QtWidgets.QTreeView): if parent_id not in version_parents: version_parents.append(parent_id) - all_versions = legacy_io.find({ - "type": {"$in": ["hero_version", "version"]}, - "parent": {"$in": version_parents} - }) + all_versions = get_versions( + project_name, subset_ids=version_parents, hero=True + ) hero_versions = [] versions = [] for version in all_versions: @@ -150,12 +152,10 @@ class SceneInventoryView(QtWidgets.QTreeView): if item_id not in repre_ids: repre_ids.append(item_id) - repre_docs = legacy_io.find( - { - "type": "representation", - "_id": {"$in": repre_ids} - }, - {"parent": 1} + repre_docs = get_representations( + project_name, + representation_ids=repre_ids, + fields=["parent"] ) version_ids = [] @@ -165,13 +165,13 @@ class SceneInventoryView(QtWidgets.QTreeView): version_id_by_repre_id[repre_doc["_id"]] = version_id if version_id not in version_ids: version_ids.append(version_id) - hero_versions = legacy_io.find( - { - "_id": {"$in": version_ids}, - "type": "hero_version" - }, - {"version_id": 1} + + hero_versions = get_hero_versions( + project_name, + version_ids=version_ids, + fields=["version_id"] ) + version_ids = set() for hero_version in hero_versions: version_id = hero_version["version_id"] @@ -183,12 +183,10 @@ class SceneInventoryView(QtWidgets.QTreeView): if current_version_id == hero_version_id: version_id_by_repre_id[_repre_id] = version_id - version_docs = legacy_io.find( - { - "_id": {"$in": list(version_ids)}, - "type": "version" - }, - {"name": 1} + version_docs = get_versions( + project_name, + version_ids=version_ids, + fields=["name"] ) version_name_by_id = {} for version_doc in version_docs: @@ -370,10 +368,9 @@ class SceneInventoryView(QtWidgets.QTreeView): active_site = self.sync_server.get_active_site(project_name) remote_site = self.sync_server.get_remote_site(project_name) - repre_docs = legacy_io.find({ - "type": "representation", - "_id": {"$in": repre_ids} - }) + repre_docs = get_representations( + project_name, representation_ids=repre_ids + ) repre_docs_by_id = { repre_doc["_id"]: repre_doc for repre_doc in repre_docs @@ -658,25 +655,37 @@ class SceneInventoryView(QtWidgets.QTreeView): active = items[-1] + project_name = legacy_io.active_project() # Get available versions for active representation representation_id = ObjectId(active["representation"]) - representation = legacy_io.find_one({"_id": representation_id}) - version = legacy_io.find_one({ - "_id": representation["parent"] - }) - versions = list(legacy_io.find( - { - "parent": version["parent"], - "type": "version" - }, - sort=[("name", 1)] + repre_doc = get_representation( + project_name, + representation_id=representation_id, + fields=["parent"] + ) + + repre_version_doc = get_version( + project_name, + version_id=repre_doc["parent"], + fields=["parent"] + ) + + version_docs = get_versions( + project_name, + subset_ids=[repre_version_doc["parent"]], + hero=True + ) + hero_version = None + standard_versions = [] + for version_doc in version_docs: + if version_doc["type"] == "hero_version": + hero_version = version_doc + else: + standard_versions.append(version_doc) + versions = list(reversed( + sorted(standard_versions, key=lambda item: item["name"]) )) - - hero_version = legacy_io.find_one({ - "parent": version["parent"], - "type": "hero_version" - }) if hero_version: _version_id = hero_version["version_id"] for _version in versions: @@ -703,7 +712,7 @@ class SceneInventoryView(QtWidgets.QTreeView): all_versions = [] if hero_version: all_versions.append(hero_version) - all_versions.extend(reversed(versions)) + all_versions.extend(versions) if current_item: index = all_versions.index(current_item) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 1ad5cd119e..4831db038c 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -6,6 +6,8 @@ import signal from bson.objectid import ObjectId from Qt import QtWidgets, QtCore, QtGui +from openpype.client import get_asset + from .widgets import ( AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget ) @@ -126,17 +128,6 @@ class Window(QtWidgets.QDialog): if event: super().resizeEvent(event) - def get_avalon_parent(self, entity): - ''' Avalon DB entities helper - get all parents (exclude project). - ''' - parent_id = entity['data']['visualParent'] - parents = [] - if parent_id is not None: - parent = self.db.find_one({'_id': parent_id}) - parents.extend(self.get_avalon_parent(parent)) - parents.append(parent['name']) - return parents - def on_project_change(self, project_name): self.widget_family.refresh() @@ -152,7 +143,10 @@ class Window(QtWidgets.QDialog): ] if len(selected) == 1: self.valid_parent = True - asset = self.db.find_one({"_id": selected[0], "type": "asset"}) + project_name = self.db.active_project() + asset = get_asset( + project_name, asset_id=selected[0], fields=["name"] + ) self.widget_family.change_asset(asset['name']) else: self.valid_parent = False diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py index 02e9073555..abfc0a2145 100644 --- a/openpype/tools/standalonepublish/widgets/model_asset.py +++ b/openpype/tools/standalonepublish/widgets/model_asset.py @@ -4,6 +4,7 @@ import collections from Qt import QtCore, QtGui import qtawesome +from openpype.client import get_assets from openpype.style import ( get_default_entity_icon_color, get_deprecated_entity_font_color, @@ -104,17 +105,18 @@ class AssetModel(TreeModel): def refresh(self): """Refresh the data for the model.""" + project_name = self.dbcon.active_project() self.clear() - if ( - self.dbcon.active_project() is None or - self.dbcon.active_project() == '' - ): + if not project_name: return self.beginResetModel() # Get all assets in current project sorted by name - db_assets = self.dbcon.find({"type": "asset"}).sort("name", 1) + asset_docs = get_assets(project_name) + db_assets = list( + sorted(asset_docs, key=lambda item: item["name"]) + ) # Group the assets by their visual parent's id assets_by_parent = collections.defaultdict(list) diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 8b43cd7cf8..0b5802ed9e 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -2,6 +2,10 @@ import contextlib from Qt import QtWidgets, QtCore import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.tools.utils import PlaceholderLineEdit from openpype.style import get_default_tools_icon_color @@ -218,7 +222,8 @@ class AssetWidget(QtWidgets.QWidget): self.view = view def collect_data(self): - project = self.dbcon.find_one({'type': 'project'}) + project_name = self.dbcon.active_project() + project = get_project(project_name, fields=["name"]) asset = self.get_active_asset() try: @@ -241,9 +246,16 @@ class AssetWidget(QtWidgets.QWidget): return ent_parents output = [] - if entity.get('data', {}).get('visualParent', None) is None: + parent_asset_id = entity.get('data', {}).get('visualParent', None) + if parent_asset_id is None: return output - parent = self.dbcon.find_one({'_id': entity['data']['visualParent']}) + + project_name = self.dbcon.active_project() + parent = get_asset( + project_name, + asset_id=parent_asset_id, + fields=["name", "data.visualParent"] + ) output.append(parent['name']) output.extend(self.get_parents(parent)) return output @@ -349,9 +361,10 @@ class AssetWidget(QtWidgets.QWidget): tasks = [] selected = self.get_selected_assets() if len(selected) == 1: - asset = self.dbcon.find_one({ - "_id": selected[0], "type": "asset" - }) + project_name = self.dbcon.active_project() + asset = get_asset( + project_name, asset_id=selected[0], fields=["data.tasks"] + ) if asset: tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) @@ -400,7 +413,7 @@ class AssetWidget(QtWidgets.QWidget): # Select mode = selection_model.Select | selection_model.Rows - for index in lib.iter_model_rows( + for index in _iter_model_rows( self.proxy, column=0, include_root=False ): # stop iteration if there are no assets to process diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 08cd45bbf2..ed9f405f38 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -1,14 +1,21 @@ import re from Qt import QtWidgets, QtCore -from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole -from . import FamilyDescriptionWidget +from openpype.client import ( + get_asset, + get_subset, + get_subsets, + get_last_version_for_subset, +) from openpype.api import get_project_settings from openpype.pipeline import LegacyCreator from openpype.lib import TaskNotSetError from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS +from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole +from . import FamilyDescriptionWidget + class FamilyWidget(QtWidgets.QWidget): @@ -180,12 +187,9 @@ class FamilyWidget(QtWidgets.QWidget): asset_doc = None if asset_name != self.NOT_SELECTED: # Get the assets from the database which match with the name - asset_doc = self.dbcon.find_one( - { - "type": "asset", - "name": asset_name - }, - {"_id": 1} + project_name = self.dbcon.active_project() + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin and family @@ -200,14 +204,13 @@ class FamilyWidget(QtWidgets.QWidget): return # Get the asset from the database which match with the name - asset_doc = self.dbcon.find_one( - {"name": asset_name, "type": "asset"}, - projection={"_id": 1} + project_name = self.dbcon.active_project() + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) # Get plugin plugin = item.data(PluginRole) if asset_doc and plugin: - project_name = self.dbcon.Session["AVALON_PROJECT"] asset_id = asset_doc["_id"] task_name = self.dbcon.Session["AVALON_TASK"] @@ -231,14 +234,14 @@ class FamilyWidget(QtWidgets.QWidget): self.input_result.setText("Select task please") # Get all subsets of the current asset - subset_docs = self.dbcon.find( - { - "type": "subset", - "parent": asset_id - }, - {"name": 1} + subset_docs = get_subsets( + project_name, asset_ids=[asset_id], fields=["name"] ) - existing_subset_names = set(subset_docs.distinct("name")) + + existing_subset_names = { + subset_doc["name"] + for subset_doc in subset_docs + } # Defaults to dropdown defaults = [] @@ -296,47 +299,37 @@ class FamilyWidget(QtWidgets.QWidget): if not auto_version: return + project_name = self.dbcon.active_project() asset_name = self.asset_name subset_name = self.input_result.text() version = 1 asset_doc = None subset_doc = None - versions = None if ( asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): - asset_doc = self.dbcon.find_one( - { - 'type': 'asset', - 'name': asset_name - }, - {"_id": 1} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["_id"] ) if asset_doc: - subset_doc = self.dbcon.find_one( - { - 'type': 'subset', - 'parent': asset_doc['_id'], - 'name': subset_name - }, - {"_id": 1} + subset_doc = get_subset( + project_name, + subset_name=subset_name, + asset_id=asset_doc['_id'], + fields=["_id"] ) if subset_doc: - versions = self.dbcon.find( - { - 'type': 'version', - 'parent': subset_doc['_id'] - }, - {"name": 1} - ).distinct("name") - - if versions: - versions = sorted(versions) - version = int(versions[-1]) + 1 + last_version = get_last_version_for_subset( + project_name, + subset_id=subset_doc["_id"], + fields=["name"] + ) + if last_version: + version = last_version["name"] + 1 self.version_spinbox.setValue(version) diff --git a/openpype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py index fd8d6dc02e..8703f075d3 100644 --- a/openpype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -4,6 +4,7 @@ import click import speedcopy +from openpype.client import get_project, get_asset from openpype.lib import Terminal from openpype.api import Anatomy from openpype.pipeline import legacy_io @@ -29,20 +30,6 @@ class TextureCopy: if os.path.splitext(x)[1].lower() in texture_extensions) return textures - def _get_project(self, project_name): - project = legacy_io.find_one({ - 'type': 'project', - 'name': project_name - }) - return project - - def _get_asset(self, asset_name): - asset = legacy_io.find_one({ - 'type': 'asset', - 'name': asset_name - }) - return asset - def _get_destination_path(self, asset, project): project_name = project["name"] hierarchy = "" @@ -88,11 +75,12 @@ class TextureCopy: t.echo("!!! {}".format(e)) exit(1) - def process(self, asset, project, path): + def process(self, asset_name, project_name, path): """ Process all textures found in path and copy them to asset under project. """ + t.echo(">>> Looking for textures ...") textures = self._get_textures(path) if len(textures) < 1: @@ -101,14 +89,14 @@ class TextureCopy: else: t.echo(">>> Found {} textures ...".format(len(textures))) - project = self._get_project(project) + project = get_project(project_name) if not project: - t.echo("!!! Project name [ {} ] not found.".format(project)) + t.echo("!!! Project name [ {} ] not found.".format(project_name)) exit(1) - asset = self._get_asset(asset) - if not project: - t.echo("!!! Asset [ {} ] not found in project".format(asset)) + asset = get_asset(project_name, asset_name=asset_name) + if not asset: + t.echo("!!! Asset [ {} ] not found in project".format(asset_name)) exit(1) t.echo((">>> Project [ {} ] and " "asset [ {} ] seems to be OK ...").format(project['name'], diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 82bdcd63a2..772946e9e1 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -5,6 +5,10 @@ import Qt from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_assets, +) from openpype.style import ( get_objected_colors, get_default_tools_icon_color, @@ -525,21 +529,18 @@ class AssetModel(QtGui.QStandardItemModel): self._doc_fetched.emit() def _fetch_asset_docs(self): - if not self.dbcon.Session.get("AVALON_PROJECT"): + project_name = self.dbcon.current_project() + if not project_name: return [] - project_doc = self.dbcon.find_one( - {"type": "project"}, - {"_id": True} - ) + project_doc = get_project(project_name, fields=["_id"]) if not project_doc: return [] # Get all assets sorted by name - return list(self.dbcon.find( - {"type": "asset"}, - self._asset_projection - )) + return list( + get_assets(project_name, fields=self._asset_projection.keys()) + ) def _stop_fetch_thread(self): self._refreshing = False diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 71f817a1d7..d6c2d69e76 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -6,15 +6,19 @@ import numbers import Qt from Qt import QtWidgets, QtGui, QtCore -from openpype.pipeline import HeroVersionType -from .models import TreeModel -from . import lib - if Qt.__binding__ == "PySide": from PySide.QtGui import QStyleOptionViewItemV4 elif Qt.__binding__ == "PyQt4": from PyQt4.QtGui import QStyleOptionViewItemV4 +from openpype.client import ( + get_versions, + get_hero_versions, +) +from openpype.pipeline import HeroVersionType +from .models import TreeModel +from . import lib + log = logging.getLogger(__name__) @@ -114,26 +118,24 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): "Version is not integer" ) + project_name = self.dbcon.active_project() # Add all available versions to the editor parent_id = item["version_document"]["parent"] - version_docs = list(self.dbcon.find( - { - "type": "version", - "parent": parent_id - }, - sort=[("name", 1)] + version_docs = list(sorted( + get_versions(project_name, subset_ids=[parent_id]), + key=lambda item: item["name"] )) - hero_version_doc = self.dbcon.find_one( - { - "type": "hero_version", - "parent": parent_id - }, { - "name": 1, - "data.tags": 1, - "version_id": 1 - } + hero_versions = list( + get_hero_versions( + project_name, + subset_ids=[parent_id], + fields=["name", "data.tags", "version_id"] + ) ) + hero_version_doc = None + if hero_versions: + hero_version_doc = hero_versions[0] doc_for_hero_version = None diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 20fea6600b..72ebfcc063 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -6,6 +6,10 @@ import collections from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.style import ( get_default_entity_icon_color, get_objected_colors, @@ -430,9 +434,8 @@ class FamilyConfigCache: database = getattr(self.dbcon, "database", None) if database is None: database = self.dbcon._database - asset_doc = database[project_name].find_one( - {"type": "asset", "name": asset_name}, - {"data.tasks": True} + asset_doc = get_asset( + project_name, asset_name=asset_name, fields=["data.tasks"] ) or {} tasks_info = asset_doc.get("data", {}).get("tasks") or {} task_type = tasks_info.get(task_name, {}).get("type") @@ -500,10 +503,7 @@ class GroupsConfig: project_name = self.dbcon.Session.get("AVALON_PROJECT") if project_name: # Get pre-defined group name and appearance from project config - project_doc = self.dbcon.find_one( - {"type": "project"}, - projection={"config.groups": True} - ) + project_doc = get_project(project_name, fields=["config.groups"]) if project_doc: group_configs = project_doc["config"].get("groups") or [] diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index eab183d5f3..fcbec318f5 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -1,6 +1,10 @@ from Qt import QtWidgets, QtCore, QtGui import qtawesome +from openpype.client import ( + get_project, + get_asset, +) from openpype.style import get_disabled_entity_icon_color from openpype.tools.utils.lib import get_task_icon @@ -47,7 +51,8 @@ class TasksModel(QtGui.QStandardItemModel): # Get the project configured icons from database project_doc = {} if self._context_is_valid(): - project_doc = self.dbcon.find_one({"type": "project"}) + project_name = self.dbcon.active_project() + project_doc = get_project(project_name) self._loaded_project_name = self._get_current_project() self._project_doc = project_doc @@ -71,9 +76,9 @@ class TasksModel(QtGui.QStandardItemModel): def set_asset_id(self, asset_id): asset_doc = None if self._context_is_valid(): - asset_doc = self.dbcon.find_one( - {"_id": asset_id}, - {"data.tasks": True} + project_name = self._get_current_project() + asset_doc = get_asset( + project_name, asset_id=asset_id, fields=["data.tasks"] ) self._set_asset(asset_doc) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 977111b71b..36b9a055d8 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -5,6 +5,7 @@ import shutil import Qt from Qt import QtWidgets, QtCore +from openpype.client import get_asset from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -384,7 +385,9 @@ class FilesWidget(QtWidgets.QWidget): return None if self._asset_doc is None: - self._asset_doc = legacy_io.find_one({"_id": self._asset_id}) + project_name = legacy_io.active_project() + self._asset_doc = get_asset(project_name, asset_id=self._asset_id) + return self._asset_doc def _get_session(self): diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py index 8f9dd8c6ba..d5b7cef339 100644 --- a/openpype/tools/workfiles/model.py +++ b/openpype/tools/workfiles/model.py @@ -4,6 +4,11 @@ import logging from Qt import QtCore, QtGui import qtawesome +from openpype.client import ( + get_subsets, + get_versions, + get_representations, +) from openpype.style import ( get_default_entity_icon_color, get_disabled_entity_icon_color, @@ -215,6 +220,7 @@ class PublishFilesModel(QtGui.QStandardItemModel): self._dbcon = dbcon self._anatomy = anatomy + self._file_extensions = extensions self._invalid_context_item = None @@ -234,6 +240,10 @@ class PublishFilesModel(QtGui.QStandardItemModel): self._asset_id = None self._task_name = None + @property + def project_name(self): + return self._dbcon.Session["AVALON_PROJECT"] + def _set_item_invalid(self, item): item.setFlags(QtCore.Qt.NoItemFlags) item.setData(self._invalid_icon, QtCore.Qt.DecorationRole) @@ -285,15 +295,11 @@ class PublishFilesModel(QtGui.QStandardItemModel): def _get_workfie_representations(self): output = [] # Get subset docs of asset - subset_docs = self._dbcon.find( - { - "type": "subset", - "parent": self._asset_id - }, - { - "_id": True, - "name": True - } + subset_docs = get_subsets( + self.project_name, + asset_ids=[self._asset_id], + fields=["_id", "name"] + ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] @@ -301,17 +307,12 @@ class PublishFilesModel(QtGui.QStandardItemModel): return output # Get version docs of subsets with their families - version_docs = self._dbcon.find( - { - "type": "version", - "parent": {"$in": subset_ids} - }, - { - "_id": True, - "data.families": True, - "parent": True - } + version_docs = get_versions( + self.project_name, + subset_ids=subset_ids, + fields=["_id", "parent", "data.families"] ) + # Filter versions if they contain 'workfile' family filtered_versions = [] for version_doc in version_docs: @@ -327,13 +328,10 @@ class PublishFilesModel(QtGui.QStandardItemModel): # Query representations of filtered versions and add filter for # extension extensions = [ext.replace(".", "") for ext in self._file_extensions] - repre_docs = self._dbcon.find( - { - "type": "representation", - "parent": {"$in": version_ids}, - "context.ext": {"$in": extensions} - } + repre_docs = get_representations( + self.project_name, version_ids, extensions ) + # Filter queried representations by task name if task is set filtered_repre_docs = [] for repre_doc in repre_docs: diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 3e97d6c938..1fbcbfeb22 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -5,6 +5,10 @@ import logging from Qt import QtWidgets, QtCore +from openpype.client import ( + get_project, + get_asset, +) from openpype.lib import ( get_last_workfile_with_version, get_workdir_data, @@ -22,29 +26,19 @@ def build_workfile_data(session): """Get the data required for workfile formatting from avalon `session`""" # Set work file data for template formatting + project_name = session["AVALON_PROJECT"] asset_name = session["AVALON_ASSET"] task_name = session["AVALON_TASK"] host_name = session["AVALON_APP"] - project_doc = legacy_io.find_one( - {"type": "project"}, - { - "name": True, - "data.code": True, - "config.tasks": True, - } + project_doc = get_project( + project_name, fields=["name", "data.code", "config.tasks"] + ) + asset_doc = get_asset( + project_name, + asset_name=asset_name, + fields=["name", "data.tasks", "data.parents"] ) - asset_doc = legacy_io.find_one( - { - "type": "asset", - "name": asset_name - }, - { - "name": True, - "data.tasks": True, - "data.parents": True - } - ) data = get_workdir_data(project_doc, asset_doc, task_name, host_name) data.update({ "version": 1, diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 02a22af26c..45d8d41d16 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -2,6 +2,7 @@ import os import datetime from Qt import QtCore, QtWidgets +from openpype.client import get_asset from openpype import style from openpype.lib import ( get_workfile_doc, @@ -223,6 +224,10 @@ class Window(QtWidgets.QMainWindow): self._first_show = True self._context_to_set = None + @property + def project_name(self): + return legacy_io.Session["AVALON_PROJECT"] + def showEvent(self, event): super(Window, self).showEvent(event) if self._first_show: @@ -296,7 +301,8 @@ class Window(QtWidgets.QMainWindow): if not workfile_doc: workdir, filename = os.path.split(filepath) asset_id = self.assets_widget.get_selected_asset_id() - asset_doc = legacy_io.find_one({"_id": asset_id}) + project_name = legacy_io.active_project() + asset_doc = get_asset(project_name, asset_id=asset_id) task_name = self.tasks_widget.get_selected_task_name() create_workfile_doc( asset_doc, task_name, filename, workdir, legacy_io @@ -322,14 +328,13 @@ class Window(QtWidgets.QMainWindow): self._context_to_set, context = None, self._context_to_set if "asset" in context: - asset_doc = legacy_io.find_one( - { - "name": context["asset"], - "type": "asset" - }, - {"_id": 1} - ) or {} - asset_id = asset_doc.get("_id") + asset_doc = get_asset( + self.project_name, context["asset"], fields=["_id"] + ) + + asset_id = None + if asset_doc: + asset_id = asset_doc["_id"] # Select the asset self.assets_widget.select_asset(asset_id) self.tasks_widget.set_asset_id(asset_id) From 8bb97b9bbc9f14315aea37de033fa59758c5185a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 6 Jun 2022 11:06:03 +0200 Subject: [PATCH 047/195] Fix - correct project settings selected --- openpype/modules/sync_server/tray/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 049a3f0127..e41910fa4f 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -127,7 +127,7 @@ class SyncProjectListWidget(QtWidgets.QWidget): if self.sync_server.is_paused() or \ self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") - elif not sync_settings["enabled"]: + elif not sync_settings[project_name]["enabled"]: icon = self._get_icon("disabled") else: icon = self._get_icon("synced") From b3098a4a1ed2f06c622392870f6fad4f7d122e29 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 6 Jun 2022 12:12:10 +0300 Subject: [PATCH 048/195] Fix path bug causing output path to equal input path. --- repos/avalon-core | 1 - 1 file changed, 1 deletion(-) delete mode 160000 repos/avalon-core diff --git a/repos/avalon-core b/repos/avalon-core deleted file mode 160000 index 2fa14cea6f..0000000000 --- a/repos/avalon-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2fa14cea6f6a9d86eec70bbb96860cbe4c75c8eb From a922b93202cc8ec898981109f260141bb0707ef2 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Mon, 6 Jun 2022 12:12:30 +0300 Subject: [PATCH 049/195] Fix path bug. --- openpype/plugins/publish/extract_jpeg_exr.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index daf7430a32..f474714780 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -159,9 +159,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # we just want one frame from movie files jpeg_items.append("-vframes 1") # output file - jpeg_items.append(path_to_subprocess_arg(src_path)) + jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) - run_subprocess( subprocess_command, shell=True, logger=self.log ) From 2f945b9a7b3ce04fa3a37eb3eafac5cdc3efe942 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Jun 2022 11:06:10 +0200 Subject: [PATCH 050/195] Added checkbox to filter only enabled projects Default is true, is not persistent between opening of dialog. --- openpype/modules/sync_server/tray/app.py | 36 +++++++++++++++----- openpype/modules/sync_server/tray/widgets.py | 3 ++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index fc8558bdbc..4aa3d430fa 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -46,6 +46,14 @@ class SyncServerWindow(QtWidgets.QDialog): left_column_layout.addWidget(self.pause_btn) + checkbox = QtWidgets.QCheckBox("Show only enabled", self) + checkbox.setStyleSheet("QCheckBox{spacing: 5px;" + "padding:5px 5px 5px 5px;}") + checkbox.setChecked(True) + self.show_only_enabled_chk = checkbox + + left_column_layout.addWidget(self.show_only_enabled_chk) + repres = SyncRepresentationSummaryWidget( sync_server, project=self.projects.current_project, @@ -86,8 +94,23 @@ class SyncServerWindow(QtWidgets.QDialog): repres.message_generated.connect(self._update_message) self.projects.message_generated.connect(self._update_message) + self.show_only_enabled_chk.stateChanged.connect( + self._on_enabled_change + ) + self.representationWidget = repres + def showEvent(self, event): + self.representationWidget.model.set_project( + self.projects.current_project) + self.projects.refresh() + self._set_running(True) + super().showEvent(event) + + def closeEvent(self, event): + self._set_running(False) + super().closeEvent(event) + def _on_project_change(self): if self.projects.current_project is None: return @@ -103,16 +126,11 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.refresh() return - def showEvent(self, event): - self.representationWidget.model.set_project( - self.projects.current_project) + def _on_enabled_change(self): + """Called when enabled projects only checkbox is toggled.""" + self.projects.show_only_enabled = \ + self.show_only_enabled_chk.isChecked() self.projects.refresh() - self._set_running(True) - super().showEvent(event) - - def closeEvent(self, event): - self._set_running(False) - super().closeEvent(event) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index e41910fa4f..88ed2c1d37 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -47,6 +47,7 @@ class SyncProjectListWidget(QtWidgets.QWidget): message_generated = QtCore.Signal(str) refresh_msec = 10000 + show_only_enabled = True def __init__(self, sync_server, parent): super(SyncProjectListWidget, self).__init__(parent) @@ -128,6 +129,8 @@ class SyncProjectListWidget(QtWidgets.QWidget): self.sync_server.is_project_paused(project_name): icon = self._get_icon("paused") elif not sync_settings[project_name]["enabled"]: + if self.show_only_enabled: + continue icon = self._get_icon("disabled") else: icon = self._get_icon("synced") From 9babdb7832c9cc6644e0dc4ff8f3d024b068fd52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 7 Jun 2022 14:18:48 +0200 Subject: [PATCH 051/195] :recycle: limit the scope of file attribute skipping filter --- openpype/hosts/maya/plugins/publish/collect_look.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 9b6d1d999c..60af5238d0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -601,8 +601,10 @@ class CollectLook(pyblish.api.InstancePlugin): source, computed_source)) - if not source: - self.log.info("source is empty, skipping...") + # renderman allows nodes to have filename attribute empty while + # you can have another incoming connection from different node. + if not source and cmds.nodeType(node).lower().startswith("pxr"): + self.log.info("Renderman: source is empty, skipping...") continue # We replace backslashes with forward slashes because V-Ray # can't handle the UDIM files with the backslashes in the From 71cabc76c4386bff5c8d89ab0afcc6d5d15c61ab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Jun 2022 15:13:08 +0200 Subject: [PATCH 052/195] Handle when no projects are shown or selected --- openpype/modules/sync_server/tray/app.py | 1 + openpype/modules/sync_server/tray/models.py | 33 +++++++++++++------- openpype/modules/sync_server/tray/widgets.py | 6 ++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index 4aa3d430fa..dee3bf0ecc 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -131,6 +131,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.show_only_enabled = \ self.show_only_enabled_chk.isChecked() self.projects.refresh() + self.representationWidget.model.set_project(None) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py index 6b309312a2..c49edeafb9 100644 --- a/openpype/modules/sync_server/tray/models.py +++ b/openpype/modules/sync_server/tray/models.py @@ -52,7 +52,8 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): All queries should go through this (because of collection). """ - return self.sync_server.connection.database[self.project] + if self.project: + return self.sync_server.connection.database[self.project] @property def project(self): @@ -150,6 +151,9 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): @property def can_edit(self): """Returns true if some site is user local site, eg. could edit""" + if not self.project: + return False + return get_local_site_id() in (self.active_site, self.remote_site) def get_column(self, index): @@ -190,7 +194,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): actually queried (scrolled a couple of times to list more than single page of records) """ - if self.is_editing or not self.is_running: + if self.is_editing or not self.is_running or not self.project: return self.refresh_started.emit() self.beginResetModel() @@ -232,6 +236,9 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): more records in DB than loaded. """ log.debug("fetchMore") + if not self.dbcon: + return + items_to_fetch = min(self._total_records - self._rec_loaded, self.PAGE_SIZE) self.query = self.get_query(self._rec_loaded) @@ -286,9 +293,10 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): # replace('False', 'false').\ # replace('True', 'true').replace('None', 'null')) - representations = self.dbcon.aggregate(pipeline=self.query, - allowDiskUse=True) - self.refresh(representations) + if self.dbcon: + representations = self.dbcon.aggregate(pipeline=self.query, + allowDiskUse=True) + self.refresh(representations) def set_word_filter(self, word_filter): """ @@ -380,6 +388,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel): self._project = project # project might have been deactivated in the meantime if not self.sync_server.get_sync_project_setting(project): + self._data = {} return self.active_site = self.sync_server.get_active_site(self.project) @@ -508,21 +517,23 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel): self._word_filter = None - if not self._project or self._project == lib.DUMMY_PROJECT: - return - self.sync_server = sync_server # TODO think about admin mode + self.sort_criteria = self.DEFAULT_SORT + + self.timer = QtCore.QTimer() + if not self._project or self._project == lib.DUMMY_PROJECT: + self.active_site = sync_server.DEFAULT_SITE + self.remote_site = sync_server.DEFAULT_SITE + return + # this is for regular user, always only single local and single remote self.active_site = self.sync_server.get_active_site(self.project) self.remote_site = self.sync_server.get_remote_site(self.project) - self.sort_criteria = self.DEFAULT_SORT - self.query = self.get_query() self.default_query = list(self.get_query()) - self.timer = QtCore.QTimer() self.timer.timeout.connect(self.tick) self.timer.start(self.REFRESH_SEC) diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 88ed2c1d37..89ab12ea4d 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -144,12 +144,12 @@ class SyncProjectListWidget(QtWidgets.QWidget): if self.current_project == project_name: selected_item = item + if model.item(0) is None: + return + if selected_item: selected_index = model.indexFromItem(selected_item) - if len(self.sync_server.sync_project_settings.keys()) == 0: - model.appendRow(QtGui.QStandardItem(lib.DUMMY_PROJECT)) - if not self.current_project: self.current_project = model.item(0).data(QtCore.Qt.DisplayRole) From 9829410932e18f85f4e899a2af045e65aa1d12ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 15:16:39 +0200 Subject: [PATCH 053/195] implemented get_representation_parents for single representation --- openpype/client/__init__.py | 2 ++ openpype/client/entities.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 861f828e67..e4c01fc0cc 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -22,6 +22,7 @@ from .entities import ( get_representation, get_representation_by_name, get_representations, + get_representation_parents, get_representations_parents, get_thumbnail, @@ -52,6 +53,7 @@ __all__ = ( "get_representation", "get_representation_by_name", "get_representations", + "get_representation_parents", "get_representations_parents", "get_thubmnail", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 4b52f8cf2d..e9b820dd1a 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -641,6 +641,15 @@ def get_representations_parents(project_name, representations): return output +def get_representation_parents(project_name, representation): + if not representation: + return None + + repre_id = representation["_id"] + parents_by_repre_id = get_representations(project_name, [representation]) + return parents_by_repre_id.get(repre_id) + + def get_thumbnail_id_from_source(project_name, src_type, src_id): if not src_type or not src_id: return None From f760c9d767c46a0ee433e83890cc15b4565f412f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 7 Jun 2022 15:28:11 +0200 Subject: [PATCH 054/195] :recycle: more explicit renderman check --- openpype/hosts/maya/plugins/publish/collect_look.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 60af5238d0..4a34cfdea2 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -603,7 +603,14 @@ class CollectLook(pyblish.api.InstancePlugin): # renderman allows nodes to have filename attribute empty while # you can have another incoming connection from different node. - if not source and cmds.nodeType(node).lower().startswith("pxr"): + pxr_nodes = set() + if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True): + pxr_nodes = set( + cmds.pluginInfo("RenderMan_for_Maya", + query=True, + dependNode=True) + ) + if not source and cmds.nodeType(node) in pxr_nodes: self.log.info("Renderman: source is empty, skipping...") continue # We replace backslashes with forward slashes because V-Ray From 0d38c76f5402cd99e5a70385667ff1a5f0885eb7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:28:53 +0200 Subject: [PATCH 055/195] separated functions to query asset by name and id --- openpype/client/__init__.py | 8 ++-- openpype/client/entities.py | 43 ++++++++++++++----- openpype/tools/creator/window.py | 6 +-- openpype/tools/mayalookassigner/commands.py | 4 +- .../tools/publisher/widgets/create_dialog.py | 4 +- openpype/tools/sceneinventory/model.py | 4 +- .../tools/sceneinventory/switch_dialog.py | 32 +++++++------- openpype/tools/sceneinventory/view.py | 4 +- openpype/tools/standalonepublish/app.py | 6 +-- .../standalonepublish/widgets/widget_asset.py | 10 ++--- .../widgets/widget_family.py | 14 +++--- openpype/tools/texture_copy/app.py | 4 +- openpype/tools/utils/lib.py | 6 +-- openpype/tools/utils/tasks_widget.py | 6 +-- openpype/tools/workfiles/files_widget.py | 4 +- openpype/tools/workfiles/save_as_dialog.py | 6 +-- openpype/tools/workfiles/window.py | 6 +-- 17 files changed, 94 insertions(+), 73 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e4c01fc0cc..2ba2a3693e 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -2,7 +2,8 @@ from .entities import ( get_projects, get_project, - get_asset, + get_asset_by_id, + get_asset_by_name, get_assets, get_asset_ids_with_subsets, @@ -33,7 +34,8 @@ __all__ = ( "get_projects", "get_project", - "get_asset", + "get_asset_by_id", + "get_asset_by_name", "get_assets", "get_asset_ids_with_subsets", @@ -56,6 +58,6 @@ __all__ = ( "get_representation_parents", "get_representations_parents", - "get_thubmnail", + "get_thumbnail", "get_thumbnail_id_from_source", ) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index e9b820dd1a..97553f30fe 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -90,23 +90,44 @@ def get_project(project_name, active=True, inactive=False, fields=None): return conn.find_one(query_filter, _prepare_fields(fields)) -def get_asset(project_name, asset_name=None, asset_id=None, fields=None): - query_filter = {"type": "asset"} - has_filter = False - if asset_name: - has_filter = True - query_filter["name"] = asset_name +def get_asset_by_id(project_name, asset_id, fields=None): + """Receive asset data by it's id. - if asset_id: - has_filter = True - query_filter["_id"] = _convert_id(asset_id) + Args: + project_name (str): Name of project where to look for subset. + asset_id (str|ObjectId): Asset's id. - # Avoid random asset quqery - if not has_filter: + Returns: + dict: Asset entity data. + None: Asset was not found by id. + """ + + asset_id = _convert_id(asset_id) + if not asset_id: return None + query_filter = {"type": "asset", "_id": asset_id} conn = _get_project_connection(project_name) + return conn.find_one(query_filter, _prepare_fields(fields)) + +def get_asset_by_name(project_name, asset_name, fields=None): + """Receive asset data by it's name. + + Args: + project_name (str): Name of project where to look for subset. + asset_name (str): Asset's name. + + Returns: + dict: Asset entity data. + None: Asset was not found by name. + """ + + if not asset_name: + return None + + query_filter = {"type": "asset", "name": asset_name} + conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/creator/window.py b/openpype/tools/creator/window.py index a85f47a060..a3937d6a40 100644 --- a/openpype/tools/creator/window.py +++ b/openpype/tools/creator/window.py @@ -4,7 +4,7 @@ import re from Qt import QtWidgets, QtCore -from openpype.client import get_asset, get_subsets +from openpype.client import get_asset_by_name, get_subsets from openpype import style from openpype.api import get_current_project_settings from openpype.tools.utils.lib import qt_app_context @@ -220,8 +220,8 @@ class CreatorWindow(QtWidgets.QDialog): asset_doc = None if creator_plugin: # Get the asset from the database which match with the name - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index a4fc1fab70..2e7a51efde 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -4,7 +4,7 @@ import os import maya.cmds as cmds -from openpype.client import get_asset +from openpype.client import get_asset_by_id from openpype.pipeline import ( legacy_io, remove_container, @@ -161,7 +161,7 @@ def create_items_from_nodes(nodes): project_name = legacy_io.active_project() for _id, id_nodes in id_hashes.items(): - asset = get_asset(project_name, asset_id=_id, fields=["name"]) + asset = get_asset_by_id(project_name, _id, fields=["name"]) # Skip if asset id is not found if not asset: diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py index d579831b21..53bbef8b75 100644 --- a/openpype/tools/publisher/widgets/create_dialog.py +++ b/openpype/tools/publisher/widgets/create_dialog.py @@ -10,7 +10,7 @@ except Exception: commonmark = None from Qt import QtWidgets, QtCore, QtGui -from openpype.client import get_asset, get_subsets +from openpype.client import get_asset_by_name, get_subsets from openpype.lib import TaskNotSetError from openpype.pipeline.create import ( CreatorError, @@ -650,7 +650,7 @@ class CreateDialog(QtWidgets.QDialog): return project_name = self.dbcon.active_project() - asset_doc = get_asset(project_name, asset_name=asset_name) + asset_doc = get_asset_by_name(project_name, asset_name) self._asset_doc = asset_doc if asset_doc: diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 9cf69ed650..6d813af45b 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -7,7 +7,7 @@ from Qt import QtCore, QtGui import qtawesome from openpype.client import ( - get_asset, + get_asset_by_id, get_subset, get_version, get_last_version_for_subset, @@ -342,7 +342,7 @@ class InventoryModel(TreeModel): not_found_ids.append(repre_id) continue - asset = get_asset(project_name, asset_id=subset["parent"]) + asset = get_asset_by_id(project_name, subset["parent"]) if not asset: not_found["asset"].append(group_items) not_found_ids.append(repre_id) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index b940c66a56..6d6294af6f 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -5,7 +5,7 @@ import qtawesome from bson.objectid import ObjectId from openpype.client import ( - get_asset, + get_asset_by_name, get_assets, get_subset, get_subsets, @@ -484,9 +484,9 @@ class SwitchAssetDialog(QtWidgets.QDialog): # Prepare asset document if asset is selected asset_doc = None if selected_asset: - asset_doc = get_asset( + asset_doc = get_asset_by_name( self.active_project(), - asset_name=selected_asset, + selected_asset, fields=["_id"] ) if not asset_doc: @@ -768,8 +768,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): project_name = self.active_project() selected_asset = self._assets_box.get_valid_value() if selected_asset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) asset_ids = [asset_doc["_id"]] else: @@ -832,8 +832,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [?] if selected_asset and selected_subset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_doc = get_subset( project_name, @@ -858,8 +858,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] # If asset only is selected if selected_asset: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) if not asset_doc: return list() @@ -995,8 +995,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [?] project_name = self.active_project() - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_docs = get_subsets( project_name, asset_ids=[asset_doc["_id"]], fields=["name"] @@ -1043,8 +1043,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [x] [ ] project_name = self.active_project() if selected_asset is not None and selected_subset is not None: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_doc = get_subset( project_name, @@ -1077,8 +1077,8 @@ class SwitchAssetDialog(QtWidgets.QDialog): # [x] [ ] [ ] if selected_asset is not None: - asset_doc = get_asset( - project_name, asset_name=selected_asset, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, selected_asset, fields=["_id"] ) subset_docs = list(get_subsets( project_name, @@ -1189,7 +1189,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): project_name = self.active_project() if selected_asset: - asset_doc = get_asset(project_name, asset_name=selected_asset) + asset_doc = get_asset_by_name(project_name, selected_asset) asset_docs_by_id = {asset_doc["_id"]: asset_doc} else: asset_docs_by_id = self.content_assets diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 6a95ccb57b..04e0f67a15 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -657,11 +657,9 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() # Get available versions for active representation - representation_id = ObjectId(active["representation"]) - repre_doc = get_representation( project_name, - representation_id=representation_id, + representation_id=active["representation"], fields=["parent"] ) diff --git a/openpype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py index 4831db038c..3ceeb3ad48 100644 --- a/openpype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -6,7 +6,7 @@ import signal from bson.objectid import ObjectId from Qt import QtWidgets, QtCore, QtGui -from openpype.client import get_asset +from openpype.client import get_asset_by_id from .widgets import ( AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget @@ -144,8 +144,8 @@ class Window(QtWidgets.QDialog): if len(selected) == 1: self.valid_parent = True project_name = self.db.active_project() - asset = get_asset( - project_name, asset_id=selected[0], fields=["name"] + asset = get_asset_by_id( + project_name, selected[0], fields=["name"] ) self.widget_family.change_asset(asset['name']) else: diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py index 0b5802ed9e..73114f7960 100644 --- a/openpype/tools/standalonepublish/widgets/widget_asset.py +++ b/openpype/tools/standalonepublish/widgets/widget_asset.py @@ -4,7 +4,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_id, ) from openpype.tools.utils import PlaceholderLineEdit @@ -251,9 +251,9 @@ class AssetWidget(QtWidgets.QWidget): return output project_name = self.dbcon.active_project() - parent = get_asset( + parent = get_asset_by_id( project_name, - asset_id=parent_asset_id, + parent_asset_id, fields=["name", "data.visualParent"] ) output.append(parent['name']) @@ -362,8 +362,8 @@ class AssetWidget(QtWidgets.QWidget): selected = self.get_selected_assets() if len(selected) == 1: project_name = self.dbcon.active_project() - asset = get_asset( - project_name, asset_id=selected[0], fields=["data.tasks"] + asset = get_asset_by_id( + project_name, selected[0], fields=["data.tasks"] ) if asset: tasks = asset.get('data', {}).get('tasks', []) diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index ed9f405f38..fa157d37f1 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -3,7 +3,7 @@ import re from Qt import QtWidgets, QtCore from openpype.client import ( - get_asset, + get_asset_by_name, get_subset, get_subsets, get_last_version_for_subset, @@ -188,8 +188,8 @@ class FamilyWidget(QtWidgets.QWidget): if asset_name != self.NOT_SELECTED: # Get the assets from the database which match with the name project_name = self.dbcon.active_project() - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin and family @@ -205,8 +205,8 @@ class FamilyWidget(QtWidgets.QWidget): # Get the asset from the database which match with the name project_name = self.dbcon.active_project() - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) # Get plugin plugin = item.data(PluginRole) @@ -310,8 +310,8 @@ class FamilyWidget(QtWidgets.QWidget): asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["_id"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["_id"] ) if asset_doc: diff --git a/openpype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py index 8703f075d3..746a72b3ec 100644 --- a/openpype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -4,7 +4,7 @@ import click import speedcopy -from openpype.client import get_project, get_asset +from openpype.client import get_project, get_asset_by_name from openpype.lib import Terminal from openpype.api import Anatomy from openpype.pipeline import legacy_io @@ -94,7 +94,7 @@ class TextureCopy: t.echo("!!! Project name [ {} ] not found.".format(project_name)) exit(1) - asset = get_asset(project_name, asset_name=asset_name) + asset = get_asset_by_name(project_name, asset_name) if not asset: t.echo("!!! Asset [ {} ] not found in project".format(asset_name)) exit(1) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 72ebfcc063..ea1362945f 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -8,7 +8,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_name, ) from openpype.style import ( get_default_entity_icon_color, @@ -434,8 +434,8 @@ class FamilyConfigCache: database = getattr(self.dbcon, "database", None) if database is None: database = self.dbcon._database - asset_doc = get_asset( - project_name, asset_name=asset_name, fields=["data.tasks"] + asset_doc = get_asset_by_name( + project_name, asset_name, fields=["data.tasks"] ) or {} tasks_info = asset_doc.get("data", {}).get("tasks") or {} task_type = tasks_info.get(task_name, {}).get("type") diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index fcbec318f5..0353f3dd2f 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -3,7 +3,7 @@ import qtawesome from openpype.client import ( get_project, - get_asset, + get_asset_by_id, ) from openpype.style import get_disabled_entity_icon_color from openpype.tools.utils.lib import get_task_icon @@ -77,8 +77,8 @@ class TasksModel(QtGui.QStandardItemModel): asset_doc = None if self._context_is_valid(): project_name = self._get_current_project() - asset_doc = get_asset( - project_name, asset_id=asset_id, fields=["data.tasks"] + asset_doc = get_asset_by_id( + project_name, asset_id, fields=["data.tasks"] ) self._set_asset(asset_doc) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 36b9a055d8..68fe8301c9 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -5,7 +5,7 @@ import shutil import Qt from Qt import QtWidgets, QtCore -from openpype.client import get_asset +from openpype.client import get_asset_by_id from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -386,7 +386,7 @@ class FilesWidget(QtWidgets.QWidget): if self._asset_doc is None: project_name = legacy_io.active_project() - self._asset_doc = get_asset(project_name, asset_id=self._asset_id) + self._asset_doc = get_asset_by_id(project_name, self._asset_id) return self._asset_doc diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index 1fbcbfeb22..b62fd2c889 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -7,7 +7,7 @@ from Qt import QtWidgets, QtCore from openpype.client import ( get_project, - get_asset, + get_asset_by_name, ) from openpype.lib import ( get_last_workfile_with_version, @@ -33,9 +33,9 @@ def build_workfile_data(session): project_doc = get_project( project_name, fields=["name", "data.code", "config.tasks"] ) - asset_doc = get_asset( + asset_doc = get_asset_by_name( project_name, - asset_name=asset_name, + asset_name, fields=["name", "data.tasks", "data.parents"] ) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 45d8d41d16..9f4cea2f8a 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -2,7 +2,7 @@ import os import datetime from Qt import QtCore, QtWidgets -from openpype.client import get_asset +from openpype.client import get_asset_by_id, get_asset_by_name from openpype import style from openpype.lib import ( get_workfile_doc, @@ -302,7 +302,7 @@ class Window(QtWidgets.QMainWindow): workdir, filename = os.path.split(filepath) asset_id = self.assets_widget.get_selected_asset_id() project_name = legacy_io.active_project() - asset_doc = get_asset(project_name, asset_id=asset_id) + asset_doc = get_asset_by_id(project_name, asset_id) task_name = self.tasks_widget.get_selected_task_name() create_workfile_doc( asset_doc, task_name, filename, workdir, legacy_io @@ -328,7 +328,7 @@ class Window(QtWidgets.QMainWindow): self._context_to_set, context = None, self._context_to_set if "asset" in context: - asset_doc = get_asset( + asset_doc = get_asset_by_name( self.project_name, context["asset"], fields=["_id"] ) From 20ecefa4aae7918fb6a0dbdd5d3de141249eb69d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:43:07 +0200 Subject: [PATCH 056/195] added docstrings for asset queries --- openpype/client/entities.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 97553f30fe..16dc3d76a9 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -94,8 +94,10 @@ def get_asset_by_id(project_name, asset_id, fields=None): """Receive asset data by it's id. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. asset_id (str|ObjectId): Asset's id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. Returns: dict: Asset entity data. @@ -115,8 +117,10 @@ def get_asset_by_name(project_name, asset_name, fields=None): """Receive asset data by it's name. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. asset_name (str): Asset's name. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. Returns: dict: Asset entity data. @@ -134,6 +138,25 @@ def get_asset_by_name(project_name, asset_name, fields=None): def get_assets( project_name, asset_ids=None, asset_names=None, archived=False, fields=None ): + """Assets for specified project by passed filters. + + Passed filters (ids and names) are always combined so all conditions must + match. + + To receive all assets from project just keep filters empty. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str, ObjectId]): Asset ids that should be found. + asset_names (list[str]): Name assets that should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Query cursor as iterable which returns asset documents matching + passed filters. + """ + asset_types = ["asset"] if archived: asset_types.append("archived_asset") @@ -160,6 +183,16 @@ def get_assets( def get_asset_ids_with_subsets(project_name, asset_ids=None): + """Find out which assets have existing subsets. + + Args: + project_name (str): Name of project where to look for queried entities. + asset_ids (list[str|ObjectId]): Look only for entered asset ids. + + Returns: + List[ObjectId]: Asset ids that have existing subsets. + """ + subset_query = { "type": "subset" } From 9456dac8b92862120f580bfbd59327b83a78fd4c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 16:44:26 +0200 Subject: [PATCH 057/195] separated function get_subset into 2 separated functions --- openpype/client/__init__.py | 6 +- openpype/client/entities.py | 73 ++++++++++--------- openpype/tools/loader/widgets.py | 4 +- openpype/tools/sceneinventory/model.py | 4 +- .../tools/sceneinventory/switch_dialog.py | 26 +++---- .../widgets/widget_family.py | 8 +- 6 files changed, 63 insertions(+), 58 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2ba2a3693e..34257cf3dc 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -7,7 +7,8 @@ from .entities import ( get_assets, get_asset_ids_with_subsets, - get_subset, + get_subset_by_id, + get_subset_by_name, get_subsets, get_subset_families, @@ -39,7 +40,8 @@ __all__ = ( "get_assets", "get_asset_ids_with_subsets", - "get_subset", + "get_subset_by_id", + "get_subset_by_name", "get_subsets", "get_subset_families", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 16dc3d76a9..07f6bb2e65 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -223,29 +223,13 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): return asset_ids_with_subsets -def get_subset( - project_name, - subset_id=None, - subset_name=None, - asset_id=None, - fields=None -): - """Single subset document by subset id or name and parent id. - - When subset id is defined it is not needed to add any other arguments but - subset name filter must be always combined with asset id (or subset id). - - Question: - This could be split into more functions? +def get_subset_by_id(project_name, subset_id, fields=None): + """Single subset document by it's id. Args: - project_name (str): Name of project where to look for subset. + project_name (str): Name of project where to look for queried entities. subset_id (ObjectId): Id of subset which should be found. - subset_name (str): Name of asset. Must be combined with 'asset_id' or - 'subset_id' arguments otherwise result is 'None'. - asset_id (ObjectId): Id of parent asset. Must be combined with - 'subset_name' or 'subset_id'. - fields (list): Fields that should be returned. All fields are + fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. Returns: @@ -253,23 +237,42 @@ def get_subset( Dict: Subset document which can be reduced to specified 'fields'. """ - query_filters = {"type": "subset"} - has_valid_filters = False - if subset_id is not None: - query_filters["_id"] = _convert_id(asset_id) - has_valid_filters = True - - if subset_name is not None: - if asset_id is not None: - has_valid_filters = True - query_filters["name"] = subset_name - - if asset_id is not None: - query_filters["parent"] = _convert_id(asset_id) - - if not has_valid_filters: + subset_id = _convert_id(subset_id) + if not subset_id: return None + query_filters = {"type": "subset", "_id": subset_id} + conn = _get_project_connection(project_name) + return conn.find_one(query_filters, _prepare_fields(fields)) + + +def get_subset_by_name(project_name, subset_name, asset_id, fields=None): + """Single subset document by subset name and it's version id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_name (str): Name of subset. + asset_id (str|ObjectId): Id of parent asset. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If subset with specified filters was not found. + Dict: Subset document which can be reduced to specified 'fields'. + """ + + if not subset_name: + return None + + asset_id = _convert_id(asset_id) + if not asset_id: + return None + + query_filters = { + "type": "subset", + "name": subset_name, + "parent": asset_id + } conn = _get_project_connection(project_name) return conn.find_one(query_filters, _prepare_fields(fields)) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 6c7acc593d..4a9a911f93 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -9,7 +9,7 @@ from Qt import QtWidgets, QtCore, QtGui from openpype.client import ( get_subset_families, - get_subset, + get_subset_by_id, get_subsets, get_version, get_versions, @@ -688,7 +688,7 @@ class VersionTextEdit(QtWidgets.QTextEdit): _version_doc["name"] ) - subset = get_subset(project_name, subset_id=version_doc["parent"]) + subset = get_subset_by_id(project_name, version_doc["parent"]) assert subset, "No valid subset parent for version" # Define readable creation timestamp diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 6d813af45b..b36f7f4cea 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -8,7 +8,7 @@ import qtawesome from openpype.client import ( get_asset_by_id, - get_subset, + get_subset_by_id, get_version, get_last_version_for_subset, get_representation, @@ -336,7 +336,7 @@ class InventoryModel(TreeModel): version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] - subset = get_subset(project_name, subset_id=version["parent"]) + subset = get_subset_by_id(project_name, version["parent"]) if not subset: not_found["subset"].append(group_items) not_found_ids.append(repre_id) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 6d6294af6f..92ef2b3553 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -7,7 +7,7 @@ from bson.objectid import ObjectId from openpype.client import ( get_asset_by_name, get_assets, - get_subset, + get_subset_by_name, get_subsets, get_versions, get_hero_versions, @@ -537,10 +537,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): self, asset_doc, selected_subset, selected_repre ): project_name = self.active_project() - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - subset_name=selected_subset, - asset_id=asset_doc["_id"], + selected_subset, + asset_doc["_id"], fields=["_id"] ) @@ -560,10 +560,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): def _get_current_output_repre_ids_xxo(self, asset_doc, selected_subset): project_name = self.active_project() - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) if not subset_doc: @@ -835,10 +835,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_doc = get_asset_by_name( project_name, selected_asset, fields=["_id"] ) - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) @@ -1046,10 +1046,10 @@ class SwitchAssetDialog(QtWidgets.QDialog): asset_doc = get_asset_by_name( project_name, selected_asset, fields=["_id"] ) - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - asset_id=asset_doc["_id"], - subset_name=selected_subset, + selected_subset, + asset_doc["_id"], fields=["_id"] ) subset_id = subset_doc["_id"] diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index fa157d37f1..2f00cfe7bb 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -4,7 +4,7 @@ from Qt import QtWidgets, QtCore from openpype.client import ( get_asset_by_name, - get_subset, + get_subset_by_name, get_subsets, get_last_version_for_subset, ) @@ -315,10 +315,10 @@ class FamilyWidget(QtWidgets.QWidget): ) if asset_doc: - subset_doc = get_subset( + subset_doc = get_subset_by_name( project_name, - subset_name=subset_name, - asset_id=asset_doc['_id'], + subset_name, + asset_doc['_id'], fields=["_id"] ) From 0d5cc529d502cdf2a421be4f7db27dcbd54eb9ff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 17:18:21 +0200 Subject: [PATCH 058/195] renamed 'get_version' to 'get_version_by_id' --- openpype/client/__init__.py | 4 +- openpype/client/entities.py | 107 ++++++++++++++++++++++--- openpype/tools/loader/widgets.py | 8 +- openpype/tools/sceneinventory/model.py | 10 +-- openpype/tools/sceneinventory/view.py | 6 +- 5 files changed, 108 insertions(+), 27 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 34257cf3dc..e8e3a81d5d 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -12,8 +12,8 @@ from .entities import ( get_subsets, get_subset_families, + get_version_by_id, get_version_by_name, - get_version, get_versions, get_last_versions, get_last_version_for_subset, @@ -45,8 +45,8 @@ __all__ = ( "get_subsets", "get_subset_families", + "get_version_by_id", "get_version_by_name", - "get_version", "get_versions", "get_last_versions", "get_last_version_for_subset", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 07f6bb2e65..1a45be6e93 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -224,7 +224,7 @@ def get_asset_ids_with_subsets(project_name, asset_ids=None): def get_subset_by_id(project_name, subset_id, fields=None): - """Single subset document by it's id. + """Single subset entity data by it's id. Args: project_name (str): Name of project where to look for queried entities. @@ -247,7 +247,7 @@ def get_subset_by_id(project_name, subset_id, fields=None): def get_subset_by_name(project_name, subset_name, asset_id, fields=None): - """Single subset document by subset name and it's version id. + """Single subset entity data by it's name and it's version id. Args: project_name (str): Name of project where to look for queried entities. @@ -279,12 +279,31 @@ def get_subset_by_name(project_name, subset_name, asset_id, fields=None): def get_subsets( project_name, - asset_ids=None, subset_ids=None, subset_names=None, + asset_ids=None, archived=False, fields=None ): + """Subset entities data from one project filtered by entered filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str, ObjectId]): Subset ids that should be queried. + Filter ignored if 'None' is passed. + subset_names (list[str]): Subset names that should be queried. + 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. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching subsets. + """ + subset_types = ["subset"] if archived: subset_types.append("archived_subset") @@ -316,6 +335,17 @@ def get_subsets( def get_subset_families(project_name, subset_ids=None): + """Set of main families of subsets. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str, ObjectId]): Subset ids that should be queried. + All subsets from project are used if 'None' is passed. + + Returns: + set[str]: Main families of matching subsets. + """ + subset_filter = { "type": "subset" } @@ -340,23 +370,56 @@ def get_subset_families(project_name, subset_ids=None): return set() -def get_version_by_name(project_name, subset_id, version, fields=None): - conn = _get_project_connection(project_name) +def get_version_by_id(project_name, version_id, fields=None): + """Single version entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + version_id = _convert_id(version_id) + if not version_id: + return None + query_filter = { - "type": "version", - "parent": _convert_id(subset_id), - "name": version + "type": {"$in": ["version", "hero_version"]}, + "_id": version_id } + conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) -def get_version(project_name, version_id, fields=None): - if not version_id: +def get_version_by_name(project_name, version, subset_id, fields=None): + """Single version entity data by it's name and subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + version (int): name of version entity (it's version). + subset_id (ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None + conn = _get_project_connection(project_name) query_filter = { - "type": {"$in": ["version", "hero_version"]}, - "_id": _convert_id(version_id) + "type": "version", + "parent": subset_id, + "name": version } return conn.find_one(query_filter, _prepare_fields(fields)) @@ -402,11 +465,29 @@ def _get_versions( def get_versions( project_name, - subset_ids=None, version_ids=None, + subset_ids=None, hero=False, fields=None ): + """Version entities data from one project filtered by entered filters. + + Filters are additive (all conditions must pass to return subset). + + Args: + project_name (str): Name of project where to look for queried entities. + version_ids (list[str, ObjectId]): Version ids that will be queried. + Filter ignored if 'None' is passed. + subset_ids (list[str]): Subset ids that will be queried. + Filter ignored if 'None' is passed. + hero (bool): Look also for hero versions. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor: Iterable cursor yielding all matching versions. + """ + return _get_versions( project_name, subset_ids, diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 4a9a911f93..921708922e 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -11,7 +11,7 @@ from openpype.client import ( get_subset_families, get_subset_by_id, get_subsets, - get_version, + get_version_by_id, get_versions, get_representations, get_thumbnail_id_from_source, @@ -676,12 +676,12 @@ class VersionTextEdit(QtWidgets.QTextEdit): project_name = self.dbcon.active_project() if not version_doc: - version_doc = get_version(project_name, version_id=version_id) + version_doc = get_version_by_id(project_name, version_id) assert version_doc, "Not a valid version id" if version_doc["type"] == "hero_version": - _version_doc = get_version( - project_name, version_id=version_doc["version_id"] + _version_doc = get_version_by_id( + project_name, version_doc["version_id"] ) version_doc["data"] = _version_doc["data"] version_doc["name"] = HeroVersionType( diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index b36f7f4cea..a5d856fe72 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -9,7 +9,7 @@ import qtawesome from openpype.client import ( get_asset_by_id, get_subset_by_id, - get_version, + get_version_by_id, get_last_version_for_subset, get_representation, ) @@ -321,8 +321,8 @@ class InventoryModel(TreeModel): not_found_ids.append(repre_id) continue - version = get_version( - project_name, version_id=representation["parent"] + version = get_version_by_id( + project_name, representation["parent"] ) if not version: not_found["version"].append(group_items) @@ -330,8 +330,8 @@ class InventoryModel(TreeModel): continue elif version["type"] == "hero_version": - _version = get_version( - project_name, version_id=version["version_id"] + _version = get_version_by_id( + project_name, version["version_id"] ) version["name"] = HeroVersionType(_version["name"]) version["data"] = _version["data"] diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 04e0f67a15..d1ff91535f 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -7,7 +7,7 @@ import qtawesome from bson.objectid import ObjectId from openpype.client import ( - get_version, + get_version_by_id, get_versions, get_hero_versions, get_representation, @@ -663,9 +663,9 @@ class SceneInventoryView(QtWidgets.QTreeView): fields=["parent"] ) - repre_version_doc = get_version( + repre_version_doc = get_version_by_id( project_name, - version_id=repre_doc["parent"], + repre_doc["parent"], fields=["parent"] ) From 0f95a66359c693382af236e4d695764ecbfb7f14 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 17:43:44 +0200 Subject: [PATCH 059/195] renamed get_version_links to get_output_link_versions --- openpype/client/__init__.py | 4 ++-- openpype/client/entities.py | 26 ++++++++++++++++++++++++-- openpype/tools/assetlinks/widgets.py | 4 ++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index e8e3a81d5d..7230a7c153 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -19,7 +19,7 @@ from .entities import ( get_last_version_for_subset, get_hero_version, get_hero_versions, - get_version_links, + get_output_link_versions, get_representation, get_representation_by_name, @@ -52,7 +52,7 @@ __all__ = ( "get_last_version_for_subset", "get_hero_version", "get_hero_versions", - "get_version_links", + "get_output_link_versions", "get_representation", "get_representation_by_name", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 1a45be6e93..0eb0367452 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -544,12 +544,34 @@ def get_hero_versions( ) -def get_version_links(project_name, version_id, fields=None): +def get_output_link_versions(project_name, version_id, fields=None): + """Versions where passed version was used as input. + + Question: + Not 100% sure about the usage of the function so the name and docstring + maybe does not match what it does? + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Version id which can be used as input link + for other versions. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor|list: Iterable cursor yielding versions that are used as input + links for passed version. + """ + + version_id = _convert_id(version_id) + if not version_id: + return [] + conn = _get_project_connection(project_name) # Does make sense to look for hero versions? query_filter = { "type": "version", - "data.inputLinks.input": _convert_id(version_id) + "data.inputLinks.input": version_id } return conn.find(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 5ce2a835ef..3078585ed1 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -3,7 +3,7 @@ from openpype.client import ( get_versions, get_subsets, get_assets, - get_version_links, + get_output_link_versions, ) from Qt import QtWidgets @@ -112,7 +112,7 @@ class SimpleLinkView(QtWidgets.QWidget): )) def _fill_outputs(self, version_doc): - version_docs = list(get_version_links( + version_docs = list(get_output_link_versions( self.project_name, version_doc["_id"], fields=["name", "parent"] From 675da63f8ccb862797c298ba9acd6a57b7f7e1e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:04:04 +0200 Subject: [PATCH 060/195] split 'get_hero_version' to 'get_hero_version_by_id' and 'get_hero_version_by_subset_id' --- openpype/client/__init__.py | 10 +++-- openpype/client/entities.py | 79 +++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 7230a7c153..4f8b948c93 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -15,10 +15,11 @@ from .entities import ( get_version_by_id, get_version_by_name, get_versions, + get_hero_version_by_id, + get_hero_version_by_subset_id, + get_hero_versions, get_last_versions, get_last_version_for_subset, - get_hero_version, - get_hero_versions, get_output_link_versions, get_representation, @@ -48,10 +49,11 @@ __all__ = ( "get_version_by_id", "get_version_by_name", "get_versions", + "get_hero_version_by_id", + "get_hero_version_by_subset_id", + "get_hero_versions", "get_last_versions", "get_last_version_for_subset", - "get_hero_version", - "get_hero_versions", "get_output_link_versions", "get_representation", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 0eb0367452..bf033e7c81 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -498,27 +498,57 @@ def get_versions( ) -def get_hero_version( - project_name, - subset_id=None, - version_id=None, - fields=None -): - if not subset_id and not version_id: +def get_hero_version_by_subset_id(project_name, subset_id, fields=None): + """Hero version by subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Subset id under which is hero version. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If hero version for passed subset id does not exists. + Dict: Hero version entity data. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None - subset_ids = None - if subset_id is not None: - subset_ids = [subset_id] - - version_ids = None - if version_id is not None: - version_ids = [version_id] - versions = list(_get_versions( project_name, - subset_ids=subset_ids, - version_ids=version_ids, + subset_ids=[subset_id], + standard=False, + hero=True, + fields=fields + )) + if versions: + return versions[0] + return None + + +def get_hero_version_by_id(project_name, version_id, fields=None): + """Hero version by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + version_id (str|ObjectId): Hero version id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If hero version with passed id was not found. + Dict: Hero version entity data. + """ + + version_id = _convert_id(version_id) + if not version_id: + return None + + versions = list(_get_versions( + project_name, + version_ids=[version_id], standard=False, hero=True, fields=fields @@ -534,6 +564,21 @@ def get_hero_versions( version_ids=None, fields=None ): + """Hero version entities data from one project filtered by entered filters. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_ids (list[str|ObjectId]): Subset ids for which should look for + hero versions. Filter ignored if 'None' is passed. + version_ids (list[str|ObjectId]): Hero version ids. Filter ignored if + 'None' is passed. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + Cursor|list: Iterable yielding hero versions matching passed filters. + """ + return _get_versions( project_name, subset_ids, From ba6ef6d2ae035361d0a7282983555990c322cc7c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:23:44 +0200 Subject: [PATCH 061/195] split 'get_last_version_for_subset' into 'get_last_version_by_subset_id' and 'get_last_version_by_subset_name' --- openpype/client/__init__.py | 6 +- openpype/client/entities.py | 81 ++++++++++++------- openpype/tools/mayalookassigner/app.py | 6 +- .../tools/mayalookassigner/vray_proxies.py | 4 +- openpype/tools/sceneinventory/model.py | 6 +- .../widgets/widget_family.py | 6 +- 6 files changed, 65 insertions(+), 44 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 4f8b948c93..2ef32d6a83 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -19,7 +19,8 @@ from .entities import ( get_hero_version_by_subset_id, get_hero_versions, get_last_versions, - get_last_version_for_subset, + get_last_version_by_subset_id, + get_last_version_by_subset_name, get_output_link_versions, get_representation, @@ -53,7 +54,8 @@ __all__ = ( "get_hero_version_by_subset_id", "get_hero_versions", "get_last_versions", - "get_last_version_for_subset", + "get_last_version_by_subset_id", + "get_last_version_by_subset_name", "get_output_link_versions", "get_representation", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index bf033e7c81..232d9aebcc 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -228,7 +228,7 @@ def get_subset_by_id(project_name, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - subset_id (ObjectId): Id of subset which should be found. + subset_id (str|ObjectId): Id of subset which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -375,7 +375,7 @@ def get_version_by_id(project_name, version_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. - version_id (ObjectId): Id of version which should be found. + version_id (str|ObjectId): Id of version which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -402,7 +402,7 @@ def get_version_by_name(project_name, version, subset_id, fields=None): Args: project_name (str): Name of project where to look for queried entities. version (int): name of version entity (it's version). - subset_id (ObjectId): Id of version which should be found. + subset_id (str|ObjectId): Id of version which should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -622,13 +622,13 @@ def get_output_link_versions(project_name, version_id, fields=None): def get_last_versions(project_name, subset_ids, fields=None): - """Retrieve all latest versions for entered subset_ids. + """Latest versions for entered subset_ids. Args: subset_ids (list): List of subset ids. Returns: - dict: Key is subset id and value is last version name. + dict[ObjectId, int]: Key is subset id and value is last version name. """ subset_ids = _convert_ids(subset_ids) @@ -665,35 +665,60 @@ def get_last_versions(project_name, subset_ids, fields=None): } -def get_last_version_for_subset( - project_name, subset_id=None, subset_name=None, asset_id=None, fields=None -): - subset_doc = get_subset( - project_name, - subset_id=subset_id, - subset_name=subset_name, - asset_id=asset_id, - fields=["_id"] - ) - if not subset_doc: +def get_last_version_by_subset_id(project_name, subset_id, fields=None): + """Last version for passed subset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_id (str|ObjectId): Id of version which should be found. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_id = _convert_id(subset_id) + if not subset_id: return None - subset_id = subset_doc["_id"] + last_versions = get_last_versions( project_name, subset_ids=[subset_id], fields=fields ) return last_versions.get(subset_id) -def get_representation( - project_name, - representation_id=None, - representation_name=None, - version_id=None, - fields=None +def get_last_version_by_subset_name( + project_name, subset_name, asset_id, fields=None ): + """Last version for passed subset name under asset id. + + Args: + project_name (str): Name of project where to look for queried entities. + subset_name (str): Name of subset. + asset_id (str|ObjectId): Asset id which is parnt of passed subset name. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If version with specified filters was not found. + Dict: Version document which can be reduced to specified 'fields'. + """ + + subset_doc = get_subset_by_name( + project_name, subset_name, asset_id, fields=["_id"] + ) + if not subset_doc: + return None + return get_last_version_by_subset_id( + project_name, subset_doc["_id"], fields=fields + ) + + +def get_representation(project_name, representation_id, fields=None): if not representation_id: - if not representation_name or not version_id: - return None + return None repre_types = ["representation", "archived_representations"] query_filter = { @@ -702,12 +727,6 @@ def get_representation( if representation_id is not None: query_filter["_id"] = _convert_id(representation_id) - if representation_name is not None: - query_filter["name"] = representation_name - - if version_id is not None: - query_filter["parent"] = version_id - conn = _get_project_connection(project_name) return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 427edf8245..5665acea42 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -4,7 +4,7 @@ import logging from Qt import QtWidgets, QtCore -from openpype.client import get_last_version_for_subset +from openpype.client import get_last_version_by_subset_id from openpype import style from openpype.pipeline import legacy_io from openpype.tools.utils.lib import qt_app_context @@ -230,8 +230,8 @@ class MayaLookAssignerWindow(QtWidgets.QWidget): continue # Get the latest version of this asset's look subset - version = get_last_version_for_subset( - project_name, subset_id=assign_look["_id"], fields=["_id"] + version = get_last_version_by_subset_id( + project_name, assign_look["_id"], fields=["_id"] ) subset_name = assign_look["name"] diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py index b2ba21f944..889396e555 100644 --- a/openpype/tools/mayalookassigner/vray_proxies.py +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -12,7 +12,7 @@ from maya import cmds from openpype.client import ( get_representation_by_name, - get_last_version_for_subset, + get_last_version_by_subset_name, ) from openpype.pipeline import ( legacy_io, @@ -251,7 +251,7 @@ def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): for asset_id, node_ids in node_ids_by_asset_id.items(): # Get latest look version - version = get_last_version_for_subset( + version = get_last_version_by_subset_name( project_name, subset_name=subset, asset_id=asset_id, diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index a5d856fe72..8c49933e80 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -10,7 +10,7 @@ from openpype.client import ( get_asset_by_id, get_subset_by_id, get_version_by_id, - get_last_version_for_subset, + get_last_version_by_subset_id, get_representation, ) from openpype.pipeline import ( @@ -403,8 +403,8 @@ class InventoryModel(TreeModel): # Store the highest available version so the model can know # whether current version is currently up-to-date. - highest_version = get_last_version_for_subset( - project_name, subset_id=version["parent"] + highest_version = get_last_version_by_subset_id( + project_name, version["parent"] ) # create the group header diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 2f00cfe7bb..1736be84ab 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -6,7 +6,7 @@ from openpype.client import ( get_asset_by_name, get_subset_by_name, get_subsets, - get_last_version_for_subset, + get_last_version_by_subset_id, ) from openpype.api import get_project_settings from openpype.pipeline import LegacyCreator @@ -323,9 +323,9 @@ class FamilyWidget(QtWidgets.QWidget): ) if subset_doc: - last_version = get_last_version_for_subset( + last_version = get_last_version_by_subset_id( project_name, - subset_id=subset_doc["_id"], + subset_doc["_id"], fields=["name"] ) if last_version: From cde47eefa8201fb6fc7b922427436ec2f3bd0202 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:27:54 +0200 Subject: [PATCH 062/195] renamed 'get_representation' to 'get_representation_by_id' --- openpype/client/__init__.py | 4 +-- openpype/client/entities.py | 35 +++++++++++++++++++++++--- openpype/tools/sceneinventory/model.py | 6 ++--- openpype/tools/sceneinventory/view.py | 6 ++--- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2ef32d6a83..2fa7730839 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -23,7 +23,7 @@ from .entities import ( get_last_version_by_subset_name, get_output_link_versions, - get_representation, + get_representation_by_id, get_representation_by_name, get_representations, get_representation_parents, @@ -58,7 +58,7 @@ __all__ = ( "get_last_version_by_subset_name", "get_output_link_versions", - "get_representation", + "get_representation_by_id", "get_representation_by_name", "get_representations", "get_representation_parents", diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 232d9aebcc..7abb25e380 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -716,7 +716,21 @@ def get_last_version_by_subset_name( ) -def get_representation(project_name, representation_id, fields=None): +def get_representation_by_id(project_name, representation_id, fields=None): + """Representation entity data by it's id. + + Args: + project_name (str): Name of project where to look for queried entities. + representation_id (str|ObjectId): Representation id. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If representation with specified filters was not found. + Dict: Representation entity data which can be reduced + to specified 'fields'. + """ + if not representation_id: return None @@ -735,17 +749,32 @@ def get_representation(project_name, representation_id, fields=None): def get_representation_by_name( project_name, representation_name, version_id, fields=None ): + """Representation entity data by it's name and it's version id. + + Args: + project_name (str): Name of project where to look for queried entities. + representation_name (str): Representation name. + version_id (str|ObjectId): Id of parent version entity. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If representation with specified filters was not found. + Dict: Representation entity data which can be reduced + to specified 'fields'. + """ + + version_id = _convert_id(version_id) if not version_id or not representation_name: return None repre_types = ["representation", "archived_representations"] query_filter = { "type": {"$in": repre_types}, "name": representation_name, - "parent": _convert_id(version_id) + "parent": version_id } conn = _get_project_connection(project_name) - return conn.find_one(query_filter, _prepare_fields(fields)) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 8c49933e80..117bdfcba1 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -11,7 +11,7 @@ from openpype.client import ( get_subset_by_id, get_version_by_id, get_last_version_by_subset_id, - get_representation, + get_representation_by_id, ) from openpype.pipeline import ( legacy_io, @@ -313,8 +313,8 @@ class InventoryModel(TreeModel): for repre_id, group_dict in sorted(grouped.items()): group_items = group_dict["items"] # Get parenthood per group - representation = get_representation( - project_name, representation_id=repre_id + representation = get_representation_by_id( + project_name, repre_id ) if not representation: not_found["representation"].append(group_items) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index d1ff91535f..8164c48a5d 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -10,7 +10,7 @@ from openpype.client import ( get_version_by_id, get_versions, get_hero_versions, - get_representation, + get_representation_by_id, get_representations, ) from openpype import style @@ -657,9 +657,9 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() # Get available versions for active representation - repre_doc = get_representation( + repre_doc = get_representation_by_id( project_name, - representation_id=active["representation"], + active["representation"], fields=["parent"] ) From aa91db6883485f33ba7bd2c74e2250ec5ebd906d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:36:54 +0200 Subject: [PATCH 063/195] added docstring for get_representations --- openpype/client/entities.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 7abb25e380..6c0170ea40 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -147,7 +147,7 @@ def get_assets( Args: project_name (str): Name of project where to look for queried entities. - asset_ids (list[str, ObjectId]): Asset ids that should be found. + asset_ids (list[str|ObjectId]): Asset ids that should be found. asset_names (list[str]): Name assets that should be found. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -291,11 +291,11 @@ def get_subsets( Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str, ObjectId]): Subset ids that should be queried. + subset_ids (list[str|ObjectId]): Subset ids that should be queried. Filter ignored if 'None' is passed. subset_names (list[str]): Subset names that should be queried. Filter ignored if 'None' is passed. - asset_ids (list[str, ObjectId]): Asset ids under which should look for + asset_ids (list[str|ObjectId]): Asset ids under which should look for the subsets. Filter ignored if 'None' is passed. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -339,7 +339,7 @@ def get_subset_families(project_name, subset_ids=None): Args: project_name (str): Name of project where to look for queried entities. - subset_ids (list[str, ObjectId]): Subset ids that should be queried. + subset_ids (list[str|ObjectId]): Subset ids that should be queried. All subsets from project are used if 'None' is passed. Returns: @@ -476,7 +476,7 @@ def get_versions( Args: project_name (str): Name of project where to look for queried entities. - version_ids (list[str, ObjectId]): Version ids that will be queried. + version_ids (list[str|ObjectId]): Version ids that will be queried. Filter ignored if 'None' is passed. subset_ids (list[str]): Subset ids that will be queried. Filter ignored if 'None' is passed. @@ -789,6 +789,32 @@ def get_representations( archived=False, 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. + check_site_name (bool): Filter only representation that have existing + site name. + 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"] if archived: repre_types.append("archived_representations") From 7d13ba2706d0cdc11f6b6358bafc432c87522d04 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:37:09 +0200 Subject: [PATCH 064/195] added missing functions to legacy_io and mongodb --- openpype/pipeline/legacy_io.py | 9 +++++++++ openpype/pipeline/mongodb.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/openpype/pipeline/legacy_io.py b/openpype/pipeline/legacy_io.py index c8e7e79600..9359e3057b 100644 --- a/openpype/pipeline/legacy_io.py +++ b/openpype/pipeline/legacy_io.py @@ -144,3 +144,12 @@ def parenthood(*args, **kwargs): @requires_install def bulk_write(*args, **kwargs): return _connection_object.bulk_write(*args, **kwargs) + + +@requires_install +def active_project(*args, **kwargs): + return _connection_object.active_project(*args, **kwargs) + + +def current_project(*args, **kwargs): + return Session.get("AVALON_PROJECT") diff --git a/openpype/pipeline/mongodb.py b/openpype/pipeline/mongodb.py index 565e26b966..dab5bb9e13 100644 --- a/openpype/pipeline/mongodb.py +++ b/openpype/pipeline/mongodb.py @@ -199,6 +199,10 @@ class AvalonMongoDB: """Return the name of the active project""" return self.Session["AVALON_PROJECT"] + def current_project(self): + """Currently set project in Session without triggering installation.""" + return self.Session.get("AVALON_PROJECT") + @requires_install @auto_reconnect def projects(self, projection=None, only_active=True): From 0ce3045ad0bf399389cdbc7605762ecfe7b638f2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:47:42 +0200 Subject: [PATCH 065/195] added missing docstrings --- openpype/client/__init__.py | 2 + openpype/client/entities.py | 88 ++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/openpype/client/__init__.py b/openpype/client/__init__.py index 2fa7730839..16b1dcf321 100644 --- a/openpype/client/__init__.py +++ b/openpype/client/__init__.py @@ -30,6 +30,7 @@ from .entities import ( get_representations_parents, get_thumbnail, + get_thumbnails, get_thumbnail_id_from_source, ) @@ -65,5 +66,6 @@ __all__ = ( "get_representations_parents", "get_thumbnail", + "get_thumbnails", "get_thumbnail_id_from_source", ) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 6c0170ea40..c50ae32d7f 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -866,6 +866,20 @@ def get_representations( def get_representations_parents(project_name, representations): + """Prepare parents of representation entities. + + Each item of returned dictionary contains version, subset, asset + and project in that order. + + Args: + project_name (str): Name of project where to look for queried entities. + representations (list[dict]): Representation entities with at least + '_id' and 'parent' keys. + + Returns: + dict[ObjectId, tuple]: Parents by representation id. + """ + repres_by_version_id = collections.defaultdict(list) versions_by_version_id = {} versions_by_subset_id = collections.defaultdict(list) @@ -921,15 +935,43 @@ def get_representations_parents(project_name, representations): def get_representation_parents(project_name, representation): + """Prepare parents of representation entity. + + Each item of returned dictionary contains version, subset, asset + and project in that order. + + Args: + project_name (str): Name of project where to look for queried entities. + representation (dict): Representation entities with at least + '_id' and 'parent' keys. + + Returns: + dict[ObjectId, tuple]: Parents by representation id. + """ + if not representation: return None repre_id = representation["_id"] - parents_by_repre_id = get_representations(project_name, [representation]) + parents_by_repre_id = get_representations_parents( + project_name, [representation] + ) return parents_by_repre_id.get(repre_id) def get_thumbnail_id_from_source(project_name, src_type, src_id): + """Receive thumbnail id from source entity. + + Args: + project_name (str): Name of project where to look for queried entities. + src_type (str): Type of source entity ('asset', 'version'). + src_id (str|objectId): Id of source entity. + + Returns: + ObjectId: Thumbnail id assigned to entity. + None: If Source entity does not have any thumbnail id assigned. + """ + if not src_type or not src_id: return None @@ -942,12 +984,54 @@ def get_thumbnail_id_from_source(project_name, src_type, src_id): return None +def get_thumbnails(project_name, thumbnail_ids, fields=None): + """Receive thumbnails entity data. + + Thumbnail entity can be used to receive binary content of thumbnail based + on it's content and ThumbnailResolvers. + + Args: + project_name (str): Name of project where to look for queried entities. + thumbnail_ids (list[str|ObjectId]): Ids of thumbnail entities. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + cursor: Cursor of queried documents. + """ + + if thumbnail_ids: + thumbnail_ids = _convert_ids(thumbnail_ids) + + if not thumbnail_ids: + return [] + query_filter = { + "type": "thumbnail", + "_id": {"$in": thumbnail_ids} + } + conn = _get_project_connection(project_name) + return conn.find(query_filter, _prepare_fields(fields)) + + def get_thumbnail(project_name, thumbnail_id, fields=None): + """Receive thumbnail entity data. + + Args: + project_name (str): Name of project where to look for queried entities. + thumbnail_id (str|ObjectId): Id of thumbnail entity. + fields (list[str]): Fields that should be returned. All fields are + returned if 'None' is passed. + + Returns: + None: If thumbnail with specified id was not found. + Dict: Thumbnail entity data which can be reduced to specified 'fields'. + """ + if not thumbnail_id: return None query_filter = {"type": "thumbnail", "_id": _convert_id(thumbnail_id)} conn = _get_project_connection(project_name) - return conn.find(query_filter, _prepare_fields(fields)) + return conn.find_one(query_filter, _prepare_fields(fields)) """ From 125af17c2244f902447048c803d649f657f16f38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 7 Jun 2022 18:47:47 +0200 Subject: [PATCH 066/195] updated usages --- openpype/client/entities.py | 57 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index c50ae32d7f..2459ea3e92 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1045,12 +1045,12 @@ openpype/tools/assetlinks/widgets.py - get_versions - get_subsets - get_assets - - get_version_links + - get_output_link_versions openpype/tools/creator/window.py - CreatorWindow Query: - - get_asset + - get_asset_by_name - get_subsets openpype/tools/launcher/models.py @@ -1093,12 +1093,14 @@ openpype/tools/loader/widgets.py - get_subset_families - VersionTextEdit Query: - - get_subset - - get_version + - get_subset_by_id + - get_version_by_id - SubsetWidget Query: - get_subsets - get_representations + Update: + - Subset groups (combination of asset id and subset names) - RepresentationWidget Query: - get_subsets @@ -1112,12 +1114,12 @@ openpype/tools/loader/widgets.py openpype/tools/mayalookassigner/app.py - MayaLookAssignerWindow Query: - - get_last_version_for_subset + - get_last_version_by_subset_id openpype/tools/mayalookassigner/commands.py - create_items_from_nodes Query: - - get_asset + - get_asset_by_id openpype/tools/mayalookassigner/vray_proxies.py - get_look_relationships @@ -1128,7 +1130,7 @@ openpype/tools/mayalookassigner/vray_proxies.py - get_representation_by_name - vrayproxy_assign_look Query: - - get_last_version_for_subset + - get_last_version_by_subset_name openpype/tools/project_manager/project_manager/model.py - HierarchyModel @@ -1150,7 +1152,7 @@ openpype/tools/project_manager/project_manager/widgets.py openpype/tools/publisher/widgets/create_dialog.py - CreateDialog Query: - - get_asset + - get_asset_by_name - get_subsets openpype/tools/publisher/control.py @@ -1161,18 +1163,18 @@ openpype/tools/publisher/control.py openpype/tools/sceneinventory/model.py - InventoryModel Query: - - get_asset - - get_subset - - get_version - - get_last_version_for_subset + - get_asset_by_id + - get_subset_by_id + - get_version_by_id + - get_last_version_by_subset_id - get_representation openpype/tools/sceneinventory/switch_dialog.py - SwitchAssetDialog Query: - - get_asset + - get_asset_by_name - get_assets - - get_subset + - get_subset_by_name - get_subsets - get_versions - get_hero_versions @@ -1182,10 +1184,10 @@ openpype/tools/sceneinventory/switch_dialog.py openpype/tools/sceneinventory/view.py - SceneInventoryView Query: - - get_version + - get_version_by_id - get_versions - get_hero_versions - - get_representation + - get_representation_by_id - get_representations openpype/tools/standalonepublish/widgets/model_asset.py @@ -1197,31 +1199,31 @@ openpype/tools/standalonepublish/widgets/widget_asset.py - AssetWidget Query: - get_project - - get_asset + - get_asset_by_id openpype/tools/standalonepublish/widgets/widget_family.py - FamilyWidget Query: - - get_asset - - get_subset + - get_asset_by_name + - get_subset_by_name - get_subsets - - get_last_version_for_subset + - get_last_version_by_subset_id openpype/tools/standalonepublish/app.py - Window Query: - - get_asset + - get_asset_by_id openpype/tools/texture_copy/app.py - TextureCopy Query: - get_project - - get_asset + - get_asset_by_name openpype/tools/workfiles/files_widget.py - FilesWidget Query: - - get_asset + - get_asset_by_id openpype/tools/workfiles/model.py - PublishFilesModel @@ -1234,12 +1236,13 @@ openpype/tools/workfiles/save_as_dialog.py - build_workfile_data Query: - get_project - - get_asset + - get_asset_by_name openpype/tools/workfiles/window.py - Window Query: - - get_asset + - get_asset_by_id + - get_asset_by_name openpype/tools/utils/assets_widget.py - AssetModel @@ -1259,11 +1262,11 @@ openpype/tools/utils/lib.py - get_project - FamilyConfigCache Query: - - get_asset + - get_asset_by_name openpype/tools/utils/tasks_widget.py - TasksModel Query: - get_project - - get_asset + - get_asset_by_id """ From 1133dedb545efe81aaf5febc5819d7bd3ab6a3ab Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Tue, 7 Jun 2022 16:07:54 -0700 Subject: [PATCH 067/195] Support Maya instances in pointCache extraction. --- .../hosts/maya/plugins/publish/extract_pointcache.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index c4c8610ebb..4aaf223403 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -65,7 +65,7 @@ class ExtractAlembic(openpype.api.Extractor): "writeColorSets": writeColorSets, "writeFaceSets": writeFaceSets, "uvWrite": True, - "selection": True, + "selection": False, "worldSpace": instance.data.get("worldSpace", True) } @@ -80,12 +80,10 @@ class ExtractAlembic(openpype.api.Extractor): options["writeUVSets"] = True with suspended_refresh(): - with maintained_selection(): - cmds.select(nodes, noExpand=True) - extract_alembic(file=path, - startFrame=start, - endFrame=end, - **options) + extract_alembic(file=path, + startFrame=start, + endFrame=end, + **options) if "representations" not in instance.data: instance.data["representations"] = [] From 10bf3a33e1c90104b5f9cfe709b46607a5c98807 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 11:45:12 +0200 Subject: [PATCH 068/195] Nuke: no need to add Nuke host to filter It is actually adding review even not needed --- openpype/settings/defaults/project_settings/deadline.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5c5a14bf21..a6e7b4a94a 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -83,9 +83,6 @@ "maya": [ ".*([Bb]eauty).*" ], - "nuke": [ - ".*" - ], "aftereffects": [ ".*" ], From 63a83dc23a7075bb40f2ea827c24aa0b0dbf3088 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Jun 2022 16:04:55 +0200 Subject: [PATCH 069/195] Added set_project method to widget It makes sending of project name to model clearer. --- openpype/modules/sync_server/tray/app.py | 9 +++------ openpype/modules/sync_server/tray/widgets.py | 3 +++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py index dee3bf0ecc..96fad6a247 100644 --- a/openpype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -101,8 +101,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.representationWidget = repres def showEvent(self, event): - self.representationWidget.model.set_project( - self.projects.current_project) + self.representationWidget.set_project(self.projects.current_project) self.projects.refresh() self._set_running(True) super().showEvent(event) @@ -115,9 +114,7 @@ class SyncServerWindow(QtWidgets.QDialog): if self.projects.current_project is None: return - self.representationWidget.table_view.model().set_project( - self.projects.current_project - ) + self.representationWidget.set_project(self.projects.current_project) project_name = self.projects.current_project if not self.sync_server.get_sync_project_setting(project_name): @@ -131,7 +128,7 @@ class SyncServerWindow(QtWidgets.QDialog): self.projects.show_only_enabled = \ self.show_only_enabled_chk.isChecked() self.projects.refresh() - self.representationWidget.model.set_project(None) + self.representationWidget.set_project(None) def _set_running(self, running): self.representationWidget.model.is_running = running diff --git a/openpype/modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py index 89ab12ea4d..b4ee447ac4 100644 --- a/openpype/modules/sync_server/tray/widgets.py +++ b/openpype/modules/sync_server/tray/widgets.py @@ -253,6 +253,9 @@ class _SyncRepresentationWidget(QtWidgets.QWidget): active_changed = QtCore.Signal() # active index changed message_generated = QtCore.Signal(str) + def set_project(self, project): + self.model.set_project(project) + def _selection_changed(self, _new_selected, _all_selected): idxs = self.selection_model.selectedRows() self._selected_ids = set() From 248e38b52d1bfbd14226fbe79a12d4f2f5a8a016 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 17:22:30 +0200 Subject: [PATCH 070/195] Deadline: adding condition for `review` instance data key to be able to pass review to metadata json from nuke --- .../deadline/plugins/publish/submit_publish_job.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 78ab935e42..1891731c93 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -640,6 +640,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): def _solve_families(self, instance, preview=False): families = instance.get("families") + + # test also instance data review attribute + preview = preview or instance.get("review") + # if we have one representation with preview tag # flag whole instance for review and for ftrack if preview: @@ -749,6 +753,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "family": "prerender", "families": []}) + # also include review attribute if available + if "review" in data: + instance_skeleton_data["review"] = data["review"] + # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From 6758bcc2a2aec0139ed2a448daba3c08ea60a584 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 8 Jun 2022 17:22:55 +0200 Subject: [PATCH 071/195] Nuke: wrong name in docstring --- openpype/hosts/nuke/plugins/publish/extract_render_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 50a5d01483..057bca11ac 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -7,7 +7,7 @@ import clique class NukeRenderLocal(openpype.api.Extractor): # TODO: rewrite docstring to nuke - """Render the current Fusion composition locally. + """Render the current Nuke composition locally. Extract the result of savers by starting a comp render This will run the local render of Fusion. From e4cee4a98ddd4150ee371d338350aefc4964df49 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:18:20 +0200 Subject: [PATCH 072/195] fix loader --- openpype/tools/loader/model.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index e8e0480d9c..f2b7e9a6a4 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -206,9 +206,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): if subset_doc_projection: self.subset_doc_projection = subset_doc_projection - self.asset_doc_projection = asset_doc_projection - self.subset_doc_projection = subset_doc_projection - self.repre_icons = {} self.sync_server = None self.active_site = self.active_provider = None From 1cea16b97b416cda2cd12fca9365b046bc05a2eb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:18:27 +0200 Subject: [PATCH 073/195] fix launcher --- openpype/tools/launcher/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 307f591d96..3f899cc05e 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -651,9 +651,9 @@ class LauncherModel(QtCore.QObject): self._asset_refresh_thread = None def _refresh_assets(self): - asset_docs = get_assets( - self._last_project_name, fields=list(self._asset_projection.keys()) - ) + asset_docs = list(get_assets( + self._last_project_name, fields=self._asset_projection.keys() + )) if not self._refreshing_assets: return self._refreshing_assets = False From 6fcf8722370fcbdf7fabba7dcc1012f8484f836a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 8 Jun 2022 19:20:53 +0200 Subject: [PATCH 074/195] fix get_last_version --- openpype/client/entities.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 2459ea3e92..66204a4b19 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -655,6 +655,10 @@ def get_last_versions(project_name, subset_ids, fields=None): doc["_version_id"] for doc in conn.aggregate(_pipeline) ] + fields = _prepare_fields(fields) + if fields and "parent" not in fields: + fields.append("parent") + version_docs = get_versions( project_name, version_ids=version_ids, fields=fields ) From b63ae623f4715401528f61fe8e469f47a70a0e5c Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 8 Jun 2022 14:54:42 -0700 Subject: [PATCH 075/195] Borrowed child node discovery logic from Colorbleed, to properly support instanced objects. --- .../maya/plugins/publish/collect_instances.py | 49 ++++++++++++++++--- .../plugins/publish/extract_pointcache.py | 11 +++-- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 1d59a68bf6..48cc446541 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -1,9 +1,50 @@ from maya import cmds +import maya.api.OpenMaya as om import pyblish.api import json +def get_all_children(nodes): + """Return all children of `nodes` including each instanced child. + Using maya.cmds.listRelatives(allDescendents=True) includes only the first + instance. As such, this function acts as an optimal replacement with a + focus on a fast query. + Borrowed from Colorbleed: https://tinyurl.com/bdht6fyh + + """ + + sel = om.MSelectionList() + traversed = set() + iterator = om.MItDag(om.MItDag.kDepthFirst) + for node in nodes: + + if node in traversed: + # Ignore if already processed as a child + # before + continue + + sel.clear() + sel.add(node) + dag = sel.getDagPath(0) + + iterator.reset(dag) + iterator.next() # ignore self + while not iterator.isDone(): + + path = iterator.fullPathName() + + if path in traversed: + iterator.prune() + iterator.next() + continue + + traversed.add(path) + iterator.next() + + return list(traversed) + + class CollectInstances(pyblish.api.ContextPlugin): """Gather instances by objectSet and pre-defined attribute @@ -86,12 +127,8 @@ class CollectInstances(pyblish.api.ContextPlugin): # Collect members members = cmds.ls(members, long=True) or [] - # `maya.cmds.listRelatives(noIntermediate=True)` only works when - # `shapes=True` argument is passed, since we also want to include - # transforms we filter afterwards. - children = cmds.listRelatives(members, - allDescendents=True, - fullPath=True) or [] + dag_members = cmds.ls(members, type="dagNode", long=True) + children = get_all_children(dag_members) children = cmds.ls(children, noIntermediate=True, long=True) parents = [] diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 4aaf223403..33bcdaf92c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -65,7 +65,7 @@ class ExtractAlembic(openpype.api.Extractor): "writeColorSets": writeColorSets, "writeFaceSets": writeFaceSets, "uvWrite": True, - "selection": False, + "selection": True, "worldSpace": instance.data.get("worldSpace", True) } @@ -80,10 +80,11 @@ class ExtractAlembic(openpype.api.Extractor): options["writeUVSets"] = True with suspended_refresh(): - extract_alembic(file=path, - startFrame=start, - endFrame=end, - **options) + with maintained_selection(): + extract_alembic(file=path, + startFrame=start, + endFrame=end, + **options) if "representations" not in instance.data: instance.data["representations"] = [] From 122536675fb043fda7d62710149c65cfd089d674 Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 8 Jun 2022 14:58:50 -0700 Subject: [PATCH 076/195] Use next() builtin --- openpype/hosts/maya/plugins/publish/collect_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 48cc446541..c76afe53f6 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -29,18 +29,18 @@ def get_all_children(nodes): dag = sel.getDagPath(0) iterator.reset(dag) - iterator.next() # ignore self + next(iterator) # ignore self while not iterator.isDone(): path = iterator.fullPathName() if path in traversed: iterator.prune() - iterator.next() + next(iterator) continue traversed.add(path) - iterator.next() + next(iterator) return list(traversed) From ce7b19d51b687f2e5a8abff64c8b741fd933bbad Mon Sep 17 00:00:00 2001 From: "felix.wang" Date: Wed, 8 Jun 2022 15:04:55 -0700 Subject: [PATCH 077/195] Change back to do actual selection. --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 33bcdaf92c..c4c8610ebb 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -81,6 +81,7 @@ class ExtractAlembic(openpype.api.Extractor): with suspended_refresh(): with maintained_selection(): + cmds.select(nodes, noExpand=True) extract_alembic(file=path, startFrame=start, endFrame=end, From a1e95dd15ad7c4e73837262b8f2de3bf543213c4 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 10:58:18 +0300 Subject: [PATCH 078/195] Fix oiio subprocess arguments. --- openpype/plugins/publish/extract_jpeg_exr.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index f474714780..e509063dca 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -130,9 +130,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): src_path, "-o", dst_path ] - subprocess_exr = " ".join(oiio_cmd) - self.log.info(f"running: {subprocess_exr}") - run_subprocess(subprocess_exr, logger=self.log) + self.log.info(f"running: {oiio_cmd}") + run_subprocess(oiio_cmd, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) From f79f9347ece5db8001608b595cff4cee67e16164 Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Thu, 9 Jun 2022 11:12:48 +0300 Subject: [PATCH 079/195] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index e509063dca..0bbfd95862 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -161,5 +161,5 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) run_subprocess( - subprocess_command, shell=True, logger=self.log - ) + subprocess_command, shell=True, logger=self.log + ) From 012f21ce86e12daeee549830b4cbfdca03991709 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:17:01 +0300 Subject: [PATCH 080/195] Fix error messages. --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index e509063dca..874a1dc40d 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -83,12 +83,12 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.info("Input can be read by OIIO, converting with oiiotool now.") thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa else: - self.log.info("converting with") + self.log.info("Converting with FFMPEG because input can't be read by OIIO.") thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa # Skip the rest of the process if the thumbnail wasn't created if not thumbnail_created: - + self.log.warning("Thumbanil has not been created.") return new_repre = { From 5e817c57e4c46087542fdf85b8866d063e80c933 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:18:14 +0300 Subject: [PATCH 081/195] Fix style warning. --- openpype/plugins/publish/extract_jpeg_exr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c40c99b1f8..730d0167c7 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -80,10 +80,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # 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.") + 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("Converting with FFMPEG because input can't be read by OIIO.") + 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 # Skip the rest of the process if the thumbnail wasn't created From 5e0589dc57e77f813a32f95afcfc8f508d7cf2ec Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 9 Jun 2022 11:20:27 +0300 Subject: [PATCH 082/195] Restore removed logging messaage. --- openpype/plugins/publish/extract_jpeg_exr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 730d0167c7..c658e17cab 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -130,7 +130,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): src_path, "-o", dst_path ] - self.log.info(f"running: {oiio_cmd}") + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") run_subprocess(oiio_cmd, logger=self.log) def create_thumbnail_ffmpeg(self, src_path, dst_path): From 6b9a1b834d3de4f1d2b399c916a50ac1282a4b6f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:48:29 +0200 Subject: [PATCH 083/195] convert queried cursor to list in scene inventory --- openpype/tools/sceneinventory/view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 8164c48a5d..0b54d40ed7 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -669,11 +669,11 @@ class SceneInventoryView(QtWidgets.QTreeView): fields=["parent"] ) - version_docs = get_versions( + version_docs = list(get_versions( project_name, subset_ids=[repre_version_doc["parent"]], hero=True - ) + )) hero_version = None standard_versions = [] for version_doc in version_docs: From 39ea438d7d8c2010b914c300ae84f6536e71e0a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:51:33 +0200 Subject: [PATCH 084/195] fix typo in passed kwarg --- openpype/tools/sceneinventory/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 0b54d40ed7..63d181b2d6 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -92,7 +92,7 @@ class SceneInventoryView(QtWidgets.QTreeView): project_name = legacy_io.active_project() repre_docs = get_representations( - project_name, representaion_ids=repre_ids, fields=["parent"] + project_name, representation_ids=repre_ids, fields=["parent"] ) version_ids = [] From a48d77e609f84a7a04bf5de95fce12e05461ca6c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:53:15 +0200 Subject: [PATCH 085/195] conver repres cursor to list --- openpype/tools/sceneinventory/switch_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 92ef2b3553..aa4dcd73cd 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -165,11 +165,11 @@ class SwitchAssetDialog(QtWidgets.QDialog): content_loaders.add(item["loader"]) project_name = self.active_project() - repres = get_representations( + repres = list(get_representations( project_name, representation_ids=repre_ids, archived=True - ) + )) repres_by_id = {repre["_id"]: repre for repre in repres} # stash context values, works only for single representation From bae1e38112cc8a756388b92f1447c5e69112e6ab Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 10:59:04 +0200 Subject: [PATCH 086/195] fix kwargs name in switch dialog --- openpype/tools/sceneinventory/switch_dialog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index aa4dcd73cd..1d1d5cbb91 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -944,7 +944,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_docs = list( get_representations( project_name, - subset_ids=subset_id_by_version_id.keys(), + version_ids=subset_id_by_version_id.keys(), fields=["name", "parent"] ) ) @@ -1102,7 +1102,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_docs = get_representations( project_name, - subset_ids=subset_id_by_version_id.keys(), + version_ids=subset_id_by_version_id.keys(), fields=["name", "parent"] ) repres_by_subset_name = {} From 57e72c2c3722f232b58a81454b3f46dbd7cba647 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:32:49 +0200 Subject: [PATCH 087/195] minor changesminor changes in representation widget --- openpype/tools/loader/model.py | 23 +++++++++++------------ openpype/tools/loader/widgets.py | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index f2b7e9a6a4..f030e94256 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -231,7 +231,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._doc_fetching_stop = False self._doc_payload = {} - self.doc_fetched.connect(self.on_doc_fetched) + self.doc_fetched.connect(self._on_doc_fetched) self.refresh() @@ -250,7 +250,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): def set_grouping(self, state): self._grouping = state - self.on_doc_fetched() + self._on_doc_fetched() def get_subsets_families(self): return self._doc_payload.get("subset_families") or set() @@ -528,7 +528,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self.fetch_subset_and_version() - def on_doc_fetched(self): + def _on_doc_fetched(self): self.clear() self._items_by_id = {} self.beginResetModel() @@ -1019,7 +1019,6 @@ class RepresentationSortProxyModel(GroupMemberFilterProxyModel): class RepresentationModel(TreeModel, BaseRepresentationModel): - doc_fetched = QtCore.Signal() refreshed = QtCore.Signal(bool) @@ -1055,12 +1054,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): 'files.sites': 1 } - def __init__(self, dbcon, header, version_ids): + def __init__(self, dbcon, header): super(RepresentationModel, self).__init__() self.dbcon = dbcon self._data = [] self._header = header - self.version_ids = version_ids + self._version_ids = [] manager = ModulesManager() sync_server = active_site = remote_site = None @@ -1092,7 +1091,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self.remote_site = remote_site self.remote_provider = remote_provider - self.doc_fetched.connect(self.on_doc_fetched) + self.doc_fetched.connect(self._on_doc_fetched) self._docs = {} self._icons = lib.get_repre_icons() @@ -1103,7 +1102,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): self._items_by_id = {} def set_version_ids(self, version_ids): - self.version_ids = version_ids + self._version_ids = version_ids self.refresh() def data(self, index, role): @@ -1171,7 +1170,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return super(RepresentationModel, self).data(index, role) - def on_doc_fetched(self): + def _on_doc_fetched(self): self.clear() self.beginResetModel() subsets = set() @@ -1181,7 +1180,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): group = None self._items_by_id = {} for doc in self._docs: - if len(self.version_ids) > 1: + if len(self._version_ids) > 1: group = repre_groups.get(doc["name"]) if not group: group_item = Item() @@ -1265,12 +1264,12 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): return repre_docs = [] - if self.version_ids: + if self._version_ids: # Simple find here for now, expected to receive lower number of # representations and logic could be in Python repre_docs = list(get_representations( project_name, - version_ids=self.version_ids, + version_ids=self._version_ids, check_site_name=True, fields=self.repre_projection.keys() )) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 921708922e..fd43435b15 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1164,7 +1164,7 @@ class RepresentationWidget(QtWidgets.QWidget): headers = [item[0] for item in self.default_widths] - model = RepresentationModel(self.dbcon, headers, []) + model = RepresentationModel(self.dbcon, headers) proxy_model = RepresentationSortProxyModel(self) proxy_model.setSourceModel(model) @@ -1231,7 +1231,7 @@ class RepresentationWidget(QtWidgets.QWidget): for item in items: repre_ids.append(item["_id"]) - project_name = self.dbcon.actual_project() + project_name = self.dbcon.active_project() repre_docs = get_representations( project_name, representation_ids=repre_ids, From de2fb5d1ff808d81a56a84c8240cac2465feae42 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:38:18 +0200 Subject: [PATCH 088/195] convert cursor of representations to list --- openpype/tools/loader/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index fd43435b15..0482bad642 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1232,11 +1232,11 @@ class RepresentationWidget(QtWidgets.QWidget): repre_ids.append(item["_id"]) project_name = self.dbcon.active_project() - repre_docs = get_representations( + repre_docs = list(get_representations( project_name, representation_ids=repre_ids, fields=["name", "parent"] - ) + )) version_ids = [ repre_doc["parent"] From 2df00d5caefd92aad61112b110d8b7a9303f5230 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:38:43 +0200 Subject: [PATCH 089/195] remove site name check in representations widget --- openpype/client/entities.py | 6 ------ openpype/tools/loader/model.py | 1 - 2 files changed, 7 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 66204a4b19..91646e7a1d 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -789,7 +789,6 @@ def get_representations( version_ids=None, extensions=None, names_by_version_ids=None, - check_site_name=False, archived=False, fields=None ): @@ -809,8 +808,6 @@ def get_representations( file (without dot). names_by_version_ids (dict[ObjectId, list[str]]): Complex filtering using version ids and list of names under the version. - check_site_name (bool): Filter only representation that have existing - site name. archived (bool): Output will also contain archived representations. fields (list[str]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -827,9 +824,6 @@ def get_representations( else: query_filter = {"type": {"$in": repre_types}} - if check_site_name: - query_filter["files.site.name"] = {"$exists": True} - if representation_ids is not None: representation_ids = _convert_ids(representation_ids) if not representation_ids: diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index f030e94256..46acc68f67 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -1270,7 +1270,6 @@ class RepresentationModel(TreeModel, BaseRepresentationModel): repre_docs = list(get_representations( project_name, version_ids=self._version_ids, - check_site_name=True, fields=self.repre_projection.keys() )) From 483ca9b301680cdfee8eee7f4270eb5a1654f312 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:49:03 +0200 Subject: [PATCH 090/195] fix args order --- openpype/tools/loader/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 46acc68f67..b5dc16a680 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -267,7 +267,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): else: project_name = self.dbcon.active_project() version_doc = get_version_by_name( - project_name, subset_id, value + project_name, value, subset_id ) # update availability on active site when version changes From a63e5592ab02c61c3712d6cc0a10716ffb2efb33 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 11:52:59 +0200 Subject: [PATCH 091/195] fixed asset links widget --- openpype/tools/assetlinks/widgets.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/tools/assetlinks/widgets.py b/openpype/tools/assetlinks/widgets.py index 3078585ed1..1b168e542c 100644 --- a/openpype/tools/assetlinks/widgets.py +++ b/openpype/tools/assetlinks/widgets.py @@ -74,13 +74,13 @@ class SimpleLinkView(QtWidgets.QWidget): fields=["name", "parent"] )) - subset_docs = [] versions_by_subset_id = collections.defaultdict(list) - if versions_by_subset_id: - for version_doc in version_docs: - subset_id = version_doc["parent"] - versions_by_subset_id[subset_id].append(version_doc) + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + subset_docs = [] + if versions_by_subset_id: subset_docs = list(get_subsets( self.project_name, subset_ids=versions_by_subset_id.keys(), @@ -117,13 +117,13 @@ class SimpleLinkView(QtWidgets.QWidget): version_doc["_id"], fields=["name", "parent"] )) - subset_docs = [] versions_by_subset_id = collections.defaultdict(list) - if versions_by_subset_id: - for version_doc in version_docs: - subset_id = version_doc["parent"] - versions_by_subset_id[subset_id].append(version_doc) + for version_doc in version_docs: + subset_id = version_doc["parent"] + versions_by_subset_id[subset_id].append(version_doc) + subset_docs = [] + if versions_by_subset_id: subset_docs = list(get_subsets( self.project_name, subset_ids=versions_by_subset_id.keys(), From 34156e280bf7ebbd565e9754ae9f71fdf463d79f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 18:17:38 +0200 Subject: [PATCH 092/195] create new action to create daily review sessions --- .../action_create_review_session.py | 286 ++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 openpype/modules/ftrack/event_handlers_server/action_create_review_session.py diff --git a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py new file mode 100644 index 0000000000..3b7cb224f0 --- /dev/null +++ b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py @@ -0,0 +1,286 @@ +import threading +import datetime +import copy +import collections + +import ftrack_api + +from openpype.lib import get_datetime_data +from openpype.api import get_project_settings +from openpype_modules.ftrack.lib import ServerAction + + +class CreateDailyReviewSessionServerAction(ServerAction): + """Create daily review session object per project. + + Action creates review sessions based on settings. Settings define if is + action enabled and what is a template for review session name. Logic works + in a way that if review session with the name already exists then skip + process. If review session for current day does not exist but yesterdays + review exists and is empty then yesterdays is renamed otherwise creates + new review session. + + Also contains cycle creation of dailies which is triggered each morning. + This option must be enabled in project settings. Cycle creation is also + checked on registration of action. + """ + + identifier = "create.daily.review.session" + #: Action label. + label = "OpenPype Admin" + variant = "- Create Daily Review Session (Server)" + #: Action description. + description = "Manually create daily review session" + role_list = {"Pypeclub", "Administrator", "Project Manager"} + + settings_key = "create_daily_review_session" + default_template = "{yy}{mm}{dd}" + + def __init__(self, *args, **kwargs): + super(CreateDailyReviewSessionServerAction, self).__init__(*args, **kwargs) + + self._cycle_timer = None + self._last_cyle_time = None + self._day_delta = datetime.timedelta(days=1) + + def discover(self, session, entities, event): + """Show action only on AssetVersions.""" + + valid_selection = False + for ent in event["data"]["selection"]: + # Ignore entities that are not tasks or projects + if ent["entityType"].lower() in ( + "show", "task", "reviewsession", "assetversion" + ): + valid_selection = True + break + else: + self.log.info(ent["entityType"]) + + if not valid_selection: + return False + return self.valid_roles(session, entities, event) + + def launch(self, session, entities, event): + project_entity = self.get_project_from_entity(entities[0], session) + project_name = project_entity["full_name"] + project_settings = self.get_project_settings_from_event( + event, project_name + ) + action_settings = self._extract_action_settings(project_settings) + project_name_by_id = { + project_entity["id"]: project_name + } + settings_by_project_id = { + project_entity["id"]: action_settings + } + self._process_review_session( + session, settings_by_project_id, project_name_by_id + ) + return True + + def register(self, *args, **kwargs): + """Override register to be able trigger """ + # Register server action as would be normally + super(CreateDailyReviewSessionServerAction, self).register(*args, **kwargs) + + # Create threading timer which will trigger creation of report + # at the 00:00:01 of next day + # - callback will trigger another timer which will have 1 day offset + now = datetime.datetime.now() + # Create object of today morning + today_morning = datetime.datetime( + now.year, now.month, now.day, 0, 0, 1 + ) + # Add a day delta (to calculate next day date) + next_day_morning = today_morning + self._day_delta + # Calculate first delta in seconds for first threading timer + first_delta = (next_day_morning - now).total_seconds() + # Store cycle time which will be used to create next timer + self._last_cyle_time = next_day_morning + # Create timer thread + self._cycle_timer = threading.Timer(first_delta, self._timer_callback) + self._cycle_timer.start() + + self._check_review_session() + + def _timer_callback(self): + if ( + self._cycle_timer is not None + and self._last_cyle_time is not None + ): + now = datetime.datetime.now() + while self._last_cyle_time < now: + self._last_cyle_time = self._last_cyle_time + self._day_delta + + delay = (self._last_cyle_time - now).total_seconds() + + self._cycle_timer = threading.Timer(delay, self._timer_callback) + self._cycle_timer.start() + self._check_review_session() + + def _check_review_session(self): + session = ftrack_api.Session( + server_url=self.session.server_url, + api_key=self.session.api_key, + api_user=self.session.api_user, + auto_connect_event_hub=False + ) + project_entities = session.query( + "select id, full_name from Project" + ).all() + project_names_by_id = { + project_entity["id"]: project_entity["full_name"] + for project_entity in project_entities + } + + action_settings_by_project_id = self._get_action_settings( + project_names_by_id + ) + enabled_action_settings_by_project_id = {} + for item in action_settings_by_project_id.items(): + project_id, action_settings = item + if action_settings.get("cycle_enabled"): + enabled_action_settings_by_project_id[project_id] = ( + action_settings + ) + + if not enabled_action_settings_by_project_id: + self.log.info(( + "There are no projects that have enabled" + " cycle review sesison creation" + )) + + else: + self._process_review_session( + session, + enabled_action_settings_by_project_id, + project_names_by_id + ) + + session.close() + + def _process_review_session( + self, session, settings_by_project_id, project_names_by_id + ): + review_sessions = session.query(( + "select id, name, project_id" + " from ReviewSession where project_id in ({})" + ).format(self.join_query_keys(settings_by_project_id))).all() + + review_sessions_by_project_id = collections.defaultdict(list) + for review_session in review_sessions: + project_id = review_session["project_id"] + review_sessions_by_project_id[project_id].append(review_session) + + # Prepare fill data for today's review sesison and yesterdays + now = datetime.datetime.now() + today_obj = datetime.datetime( + now.year, now.month, now.day, 0, 0, 0 + ) + yesterday_obj = today_obj - self._day_delta + + today_fill_data = get_datetime_data(today_obj) + yesterday_fill_data = get_datetime_data(yesterday_obj) + + # Loop through projects and try to create daily reviews + for project_id, action_settings in settings_by_project_id.items(): + review_session_template = ( + action_settings["review_session_template"] + ).strip() or self.default_template + + today_project_fill_data = copy.deepcopy(today_fill_data) + yesterday_project_fill_data = copy.deepcopy(yesterday_fill_data) + project_name = project_names_by_id[project_id] + today_project_fill_data["project_name"] = project_name + yesterday_project_fill_data["project_name"] = project_name + + today_session_name = self._fill_review_template( + review_session_template, today_project_fill_data + ) + yesterday_session_name = self._fill_review_template( + review_session_template, yesterday_project_fill_data + ) + # Skip if today's session name could not be filled + if not today_session_name: + continue + + # Find matchin review session + project_review_sessions = review_sessions_by_project_id[project_id] + todays_session = None + yesterdays_session = None + for review_session in project_review_sessions: + session_name = review_session["name"] + if session_name == today_session_name: + todays_session = review_session + break + elif session_name == yesterday_session_name: + yesterdays_session = review_session + + # Skip if today's session already exist + if todays_session is not None: + self.log.debug(( + "Todays ReviewSession \"{}\"" + " in project \"{}\" already exists" + ).format(today_session_name, project_name)) + continue + + # Check if there is yesterday's session and is empty + # - in that case just rename it + if ( + yesterdays_session is not None + and len(yesterdays_session["review_session_objects"]) == 0 + ): + self.log.debug(( + "Renaming yesterdays empty review session \"{}\" to \"{}\"" + " in project \"{}\"" + ).format( + yesterday_session_name, today_session_name, project_name + )) + yesterdays_session["name"] = today_session_name + session.commit() + continue + + # Create new review session with new name + self.log.debug(( + "Creating new review session \"{}\" in project \"{}\"" + ).format(today_session_name, project_name)) + session.create("ReviewSession", { + "project_id": project_id, + "name": today_session_name + }) + session.commit() + + def _get_action_settings(self, project_names_by_id): + settings_by_project_id = {} + for project_id, project_name in project_names_by_id.items(): + project_settings = get_project_settings(project_name) + action_settings = self._extract_action_settings(project_settings) + settings_by_project_id[project_id] = action_settings + return settings_by_project_id + + def _extract_action_settings(self, project_settings): + return ( + project_settings + .get("ftrack", {}) + .get(self.settings_frack_subkey, {}) + .get(self.settings_key) + ) or {} + + def _fill_review_template(self, template, data): + output = None + try: + output = template.format(**data) + except Exception: + self.log.warning( + ( + "Failed to fill review session template {} with data {}" + ).format(template, data), + exc_info=True + ) + return output + + +def register(session): + '''Register plugin. Called when used as an plugin.''' + CreateDailyReviewSessionServerAction(session).register() From 08d1f97f1112e7efc0190f146139c366d8f84de8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 18:17:54 +0200 Subject: [PATCH 093/195] added settings for new action --- .../defaults/project_settings/ftrack.json | 9 +++++ .../schema_project_ftrack.json | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 9d59deea3d..831c34835e 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -116,6 +116,15 @@ "Administrator", "Project manager" ] + }, + "create_daily_review_session": { + "enabled": true, + "role_list": [ + "Administrator", + "Project Manager" + ], + "cycle_enabled": false, + "review_session_template": "{yy}{mm}{dd}" } }, "user_handlers": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index 16cab49d5d..f8f9d5093d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -388,6 +388,44 @@ "object_type": "text" } ] + }, + { + "key": "create_daily_review_session", + "label": "Create daily review session", + "type": "dict", + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled" + }, + { + "type": "list", + "key": "role_list", + "label": "Roles", + "object_type": "text", + "use_label_wrap": true + }, + { + "type": "boolean", + "key": "cycle_enabled", + "label": "Create daily review session" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "review_session_template", + "label": "ReviewSession template", + "placeholder": "Default: {yy}{mm}{dd}" + }, + { + "type": "label", + "label": "Possible formatting keys in template:
- \"project_name\" - <Name of project>
- \"d\" - <Day of month number> in shortest possible way.
- \"dd\" - <Day of month number> with 2 digits.
- \"ddd\" - <Week day name> shortened week day. e.g.: `Mon`, ...
- \"dddd\" - <Week day name> full name of week day. e.g.: `Monday`, ...
- \"m\" - <Month number> in shortest possible way. e.g.: `1` if January
- \"mm\" - <Month number> with 2 digits.
- \"mmm\" - <Month name> shortened month name. e.g.: `Jan`, ...
- \"mmmm\" -<Month name> full month name. e.g.: `January`, ...
- \"yy\" - <Year number> shortened year. e.g.: `19`, `20`, ...
- \"yyyy\" - <Year number> full year. e.g.: `2019`, `2020`, ..." + } + ] } ] }, From 5999693fdf748025aa79cf9cf232d77406c4c9a6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 9 Jun 2022 18:27:00 +0200 Subject: [PATCH 094/195] fix too long lines --- .../event_handlers_server/action_create_review_session.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py index 3b7cb224f0..8a1d898193 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py @@ -37,7 +37,9 @@ class CreateDailyReviewSessionServerAction(ServerAction): default_template = "{yy}{mm}{dd}" def __init__(self, *args, **kwargs): - super(CreateDailyReviewSessionServerAction, self).__init__(*args, **kwargs) + super(CreateDailyReviewSessionServerAction, self).__init__( + *args, **kwargs + ) self._cycle_timer = None self._last_cyle_time = None @@ -82,7 +84,9 @@ class CreateDailyReviewSessionServerAction(ServerAction): def register(self, *args, **kwargs): """Override register to be able trigger """ # Register server action as would be normally - super(CreateDailyReviewSessionServerAction, self).register(*args, **kwargs) + super(CreateDailyReviewSessionServerAction, self).register( + *args, **kwargs + ) # Create threading timer which will trigger creation of report # at the 00:00:01 of next day From 0616db322d92be36cebd96c8d6e020fd7e9590c7 Mon Sep 17 00:00:00 2001 From: Felix Wang Date: Thu, 9 Jun 2022 10:42:20 -0700 Subject: [PATCH 095/195] Update openpype/hosts/maya/plugins/publish/collect_instances.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index c76afe53f6..433fa9886d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -10,7 +10,6 @@ def get_all_children(nodes): Using maya.cmds.listRelatives(allDescendents=True) includes only the first instance. As such, this function acts as an optimal replacement with a focus on a fast query. - Borrowed from Colorbleed: https://tinyurl.com/bdht6fyh """ From 79b181176ba470c77e690f11c980558e956da97c Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Thu, 9 Jun 2022 22:34:28 +0200 Subject: [PATCH 096/195] updated poetry installation source --- tools/create_env.ps1 | 2 +- tools/create_env.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index f19a98f11b..c307ba2031 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -63,7 +63,7 @@ function Install-Poetry() { } $env:POETRY_HOME="$openpype_root\.poetry" - (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | & $($python) - + (Invoke-WebRequest -Uri https://install.python-poetry.org/ -UseBasicParsing).Content | & $($python) - } diff --git a/tools/create_env.sh b/tools/create_env.sh index 94b11d6776..fba3942b87 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -110,7 +110,7 @@ install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." export POETRY_HOME="$openpype_root/.poetry" command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } - curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python - + curl -sSL https://install.python-poetry.org/ | python - } ############################################################################## From 242b9f5f3a9ed734743d7b88cba7191be4841fa3 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 00:49:01 +0200 Subject: [PATCH 097/195] added module setting and tray item --- openpype/modules/ftrack/tray/ftrack_tray.py | 44 ++++++++++++++----- .../defaults/system_settings/modules.json | 14 ++++++ .../module_settings/schema_ftrack.json | 37 ++++++++++++++++ tools/run_ftrack_eventserver.ps1 | 39 ++++++++++++++++ 4 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 tools/run_ftrack_eventserver.ps1 diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index c6201a94f6..699b33e187 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -2,6 +2,8 @@ import os import time import datetime import threading +import platform +from subprocess import Popen from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -12,6 +14,7 @@ from ..ftrack_module import FTRACK_MODULE_DIR from . import login_dialog from openpype.api import Logger, resources +from openpype.settings import get_system_settings log = Logger().get_logger("FtrackModule") @@ -42,12 +45,25 @@ class FtrackTrayWrapper: self.icon_not_logged = QtGui.QIcon( resources.get_resource("icons", "circle_orange.png") ) + self.icon_ftrackapp = QtGui.QIcon( + resources.get_resource("icons", "inventory.png") + ) def show_login_widget(self): self.widget_login.show() self.widget_login.activateWindow() self.widget_login.raise_() + def show_ftrack_browser(self): + am = get_system_settings() + browser_path = am["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()][0] + browser_arg = am["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()][0] + if "=" not in browser_arg: + browser_arg = '{:1}'.format(browser_arg) + cmd = f"{browser_path} {browser_arg}{self.module.ftrack_url}" + log.info(f"Opening Ftrack Browser: {cmd}") + Popen(cmd) + def validate(self): validation = False cred = credentials.get_credentials() @@ -251,16 +267,12 @@ class FtrackTrayWrapper: # Menu for Tray App tray_menu = QtWidgets.QMenu("Ftrack", parent_menu) - # Actions - basic - action_credentials = QtWidgets.QAction("Credentials", tray_menu) - action_credentials.triggered.connect(self.show_login_widget) - if self.bool_logged: - icon = self.icon_logged - else: - icon = self.icon_not_logged - action_credentials.setIcon(icon) - tray_menu.addAction(action_credentials) - self.action_credentials = action_credentials + # Ftrack Browser + browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) + browser_open.triggered.connect(self.show_ftrack_browser) + browser_open.setIcon(self.icon_ftrackapp) + tray_menu.addAction(browser_open) + self.browser_open = browser_open # Actions - server tray_server_menu = tray_menu.addMenu("Action server") @@ -284,6 +296,18 @@ class FtrackTrayWrapper: tray_server_menu.addAction(self.action_server_stop) self.tray_server_menu = tray_server_menu + + # Actions - basic + action_credentials = QtWidgets.QAction("Credentials", tray_menu) + action_credentials.triggered.connect(self.show_login_widget) + if self.bool_logged: + icon = self.icon_logged + else: + icon = self.icon_not_logged + action_credentials.setIcon(icon) + tray_menu.addAction(action_credentials) + self.action_credentials = action_credentials + self.bool_logged = False self.set_menu_visibility() diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 537e287366..aaf01b1631 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,6 +15,20 @@ "ftrack": { "enabled": false, "ftrack_server": "", + "ftrack_browser_path": { + "windows": [ + "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" + ], + "darwin": [], + "linux": [] + }, + "ftrack_browser_arguments": { + "windows": [ + "--app=" + ], + "darwin": [], + "linux": [] + }, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 654ddf2938..f30d536052 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -19,6 +19,43 @@ { "type": "splitter" }, + { + "type": "path", + "key": "ftrack_browser_path", + "label": "Browser Path", + "use_label_wrap": true, + "multipath": true, + "multiplatform": true + }, + { + "type": "dict", + "key": "ftrack_browser_arguments", + "label": "Browser Arguments", + "use_label_wrap": true, + "children": [ + { + "key": "windows", + "label": "Windows", + "type": "list", + "object_type": "text" + }, + { + "key": "darwin", + "label": "MacOS", + "type": "list", + "object_type": "text" + }, + { + "key": "linux", + "label": "Linux", + "type": "list", + "object_type": "text" + } + ] + }, + { + "type": "splitter" + }, { "type": "label", "label": "Additional Ftrack event handlers paths" diff --git a/tools/run_ftrack_eventserver.ps1 b/tools/run_ftrack_eventserver.ps1 new file mode 100644 index 0000000000..9c22f3d88e --- /dev/null +++ b/tools/run_ftrack_eventserver.ps1 @@ -0,0 +1,39 @@ +<# +.SYNOPSIS + Helper script to start OpenPype Ftrack EventServer without relying on built executables. + +.DESCRIPTION + + +.EXAMPLE + +PS> .\run_eventserver.ps1 + +#> +$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent +$openpype_root = (Get-Item $script_dir).parent.FullName + +$env:_INSIDE_OPENPYPE_TOOL = "1" +$env:OPENPYPE_DEBUG = "1" +# $env:OPENPYPE_MONGO = "mongodb://127.0.0.1:27017" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + +Set-Location -Path $openpype_root + +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green +} + +& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" eventserver \ No newline at end of file From 3a7e329e85793962b7b15a70df33e5930147f94e Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:02:30 +0300 Subject: [PATCH 098/195] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c658e17cab..b336787599 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -161,6 +161,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # output file jpeg_items.append(path_to_subprocess_arg(dst_path)) subprocess_command = " ".join(jpeg_items) - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) + try: + run_subprocess( + subprocess_command, shell=True, logger=self.log + ) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using ffmpeg", + exc_info=True + ) + return False From 28d0ea37a7523c2a4560d21ec07fda68d04bc89f Mon Sep 17 00:00:00 2001 From: "Allan I. A" <76656700+Allan-I@users.noreply.github.com> Date: Fri, 10 Jun 2022 12:02:42 +0300 Subject: [PATCH 099/195] Update openpype/plugins/publish/extract_jpeg_exr.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_jpeg_exr.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index b336787599..c1f7f38024 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -132,7 +132,15 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ] subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") - run_subprocess(oiio_cmd, logger=self.log) + try: + run_subprocess(oiio_cmd, logger=self.log) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using oiiotool", + exc_info=True + ) + return False def create_thumbnail_ffmpeg(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) From 51fb0385f81aefe0ebc71a4f8256b19976e8fce0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 12:16:31 +0200 Subject: [PATCH 100/195] mapped hosts queries --- openpype/client/entities.py | 294 +++++++++++++++++++++++++++++++++++- 1 file changed, 293 insertions(+), 1 deletion(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 91646e7a1d..861ac300e5 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1033,10 +1033,302 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): """ -Custom data storage: +## Custom data storage: - Webpublisher - jobs - Ftrack - events +- Maya - Shaders + - openpype/hosts/maya/api/shader_definition_editor.py + - openpype/hosts/maya/plugins/publish/validate_model_name.py +## Global launch hooks +- openpype/hooks/pre_global_host_data.py + Query: + - project + - asset + +## Hosts +### Aftereffects +- openpype/hosts/aftereffects/plugins/create/workfile_creator.py + Query: + - asset + +### Blender +- openpype/hosts/blender/api/pipeline.py + Query: + - asset +- openpype/hosts/blender/plugins/publish/extract_layout.py + Query: + - representation + +### Celaction +- openpype/hosts/celaction/plugins/publish/collect_audio.py + Query: + - subsets + - last versions + - representations + +### Fusion +- openpype/hosts/fusion/api/lib.py + Query: + - asset + - subset + - version + - representation +- openpype/hosts/fusion/plugins/load/load_sequence.py + Query: + - version +- openpype/hosts/fusion/scripts/fusion_switch_shot.py + Query: + - project + - asset + - versions +- openpype/hosts/fusion/utility_scripts/switch_ui.py + Query: + - assets + +### Harmony +- openpype/hosts/harmony/api/pipeline.py + Query: + - representation + +### Hiero +- openpype/hosts/hiero/api/lib.py + Query: + - project + - version + - versions + - representation +- openpype/hosts/hiero/api/tags.py + Query: + - task types + - assets +- openpype/hosts/hiero/plugins/load/load_clip.py + Query: + - version + - versions +- openpype/hosts/hiero/plugins/publish_old_workflow/collect_assetbuilds.py + Query: + - assets + +### Houdini +- openpype/hosts/houdini/api/lib.py + Query: + - asset +- openpype/hosts/houdini/api/usd.py + Query: + - asset +- openpype/hosts/houdini/plugins/create/create_hda.py + Query: + - asset + - subsets +- openpype/hosts/houdini/plugins/publish/collect_usd_bootstrap.py + Query: + - asset + - subset +- openpype/hosts/houdini/plugins/publish/extract_usd_layered.py + Query: + - asset + - subset + - version + - representation +- openpype/hosts/houdini/plugins/publish/validate_usd_shade_model_exists.py + Query: + - asset + - subset +- openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py + Query: + - project + - asset + +### Maya +- openpype/hosts/maya/api/action.py + Query: + - asset +- openpype/hosts/maya/api/commands.py + Query: + - asset + - project +- openpype/hosts/maya/api/lib.py + Query: + - project + - asset + - subset + - subsets + - version + - representation +- openpype/hosts/maya/api/setdress.py + Query: + - version + - representation +- openpype/hosts/maya/plugins/inventory/import_modelrender.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_audio.py + Query: + - asset + - subset + - version +- openpype/hosts/maya/plugins/load/load_image_plane.py + Query: + - asset + - subset + - version +- openpype/hosts/maya/plugins/load/load_look.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_vrayproxy.py + Query: + - representation +- openpype/hosts/maya/plugins/load/load_yeti_cache.py + Query: + - representation +- openpype/hosts/maya/plugins/publish/collect_review.py + Query: + - subsets +- openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py + Query: + - assets +- openpype/hosts/maya/plugins/publish/validate_node_ids_related.py + Query: + - asset +- openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py + Query: + - asset + - subset + +### Nuke +- openpype/hosts/nuke/api/command.py + Query: + - project + - asset +- openpype/hosts/nuke/api/lib.py + Query: + - project + - asset + - version + - versions + - representation +- openpype/hosts/nuke/plugins/load/load_backdrop.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_camera_abc.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_clip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_effects_ip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_effects.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_gizmo_ip.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_gizmo.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_image.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_model.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/load/load_script_precomp.py + Query: + - version + - versions +- openpype/hosts/nuke/plugins/publish/collect_reads.py + Query: + - asset +- openpype/hosts/nuke/plugins/publish/precollect_instances.py + Query: + - asset +- openpype/hosts/nuke/plugins/publish/precollect_writes.py + Query: + - representation +- openpype/hosts/nuke/plugins/publish/validate_script.py + Query: + - asset + - project + +### Photoshop +- openpype/hosts/photoshop/plugins/create/workfile_creator.py + Query: + - asset + +### Resolve +- openpype/hosts/resolve/plugins/load/load_clip.py + Query: + - version + - versions + +### Standalone publisher +- openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py + Query: + - asset +- openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py + Query: + - assets +- openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py + Query: + - project + - asset +- openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py + Query: + - assets + +### TVPaint +- openpype/hosts/tvpaint/api/pipeline.py + Query: + - project + - asset +- openpype/hosts/tvpaint/plugins/load/load_workfile.py + Query: + - project + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_instances.py + Query: + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_scene_render.py + Query: + - asset +- openpype/hosts/tvpaint/plugins/publish/collect_workfile.py + Query: + - asset + +### Unreal +- openpype/hosts/unreal/plugins/load/load_camera.py + Query: + - asset + - assets +- openpype/hosts/unreal/plugins/load/load_layout.py + Query: + - asset + - assets +- openpype/hosts/unreal/plugins/publish/extract_layout.py + Query: + - representation + +### Webpublisher +- openpype/hosts/webpublisher/webserver_service/webpublish_routes.py + Query: + - assets +- openpype/hosts/webpublisher/plugins/publish/collect_published_files.py + Query: + - last versions + +## Tools openpype/tools/assetlinks/widgets.py - SimpleLinkView Query: From 79db1b1c4354c95d4732f54f09bf2a2357c59572 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 12:40:57 +0200 Subject: [PATCH 101/195] flame: adding staging dir to `cleanupFullPaths` --- .../hosts/flame/plugins/publish/extract_subset_resources.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index f3239af23e..6ad8f8cf96 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -445,4 +445,6 @@ class ExtractSubsetResources(openpype.api.Extractor): ) instance.data['stagingDir'] = staging_dir + instance.context.data["cleanupFullPaths"].append(staging_dir) + return staging_dir From b19fc26cf68043574f704ecc3eac6703734bf33f Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 10 Jun 2022 14:52:54 +0300 Subject: [PATCH 102/195] Handle muiltilayered flag --- openpype/plugins/publish/extract_jpeg_exr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index c658e17cab..907b3ea4af 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -126,7 +126,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): def create_thumbnail_oiio(self, src_path, dst_path): self.log.info("outputting {}".format(dst_path)) oiio_tool_path = get_oiio_tools_path() - oiio_cmd = [oiio_tool_path, + oiio_cmd = [oiio_tool_path, "-a", src_path, "-o", dst_path ] From f782cf4f6df2bd1762c662a77d81dd33e54239dc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 14:53:40 +0200 Subject: [PATCH 103/195] hiero: otio p3 compatibility issue - metadata on effect use update rather then __setter__ --- openpype/hosts/hiero/api/otio/hiero_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/otio/hiero_export.py b/openpype/hosts/hiero/api/otio/hiero_export.py index 1e4088d9c0..81cb43fa12 100644 --- a/openpype/hosts/hiero/api/otio/hiero_export.py +++ b/openpype/hosts/hiero/api/otio/hiero_export.py @@ -132,7 +132,7 @@ def create_time_effects(otio_clip, track_item): otio_effect = otio.schema.TimeEffect() otio_effect.name = name otio_effect.effect_name = effect_name - otio_effect.metadata = metadata + otio_effect.metadata.update(metadata) # add otio effect to clip effects otio_clip.effects.append(otio_effect) From b227ba58568a3521ead74da5f2135b6e7645b132 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 14:54:48 +0200 Subject: [PATCH 104/195] removed debug log --- .../event_handlers_server/action_create_review_session.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py index 8a1d898193..8a8e86e7b9 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_create_review_session.py @@ -56,8 +56,6 @@ class CreateDailyReviewSessionServerAction(ServerAction): ): valid_selection = True break - else: - self.log.info(ent["entityType"]) if not valid_selection: return False From 0ea1032a6114f80cee0a87c1d5736ebe293cefd9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:06:30 +0200 Subject: [PATCH 105/195] added global plugins --- openpype/client/entities.py | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 861ac300e5..b61577ac70 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1046,6 +1046,54 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): - project - asset +## Global load plugins +- openpype/plugins/load/delete_old_versions.py + Query: + - versions + - representations +- openpype/plugins/load/delivery.py + Query: + - representations + +## Global publish plugins +- openpype/plugins/publish/collect_avalon_entities.py + Query: + - asset + - project +- openpype/plugins/publish/collect_anatomy_instance_data.py + Query: + - assets + - subsets +- openpype/plugins/publish/collect_scene_loaded_versions.py + Query: + - representations +- openpype/plugins/publish/extract_hierarchy_avalon.py + Query: + - asset + - assets + - project + Create: + - asset + Update: + - asset +- openpype/plugins/publish/integrate_hero_version.py + Query: + - version + - hero version + - representations +- openpype/plugins/publish/integrate_new.py + Query: + - asset + - subset + - version + - representations +- openpype/plugins/publish/integrate_thumbnail.py + Query: + - version +- openpype/plugins/publish/validate_editorial_asset_name.py + Query: + - assets + ## Hosts ### Aftereffects - openpype/hosts/aftereffects/plugins/create/workfile_creator.py From 22372361eb29d243ba16df4e83e827d1c8d54f62 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:12:50 +0200 Subject: [PATCH 106/195] added pipeline queries --- openpype/client/entities.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index b61577ac70..82f16ff032 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1064,6 +1064,7 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets - subsets + - last version - openpype/plugins/publish/collect_scene_loaded_versions.py Query: - representations @@ -1094,6 +1095,23 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets +## Pipeline +- openpype/pipeline/load/utils.py + Query: + - project + - assets + - subsets + - version + - versions + - representation + - representations +- openpype/pipeline/mongodb.py + Query: + - project +- openpype/pipeline/thumbnail.py + Query: + - project + ## Hosts ### Aftereffects - openpype/hosts/aftereffects/plugins/create/workfile_creator.py From 956ad8299b652b38e77215b545454ddba3715d87 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 15:19:18 +0200 Subject: [PATCH 107/195] mapped lib --- openpype/client/entities.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 82f16ff032..82caaee7c5 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1095,6 +1095,39 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): Query: - assets +## Lib +- openpype/lib/applications.py + Query: + - project + - asset +- openpype/lib/avalon_context.py + Query: + - project + - asset + - linked assets (new function get_linked_assets?) + - subset + - subsets + - version + - versions + - last version + - representations + - linked representations (new function get_linked_ids_for_representations) + Update: + - workfile data +- openpype/lib/plugin_tools.py + Query: + - asset +- openpype/lib/project_backpack.py + Query: + - project + - everything from mongo + Update: + - project +- openpype/lib/usdlib.py + Query: + - project + - asset + ## Pipeline - openpype/pipeline/load/utils.py Query: From 924535a6ce57c3d07bb3cdb61cde92978edc9236 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 15:30:14 +0200 Subject: [PATCH 108/195] removed icon since not the same as other entries --- openpype/modules/ftrack/tray/ftrack_tray.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 699b33e187..54d3f3132f 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -45,9 +45,6 @@ class FtrackTrayWrapper: self.icon_not_logged = QtGui.QIcon( resources.get_resource("icons", "circle_orange.png") ) - self.icon_ftrackapp = QtGui.QIcon( - resources.get_resource("icons", "inventory.png") - ) def show_login_widget(self): self.widget_login.show() @@ -270,7 +267,6 @@ class FtrackTrayWrapper: # Ftrack Browser browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) browser_open.triggered.connect(self.show_ftrack_browser) - browser_open.setIcon(self.icon_ftrackapp) tray_menu.addAction(browser_open) self.browser_open = browser_open From 5e82c96a3da493de7259ffc122c21ecc617340c7 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 15:53:39 +0200 Subject: [PATCH 109/195] info label in settings, handles lists better --- openpype/modules/ftrack/tray/ftrack_tray.py | 17 +++++++++++------ .../module_settings/schema_ftrack.json | 4 ++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 54d3f3132f..30e1d9f983 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -52,12 +52,17 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - am = get_system_settings() - browser_path = am["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()][0] - browser_arg = am["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()][0] - if "=" not in browser_arg: - browser_arg = '{:1}'.format(browser_arg) - cmd = f"{browser_path} {browser_arg}{self.module.ftrack_url}" + settings = get_system_settings() + browser_paths = settings["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()] + browser_args = settings["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()] + browser_args.append(self.module.ftrack_url) + path = "" + for p in browser_paths: + if os.path.exists(p): + path = p + break + args = " ".join(str(item) for item in browser_args).replace("= ", "=") + cmd = f"{path} {args}" log.info(f"Opening Ftrack Browser: {cmd}") Popen(cmd) diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index f30d536052..268c5479fe 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -33,6 +33,10 @@ "label": "Browser Arguments", "use_label_wrap": true, "children": [ + { + "type": "label", + "label": "Any arguent which is used to open Ftrack URL (as in \"app=\" for chrome) needs to be placed last in the list!" + }, { "key": "windows", "label": "Windows", From 8a21439b99e6d43426cad8c47d29631b73e5706c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 16:37:52 +0200 Subject: [PATCH 110/195] aded ability to parse value from clipboard that does not come from settings ui --- openpype/tools/settings/settings/base.py | 96 ++++++++++++++++++------ 1 file changed, 71 insertions(+), 25 deletions(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 44ec09b2ca..64e0a95fee 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -1,3 +1,4 @@ +import os import sys import json import traceback @@ -190,24 +191,29 @@ class BaseWidget(QtWidgets.QWidget): actions_mapping[action] = remove_from_project_override menu.addAction(action) + def _get_mime_data_from_entity(self): + if self.entity.is_dynamic_item or self.entity.is_in_dynamic_item: + entity_path = None + else: + entity_path = "/".join( + [self.entity.root_key, self.entity.path] + ) + + value = self.entity.value + + # Copy for settings tool + return { + "value": value, + "__root_key__": self.entity.root_key, + "__settings_path__": entity_path + } + def _copy_value_actions(self, menu): def copy_value(): mime_data = QtCore.QMimeData() - if self.entity.is_dynamic_item or self.entity.is_in_dynamic_item: - entity_path = None - else: - entity_path = "/".join( - [self.entity.root_key, self.entity.path] - ) - - value = self.entity.value # Copy for settings tool - settings_data = { - "root_key": self.entity.root_key, - "value": value, - "path": entity_path - } + settings_data = self._get_mime_data_from_entity() settings_encoded_data = QtCore.QByteArray() settings_stream = QtCore.QDataStream( settings_encoded_data, QtCore.QIODevice.WriteOnly @@ -218,6 +224,7 @@ class BaseWidget(QtWidgets.QWidget): ) # Copy as json + value = settings_data["value"] json_encoded_data = None if isinstance(value, (dict, list)): json_encoded_data = QtCore.QByteArray() @@ -241,25 +248,64 @@ class BaseWidget(QtWidgets.QWidget): action = QtWidgets.QAction("Copy", menu) return [(action, copy_value)] + def _parse_source_data_for_paste(self, data): + settings_path = None + root_key = None + if isinstance(data, dict): + settings_path = data.pop("__settings_path__", settings_path) + root_key = data.pop("__root_key__", root_key) + data = data.pop("__value__", data) + + return { + "value": data, + "__settings_path__": settings_path, + "__root_key__": root_key + } + + def _get_value_from_clipboard(self): + clipboard = QtWidgets.QApplication.clipboard() + mime_data = clipboard.mimeData() + app_value = mime_data.data("application/copy_settings_value") + if app_value: + settings_stream = QtCore.QDataStream( + app_value, QtCore.QIODevice.ReadOnly + ) + mime_data_value_str = settings_stream.readQString() + return json.loads(mime_data_value_str) + + if mime_data.hasUrls(): + for url in mime_data.urls(): + local_file = url.toLocalFile() + try: + with open(local_file, "r") as stream: + value = json.load(stream) + except Exception: + continue + if value: + return self._parse_source_data_for_paste(value) + + if mime_data.hasText(): + text = mime_data.text() + try: + value = json.loads(text) + except Exception: + try: + value = self.entity.convert_to_valid_type(text) + except Exception: + return None + return self._parse_source_data_for_paste(value) + def _paste_value_actions(self, menu): output = [] # Allow paste of value only if were copied from this UI - clipboard = QtWidgets.QApplication.clipboard() - mime_data = clipboard.mimeData() - mime_value = mime_data.data("application/copy_settings_value") + mime_data_value = self._get_value_from_clipboard() # Skip if there is nothing to do - if not mime_value: + if not mime_data_value: return output - settings_stream = QtCore.QDataStream( - mime_value, QtCore.QIODevice.ReadOnly - ) - mime_data_value_str = settings_stream.readQString() - mime_data_value = json.loads(mime_data_value_str) - value = mime_data_value["value"] - path = mime_data_value["path"] - root_key = mime_data_value["root_key"] + path = mime_data_value["__settings_path__"] + root_key = mime_data_value["__root_key__"] # Try to find matching entity to be able paste values to same spot # - entity can't by dynamic or in dynamic item From 213aa2241f00cdf8524aa95a05a408e59a8f791d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 10 Jun 2022 16:41:09 +0200 Subject: [PATCH 111/195] :sparkles: add pointcache family to gpu cache loader --- openpype/hosts/maya/plugins/load/load_gpucache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index 6d5e945508..a9df22faaa 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -10,7 +10,7 @@ from openpype.api import get_project_settings class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" - families = ["model"] + families = ["model", "pointcache"] representations = ["abc"] label = "Import Gpu Cache" From 4a45225ca27d175adc481a8899a3379bcab77dc1 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 16:55:50 +0200 Subject: [PATCH 112/195] moved menu entry to last position --- openpype/modules/ftrack/tray/ftrack_tray.py | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 30e1d9f983..4329b03b45 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -269,11 +269,16 @@ class FtrackTrayWrapper: # Menu for Tray App tray_menu = QtWidgets.QMenu("Ftrack", parent_menu) - # Ftrack Browser - browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) - browser_open.triggered.connect(self.show_ftrack_browser) - tray_menu.addAction(browser_open) - self.browser_open = browser_open + # Actions - basic + action_credentials = QtWidgets.QAction("Credentials", tray_menu) + action_credentials.triggered.connect(self.show_login_widget) + if self.bool_logged: + icon = self.icon_logged + else: + icon = self.icon_not_logged + action_credentials.setIcon(icon) + tray_menu.addAction(action_credentials) + self.action_credentials = action_credentials # Actions - server tray_server_menu = tray_menu.addMenu("Action server") @@ -298,16 +303,11 @@ class FtrackTrayWrapper: self.tray_server_menu = tray_server_menu - # Actions - basic - action_credentials = QtWidgets.QAction("Credentials", tray_menu) - action_credentials.triggered.connect(self.show_login_widget) - if self.bool_logged: - icon = self.icon_logged - else: - icon = self.icon_not_logged - action_credentials.setIcon(icon) - tray_menu.addAction(action_credentials) - self.action_credentials = action_credentials + # Ftrack Browser + browser_open = QtWidgets.QAction("Open Ftrack...", tray_menu) + browser_open.triggered.connect(self.show_ftrack_browser) + tray_menu.addAction(browser_open) + self.browser_open = browser_open self.bool_logged = False self.set_menu_visibility() From 8add4b588d762d470a9e6138cb9c3c4542b75826 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:19:59 +0200 Subject: [PATCH 113/195] fixed checks and logged correct info --- openpype/modules/ftrack/tray/ftrack_tray.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 4329b03b45..e3df8eff12 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -3,7 +3,7 @@ import time import datetime import threading import platform -from subprocess import Popen +import subprocess from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -60,11 +60,18 @@ class FtrackTrayWrapper: for p in browser_paths: if os.path.exists(p): path = p + log.debug(f"Found valid executable at path: {p}") break + else: + log.warning(f"Path: {p} is not valid, please doublecheck your settings!") + if path == "": + log.warning("Found no valid executables to launch Ftrack with. Feature will not work as expected!") + return args = " ".join(str(item) for item in browser_args).replace("= ", "=") + log.debug(f"Computed arguments: {args}") cmd = f"{path} {args}" - log.info(f"Opening Ftrack Browser: {cmd}") - Popen(cmd) + log.debug(f"Opening Ftrack Browser...") + subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) def validate(self): validation = False From f2c252be4e442136bd98fda4691a62ab23626e71 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 10 Jun 2022 17:32:20 +0200 Subject: [PATCH 114/195] :recycle: add animation family there too --- openpype/hosts/maya/plugins/load/load_gpucache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index a9df22faaa..179819f904 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -10,7 +10,7 @@ from openpype.api import get_project_settings class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" - families = ["model", "pointcache"] + families = ["model", "animation", "pointcache"] representations = ["abc"] label = "Import Gpu Cache" From 11fc8d26e70689f76bb6ddfac60923c70cd8bbc9 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:36:38 +0200 Subject: [PATCH 115/195] Line breaks as per hound's "suggestion" --- openpype/modules/ftrack/tray/ftrack_tray.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index e3df8eff12..ee1e50f7f5 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -53,8 +53,10 @@ class FtrackTrayWrapper: def show_ftrack_browser(self): settings = get_system_settings() - browser_paths = settings["modules"]["ftrack"]["ftrack_browser_path"][platform.system().lower()] - browser_args = settings["modules"]["ftrack"]["ftrack_browser_arguments"][platform.system().lower()] + browser_paths = settings["modules"]["ftrack"]\ + ["ftrack_browser_path"][platform.system().lower()] + browser_args = settings["modules"]["ftrack"]\ + ["ftrack_browser_arguments"][platform.system().lower()] browser_args.append(self.module.ftrack_url) path = "" for p in browser_paths: @@ -63,9 +65,11 @@ class FtrackTrayWrapper: log.debug(f"Found valid executable at path: {p}") break else: - log.warning(f"Path: {p} is not valid, please doublecheck your settings!") + log.warning(f"Path: {p} is not valid, please \ + doublecheck your settings!") if path == "": - log.warning("Found no valid executables to launch Ftrack with. Feature will not work as expected!") + log.warning("Found no valid executables to launch \ + Ftrack with. Feature will not work as expected!") return args = " ".join(str(item) for item in browser_args).replace("= ", "=") log.debug(f"Computed arguments: {args}") From 99bb5250711259a1103bd21bf750a4e332aba8bc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 17:38:14 +0200 Subject: [PATCH 116/195] nuke: anatomy compatibility hack --- openpype/hosts/nuke/api/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 2c5989309b..505eb19419 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -539,7 +539,9 @@ def get_created_node_imageio_setting_legacy(nodeclass, creator, subset): imageio_nodes = get_nuke_imageio_settings()["nodes"] required_nodes = imageio_nodes["requiredNodes"] - override_nodes = imageio_nodes["overrideNodes"] + + # HACK: for backward compatibility this needs to be optional + override_nodes = imageio_nodes.get("overrideNodes", []) imageio_node = None for node in required_nodes: From 3b97531c15e17d5f4a95842dd07879772ecd2489 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 17:38:51 +0200 Subject: [PATCH 117/195] Nuke: do not offer still creator plugin if anatomy is missing its preset --- .../nuke/plugins/create/create_write_still.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index 4007ccf51e..3fbe288c78 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -2,7 +2,19 @@ import nuke from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import ( - create_write_node, create_write_node_legacy) + create_write_node, + create_write_node_legacy, + get_created_node_imageio_setting_legacy +) + +# HACK: just to disable still image on projects which +# are not having anatomy imageio preset for CreateWriteStill +imageio_writes = get_created_node_imageio_setting_legacy( + "Write", + "CreateWriteStill", + "stillMain" +) +print(imageio_writes["knobs"]) class CreateWriteStill(plugin.AbstractWriteRender): From 24a31e5e1058705d6d58fc8559fc42cfd8d661b8 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Fri, 10 Jun 2022 17:49:16 +0200 Subject: [PATCH 118/195] fixed hound complaints hopefully --- openpype/modules/ftrack/tray/ftrack_tray.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index ee1e50f7f5..7ac994e967 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -52,11 +52,10 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - settings = get_system_settings() - browser_paths = settings["modules"]["ftrack"]\ - ["ftrack_browser_path"][platform.system().lower()] - browser_args = settings["modules"]["ftrack"]\ - ["ftrack_browser_arguments"][platform.system().lower()] + cur_os = platform.system().lower() + settings = get_system_settings()["modules"]["ftrack"] + browser_paths = settings["ftrack_browser_path"][cur_os] + browser_args = settings["ftrack_browser_arguments"][cur_os] browser_args.append(self.module.ftrack_url) path = "" for p in browser_paths: From c77f9ce2e52c6f97cb9b4e8cb86ebcfeb5a11153 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 17:51:55 +0200 Subject: [PATCH 119/195] added option to extract settings to file --- openpype/tools/settings/settings/base.py | 102 +++++++++++++++++++---- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 64e0a95fee..3ade5e5ef8 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -3,6 +3,7 @@ import sys import json import traceback import functools +import datetime from Qt import QtWidgets, QtGui, QtCore @@ -13,10 +14,26 @@ from .widgets import ExpandingWidget from .lib import create_deffered_value_change_timer from .constants import DEFAULT_PROJECT_LABEL +_MENU_SEPARATOR_REQ = object() +SETTINGS_PATH_KEY = "__settings_path__" +ROOT_KEY = "__root_key__" +VALUE_KEY = "__value__" +SAVE_TIME_KEY = "__extracted__" +PROJECT_NAME_KEY = "__project_name__" + class BaseWidget(QtWidgets.QWidget): + _last_save_dir = os.path.expanduser("~") allow_actions = True + @staticmethod + def get_last_save_dir(): + return BaseWidget._last_save_dir + + @staticmethod + def set_last_save_dir(save_dir): + BaseWidget._last_save_dir = save_dir + def __init__(self, category_widget, entity, entity_widget): self.category_widget = category_widget self.entity = entity @@ -203,9 +220,9 @@ class BaseWidget(QtWidgets.QWidget): # Copy for settings tool return { - "value": value, - "__root_key__": self.entity.root_key, - "__settings_path__": entity_path + VALUE_KEY: value, + ROOT_KEY: self.entity.root_key, + SETTINGS_PATH_KEY: entity_path } def _copy_value_actions(self, menu): @@ -248,18 +265,62 @@ class BaseWidget(QtWidgets.QWidget): action = QtWidgets.QAction("Copy", menu) return [(action, copy_value)] + def _extract_to_file(self): + dialog = QtWidgets.QFileDialog( + self, + "Save settings values", + self.get_last_save_dir(), + "Values (*.json)" + ) + # dialog.setOption(dialog.DontUseNativeDialog) + dialog.setAcceptMode(dialog.AcceptSave) + if dialog.exec() != dialog.Accepted: + return + + selected_urls = dialog.selectedUrls() + if not selected_urls: + return + + filepath = selected_urls[0].toLocalFile() + if not filepath: + return + + if not filepath.lower().endswith(".json"): + filepath += ".json" + + settings_data = self._get_mime_data_from_entity() + now = datetime.datetime.now() + settings_data[SAVE_TIME_KEY] = now.strftime("%Y-%m-%d %H:%M:%S") + if hasattr(self.category_widget, "project_name"): + settings_data[PROJECT_NAME_KEY] = self.category_widget.project_name + + with open(filepath, "w") as stream: + json.dump(settings_data, stream, indent=4) + + new_dir = os.path.dirname(filepath) + self.set_last_save_dir(new_dir) + + def _extract_value_to_file_actions(self, menu): + save_action = QtWidgets.QAction("Extract to file", menu) + return [ + _MENU_SEPARATOR_REQ, + (save_action, self._extract_to_file) + ] + def _parse_source_data_for_paste(self, data): settings_path = None root_key = None if isinstance(data, dict): - settings_path = data.pop("__settings_path__", settings_path) - root_key = data.pop("__root_key__", root_key) - data = data.pop("__value__", data) + data.pop(SAVE_TIME_KEY, None) + data.pop(PROJECT_NAME_KEY, None) + settings_path = data.pop(SETTINGS_PATH_KEY, settings_path) + root_key = data.pop(ROOT_KEY, root_key) + data = data.pop(VALUE_KEY, data) return { - "value": data, - "__settings_path__": settings_path, - "__root_key__": root_key + VALUE_KEY: data, + SETTINGS_PATH_KEY: settings_path, + ROOT_KEY: root_key } def _get_value_from_clipboard(self): @@ -303,9 +364,9 @@ class BaseWidget(QtWidgets.QWidget): if not mime_data_value: return output - value = mime_data_value["value"] - path = mime_data_value["__settings_path__"] - root_key = mime_data_value["__root_key__"] + value = mime_data_value[VALUE_KEY] + path = mime_data_value[SETTINGS_PATH_KEY] + root_key = mime_data_value[ROOT_KEY] # Try to find matching entity to be able paste values to same spot # - entity can't by dynamic or in dynamic item @@ -437,10 +498,19 @@ class BaseWidget(QtWidgets.QWidget): ui_actions.extend(self._copy_value_actions(menu)) ui_actions.extend(self._paste_value_actions(menu)) if ui_actions: - menu.addSeparator() - for action, callback in ui_actions: - menu.addAction(action) - actions_mapping[action] = callback + ui_actions.insert(0, _MENU_SEPARATOR_REQ) + + ui_actions.extend(self._extract_value_to_file_actions(menu)) + + for item in ui_actions: + if item is _MENU_SEPARATOR_REQ: + if len(menu.actions()) > 0: + menu.addSeparator() + continue + + action, callback = item + menu.addAction(action) + actions_mapping[action] = callback if not actions_mapping: action = QtWidgets.QAction("< No action >") From b2ed0292adac2c914f50e95575b3494f43b33086 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Jun 2022 17:53:34 +0200 Subject: [PATCH 120/195] Nuke: adding todo comment --- openpype/hosts/nuke/plugins/create/create_write_still.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/plugins/create/create_write_still.py b/openpype/hosts/nuke/plugins/create/create_write_still.py index 3fbe288c78..bb08e8c2c6 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_still.py +++ b/openpype/hosts/nuke/plugins/create/create_write_still.py @@ -9,6 +9,7 @@ from openpype.hosts.nuke.api.lib import ( # HACK: just to disable still image on projects which # are not having anatomy imageio preset for CreateWriteStill +# TODO: remove this code as soon as it will be obsolete imageio_writes = get_created_node_imageio_setting_legacy( "Write", "CreateWriteStill", From 26b6bbab95c727b5f2dfcc07413c4d71d2d6f7a8 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 18:18:50 +0200 Subject: [PATCH 121/195] update poetry lib --- poetry.lock | 70 ++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/poetry.lock b/poetry.lock index 04be8c78f2..010dd57e17 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,11 +161,11 @@ toml = "*" [[package]] name = "babel" -version = "2.9.1" +version = "2.10.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] pytz = ">=2015.7" @@ -909,7 +909,7 @@ python-versions = "*" [[package]] name = "protobuf" -version = "3.20.0" +version = "3.20.1" description = "Protocol Buffers" category = "main" optional = false @@ -1617,11 +1617,11 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "uritemplate" @@ -1716,7 +1716,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "bd8e0a03668c380c6e76c8cd6c71020692f4ea9f32de7a4f09433564faa9dad0" +content-hash = "f7150d3d8098c26a004db9850ed328efe79695e77e830721a3e04c5941850cc5" [metadata.files] acre = [] @@ -1843,8 +1843,8 @@ autopep8 = [ {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] babel = [ - {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, - {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, + {file = "Babel-2.10.1-py3-none-any.whl", hash = "sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2"}, + {file = "Babel-2.10.1.tar.gz", hash = "sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"}, ] bcrypt = [ {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, @@ -2444,30 +2444,30 @@ prefixed = [ {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] protobuf = [ - {file = "protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4"}, - {file = "protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e"}, - {file = "protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b"}, - {file = "protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499"}, - {file = "protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab"}, - {file = "protobuf-3.20.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bc14037281db66aa60856cd4ce4541a942040686d290e3f3224dd3978f88f554"}, - {file = "protobuf-3.20.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47257d932de14a7b6c4ae1b7dbf592388153ee35ec7cae216b87ae6490ed39a3"}, - {file = "protobuf-3.20.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fbcbb068ebe67c4ff6483d2e2aa87079c325f8470b24b098d6bf7d4d21d57a69"}, - {file = "protobuf-3.20.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:542f25a4adf3691a306dcc00bf9a73176554938ec9b98f20f929a044f80acf1b"}, - {file = "protobuf-3.20.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd7133b885e356fa4920ead8289bb45dc6f185a164e99e10279f33732ed5ce15"}, - {file = "protobuf-3.20.0-cp37-cp37m-win32.whl", hash = "sha256:8d84453422312f8275455d1cb52d850d6a4d7d714b784e41b573c6f5bfc2a029"}, - {file = "protobuf-3.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:52bae32a147c375522ce09bd6af4d2949aca32a0415bc62df1456b3ad17c6001"}, - {file = "protobuf-3.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25d2fcd6eef340082718ec9ad2c58d734429f2b1f7335d989523852f2bba220b"}, - {file = "protobuf-3.20.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:88c8be0558bdfc35e68c42ae5bf785eb9390d25915d4863bbc7583d23da77074"}, - {file = "protobuf-3.20.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:38fd9eb74b852e4ee14b16e9670cd401d147ee3f3ec0d4f7652e0c921d6227f8"}, - {file = "protobuf-3.20.0-cp38-cp38-win32.whl", hash = "sha256:7dcd84dc31ebb35ade755e06d1561d1bd3b85e85dbdbf6278011fc97b22810db"}, - {file = "protobuf-3.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:1eb13f5a5a59ca4973bcfa2fc8fff644bd39f2109c3f7a60bd5860cb6a49b679"}, - {file = "protobuf-3.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d24c81c2310f0063b8fc1c20c8ed01f3331be9374b4b5c2de846f69e11e21fb"}, - {file = "protobuf-3.20.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8be43a91ab66fe995e85ccdbdd1046d9f0443d59e060c0840319290de25b7d33"}, - {file = "protobuf-3.20.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a53d4035427b9dbfbb397f46642754d294f131e93c661d056366f2a31438263"}, - {file = "protobuf-3.20.0-cp39-cp39-win32.whl", hash = "sha256:32bf4a90c207a0b4e70ca6dd09d43de3cb9898f7d5b69c2e9e3b966a7f342820"}, - {file = "protobuf-3.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:6efe066a7135233f97ce51a1aa007d4fb0be28ef093b4f88dac4ad1b3a2b7b6f"}, - {file = "protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983"}, - {file = "protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702"}, + {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, + {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, + {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, + {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, + {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, + {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, + {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, + {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, + {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, + {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, + {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, + {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, + {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, + {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, + {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, + {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2891,8 +2891,8 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"}, From 8a06a4afc1866a5837b5cab7c4ad3845eb6ee050 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Fri, 10 Jun 2022 18:25:40 +0200 Subject: [PATCH 122/195] remove unused import --- openpype/modules/shotgrid/shotgrid_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/shotgrid/shotgrid_module.py b/openpype/modules/shotgrid/shotgrid_module.py index dcc8187194..5644f0c35f 100644 --- a/openpype/modules/shotgrid/shotgrid_module.py +++ b/openpype/modules/shotgrid/shotgrid_module.py @@ -1,5 +1,4 @@ import os -import threading from openpype_interfaces import ( ITrayModule, From e21caf109fc3405afb36e86ab7f9ff9dbb8b0726 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 18:26:05 +0200 Subject: [PATCH 123/195] extraction can be called on whole settings --- openpype/tools/settings/settings/base.py | 110 +++++++++++------- .../tools/settings/settings/categories.py | 35 +++++- openpype/tools/settings/settings/constants.py | 13 +++ 3 files changed, 112 insertions(+), 46 deletions(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 3ade5e5ef8..aa2b448ccf 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -12,28 +12,71 @@ from openpype.tools.settings import CHILD_OFFSET from .widgets import ExpandingWidget from .lib import create_deffered_value_change_timer -from .constants import DEFAULT_PROJECT_LABEL +from .constants import ( + DEFAULT_PROJECT_LABEL, + SETTINGS_PATH_KEY, + ROOT_KEY, + VALUE_KEY, + SAVE_TIME_KEY, + PROJECT_NAME_KEY, +) _MENU_SEPARATOR_REQ = object() -SETTINGS_PATH_KEY = "__settings_path__" -ROOT_KEY = "__root_key__" -VALUE_KEY = "__value__" -SAVE_TIME_KEY = "__extracted__" -PROJECT_NAME_KEY = "__project_name__" + + +class ExtractHelper: + _last_save_dir = os.path.expanduser("~") + + @classmethod + def get_last_save_dir(cls): + return cls._last_save_dir + + @classmethod + def set_last_save_dir(cls, save_dir): + cls._last_save_dir = save_dir + + @classmethod + def ask_for_save_filepath(cls, parent): + dialog = QtWidgets.QFileDialog( + parent, + "Save settings values", + cls.get_last_save_dir(), + "Values (*.json)" + ) + # dialog.setOption(dialog.DontUseNativeDialog) + dialog.setAcceptMode(dialog.AcceptSave) + if dialog.exec() != dialog.Accepted: + return + + selected_urls = dialog.selectedUrls() + if not selected_urls: + return + + filepath = selected_urls[0].toLocalFile() + if not filepath: + return + + if not filepath.lower().endswith(".json"): + filepath += ".json" + return filepath + + @classmethod + def extract_settings_to_json(cls, filepath, settings_data, project_name): + now = datetime.datetime.now() + settings_data[SAVE_TIME_KEY] = now.strftime("%Y-%m-%d %H:%M:%S") + if project_name != 0: + settings_data[PROJECT_NAME_KEY] = project_name + + with open(filepath, "w") as stream: + json.dump(settings_data, stream, indent=4) + + new_dir = os.path.dirname(filepath) + cls.set_last_save_dir(new_dir) class BaseWidget(QtWidgets.QWidget): - _last_save_dir = os.path.expanduser("~") allow_actions = True - @staticmethod - def get_last_save_dir(): - return BaseWidget._last_save_dir - - @staticmethod - def set_last_save_dir(save_dir): - BaseWidget._last_save_dir = save_dir - def __init__(self, category_widget, entity, entity_widget): self.category_widget = category_widget self.entity = entity @@ -266,45 +309,24 @@ class BaseWidget(QtWidgets.QWidget): return [(action, copy_value)] def _extract_to_file(self): - dialog = QtWidgets.QFileDialog( - self, - "Save settings values", - self.get_last_save_dir(), - "Values (*.json)" - ) - # dialog.setOption(dialog.DontUseNativeDialog) - dialog.setAcceptMode(dialog.AcceptSave) - if dialog.exec() != dialog.Accepted: - return - - selected_urls = dialog.selectedUrls() - if not selected_urls: - return - - filepath = selected_urls[0].toLocalFile() + filepath = ExtractHelper.ask_for_save_filepath(self) if not filepath: return - if not filepath.lower().endswith(".json"): - filepath += ".json" - settings_data = self._get_mime_data_from_entity() - now = datetime.datetime.now() - settings_data[SAVE_TIME_KEY] = now.strftime("%Y-%m-%d %H:%M:%S") + project_name = 0 if hasattr(self.category_widget, "project_name"): - settings_data[PROJECT_NAME_KEY] = self.category_widget.project_name + project_name = self.category_widget.project_name - with open(filepath, "w") as stream: - json.dump(settings_data, stream, indent=4) - - new_dir = os.path.dirname(filepath) - self.set_last_save_dir(new_dir) + ExtractHelper.extract_settings_to_json( + filepath, settings_data, project_name + ) def _extract_value_to_file_actions(self, menu): - save_action = QtWidgets.QAction("Extract to file", menu) + extract_action = QtWidgets.QAction("Extract to file", menu) return [ _MENU_SEPARATOR_REQ, - (save_action, self._extract_to_file) + (extract_action, self._extract_to_file) ] def _parse_source_data_for_paste(self, data): diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index c8ade5fcdb..764f42f1a3 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -45,8 +45,15 @@ from .breadcrumbs_widget import ( SystemSettingsBreadcrumbs, ProjectSettingsBreadcrumbs ) - -from .base import GUIWidget +from .constants import ( + SETTINGS_PATH_KEY, + ROOT_KEY, + VALUE_KEY, +) +from .base import ( + ExtractHelper, + GUIWidget, +) from .list_item_widget import ListWidget from .list_strict_widget import ListStrictWidget from .dict_mutable_widget import DictMutableKeysWidget @@ -627,11 +634,35 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self._on_context_version_trigger ) submenu.addAction(action) + menu.addMenu(submenu) + extract_action = QtWidgets.QAction("Extract to file", menu) + extract_action.triggered.connect(self._on_extract_to_file) + + menu.addAction(extract_action) + def _on_context_version_trigger(self, version): self._on_source_version_change(version) + def _on_extract_to_file(self): + filepath = ExtractHelper.ask_for_save_filepath(self) + if not filepath: + return + + settings_data = { + SETTINGS_PATH_KEY: self.entity.root_key, + ROOT_KEY: self.entity.root_key, + VALUE_KEY: self.entity.value + } + project_name = 0 + if hasattr(self, "project_name"): + project_name = self.project_name + + ExtractHelper.extract_settings_to_json( + filepath, settings_data, project_name + ) + def _on_reset_crash(self): self.save_btn.setEnabled(False) diff --git a/openpype/tools/settings/settings/constants.py b/openpype/tools/settings/settings/constants.py index 9d6d7904d7..d98d18c8bf 100644 --- a/openpype/tools/settings/settings/constants.py +++ b/openpype/tools/settings/settings/constants.py @@ -7,6 +7,12 @@ PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2 PROJECT_IS_SELECTED_ROLE = QtCore.Qt.UserRole + 3 PROJECT_VERSION_ROLE = QtCore.Qt.UserRole + 4 +# Save/Extract keys +SETTINGS_PATH_KEY = "__settings_path__" +ROOT_KEY = "__root_key__" +VALUE_KEY = "__value__" +SAVE_TIME_KEY = "__extracted__" +PROJECT_NAME_KEY = "__project_name__" __all__ = ( "DEFAULT_PROJECT_LABEL", @@ -15,4 +21,11 @@ __all__ = ( "PROJECT_IS_ACTIVE_ROLE", "PROJECT_IS_SELECTED_ROLE", "PROJECT_VERSION_ROLE", + + "SETTINGS_PATH_KEY", + "ROOT_KEY", + "SETTINGS_PATH_KEY", + "VALUE_KEY", + "SAVE_TIME_KEY", + "PROJECT_NAME_KEY", ) From 7d6ef39352ac63ce1bd3a9901f24ef74b7dd6ce4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 18:31:28 +0200 Subject: [PATCH 124/195] Fix #3319: Houdini VDB update wrong file attribute name --- openpype/hosts/houdini/plugins/load/load_vdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py index 06bb9e45e4..54535446a3 100644 --- a/openpype/hosts/houdini/plugins/load/load_vdb.py +++ b/openpype/hosts/houdini/plugins/load/load_vdb.py @@ -102,7 +102,7 @@ class VdbLoader(load.LoaderPlugin): file_path = get_representation_path(representation) file_path = self.format_path(file_path) - file_node.setParms({"fileName": file_path}) + file_node.setParms({"file": file_path}) # Update attribute node.setParms({"representation": str(representation["_id"])}) @@ -110,4 +110,4 @@ class VdbLoader(load.LoaderPlugin): def remove(self, container): node = container["node"] - node.destroy() + node.destroy() \ No newline at end of file From 86f19dfa5e187906eb3707a172cb86a665ddfa9f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 18:33:33 +0200 Subject: [PATCH 125/195] Fix no new line at end of file --- openpype/hosts/houdini/plugins/load/load_vdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py index 54535446a3..5e572c83e5 100644 --- a/openpype/hosts/houdini/plugins/load/load_vdb.py +++ b/openpype/hosts/houdini/plugins/load/load_vdb.py @@ -110,4 +110,4 @@ class VdbLoader(load.LoaderPlugin): def remove(self, container): node = container["node"] - node.destroy() \ No newline at end of file + node.destroy() From f1a5a122cf1801f0b812a3e10936b7336b630a15 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 9 Jun 2022 18:35:54 +0200 Subject: [PATCH 126/195] Project Manager: fix tools not being visible until when editable (cherry picked from commit fbdd225c8b0cc579a022d69c179a5f90fc6a387d) --- openpype/tools/project_manager/project_manager/delegates.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/project_manager/project_manager/delegates.py b/openpype/tools/project_manager/project_manager/delegates.py index 31487ff132..b066bbb159 100644 --- a/openpype/tools/project_manager/project_manager/delegates.py +++ b/openpype/tools/project_manager/project_manager/delegates.py @@ -205,3 +205,9 @@ class ToolsDelegate(QtWidgets.QStyledItemDelegate): def setModelData(self, editor, model, index): model.setData(index, editor.value(), QtCore.Qt.EditRole) + + def displayText(self, value, locale): + if value: + return ", ".join(value) + else: + return From c486fb6f59a0d4ed977d8260c08c7d54bf5e3ba5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 13:06:49 +0200 Subject: [PATCH 127/195] Force edit mode on add task button (cherry picked from commit 96ca15b565b952d4e17c2010e8846ca18272d681) --- openpype/tools/project_manager/project_manager/window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index c281479d4f..10b28f535f 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -245,7 +245,8 @@ class ProjectManagerWindow(QtWidgets.QWidget): self.hierarchy_view.add_asset() def _on_add_task(self): - self.hierarchy_view.add_task() + # Colorbleed edit: force the task to directly be in edit mode + self.hierarchy_view._add_task_and_edit() def _on_create_folders(self): project_name = self._current_project() From d2de1539136ad7d4da5902d78aed56dd204f4a50 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 10 Jun 2022 18:47:42 +0200 Subject: [PATCH 128/195] fix copy value to string --- openpype/tools/settings/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index aa2b448ccf..6def284a83 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -284,7 +284,7 @@ class BaseWidget(QtWidgets.QWidget): ) # Copy as json - value = settings_data["value"] + value = settings_data[VALUE_KEY] json_encoded_data = None if isinstance(value, (dict, list)): json_encoded_data = QtCore.QByteArray() From cbc6fb8b330abc88c0e8c64c57a966cd09fd8077 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 20:54:12 +0200 Subject: [PATCH 129/195] Make `add_task_and_edit` method public --- openpype/tools/project_manager/project_manager/view.py | 6 +++--- openpype/tools/project_manager/project_manager/window.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/project_manager/project_manager/view.py b/openpype/tools/project_manager/project_manager/view.py index 74f5a06b71..f995d45f44 100644 --- a/openpype/tools/project_manager/project_manager/view.py +++ b/openpype/tools/project_manager/project_manager/view.py @@ -386,7 +386,7 @@ class HierarchyView(QtWidgets.QTreeView): self._source_model.delete_indexes(indexes) def _on_ctrl_shift_enter_pressed(self): - self._add_task_and_edit() + self.add_task_and_edit() def add_asset(self, parent_index=None): if parent_index is None: @@ -428,9 +428,9 @@ class HierarchyView(QtWidgets.QTreeView): self.edit(new_index) def _add_task_action(self): - self._add_task_and_edit() + self.add_task_and_edit() - def _add_task_and_edit(self): + def add_task_and_edit(self): new_index = self.add_task() if new_index is None: return diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index 10b28f535f..ed3445174d 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -246,7 +246,7 @@ class ProjectManagerWindow(QtWidgets.QWidget): def _on_add_task(self): # Colorbleed edit: force the task to directly be in edit mode - self.hierarchy_view._add_task_and_edit() + self.hierarchy_view.add_task_and_edit() def _on_create_folders(self): project_name = self._current_project() From 46e17b4816b6506c2b4aabe94ec15c2c1e71fb5d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Jun 2022 20:54:33 +0200 Subject: [PATCH 130/195] Remove comment --- openpype/tools/project_manager/project_manager/window.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/project_manager/project_manager/window.py b/openpype/tools/project_manager/project_manager/window.py index ed3445174d..458a36ac39 100644 --- a/openpype/tools/project_manager/project_manager/window.py +++ b/openpype/tools/project_manager/project_manager/window.py @@ -245,7 +245,6 @@ class ProjectManagerWindow(QtWidgets.QWidget): self.hierarchy_view.add_asset() def _on_add_task(self): - # Colorbleed edit: force the task to directly be in edit mode self.hierarchy_view.add_task_and_edit() def _on_create_folders(self): From 01bec693275472ef976ffd29df2048aba16f3cf0 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 11 Jun 2022 03:44:47 +0000 Subject: [PATCH 131/195] [Automated] Bump version --- CHANGELOG.md | 44 +++++++++++++++++++------------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d7798cb48..d0d25908cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,39 @@ # Changelog -## [3.11.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) ### 📖 Documentation +- Documentation: Add app key to template documentation [\#3299](https://github.com/pypeclub/OpenPype/pull/3299) - doc: adding royal render and multiverse to the web site [\#3285](https://github.com/pypeclub/OpenPype/pull/3285) **🚀 Enhancements** +- updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316) +- TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) +- Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) +- Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) +- Ftrack: Action to transfer values of hierarchical attributes [\#3284](https://github.com/pypeclub/OpenPype/pull/3284) +- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) - TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) +- Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) +- Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) +- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) **🐛 Bug fixes** +- Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) +- General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) +- Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) +- Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) +- Maya: Fix swaped width and height in reviews [\#3300](https://github.com/pypeclub/OpenPype/pull/3300) +- Maya: point cache publish handles Maya instances [\#3297](https://github.com/pypeclub/OpenPype/pull/3297) - Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286) - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) @@ -30,24 +46,15 @@ - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) - Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) -- add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162) **Merged pull requests:** -- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269) -- Deadline: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) -- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) -- Add a gizmo menu to nuke [\#3172](https://github.com/pypeclub/OpenPype/pull/3172) +- Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0) -**🆕 New features** - -- General: OpenPype modules publish plugins are registered in host [\#3180](https://github.com/pypeclub/OpenPype/pull/3180) -- General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179) - **🚀 Enhancements** - Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) @@ -55,10 +62,6 @@ - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) - Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) - Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199) -- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) -- Maya: added clean\_import option to Import loader [\#3181](https://github.com/pypeclub/OpenPype/pull/3181) -- Add the scripts menu definition to nuke [\#3168](https://github.com/pypeclub/OpenPype/pull/3168) -- Maya: add maya 2023 to default applications [\#3167](https://github.com/pypeclub/OpenPype/pull/3167) **🐛 Bug fixes** @@ -76,12 +79,6 @@ - Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) - Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) - Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) -- Ftrack: Locations deepcopy issue [\#3177](https://github.com/pypeclub/OpenPype/pull/3177) -- General: Avoid creating multiple thumbnails [\#3176](https://github.com/pypeclub/OpenPype/pull/3176) -- General/Hiero: better clip duration calculation [\#3169](https://github.com/pypeclub/OpenPype/pull/3169) -- General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166) -- Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164) -- Harmony: fixed missing task name in render instance [\#3163](https://github.com/pypeclub/OpenPype/pull/3163) **🔀 Refactored code** @@ -92,7 +89,6 @@ - Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) - Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) -- Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) @@ -103,15 +99,13 @@ - nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206) - Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200) - Backport of fix for attaching renders to subsets [\#3195](https://github.com/pypeclub/OpenPype/pull/3195) +- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) **🐛 Bug fixes** - Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) - Nuke: render/workfile version sync doesn't work on farm [\#3184](https://github.com/pypeclub/OpenPype/pull/3184) - Ftrack: Review image only if there are no mp4 reviews [\#3182](https://github.com/pypeclub/OpenPype/pull/3182) -- Ftrack: Locations deepcopy issue [\#3175](https://github.com/pypeclub/OpenPype/pull/3175) -- General: Avoid creating multiple thumbnails [\#3174](https://github.com/pypeclub/OpenPype/pull/3174) -- General: TemplateResult can be copied [\#3170](https://github.com/pypeclub/OpenPype/pull/3170) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 4c78a6e0a1..4b0a688cbf 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.1" +__version__ = "3.11.0-nightly.2" diff --git a/pyproject.toml b/pyproject.toml index 63bd08d644..4289c74ebe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.1" # OpenPype +version = "3.11.0-nightly.2" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 0163eae3fa80c720a527b134c1030362c4843ae5 Mon Sep 17 00:00:00 2001 From: "clement.hector" Date: Mon, 13 Jun 2022 14:50:26 +0200 Subject: [PATCH 132/195] Remove unused code and add sugestions --- .../shotgrid/hooks/post_shotgrid_changes.py | 9 -------- openpype/modules/shotgrid/lib/settings.py | 23 ------------------- .../publish/collect_shotgrid_entities.py | 5 ++-- 3 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 openpype/modules/shotgrid/hooks/post_shotgrid_changes.py diff --git a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py b/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py deleted file mode 100644 index e8369ad3cb..0000000000 --- a/openpype/modules/shotgrid/hooks/post_shotgrid_changes.py +++ /dev/null @@ -1,9 +0,0 @@ -from openpype.lib import PostLaunchHook - - -class PostShotgridHook(PostLaunchHook): - order = None - - def execute(self, *args, **kwargs): - print(args, kwargs) - pass diff --git a/openpype/modules/shotgrid/lib/settings.py b/openpype/modules/shotgrid/lib/settings.py index 4a772de5b7..924099f04b 100644 --- a/openpype/modules/shotgrid/lib/settings.py +++ b/openpype/modules/shotgrid/lib/settings.py @@ -1,24 +1,11 @@ -import os - -from pymongo import MongoClient from openpype.api import get_system_settings, get_project_settings from openpype.modules.shotgrid.lib.const import MODULE_NAME -from openpype.modules.shotgrid.lib.tools import memoize -def get_project_list(): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) - db = client['avalon'] - return db.list_collection_names() - - -@memoize def get_shotgrid_project_settings(project): return get_project_settings(project).get(MODULE_NAME, {}) -@memoize def get_shotgrid_settings(): return get_system_settings().get("modules", {}).get(MODULE_NAME, {}) @@ -29,13 +16,3 @@ def get_shotgrid_servers(): def get_leecher_backend_url(): return get_shotgrid_settings().get("leecher_backend_url") - - -def filter_projects_by_login(): - return bool(get_shotgrid_settings().get("filter_projects_by_login", False)) - - -def get_shotgrid_event_mongo_info(): - database_name = os.environ["OPENPYPE_DATABASE_NAME"] - collection_name = "shotgrid_events" - return database_name, collection_name diff --git a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py index a770c1eb87..9880425a41 100644 --- a/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py +++ b/openpype/modules/shotgrid/plugins/publish/collect_shotgrid_entities.py @@ -1,7 +1,7 @@ import os import pyblish.api -from pymongo import MongoClient +from openpype.lib.mongo import OpenPypeMongoConnection from openpype.modules.shotgrid.lib.settings import ( get_shotgrid_project_settings, @@ -63,8 +63,7 @@ class CollectShotgridEntities(pyblish.api.ContextPlugin): def _get_shotgrid_collection(project): - mongo_url = os.getenv("OPENPYPE_MONGO") - client = MongoClient(mongo_url) + client = OpenPypeMongoConnection.get_mongo_client() return client.get_database("shotgrid_openpype").get_collection(project) From cef804aa1670b755b5c0072a9f0d5990f6fa92d3 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Mon, 13 Jun 2022 15:37:47 +0200 Subject: [PATCH 133/195] added eventserver utoility script for linux/mac --- tools/run_ftrack_eventserver.sh | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tools/run_ftrack_eventserver.sh diff --git a/tools/run_ftrack_eventserver.sh b/tools/run_ftrack_eventserver.sh new file mode 100644 index 0000000000..97daa14c2d --- /dev/null +++ b/tools/run_ftrack_eventserver.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +art () { + cat <<-EOF + + . . .. . .. + _oOOP3OPP3Op_. . + .PPpo~· ·· ~2p. ·· ···· · · + ·Ppo · .pPO3Op.· · O:· · · · + .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · + ·~OP 3PO· .Op3 : · ·· _____ _____ _____ + ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / + O3:· O3p~ · ·:· · ·/____/·/____/ /____/ + 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · + · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · + · '_ .. · . _OP3·· · ·https://openpype.io·· · + ~P3·OPPPO3OP~ · ·· · + · ' '· · ·· · · · ·· · + +EOF +} + +# Colors for terminal + +RST='\033[0m' # Text Reset + +# Regular Colors +Black='\033[0;30m' # Black +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow +Blue='\033[0;34m' # Blue +Purple='\033[0;35m' # Purple +Cyan='\033[0;36m' # Cyan +White='\033[0;37m' # White + +# Bold +BBlack='\033[1;30m' # Black +BRed='\033[1;31m' # Red +BGreen='\033[1;32m' # Green +BYellow='\033[1;33m' # Yellow +BBlue='\033[1;34m' # Blue +BPurple='\033[1;35m' # Purple +BCyan='\033[1;36m' # Cyan +BWhite='\033[1;37m' # White + +# Bold High Intensity +BIBlack='\033[1;90m' # Black +BIRed='\033[1;91m' # Red +BIGreen='\033[1;92m' # Green +BIYellow='\033[1;93m' # Yellow +BIBlue='\033[1;94m' # Blue +BIPurple='\033[1;95m' # Purple +BICyan='\033[1;96m' # Cyan +BIWhite='\033[1;97m' # White + + +############################################################################## +# Return absolute path +# Globals: +# None +# Arguments: +# Path to resolve +# Returns: +# None +############################################################################### +realpath () { + echo $(cd $(dirname "$1"); pwd)/$(basename "$1") +} + +# Main +main () { + echo -e "${BGreen}" + art + echo -e "${RST}" + + # Directories + openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + + pushd "$openpype_root" > /dev/null || return > /dev/null + + echo -e "${BIGreen}>>>${RST} Running Ftrack Eventserver ..." + "$POETRY_HOME/bin/poetry" run python $openpype_root/start.py eventserver +} + +main From 27b8f77855d45446af3b7b742a58d30c2143d32f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:06:21 +0200 Subject: [PATCH 134/195] fix scene inventory --- openpype/tools/sceneinventory/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py index 117bdfcba1..0bb9c4a658 100644 --- a/openpype/tools/sceneinventory/model.py +++ b/openpype/tools/sceneinventory/model.py @@ -198,12 +198,12 @@ class InventoryModel(TreeModel): self.clear() if self._hierarchy_view and selected: - if not hasattr(host.pipeline, "update_hierarchy"): # If host doesn't support hierarchical containers, then # cherry-pick only. self.add_items((item for item in items if item["objectName"] in selected)) + return # Update hierarchy info for all containers items_by_name = {item["objectName"]: item From cd6c37ece91caf7af0d40882b1a41f23190ebb38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:06:57 +0200 Subject: [PATCH 135/195] added remaining custom mongo calls --- openpype/client/entities.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 82caaee7c5..a56288c1e8 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -1034,6 +1034,8 @@ def get_thumbnail(project_name, thumbnail_id, fields=None): """ ## Custom data storage: +- Settings - OP settings overrides and local settings +- Logging - logs from PypeLogger - Webpublisher - jobs - Ftrack - events - Maya - Shaders From 9cb5372d69e30e35a441c952178bb1aa2802e5a7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 16:29:09 +0200 Subject: [PATCH 136/195] use client functions to query data in standalone publisher plugins --- .../publish/collect_bulk_mov_instances.py | 11 +++--- .../plugins/publish/collect_hierarchy.py | 36 ++++++++++--------- .../plugins/publish/collect_matching_asset.py | 5 +-- .../publish/validate_task_existence.py | 20 ++++------- 4 files changed, 33 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py index 3e7fb19c00..052a97af7d 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py @@ -3,7 +3,7 @@ import json import pyblish.api from openpype.lib import get_subset_name_with_asset_doc -from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_name class CollectBulkMovInstances(pyblish.api.InstancePlugin): @@ -24,12 +24,9 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context + project_name = context.data["projectEntity"]["name"] asset_name = instance.data["asset"] - - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) if not asset_doc: raise AssertionError(( "Couldn't find Asset document with name \"{}\"" @@ -52,7 +49,7 @@ class CollectBulkMovInstances(pyblish.api.InstancePlugin): self.subset_name_variant, task_name, asset_doc, - legacy_io.Session["AVALON_PROJECT"] + project_name ) instance_name = f"{asset_name}_{subset_name}" diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py index 77163651c4..78e63f7ef0 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py @@ -3,7 +3,7 @@ import re from copy import deepcopy import pyblish.api -from openpype.pipeline import legacy_io +from openpype.client import get_asset_by_id, get_project class CollectHierarchyInstance(pyblish.api.ContextPlugin): @@ -61,27 +61,32 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): **instance.data["anatomyData"]) def create_hierarchy(self, instance): - parents = list() - hierarchy = list() - visual_hierarchy = [instance.context.data["assetEntity"]] + asset_doc = instance.context.data["assetEntity"] + project_doc = instance.context.data["projectEntity"] + project_name = project_doc["name"] + visual_hierarchy = [asset_doc] + current_doc = asset_doc while True: - visual_parent = legacy_io.find_one( - {"_id": visual_hierarchy[-1]["data"]["visualParent"]} - ) - if visual_parent: - visual_hierarchy.append(visual_parent) - else: - visual_hierarchy.append( - instance.context.data["projectEntity"]) + visual_parent_id = current_doc["data"]["visualParent"] + visual_parent = None + if visual_parent_id: + visual_parent = get_asset_by_id(project_name, visual_parent_id) + + if not visual_parent: + visual_hierarchy.append(project_doc) break + visual_hierarchy.append(visual_parent) + current_doc = visual_parent # add current selection context hierarchy from standalonepublisher + parents = list() for entity in reversed(visual_hierarchy): parents.append({ "entity_type": entity["data"]["entityType"], "entity_name": entity["name"] }) + hierarchy = list() if self.shot_add_hierarchy: parent_template_patern = re.compile(r"\{([a-z]*?)\}") # fill the parents parts from presets @@ -129,9 +134,8 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): self.log.debug(f"Hierarchy: {hierarchy}") self.log.debug(f"parents: {parents}") + tasks_to_add = dict() if self.shot_add_tasks: - tasks_to_add = dict() - project_doc = legacy_io.find_one({"type": "project"}) project_tasks = project_doc["config"]["tasks"] for task_name, task_data in self.shot_add_tasks.items(): _task_data = deepcopy(task_data) @@ -150,9 +154,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): task_name, list(project_tasks.keys()))) - instance.data["tasks"] = tasks_to_add - else: - instance.data["tasks"] = dict() + instance.data["tasks"] = tasks_to_add # updating hierarchy data instance.data["anatomyData"].update({ diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py index 9d94bfdc91..82d7247b2b 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py @@ -4,7 +4,7 @@ import collections import pyblish.api from pprint import pformat -from openpype.pipeline import legacy_io +from openpype.client import get_assets class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): @@ -119,8 +119,9 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): def _asset_docs_by_parent_id(self, instance): # Query all assets for project and store them by parent's id to list + project_name = instance.context.data["projectEntity"]["name"] asset_docs_by_parent_id = collections.defaultdict(list) - for asset_doc in legacy_io.find({"type": "asset"}): + for asset_doc in get_assets(project_name): parent_id = asset_doc["data"]["visualParent"] asset_docs_by_parent_id[parent_id].append(asset_doc) return asset_docs_by_parent_id diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py index 4c761c7a4c..19ea1a4778 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py @@ -1,9 +1,7 @@ import pyblish.api -from openpype.pipeline import ( - PublishXmlValidationError, - legacy_io, -) +from openpype.client import get_assets +from openpype.pipeline import PublishXmlValidationError class ValidateTaskExistence(pyblish.api.ContextPlugin): @@ -20,15 +18,11 @@ class ValidateTaskExistence(pyblish.api.ContextPlugin): for instance in context: asset_names.add(instance.data["asset"]) - asset_docs = legacy_io.find( - { - "type": "asset", - "name": {"$in": list(asset_names)} - }, - { - "name": 1, - "data.tasks": 1 - } + project_name = context.data["projectEntity"]["name"] + asset_docs = get_assets( + project_name, + asset_names=asset_names, + fields=["name", "data.tasks"] ) tasks_by_asset_names = {} for asset_doc in asset_docs: From c01f65a9176413a388248986950162d21428da91 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 17:02:34 +0200 Subject: [PATCH 137/195] removed unused import --- .../standalonepublisher/plugins/publish/collect_hierarchy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py index 78e63f7ef0..7922ca7f31 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py @@ -3,7 +3,7 @@ import re from copy import deepcopy import pyblish.api -from openpype.client import get_asset_by_id, get_project +from openpype.client import get_asset_by_id class CollectHierarchyInstance(pyblish.api.ContextPlugin): From 010ee05eff630531a77964c7bfe39460cee9421c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 17:26:46 +0200 Subject: [PATCH 138/195] use query functions in blender --- openpype/hosts/blender/api/pipeline.py | 7 ++-- .../blender/plugins/publish/extract_layout.py | 39 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 5b81764644..93d81145bc 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -10,6 +10,7 @@ from . import ops import pyblish.api +from openpype.client import get_asset_by_name from openpype.pipeline import ( schema, legacy_io, @@ -83,11 +84,9 @@ def uninstall(): def set_start_end_frames(): + project_name = legacy_io.active_project() asset_name = legacy_io.Session["AVALON_ASSET"] - asset_doc = legacy_io.find_one({ - "type": "asset", - "name": asset_name - }) + asset_doc = get_asset_by_name(project_name, asset_name) scene = bpy.context.scene diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 8ecc78a2c6..2b3fa6a608 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -1,13 +1,11 @@ import os import json -from bson.objectid import ObjectId - import bpy import bpy_extras import bpy_extras.anim_utils -from openpype.pipeline import legacy_io +from openpype.client import get_representation_by_name from openpype.hosts.blender.api import plugin from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY import openpype.api @@ -131,43 +129,32 @@ class ExtractLayout(openpype.api.Extractor): fbx_count = 0 + project_name = instance.context["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) - parent = metadata["parent"] + version_id = metadata["parent"] family = metadata["family"] - self.log.debug("Parent: {}".format(parent)) + self.log.debug("Parent: {}".format(version_id)) # Get blend reference - blend = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "blend" - }, - projection={"_id": True}) + blend = get_representation_by_name( + project_name, "blend", version_id, fields=["_id"] + ) blend_id = None if blend: blend_id = blend["_id"] # Get fbx reference - fbx = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "fbx" - }, - projection={"_id": True}) + fbx = get_representation_by_name( + project_name, "fbx", version_id, fields=["_id"] + ) fbx_id = None if fbx: fbx_id = fbx["_id"] # Get abc reference - abc = legacy_io.find_one( - { - "type": "representation", - "parent": ObjectId(parent), - "name": "abc" - }, - projection={"_id": True}) + abc = get_representation_by_name( + project_name, "abc", version_id, fields=["_id"] + ) abc_id = None if abc: abc_id = abc["_id"] From 7c7c1486a401f2b260f013bf3c9dce0b3f123575 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:15:28 +0200 Subject: [PATCH 139/195] use own rest api endpoint instead of using private from avalon --- .../webserver_service/webpublish_routes.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 70324fc39c..fcf9f10ffe 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -17,14 +17,24 @@ from openpype.lib.remote_publish import ( REPROCESS_STATUS ) from openpype.pipeline import AvalonMongoDB -from openpype_modules.avalon_apps.rest_api import _RestApiEndpoint from openpype.settings import get_project_settings +from openpype_modules.webserver.base_routes import RestApiEndpoint log = PypeLogger.get_logger("WebServer") +class ResourceRestApiEndpoint(RestApiEndpoint): + def __init__(self, resource): + self.resource = resource + super(ResourceRestApiEndpoint, self).__init__() + + @property + def dbcon(self): + return self.resource.dbcon + + class RestApiResource: """Resource carrying needed info and Avalon DB connection for publish.""" def __init__(self, server_manager, executable, upload_dir, @@ -67,7 +77,7 @@ class OpenPypeRestApiResource(RestApiResource): self.dbcon = mongo_client[database_name]["webpublishes"] -class ProjectsEndpoint(_RestApiEndpoint): +class ProjectsEndpoint(ResourceRestApiEndpoint): """Returns list of dict with project info (id, name).""" async def get(self) -> Response: output = [] @@ -84,7 +94,7 @@ class ProjectsEndpoint(_RestApiEndpoint): ) -class HiearchyEndpoint(_RestApiEndpoint): +class HiearchyEndpoint(ResourceRestApiEndpoint): """Returns dictionary with context tree from assets.""" async def get(self, project_name) -> Response: query_projection = { @@ -183,7 +193,7 @@ class TaskNode(Node): self["attributes"] = {} -class BatchPublishEndpoint(_RestApiEndpoint): +class BatchPublishEndpoint(ResourceRestApiEndpoint): """Triggers headless publishing of batch.""" async def post(self, request) -> Response: # Validate existence of openpype executable @@ -288,7 +298,7 @@ class BatchPublishEndpoint(_RestApiEndpoint): ) -class TaskPublishEndpoint(_RestApiEndpoint): +class TaskPublishEndpoint(ResourceRestApiEndpoint): """Prepared endpoint triggered after each task - for future development.""" async def post(self, request) -> Response: return Response( @@ -298,7 +308,7 @@ class TaskPublishEndpoint(_RestApiEndpoint): ) -class BatchStatusEndpoint(_RestApiEndpoint): +class BatchStatusEndpoint(ResourceRestApiEndpoint): """Returns dict with info for batch_id.""" async def get(self, batch_id) -> Response: output = self.dbcon.find_one({"batch_id": batch_id}) @@ -318,7 +328,7 @@ class BatchStatusEndpoint(_RestApiEndpoint): ) -class UserReportEndpoint(_RestApiEndpoint): +class UserReportEndpoint(ResourceRestApiEndpoint): """Returns list of dict with batch info for user (email address).""" async def get(self, user) -> Response: output = list(self.dbcon.find({"user": user}, @@ -338,7 +348,7 @@ class UserReportEndpoint(_RestApiEndpoint): ) -class ConfiguredExtensionsEndpoint(_RestApiEndpoint): +class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): """Returns dict of extensions which have mapping to family. Returns: @@ -378,7 +388,7 @@ class ConfiguredExtensionsEndpoint(_RestApiEndpoint): ) -class BatchReprocessEndpoint(_RestApiEndpoint): +class BatchReprocessEndpoint(ResourceRestApiEndpoint): """Marks latest 'batch_id' for reprocessing, returns 404 if not found.""" async def post(self, batch_id) -> Response: batches = self.dbcon.find({"batch_id": batch_id, From 00c3554a5c1be7c07f48e6d018e275e2defd714f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:18:16 +0200 Subject: [PATCH 140/195] renamed 'OpenPypeRestApiResource' to 'WebpublishRestApiResource' --- .../webserver_service/webpublish_routes.py | 9 ++++----- .../webserver_service/webserver_cli.py | 15 +++++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index fcf9f10ffe..d4aca1a797 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -20,9 +20,7 @@ from openpype.pipeline import AvalonMongoDB from openpype.settings import get_project_settings from openpype_modules.webserver.base_routes import RestApiEndpoint - - -log = PypeLogger.get_logger("WebServer") +log = PypeLogger.get_logger("WebpublishRoutes") class ResourceRestApiEndpoint(RestApiEndpoint): @@ -69,9 +67,10 @@ class RestApiResource: ).encode("utf-8") -class OpenPypeRestApiResource(RestApiResource): +class WebpublishRestApiResource: """Resource carrying OP DB connection for storing batch info into DB.""" - def __init__(self, ): + + def __init__(self): mongo_client = OpenPypeMongoConnection.get_mongo_client() database_name = os.environ["OPENPYPE_DATABASE_NAME"] self.dbcon = mongo_client[database_name]["webpublishes"] diff --git a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py index 909ea38bc6..f18aac1168 100644 --- a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py +++ b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py @@ -10,7 +10,7 @@ from openpype.lib import PypeLogger from .webpublish_routes import ( RestApiResource, - OpenPypeRestApiResource, + WebpublishRestApiResource, HiearchyEndpoint, ProjectsEndpoint, ConfiguredExtensionsEndpoint, @@ -27,7 +27,7 @@ from openpype.lib.remote_publish import ( ) -log = PypeLogger().get_logger("webserver_gui") +log = PypeLogger.get_logger("webserver_gui") def run_webserver(*args, **kwargs): @@ -86,27 +86,26 @@ def run_webserver(*args, **kwargs): ) # reporting - openpype_resource = OpenPypeRestApiResource() - batch_status_endpoint = BatchStatusEndpoint(openpype_resource) + webpublish_resource = WebpublishRestApiResource() + batch_status_endpoint = BatchStatusEndpoint(webpublish_resource) server_manager.add_route( "GET", "/api/batch_status/{batch_id}", batch_status_endpoint.dispatch ) - user_status_endpoint = UserReportEndpoint(openpype_resource) + user_status_endpoint = UserReportEndpoint(webpublish_resource) server_manager.add_route( "GET", "/api/publishes/{user}", user_status_endpoint.dispatch ) - webpublisher_batch_reprocess_endpoint = \ - BatchReprocessEndpoint(openpype_resource) + batch_reprocess_endpoint = BatchReprocessEndpoint(webpublish_resource) server_manager.add_route( "POST", "/api/webpublish/reprocess/{batch_id}", - webpublisher_batch_reprocess_endpoint.dispatch + batch_reprocess_endpoint.dispatch ) server_manager.start_server() From 444b8aa67347121b44762120768fa6b84249eb3d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:19:35 +0200 Subject: [PATCH 141/195] use query functions from client --- .../webserver_service/webpublish_routes.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index d4aca1a797..cfbb0939b5 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -2,11 +2,15 @@ import os import json import datetime -from bson.objectid import ObjectId import collections -from aiohttp.web_response import Response import subprocess +from bson.objectid import ObjectId +from aiohttp.web_response import Response +from openpype.client import ( + get_projects, + get_assets, +) from openpype.lib import ( OpenPypeMongoConnection, PypeLogger, @@ -80,7 +84,7 @@ class ProjectsEndpoint(ResourceRestApiEndpoint): """Returns list of dict with project info (id, name).""" async def get(self) -> Response: output = [] - for project_doc in self.dbcon.projects(): + for project_doc in get_projects(): ret_val = { "id": project_doc["_id"], "name": project_doc["name"] @@ -105,10 +109,7 @@ class HiearchyEndpoint(ResourceRestApiEndpoint): "type": 1, } - asset_docs = self.dbcon.database[project_name].find( - {"type": "asset"}, - query_projection - ) + asset_docs = get_assets(project_name, field=query_projection.keys()) asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs From 840f1a431490f0dea02b8e5f9990cc8f5451e1f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 Jun 2022 18:20:15 +0200 Subject: [PATCH 142/195] separated endpoints to those with dbcon and without --- .../webserver_service/webpublish_routes.py | 36 ++++++++++++------- .../webserver_service/webserver_cli.py | 6 ++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index cfbb0939b5..b1041bf6cb 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -20,7 +20,6 @@ from openpype.lib.remote_publish import ( ERROR_STATUS, REPROCESS_STATUS ) -from openpype.pipeline import AvalonMongoDB from openpype.settings import get_project_settings from openpype_modules.webserver.base_routes import RestApiEndpoint @@ -32,6 +31,8 @@ class ResourceRestApiEndpoint(RestApiEndpoint): self.resource = resource super(ResourceRestApiEndpoint, self).__init__() + +class WebpublishApiEndpoint(ResourceRestApiEndpoint): @property def dbcon(self): return self.resource.dbcon @@ -49,9 +50,6 @@ class RestApiResource: studio_task_queue = collections.deque().dequeu self.studio_task_queue = studio_task_queue - self.dbcon = AvalonMongoDB() - self.dbcon.install() - @staticmethod def json_dump_handler(value): if isinstance(value, datetime.datetime): @@ -193,7 +191,7 @@ class TaskNode(Node): self["attributes"] = {} -class BatchPublishEndpoint(ResourceRestApiEndpoint): +class BatchPublishEndpoint(WebpublishApiEndpoint): """Triggers headless publishing of batch.""" async def post(self, request) -> Response: # Validate existence of openpype executable @@ -298,7 +296,7 @@ class BatchPublishEndpoint(ResourceRestApiEndpoint): ) -class TaskPublishEndpoint(ResourceRestApiEndpoint): +class TaskPublishEndpoint(WebpublishApiEndpoint): """Prepared endpoint triggered after each task - for future development.""" async def post(self, request) -> Response: return Response( @@ -308,8 +306,12 @@ class TaskPublishEndpoint(ResourceRestApiEndpoint): ) -class BatchStatusEndpoint(ResourceRestApiEndpoint): - """Returns dict with info for batch_id.""" +class BatchStatusEndpoint(WebpublishApiEndpoint): + """Returns dict with info for batch_id. + + Uses 'WebpublishRestApiResource'. + """ + async def get(self, batch_id) -> Response: output = self.dbcon.find_one({"batch_id": batch_id}) @@ -328,8 +330,12 @@ class BatchStatusEndpoint(ResourceRestApiEndpoint): ) -class UserReportEndpoint(ResourceRestApiEndpoint): - """Returns list of dict with batch info for user (email address).""" +class UserReportEndpoint(WebpublishApiEndpoint): + """Returns list of dict with batch info for user (email address). + + Uses 'WebpublishRestApiResource'. + """ + async def get(self, user) -> Response: output = list(self.dbcon.find({"user": user}, projection={"log": False})) @@ -348,7 +354,7 @@ class UserReportEndpoint(ResourceRestApiEndpoint): ) -class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): +class ConfiguredExtensionsEndpoint(WebpublishApiEndpoint): """Returns dict of extensions which have mapping to family. Returns: @@ -388,8 +394,12 @@ class ConfiguredExtensionsEndpoint(ResourceRestApiEndpoint): ) -class BatchReprocessEndpoint(ResourceRestApiEndpoint): - """Marks latest 'batch_id' for reprocessing, returns 404 if not found.""" +class BatchReprocessEndpoint(WebpublishApiEndpoint): + """Marks latest 'batch_id' for reprocessing, returns 404 if not found. + + Uses 'WebpublishRestApiResource'. + """ + async def post(self, batch_id) -> Response: batches = self.dbcon.find({"batch_id": batch_id, "status": ERROR_STATUS}).sort("_id", -1) diff --git a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py index f18aac1168..1ed8f22b2c 100644 --- a/openpype/hosts/webpublisher/webserver_service/webserver_cli.py +++ b/openpype/hosts/webpublisher/webserver_service/webserver_cli.py @@ -69,16 +69,14 @@ def run_webserver(*args, **kwargs): ) # triggers publish - webpublisher_task_publish_endpoint = \ - BatchPublishEndpoint(resource) + webpublisher_task_publish_endpoint = BatchPublishEndpoint(resource) server_manager.add_route( "POST", "/api/webpublish/batch", webpublisher_task_publish_endpoint.dispatch ) - webpublisher_batch_publish_endpoint = \ - TaskPublishEndpoint(resource) + webpublisher_batch_publish_endpoint = TaskPublishEndpoint(resource) server_manager.add_route( "POST", "/api/webpublish/task", From c177b60221aac07ecc304db832c8f2199c691d9f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Jun 2022 11:11:51 +0200 Subject: [PATCH 143/195] Fix - added OPENPYPE_MONGO to filter Submit job has filter of allowed environment values, it missed OPENPYPE_MONGO. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 78ab935e42..39a2aa2761 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -128,7 +128,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "OPENPYPE_LOG_NO_COLORS", "OPENPYPE_USERNAME", "OPENPYPE_RENDER_JOB", - "OPENPYPE_PUBLISH_JOB" + "OPENPYPE_PUBLISH_JOB", + "OPENPYPE_MONGO" ] # custom deadline attributes From fae33600f60555edab8d6187e4754eb946b1e8f4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 Jun 2022 11:14:06 +0200 Subject: [PATCH 144/195] Nuke: adding empty representations to every instance also clearing obsolete code --- .../plugins/publish/precollect_instances.py | 8 ++--- .../plugins/publish/precollect_workfile.py | 36 ++++++++----------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index 1a8fa3e6ad..8bf7280cea 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -151,15 +151,11 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): "resolutionWidth": resolution_width, "resolutionHeight": resolution_height, "pixelAspect": pixel_aspect, - "review": review + "review": review, + "representations": [] }) self.log.info("collected instance: {}".format(instance.data)) instances.append(instance) - # create instances in context data if not are created yet - if not context.data.get("instances"): - context.data["instances"] = list() - - context.data["instances"].extend(instances) self.log.debug("context: {}".format(context)) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py index a2d1c80628..7349a8f424 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py @@ -17,7 +17,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): label = "Pre-collect Workfile" hosts = ['nuke'] - def process(self, context): + def process(self, context): # sourcery skip: avoid-builtin-shadow root = nuke.root() current_file = os.path.normpath(nuke.root().name()) @@ -74,20 +74,6 @@ class CollectWorkfile(pyblish.api.ContextPlugin): } context.data.update(script_data) - # creating instance data - instance.data.update({ - "subset": subset, - "label": base_name, - "name": base_name, - "publish": root.knob('publish').value(), - "family": family, - "families": [family], - "representations": list() - }) - - # adding basic script data - instance.data.update(script_data) - # creating representation representation = { 'name': 'nk', @@ -96,12 +82,18 @@ class CollectWorkfile(pyblish.api.ContextPlugin): "stagingDir": staging_dir, } - instance.data["representations"].append(representation) + # creating instance data + instance.data.update({ + "subset": subset, + "label": base_name, + "name": base_name, + "publish": root.knob('publish').value(), + "family": family, + "families": [family], + "representations": [representation] + }) + + # adding basic script data + instance.data.update(script_data) self.log.info('Publishing script version') - - # create instances in context data if not are created yet - if not context.data.get("instances"): - context.data["instances"] = list() - - context.data["instances"].append(instance) From 6e2802cf9280fb00562b8adf25c7114a937783e4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 12:28:56 +0200 Subject: [PATCH 145/195] fix project entity access --- openpype/hosts/blender/plugins/publish/extract_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/blender/plugins/publish/extract_layout.py b/openpype/hosts/blender/plugins/publish/extract_layout.py index 2b3fa6a608..75d9cf440d 100644 --- a/openpype/hosts/blender/plugins/publish/extract_layout.py +++ b/openpype/hosts/blender/plugins/publish/extract_layout.py @@ -129,7 +129,7 @@ class ExtractLayout(openpype.api.Extractor): fbx_count = 0 - project_name = instance.context["projectEntity"]["name"] + project_name = instance.context.data["projectEntity"]["name"] for asset in asset_group.children: metadata = asset.get(AVALON_PROPERTY) From 3d294edf6ef08441e71fdaf56c7b84e339de8a5e Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 17:55:48 +0200 Subject: [PATCH 146/195] Fixed logic and settings, uses webbrowser module --- openpype/modules/ftrack/ftrack_module.py | 3 + openpype/modules/ftrack/tray/ftrack_tray.py | 63 +++++++----- .../defaults/system_settings/modules.json | 15 +-- .../module_settings/schema_ftrack.json | 42 +------- tools/run_ftrack_eventserver.ps1 | 39 -------- tools/run_ftrack_eventserver.sh | 99 ------------------- 6 files changed, 47 insertions(+), 214 deletions(-) delete mode 100644 tools/run_ftrack_eventserver.ps1 delete mode 100644 tools/run_ftrack_eventserver.sh diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index f99e189082..048e5ebfb1 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -42,6 +42,9 @@ class FtrackModule( self.ftrack_url = ftrack_url + ftrack_open_as_app = ftrack_settings["ftrack_open_as_app"] + self.ftrack_open_as_app = ftrack_open_as_app + current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 7ac994e967..065528dcff 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -1,9 +1,14 @@ +from hashlib import new +from operator import pos import os import time import datetime import threading import platform import subprocess +import posixpath, ntpath +import webbrowser +import shutil from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -14,7 +19,6 @@ from ..ftrack_module import FTRACK_MODULE_DIR from . import login_dialog from openpype.api import Logger, resources -from openpype.settings import get_system_settings log = Logger().get_logger("FtrackModule") @@ -52,29 +56,42 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - cur_os = platform.system().lower() - settings = get_system_settings()["modules"]["ftrack"] - browser_paths = settings["ftrack_browser_path"][cur_os] - browser_args = settings["ftrack_browser_arguments"][cur_os] - browser_args.append(self.module.ftrack_url) - path = "" - for p in browser_paths: - if os.path.exists(p): - path = p - log.debug(f"Found valid executable at path: {p}") - break + env_pf64 = os.environ['ProgramW6432'].replace( + ntpath.sep, posixpath.sep) + env_pf32 = os.environ['ProgramFiles(x86)'].replace( + ntpath.sep, posixpath.sep) + env_loc = os.environ['LocalAppData'].replace( + ntpath.sep, posixpath.sep) + chromium_paths_win = [ + f"{env_pf64}/Google/Chrome/Application/chrome.exe", + f"{env_pf32}/Google/Chrome/Application/chrome.exe", + f"{env_loc}/Google/Chrome/Application/chrome.exe", + f"{env_pf32}/Microsoft/Edge/Application/msedge.exe" + ] + cur_os = cur_os = platform.system().lower() + if cur_os == "windows": + is_chromium = False + for p in chromium_paths_win: + if os.path.exists(p): + is_chromium = True + chromium_path = p + break + if is_chromium and self.module.ftrack_open_as_app: + webbrowser.get(f"{chromium_path} %s").open_new( + f"--app={self.module.ftrack_url}") else: - log.warning(f"Path: {p} is not valid, please \ - doublecheck your settings!") - if path == "": - log.warning("Found no valid executables to launch \ - Ftrack with. Feature will not work as expected!") - return - args = " ".join(str(item) for item in browser_args).replace("= ", "=") - log.debug(f"Computed arguments: {args}") - cmd = f"{path} {args}" - log.debug(f"Opening Ftrack Browser...") - subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + webbrowser.get(using="windows-default").open_new( + self.module.ftrack_url) + + else: + if self.module.ftrack_open_as_app: + try: + webbrowser.get(using='chrome').open_new( + f"--app={self.module.ftrack_url}") + except webbrowser.Error: + webbrowser.open_new(self.module.ftrack_url) + else: + webbrowser.open_new(self.module.ftrack_url) def validate(self): validation = False diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index aaf01b1631..6d09652bb9 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,20 +15,7 @@ "ftrack": { "enabled": false, "ftrack_server": "", - "ftrack_browser_path": { - "windows": [ - "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" - ], - "darwin": [], - "linux": [] - }, - "ftrack_browser_arguments": { - "windows": [ - "--app=" - ], - "darwin": [], - "linux": [] - }, + "ftrack_open_as_app": false, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 268c5479fe..570d856cf8 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -17,45 +17,9 @@ "label": "Server" }, { - "type": "splitter" - }, - { - "type": "path", - "key": "ftrack_browser_path", - "label": "Browser Path", - "use_label_wrap": true, - "multipath": true, - "multiplatform": true - }, - { - "type": "dict", - "key": "ftrack_browser_arguments", - "label": "Browser Arguments", - "use_label_wrap": true, - "children": [ - { - "type": "label", - "label": "Any arguent which is used to open Ftrack URL (as in \"app=\" for chrome) needs to be placed last in the list!" - }, - { - "key": "windows", - "label": "Windows", - "type": "list", - "object_type": "text" - }, - { - "key": "darwin", - "label": "MacOS", - "type": "list", - "object_type": "text" - }, - { - "key": "linux", - "label": "Linux", - "type": "list", - "object_type": "text" - } - ] + "type": "boolean", + "key": "ftrack_open_as_app", + "label": "Open in app mode" }, { "type": "splitter" diff --git a/tools/run_ftrack_eventserver.ps1 b/tools/run_ftrack_eventserver.ps1 deleted file mode 100644 index 9c22f3d88e..0000000000 --- a/tools/run_ftrack_eventserver.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -<# -.SYNOPSIS - Helper script to start OpenPype Ftrack EventServer without relying on built executables. - -.DESCRIPTION - - -.EXAMPLE - -PS> .\run_eventserver.ps1 - -#> -$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -$openpype_root = (Get-Item $script_dir).parent.FullName - -$env:_INSIDE_OPENPYPE_TOOL = "1" -$env:OPENPYPE_DEBUG = "1" -# $env:OPENPYPE_MONGO = "mongodb://127.0.0.1:27017" - -# make sure Poetry is in PATH -if (-not (Test-Path 'env:POETRY_HOME')) { - $env:POETRY_HOME = "$openpype_root\.poetry" -} -$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" - -Set-Location -Path $openpype_root - -Write-Host ">>> " -NoNewline -ForegroundColor Green -Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) { - Write-Host "NOT FOUND" -ForegroundColor Yellow - Write-Host "*** " -NoNewline -ForegroundColor Yellow - Write-Host "We need to install Poetry create virtual env first ..." - & "$openpype_root\tools\create_env.ps1" -} else { - Write-Host "OK" -ForegroundColor Green -} - -& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" eventserver \ No newline at end of file diff --git a/tools/run_ftrack_eventserver.sh b/tools/run_ftrack_eventserver.sh deleted file mode 100644 index 97daa14c2d..0000000000 --- a/tools/run_ftrack_eventserver.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash - -art () { - cat <<-EOF - - . . .. . .. - _oOOP3OPP3Op_. . - .PPpo~· ·· ~2p. ·· ···· · · - ·Ppo · .pPO3Op.· · O:· · · · - .3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · · - ·~OP 3PO· .Op3 : · ·· _____ _____ _____ - ·P3O · oP3oP3O3P' · · · · / /·/ /·/ / - O3:· O3p~ · ·:· · ·/____/·/____/ /____/ - 'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · · - · ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · · - · '_ .. · . _OP3·· · ·https://openpype.io·· · - ~P3·OPPPO3OP~ · ·· · - · ' '· · ·· · · · ·· · - -EOF -} - -# Colors for terminal - -RST='\033[0m' # Text Reset - -# Regular Colors -Black='\033[0;30m' # Black -Red='\033[0;31m' # Red -Green='\033[0;32m' # Green -Yellow='\033[0;33m' # Yellow -Blue='\033[0;34m' # Blue -Purple='\033[0;35m' # Purple -Cyan='\033[0;36m' # Cyan -White='\033[0;37m' # White - -# Bold -BBlack='\033[1;30m' # Black -BRed='\033[1;31m' # Red -BGreen='\033[1;32m' # Green -BYellow='\033[1;33m' # Yellow -BBlue='\033[1;34m' # Blue -BPurple='\033[1;35m' # Purple -BCyan='\033[1;36m' # Cyan -BWhite='\033[1;37m' # White - -# Bold High Intensity -BIBlack='\033[1;90m' # Black -BIRed='\033[1;91m' # Red -BIGreen='\033[1;92m' # Green -BIYellow='\033[1;93m' # Yellow -BIBlue='\033[1;94m' # Blue -BIPurple='\033[1;95m' # Purple -BICyan='\033[1;96m' # Cyan -BIWhite='\033[1;97m' # White - - -############################################################################## -# Return absolute path -# Globals: -# None -# Arguments: -# Path to resolve -# Returns: -# None -############################################################################### -realpath () { - echo $(cd $(dirname "$1"); pwd)/$(basename "$1") -} - -# Main -main () { - echo -e "${BGreen}" - art - echo -e "${RST}" - - # Directories - openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) - - if [[ -z $POETRY_HOME ]]; then - export POETRY_HOME="$openpype_root/.poetry" - fi - - echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" - if [ -f "$POETRY_HOME/bin/poetry" ]; then - echo -e "${BIGreen}OK${RST}" - else - echo -e "${BIYellow}NOT FOUND${RST}" - echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." - . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } - fi - - pushd "$openpype_root" > /dev/null || return > /dev/null - - echo -e "${BIGreen}>>>${RST} Running Ftrack Eventserver ..." - "$POETRY_HOME/bin/poetry" run python $openpype_root/start.py eventserver -} - -main From 6a4387a866d52e027d74805a54fe4f8a43004c38 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 17:58:56 +0200 Subject: [PATCH 147/195] finxed hounds --- openpype/modules/ftrack/tray/ftrack_tray.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 065528dcff..70f6e69323 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -1,14 +1,12 @@ -from hashlib import new -from operator import pos import os import time import datetime import threading import platform -import subprocess -import posixpath, ntpath +import posixpath +import ntpath import webbrowser -import shutil + from Qt import QtCore, QtWidgets, QtGui import ftrack_api @@ -82,7 +80,7 @@ class FtrackTrayWrapper: else: webbrowser.get(using="windows-default").open_new( self.module.ftrack_url) - + else: if self.module.ftrack_open_as_app: try: From 780ffefea972a3fb1b0c225136c1327878b95a91 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 Jun 2022 18:08:28 +0200 Subject: [PATCH 148/195] fix unhandled empty source on instance --- openpype/plugins/publish/integrate_new.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 91f6102501..2471105250 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -940,9 +940,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): families += current_families # create relative source path for DB - if "source" in instance.data: - source = instance.data["source"] - else: + source = instance.data.get("source") + if not source: source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] source = self.get_rootless_path(anatomy, source) From e98f81c70c4d8054f3cd2b8ef4ce6e0e1b7b11ba Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Tue, 14 Jun 2022 19:17:54 +0200 Subject: [PATCH 149/195] made the browser opening non blocking --- openpype/modules/ftrack/tray/ftrack_tray.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 70f6e69323..e822fd4639 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -90,6 +90,7 @@ class FtrackTrayWrapper: webbrowser.open_new(self.module.ftrack_url) else: webbrowser.open_new(self.module.ftrack_url) + return def validate(self): validation = False From ae9064ac5960b602c324b8d7aa6105725f4b70ea Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 15 Jun 2022 03:56:39 +0000 Subject: [PATCH 150/195] [Automated] Bump version --- CHANGELOG.md | 39 ++++++++++++++++++--------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d25908cd..eb71071205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.11.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) @@ -11,7 +11,9 @@ **🚀 Enhancements** +- Settings: Settings can be extracted from UI [\#3323](https://github.com/pypeclub/OpenPype/pull/3323) - updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316) +- Ftrack: Action to easily create daily review session [\#3310](https://github.com/pypeclub/OpenPype/pull/3310) - TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309) - Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307) - Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298) @@ -20,15 +22,18 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) - Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) - Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) -- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186) **🐛 Bug fixes** +- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342) - Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322) +- Nuke: anatomy compatibility issue hacks [\#3321](https://github.com/pypeclub/OpenPype/pull/3321) +- hiero: otio p3 compatibility issue - metadata on effect use update 3.11 [\#3314](https://github.com/pypeclub/OpenPype/pull/3314) - General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305) - Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303) - Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301) @@ -38,18 +43,24 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) -- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) -- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) +- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) - Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) +**🔀 Refactored code** + +- Blender: Use client query functions [\#3331](https://github.com/pypeclub/OpenPype/pull/3331) +- General: Define query functions [\#3288](https://github.com/pypeclub/OpenPype/pull/3288) + **Merged pull requests:** - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) +- Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: message length in 21.1 [\#3258](https://github.com/pypeclub/OpenPype/pull/3258) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -57,32 +68,26 @@ **🚀 Enhancements** -- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3251](https://github.com/pypeclub/OpenPype/pull/3251) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) - Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) -- Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199) **🐛 Bug fixes** +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) - Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) - Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) - Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) - Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) - Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) - Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) -- Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185) -- Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183) - -**🔀 Refactored code** - -- Avalon repo removed from Jobs workflow [\#3193](https://github.com/pypeclub/OpenPype/pull/3193) **Merged pull requests:** @@ -98,18 +103,10 @@ - nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206) - Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200) -- Backport of fix for attaching renders to subsets [\#3195](https://github.com/pypeclub/OpenPype/pull/3195) -- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190) **🐛 Bug fixes** - Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) -- Nuke: render/workfile version sync doesn't work on farm [\#3184](https://github.com/pypeclub/OpenPype/pull/3184) -- Ftrack: Review image only if there are no mp4 reviews [\#3182](https://github.com/pypeclub/OpenPype/pull/3182) - -**Merged pull requests:** - -- hiero: otio p3 compatibility issue - metadata on effect use update [\#3194](https://github.com/pypeclub/OpenPype/pull/3194) ## [3.9.7](https://github.com/pypeclub/OpenPype/tree/3.9.7) (2022-05-11) diff --git a/openpype/version.py b/openpype/version.py index 4b0a688cbf..2f4d180983 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.2" +__version__ = "3.11.0-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 4289c74ebe..e1b5c37289 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.2" # OpenPype +version = "3.11.0-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 3977c25a804671e04d6c688346e1be5a485187e9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:31:36 +0200 Subject: [PATCH 151/195] Nuke: improving logic for `useSequenceForReview` argument --- openpype/hosts/nuke/plugins/publish/precollect_writes.py | 5 +++++ .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index 8669f4f485..a0b4b77a2d 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -175,6 +175,11 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): "frameEndHandle": last_frame, }) + # make sure rendered sequence on farm will + # be used for exctract review + if instance.data["review"]: + instance.data["useSequenceForReview"] = True + # * Add audio to instance if exists. # Find latest versions document version_doc = pype.get_latest_version( diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..3c036510b3 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -746,7 +746,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview", True) + "useSequenceForReview": data.get("useSequenceForReview") } if "prerender" in instance.data["families"]: From 21d68b0d13914a13bbfc12040ee342b260a116ce Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 14:35:40 +0200 Subject: [PATCH 152/195] use get last version for subset in collect published files --- .../publish/collect_published_files.py | 74 ++++++------------- 1 file changed, 22 insertions(+), 52 deletions(-) diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py index bdd3caccfd..20e277d794 100644 --- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py +++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py @@ -13,9 +13,13 @@ import tempfile import math import pyblish.api + +from openpype.client import ( + get_asset_by_name, + get_last_version_by_subset_name +) from openpype.lib import ( prepare_template_data, - get_asset, get_ffprobe_streams, convert_ffprobe_fps_value, ) @@ -23,7 +27,6 @@ from openpype.lib.plugin_tools import ( parse_json, get_subset_name_with_asset_doc ) -from openpype.pipeline import legacy_io class CollectPublishedFiles(pyblish.api.ContextPlugin): @@ -56,8 +59,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): self.log.info("task_sub:: {}".format(task_subfolders)) + project_name = context.data["project_name"] asset_name = context.data["asset"] - asset_doc = get_asset() + asset_doc = get_asset_by_name(project_name, asset_name) task_name = context.data["task"] task_type = context.data["taskType"] project_name = context.data["project_name"] @@ -80,7 +84,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): family, variant, task_name, asset_doc, project_name=project_name, host_name="webpublisher" ) - version = self._get_last_version(asset_name, subset_name) + 1 + version = self._get_next_version( + project_name, asset_doc, subset_name + ) instance = context.create_instance(subset_name) instance.data["asset"] = asset_name @@ -219,55 +225,19 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin): config["families"], config["tags"]) - def _get_last_version(self, asset_name, subset_name): - """Returns version number or 0 for 'asset' and 'subset'""" - query = [ - { - "$match": {"type": "asset", "name": asset_name} - }, - { - "$lookup": - { - "from": os.environ["AVALON_PROJECT"], - "localField": "_id", - "foreignField": "parent", - "as": "subsets" - } - }, - { - "$unwind": "$subsets" - }, - { - "$match": {"subsets.type": "subset", - "subsets.name": subset_name}}, - { - "$lookup": - { - "from": os.environ["AVALON_PROJECT"], - "localField": "subsets._id", - "foreignField": "parent", - "as": "versions" - } - }, - { - "$unwind": "$versions" - }, - { - "$group": { - "_id": { - "asset_name": "$name", - "subset_name": "$subsets.name" - }, - 'version': {'$max': "$versions.name"} - } - } - ] - version = list(legacy_io.aggregate(query)) + def _get_next_version(self, project_name, asset_doc, subset_name): + """Returns version number or 1 for 'asset' and 'subset'""" - if version: - return version[0].get("version") or 0 - else: - return 0 + version_doc = get_last_version_by_subset_name( + project_name, + subset_name, + asset_doc["_id"], + fields=["name"] + ) + version = 1 + if version_doc: + version += int(version_doc["name"]) + return version def _get_number_of_frames(self, file_url): """Return duration in frames""" From a7f2587483ed5ea31090f1658937cb3904043fb6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:45:57 +0200 Subject: [PATCH 153/195] Nuke: reversing logic so it will not break other hosts --- openpype/hosts/nuke/plugins/publish/precollect_writes.py | 4 ++-- .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index a0b4b77a2d..e050fc8c52 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -177,8 +177,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): # make sure rendered sequence on farm will # be used for exctract review - if instance.data["review"]: - instance.data["useSequenceForReview"] = True + if not instance.data["review"]: + instance.data["useSequenceForReview"] = False # * Add audio to instance if exists. # Find latest versions document diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 3c036510b3..0583c25b57 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -746,7 +746,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview") + "useSequenceForReview": data.get("useSequenceForReview", True) } if "prerender" in instance.data["families"]: From 65a22a1ae98fb2489bd32f6f122fa60f84261162 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 Jun 2022 14:55:02 +0200 Subject: [PATCH 154/195] removing KnobScripter also default mov colorspace --- .../nuke/startup/KnobScripter/__init__.py | 1 - .../KnobScripter/icons/icon_clearConsole.png | Bin 1860 -> 0 bytes .../KnobScripter/icons/icon_download.png | Bin 1225 -> 0 bytes .../KnobScripter/icons/icon_exitnode.png | Bin 1883 -> 0 bytes .../startup/KnobScripter/icons/icon_pick.png | Bin 2184 -> 0 bytes .../startup/KnobScripter/icons/icon_prefs.png | Bin 2277 -> 0 bytes .../KnobScripter/icons/icon_prefs2.png | Bin 2758 -> 0 bytes .../KnobScripter/icons/icon_refresh.png | Bin 1778 -> 0 bytes .../startup/KnobScripter/icons/icon_run.png | Bin 2341 -> 0 bytes .../startup/KnobScripter/icons/icon_save.png | Bin 1784 -> 0 bytes .../KnobScripter/icons/icon_search.png | Bin 2400 -> 0 bytes .../KnobScripter/icons/icon_snippets.png | Bin 1415 -> 0 bytes .../startup/KnobScripter/knob_scripter.py | 4196 ----------------- openpype/hosts/nuke/startup/init.py | 4 - 14 files changed, 4201 deletions(-) delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/__init__.py delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_download.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_exitnode.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs2.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_search.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png delete mode 100644 openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py delete mode 100644 openpype/hosts/nuke/startup/init.py diff --git a/openpype/hosts/nuke/startup/KnobScripter/__init__.py b/openpype/hosts/nuke/startup/KnobScripter/__init__.py deleted file mode 100644 index 8fe91d63f5..0000000000 --- a/openpype/hosts/nuke/startup/KnobScripter/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import knob_scripter \ No newline at end of file diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png deleted file mode 100644 index 75ac04ef84b7235c071b512d1dc60410c32c5834..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1860 zcmV-K2fO%*P)<#DIs?bN`PzW0}2v_#Jl&xLld=NpC!8j9&8?voWv4HeW-6& zV%O#g6+#LK;he7RCJ^x#SI6>R>uc+m-hJ=glhZFx|L($t3ujvvu=R&qJDz{>`O~-d?z&UNca}b; zaLeNAv-}81;cvdV_|?eB=+CRw>M8)xW&%bY9i80OyX(%71VD(oTg34M0%6=*~;x#@O)G)D%w}OQiZZ2=S3l2*_kIU5CdHA0uiLLuHWq z+}GC!N@)OK7{FY>`H<&&W#4CN2#B0dvG4nEJr_I(od+;XDSfT4ukY<>q?QZ>1V|01 z3K$s~@ov1~##{6OQFTmZ0?RUC+xFinHRk1q0q1zF99h>R*&W66YYv*<3E4 z&F1ozrKNcxgb1~Z`K9Hhd^VfQpFejF?&M_cwt-{YvsR%nsE$<}@uUy6KF!RW0Dvb6 z01Old#t)AjdbXurrwKEEu{`N|LjmW33KFHp9Ed1xbl#aY3ufeeE4`3nt567~_Vo1Z zn3kiSpz)bNn5BlP-1C-Y4ywy!wLq(^cSaOICfe&l%khep#~>hD z*>A=~;2uGs1Sk=K{4PDmZ(t$=mgGblJ$m#g*4Njs#L2NH-v}bJRx3#zlw@5|mM zh{jV&4Qd#X5YBnvy54D@En-9W+WO4ly#X9rSorh7m6a79g(BKD=^X8QSzuHEneSuL zbrIAQDcf;oosvDP*9p$~kn4G6f01e3sGP15Bhd=M0B}(V@%7o2kG387<$-%C(TX!6 zNxh_|SJ$|a^99o!%;s`GZB!?EbGf>;h6v3ISd&07;qGLOe2+Oki zCnhHD)_-e+FLD0VeNtv%qy78LnKS=5aPZ)-wrtsgMEfickj|7)?C;zElhxJLvkyP= zux(k^V01#Fmvqkc=JL6nxjZ$fQ7V=GNGUbqYEQH|H}VO225R)gRxB1j7#kb=S#7FJ zV#e1hK{mrg#Lt{UAM5j_izo6vsb5HDgS4Q~#AV?m<~ha_FV2aW7k1*xl;E!nTXu(nFy=2=-7acquKEieR1Q z+>@t*dXSA7u{|u$xQlCJGi}u42Iy*bRO(YUWqobqip-`yFOt9z_qBjM&tx2z}IdmKTje6m2N3M zZz8UtK;*KaK;*K)bs_?gZX4koh)4`FZljP|mkk9Xmkk9Xmkk9XFNA#qIfu=}JtFok z{T#OU5~m76jg8mub##25+T8qeczAeVZ*OnM{$3!EC$I>GLQNeV9o-un8;?guMg|TJ z4)&e+;fRQ$C|>ouSUeE8QX>R3H#fiC)6;WjcXxN&93ugdlUgk;Ep2^$ed8XFQVNoi zk`mA0;NX`|d~uXofk2=p5V%r<5FjBO4mZbQvFY;i@|UbZ_Bz2%!c%JAcGcI{-vi2= z_~M92DwSHFpP!#V%5*&o!jbUnu~=-{@An75h=vpviAsw^I^}d-XKZXN41DFp7sqRf zqA1t;`uZM6qfxAi5RlqTJ*lmIIVglgO4#1s#_#v*9*?KUG_C6zsyaYCJ_Ya-aQCc! zIKH6MH0|%%+1cxnNF-cW7eu7f%7<&d%P5L?YLM!Jy^5iCkINbyQWgllTM}bK*n3u1t?KO&iT3V`i-Yq$Q;zp)wVZ zLx#i;P94bS-ETITx*+CiX6}cXNaU9Ug9#HaF4!cBM;MVV4n zRVI^3nMfpLYip~-2+R@iQN9gYRoK+jbld!|Y1+}!($b7W-CjjeCVIc=y)!*Com<#v zibPda)hi7R4X>GUDwSGaTU%Sn8kC$#%9-ShOeT|Za&mI}n3l7_D}?xkJVoNx?OPw3 zJ|>e%+1=g!z4`96oxKEj%Zbr6o=Yk30zZ?7n#gKEjTPi75CMDBXm7c}4~zqup6Tp( z4z!bxM*jG*;2mHc_%u5r?wuR*qcx`iwKrJ*v7_$kN6jMJ7-{C8qA2I#)xU;$egWfy zx0IPcTQ&0Z+0(zOtE+Y)0k&`}a#F()gS#K*;PB!!8@MlPNGEMAmHOQB~z7dT^ z8+~Oy!x5Y97q#Y2WMMq8$vL)jFqW>WS9*^PR!0%<(i+Dx(daFIv9KQjFt1K#5lya{YzsJYC)2_TS(+R5+&?*h$0 n`Du(V27&-703T?<;XO-kSwPNO(j+oAb=z78ck2u&Kf@S6saWx6gHe5Ys5htCIAySJ@h67 zbxAoDd1x9tMQ`rKEsTm(B_vQnN)3u03R{=d1(q#}N=j(%ICOilS&YZrmtjvsr%W(xtTT;6Z=8J_g#kIU0>7Upn{Qw?LqQ z0|N(6F+F0M=B8m7x7vMhwW+Gg5{bm}(9qE10B~&V*l}f2(N@=1e^V-zc3Sn*GEnrj zX!6XNvoU~!05~6{Gc9ad-9f2}%HoOmQXmi*7S0L4(b3Q|%7k(%x0buUTiR_2swJRk zG@3kn_DsxHWPg%#kt7?c!$>Kr$`XnAk{k#OyZ*_UGytK{=(wUxp31GRUEggXQcZzg zdF8@S&YgSdqR>dUVlZtrY=NdPzp1cj|dc*>e2;H`@ne=$8XbG{*rqOf>8z7z<^!)_qN zyf@Gf9zOUHngpTY#hp@V`|jPl|6)3OL4s6%N>xf!Xd=>eJyI+dH}d&>O{7#+W${F! zTmZQDyL;<}^a9cg=_djF5kS5pAOOhc^MAH1>!PNy0U}hTT9u~q>SDScF^k0w!vm$L zDoezx3jjP*Q(rG;7LiJ>E!_(#V;0JaUo*t9Gw!k9)POWhRbRH;Ro07%pI7X_4n z5o^XX6Y%Qt{Mf%OE-qqz{-+{0-gAETv{v__b;B^WEX#^$8iOQxhhi59?wwah698Rj z5#K?dKN^iD19D*4yU@K8P)}zT7m=Kw7clPwc(vI;+vyZ;I#6N8SY=l5@@&^?q7&)~ z{jtHveC~CSyXGsjyL1TY#SD`3NjO#deE{DtJM9fnC6vbO5O@Y52I%Sv&K5O*OeTY5 zvVE9NfGVIg#z-)^dC#kK_4Pfq4V1|^kwN<~odVT`f*6`R(cWF(Gi<82{%mbW#$7X4 z0sNpHm~Mb7peoYO-A8y^I+ICbJ~=Oj-w{l=K($an^EHK9E!Ir8FhKV}fCJ?}34q(|Y%W}P)zIlZsXgcGjX&1Kpv`XTwBmi3Q8c-^v(~7 zUh5c0RaF*`$CqVU9^zG9LR%WPCJ^bm-ZfN5K#Hoexp;gzAj?DGo-WRlX3cfENO&~q z8Vj4I)iG4tK&qm!xw*Mze;_dAT^G*u*XW9#x<;dpCej!Sn`UvtFlx_JJq1z~mBr^0 z%d%e{sx8TUPrbgrj$kkd36eN%xc60PvYS*QG(05s1jv2WB#XmY+gkWM3&N`Z3@!!WX@X^t~p_q=bZ zPFYlFl0dq~UM!mCM!rR;ra-FVz&M99@>HlsI-SWNl}w7OR{)$d3}ef*tO(OI8-^+h z`kuO-NHqi+931rh?A>?&aQN`>*Wfv{*~Q9Jv8r@BlSXPj<%N0G`SMM}Fg8ug3^Pq9 zNqo)Qk9)P#i4#dQHFYwa%jK@$zklCqH0Fk_&&bHg6Gx65Im$gP3m{%;)o*DQ(rKiU zDY4ghVTAp1>32&P-g)OTe*{q&?M;@0Dm51^F2}%u0crf{@z)!T7ibcpe?NHe&xz-b zpM2`6W6!$Vp(nM<70T8NnWiwpZW)HLSu~3go#`mQA)~qx2LYdLeTJ)7u6|>uwDW5K zdq%108v6YwpZrOgR9-te8ghqT?`%^}v!*aERNlyMnx+|M8Y7a(yCOYZ1W+gx@Wz{O z;NHD^5`Zj#+Z_Y#mUh2bU0a=+oS1xebab@bwt(CmPm5~SI*bct7{-QKG$TxBZtT64 zp-?Cw7K`D|ojU;VR{%c*u-!2b0F+9l8*6K8rzR#Q#zUcy-~n+e!|=9Y%20V@!%4r2 z7cYg+3y8hx!u%b;Hvtse@ZHe-KA$gl`SN9+%jS4Cm*ufoj1v)e0NyJPr6W5(Gc)sO zWo3nDvsr%a+BF^w28Hjx0eHFx3^nE_i9VV-ImJ~~6%XqLjIi@#V`Gm_pFYhG9Xhn{ zFv9!*K13Pj=jaWLu=4*!3^nLRG9DwftkoU=d-}=_KzWtBde*ntF VW=lh7gf9R9002ovPDHLkV1jt?fvf-k diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png deleted file mode 100644 index 239553755060aa93162d8748796d7ea29bb7b07e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2184 zcmV;32zU31P)+O)@S-n@C;?RGcT)YN=b zU0uDWzrWuX8Z#llVPRn=hr{vfg$ozHE=dwXh(VyB-`?Kt*}8S>56+)Ie_9{=z=2I> zb3|#$_5-o8aW;^U5C{nnnx{|+@cDebnVFf1&CShq69Q~Dnqg2Jk` zx3^zfy?XUie!ssD!0@18O=fe%j?&WVSgSQj(@6qJs0Z39&6j}5lP5pa-Q8_yYHIof zKpro^=;-K~>ajTpYqeUF1R$y7DnRpvc1rVAos*N39e`=vfY-dbX6v3kdm3yu+X}PE z9I<0Z>0hkY*d%QN3V~i#jZE+BCac9_F$1r|ILzT!*R0vP=C#)f5rRM<5b$_BjWIDX z&uSt{5(vTF`2PNWQc_Zm0Q1LGVT;A`%qy!`=PM7CkPWgN6BF~Say6)^(8)QXuba?9 zaOKJsfM;Ngr-a+>ZY(Y?P7eeEf!mDoZG3_9%8J$1)d4&-=D+|oHAl|Y*49eHQ3;`2 zaChhfR8>_0w2nEjgd}BUIewIokRYKI0CjlPG%)BZBR;xcb>IL^O-%qt0E}Y=E6cJW zJ3ITm)YR0qTH;p~77Wbyv>_GNL{pOt$q!C z{ngi$mzNU=1ct(@#|-uLYp|iAfz6xq7#J7;C2|w+=56)b5)%_8S(4DC)@p}VqZQaqPJDenfX{%%fYxT- zGqA~QjwmiJ{`2$k@r$%kl_c6Ox8ZO&XlrW&xD3n(_Iuo(Ca>4KEHN=rlI5W@QIeE3 zdgja-Hf`EOUtgaJy9l_czxSSi&1Q3C$@Y>1&pr2Dy8^4|;(7{umR_T zVPAMWgV2&B2Vs@WaPi_r91h2*uy7BHE;2H5>f3L>eJCz2&Ng^f(9+UER#p~Qu5;OI!OaF@Nrl*yAH`T0BV9Qw}FPtVrZ{R_>_ z{%drIN0UOS7c|c&)lu114)8@{{VqN05v~UR8-)0yJJS< zxFdxlBO|Alm6iR?VzJDE!CgUPV;2^Q`^;i96VKg|v1 zS*10QI9Xduety2XqW=!00zE^1Sdyf--h6Y%ikDx`Rm+zI-1Pa_oR>#^eLX-Q@DdOK zR0d%SN8=e)VVljiT!RfNRCoF`E|&|x-w#Sdk_PmK!e(Y>mMvYnbe-DYN|NBljb5Bi zCykAbpk#q%z@cDRBk;v&JnBmiYvJMH=E};-3y(%cO&7sR(|N6%~d`OHhV%5LT8A&P~p}DJdzdgb-Q>*3;8NPEHONE?fX*>ZGDfrMtEff#c)j z7wY}}`}cF{(j|a@p(r~9)@U?_xm=q+OirG^Tx~BU3A(zjl9RImkEaFTD$3&g{9a&3 z1)e^A`Zx6}`_!pZpfn-N2w7l8qcMEjwrwBHnKS1_5ZZk2=;$OTCkLOh2o`Uy#TU%MbK1+r5qEx^8 zhJ_InUiXuAyVP!4LfNVWbMBXBlPSXTRLm^!$33)$J(ba!`y z(gZCC{v8whFW{9cS32Y46BemGbwCKJs;Wp^ym(-N-TwZ}nKR>!Mq{}0Ho*;;7;L@^ z3JSP(?HVX;r5#1#F|#3g*lxF{yIigc{rRpgr8S6(iW&?W=8NOOg9q8UbEjHR&IV!I z#u9c2a8h#eO9lD)`wX&d80nLdB!sr_QvJ5}cFM}iIDY)NdgU{eOmcOsVL>+~o=-?f zm1WrwI?Q@s00RR9T)upn=H_OO9zDv56DL$v0X_iU0Q$!R7IX^B2HBA3%scWuyFF3p zH;DoS6926I2YY|Fm!@+~=yX=i17reK;{~dkrf}d^;8saVi5p0?+wIys4G2Mb`TOkK zw@)9}4_pLJ1AhcQCZzH`C^Hxga;3}V5=V~Ih{J~ui#f{q*kg-H60x7OpO)6Kx#;9qP5~wBTi$g4s_ZyGo~Hu z_@hp%787YIluooo(6abP(ll0*nvTB`;}}odN z?%KKQr4K&*;AphRjyfSpmgJi1n)ZYd3CVgAzNBt<-@duz*(GCL!sUwQ_;NIr0gN9v z-jpyhAsHmx93%lqNJ9CKkOU(~B#s_8e%zX9j~{hHlP6Ek1dr=g4w`e^A}Rmf5^Woz zPRL|3&6I>{yvj-vAaud4VUx);956+5{FrDdN={B5w_?SL*Yfi6RvTht<0MJ)a)~5) zFIP#Dm^g7F2?+@qO-)V5`}+ENqG3nGNg*jI`LTj$^Vd1P=>PmgLqqMAD_1@SFc=K6 zFD_g5%NKsQwA2u1h;w@f2_&I-!;=lXuq1&H;B-3stE&F`+sev{*YDlC=L98mv$M1F z1$caT}ep* zLi^YKVap}UsgwUbd2HU?dCyHqoA9{y%x1G)EGjC>bUK~=Q6W@VSh!*BOKZ13jIn-L zk`TfZli_e#S=s9c4<7v0LlYV~V&v$*R8?L!8ix*7<3$S?ys#EOJPIt)+t=$__TsX% zuCA_-p%*sGty)#|8{?26!vUS~>O(ztdm<7X*m$>EKMX^RL*!LOMb+W#4N1r}Wy*|& zIXNp4c>B^1tKRa#ZXSMEdEXZY*RSA(3v*_fOjEua%I1(Ew{z$3-kb5w%hvjYw%;XV100^0QY`@0*a(XdGrX zoA0>IbWdk!>~J{PyLT@~jvVn?)OwL+nZ=72Q(Rn(!C-KkB1r+Dvt2})b4?|ou+!nBw6v7k+FFgwd0;*8BrsIHzV33l*4Nh7ep*seg45|# z-xdf}qlN>;oz-f^Zntj^W_QR+;pEAa$7ju&wKgFkK}n;!tv?XnwQCp0j~@s40(co% z1)KzK0S@)L1$+eTYinz})pMuk>Df=sc2BINEv^8#xw)Cr(o%YRdozF?fZx6pGAXpT zw_hk;x6X3@lTVZxmpn5lgzze7u3o*0*=$A~BnQ|FglY|3z@DT3IGST^vI?(BawiQB zK7al^#l^+6x3>da0PY4-8ZK>%o;!CIY}mBX(%RaJBs8CBDasRkW^)}brM3Af@Lm|b z9#$^bJLdZO!-_g((m>nd6x`a{%7zUa=;`SJxCG?lUA#w32*B#-cRU4rF5Dgx>aPc% zK63`(Q{c@=$!K!=)TzyGT`DQ}3Q{M{;cx()0KN@agXuB^d*CCt6GtVwyxIa!wgw4q zw%St{PRA!_>JPx zsmJa+A<4jf5bEsgY|{qu?>z;h$BY56MXFCPW5$e`plL#)8Uy}S&^s;C|NUS&PT1T-M;X_v9WOx=?Y6|*zn7*nY)V@}GHTZF)w=lOE5brfzRMZQ8U!d-m-4PfAKkdMJHCYdJ}h zq__V3)`j%6^r`OMM*^Yi1$?p7=@ca;B~PC@bLQPh$Yf<@&EK+ROP#@Bi1V~pf!lQr zH(9Np{dncdCxR7Z@1ff`}_Ot219K8`0?XR^XJchVbi9KZ!TEyy&uW493yGrtMj_9C-#K5 ziqD=qhqW`^@ieLrhJ_HUUi}j;Uv2~# z2K4&c95N|bE?;gqR$t#1Z12&{s}I)y>j5QzvSao28kv6)fDA;Cw)gKZUw*gmZor5R zWxOBOCB=xa&-;CM@3OzV9F)>(Loj>866)&idbi@hfsg!(9(9w57>6jD{IP#(I8aeR zS63InpMbLuObFm`P0jah*Q_ptq!f&R?b5|8Sp!=>m{XW>Ukt*0?xx|5r zN^CZ(yBZOI&1U6rbq&BB;1{864Ve`5l9ZIZxu78bRfoetLqh|Njg0`r#Kf?C`Er&n zUFtp`2)ADa*uSW%s-m*8l6&{=0WfLOB<9S?CN?&n!!^}(c60)4MCnu?o)8cRoCBun z?tKRQ4Op0zl$2FiSV(SeF2=z|f}IBGPkHQpcIxWtICSU`9UUE-&QriKV2RII7lBLy zwA~L%2;ebb9ZDHrs}cmIuX+_IU9)CQ-19$pUg>K*=THIh+XvsKtgKAa-4AR5ehp|R zC)0s^U=Z*Jpf#LUEpLh+ErVgV)nz5(o1 z_3Pzh^@>=c{lV*cV`Jk+&%vZ3{#aqCuER2vG#t_Al5i6BFL*7@Etc-??mJ=99^GAC zG`BPZoQ~%BQTu|Yi9m>ETl4>>PoKWXXfy)&eZA}G=wRFSZL~DEfU@>F{1ko3LPu&}#6pnf*?|p)~T#EqE1`Qq!W$Cs)>nO>u5WZ5z-NfNjsF? z5I=~?k0vHmTQt!oIvs`7PjJK{iy}y(qkO1{QBX1Ha(DOcp8mL>>@K@^H9PHed}nlg z)_c!+pZ9(5InR6U1s>TWdt~7>63)Rb7E9H0&pkJbQYuYI2pEPLC@Ly?!Dh1^4AEfQ1ks<&<2q>k{Xf!cXrc8M;Tyq0qBxW!gGazP)aLi;lHPyHRKz_L94;!)M zFvuA5O9*FIUN*1T5r{~O@_wt)2Q`!V6F$5q~s6rpi zFbs3}@R7gm+Er;CKR)ZHt5>fskBQOh{oP|bWjnmSxM&W;ID%#_D6zD(w5;+i<;S&p zZLF{W5KvH#TgaG4LTp4#DfJLSRNlXPqC%9!7=YJ5r0*TDUSyR>{z?D0R7*84*+ii zI1yfGlI@_f>hlarxVM*zSY0PRYA4M|eXn>+XSAONK@XZ&FP zM>L!|h0k~IM)TRTaJe|a5FZ~8v)PP=3m4+aC!P?4BLra(<`<}hgak~RI<5KOp+gl) zeN~(>lgTu}U^HeD+3F=e0$$j~x?r_hQBYWjMw?9_6mHO*fdFXVyIHSRkkzz26GZ>8-hUDaNrqQEwhQ-A_8Jw6%11UvGE2-E2@|R;#sY(xgeV#0&VYR}m&aLwy4Z3JbuK_zi&10t7a8_bsw34PRHjS_0ihxq5<8qu#$vI&cI3#BUGjl_O7U5Nl(7F$X`-N*0=svAAq=%5 z2q^&g{@S%`GwbT=dIvvYUgrH@G8`2Z6`hrxZT7y!hnU|Amq~*_m}Oa^Ooo>Mm>-asI8x}rLtkGX_;TMAn9E?hapPvA;HK2>_{Y+O zgKz|Zyr9GaX^Ejo0R)k1HNZVvM?)c|iPmTdKN=PNRgMYq9n&u{BHR6yV~V)PA4hzA z{A~b=x?I7G24e;t6e8IoB2gF;`ztRWVhqFFA6&~axuXD^&1QWlqzECzY&Opo9OeW7 zg@mQ{h43288#^3MpK*QE{kPwquXi|@#ZIRadc7VRO{7FqXd?pPbW?iN{{8!Rx?HYX zN{G#vF=Nr}*|S$j^ek zZD?;pc>9vcC?DS)B+v3dY@0L;C6_u{&`x}sZJS}=F+OHdJ&7!M+wT*3rN zNu!?5nJ{5`b8|~ge}DgN-{A?#&!4{Vop;{-hfb%B74DP8$p=>*sK$;RJJ8e90~dEt zMFDgGs0Hu^fXx8zi!%g@sLTuiGB>=v;REwDvq2OGAfS@|@xd(1I_v7{{%Nz>4)*l) zT#br~iZL3EnR$76ze-I_%~GmrCh+jX#Zk zW^}HoG(?#k2vaX7L&N?Sk(DcdTVgaC$NwP2va_=D=Dj@c&7q~Y0<5&33xYM8sEFd? z;%b#j6|~ZV63fWUm`n&!$#xZ~U64guDfaj3mcv9Qr-sH!Nl7DOV`C>jG_fzg{Ic9; zv)QD+i~HTXH)?8X!ri-fgAF&NH8$FC^JWhK*{Uh*k36Ox56a5kM_*sxM+mY$1WhT+ zvaZ_WwTa`#j-57q_y~R`A$J7!0XsHt+KdmjZpGfcdvWVlFZ6mno_gwOkRbZ6zrP;` z4<5wEO&d{Jxf5SkRU=_|0#XgBA}cQG1eCgAwU(i}x(Wc|0el2b9X%K^0C2NzTWw9v zPsWZLYaB6rq~IgYwYH*Q?OL2Zb4CclvGeCI=&P!#w0U`E#19+BD4C6(T0Xo zU|AO1KK&HGTeAk;-PZw5+Pn^68aVm2>hk4I?ky$Xt8xG4NfAz!w6%8tz-<730q`ui zuL!aaA3n6}^`%R3`b-m87mKp8_vEDBK=8Aa&?FVOHyrz-qob!Y3?uwj2VfDrPG}b| zSyH_8)mPsUGvs}&1`mGzi(g{EZePI@^R`z2d=#yTj$<55NTh(!zZ$Ruc!6Pdsa@pg$no{>zQcgs4se3jpH%|LVjRp-ey(LuND2t?h?_S%fLY-B zwfjR|)X~||I{0;~_)-=iKu22#z;QhnV6H0weg>{m6n`-QJlqE?Ly`i3YhBkaI-Cv{ z-@S8t5~SL{)Yb+7oe0o+$SfpMz=LH>k-6P&)?%^zAtxuN4B%Ea!pnY#!-0MK_5wqf z;s3m;0StiOU;X@fH=GlP(^vqnf!hYWZ2;UR8x{-OM`HhN`@bdjADgo))(LOj5&!@I M07*qoM6N<$f-?anq5uE@ diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png deleted file mode 100644 index 559bfd74ab8b550503194ba2409795e77a52c83b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1778 zcmV%no+>XdU+wjJ?^&>5L4iOZ zg#>9CnO#2f)tS$>ZQbU}7r>G$mr6ni(b(9yZR4hmL2a?Nbf%*}g|Y^57rBdfwD0Jv ztE+n&LB@Fr^Ok3;2KYPR$f#rmA`qf|SNmW6wf=xWAf*HWQc47Zsi`TZ{x^k?#t6;# z_v+QFPWYc2NMOT;wz^t>KngG!xO8cZXf#SJ9w#w6>Zo(sNExKGwDhh`n>Gd|fCM4n z?Aal9?b^lo__+PmYrv0+&Hh*2)%m3ivVQ&g-+MeBF8~5ACN8pb=T0UjCIJ2e>;c{b zhVyl*va<5AbLYdDI{&g@nx38Da=WBQ+^h(s_autDL!?LFeqdc|d&?Vqv9`ykOA`}XxArKhx#uiEI z{B<{lQ3Men%!aLq4Oen=JtLpb*AVImMO}p^1Oj1>`pmN5?{5qSJE8>z1#|ASiXakc z0tl|}t-K+MTt!H8H$)=SeP%BkJ?Hn=Hg<$M-kA$VeQKEoG6RyIs;a7c?iWA$=)+@3NTpJQ z!(n3aIGDK@z~tm4t!=FajDV4mf7905<{29s>t;qUR|auvB+~6kDgWHr`TgO;hY9Zv z6N|+_kTOQy%z=Q3@rf_n+FHN);`1+Pd+8+-i3GqJV5z-5P zfh3YYf!JZp;k31j;}a9VZEtTs4y*#c2Y!K(7NwQb!OB5N;00g`hyypu?ME@3-w(6^ z|C;%N8i){?9&&zYB(TM7``76wvo(s54$UdGH6k-}*73!1ogG_L&r z_qGEuQj+ZJ-4MjR(7ni8=Dpi0TWQJ66lKYhCHIQ-0?JcXD>-Z?bAl{fxNyhA9P)a&({Fd3AUD_0hEb#)Gum6a_E1Olxsn_If_9vR#1Ox_?(O-+AzWbGrrvPO5s ziskpe@y4E`W-bZ{N=rR-c6Kf+)4a0}FetQXa%0g!HVNGNoK)zJQp9iN#}P zHyQhqMAF~qJj7y6Taq3R3F8yv^H;FxW|4hb*Y)ObBy#9e^YkXAxMd^pU}fdwlFS;p zaNz<#Ql+j+8b6)X_2%8-@PQ6!I)DEBErHN9ZE1adeY1TU4-O6jTu`a6da#;KCX>J0 z{p#)mDVb_ApE(90gb>?b-u|Yi#G^@!8;(RG!P%j+0RL2JBbyWAbV{b0fdjzTI0WG> z^WMGvqwVAgh0QsU97s@AT<3~0iZH_a>q zEMBZFUA5|gM{8^Sf#Tvto=gCS!Bc~LeB>w?xBFkJIOf<{ue`i`bx%*v8LQRnu069T z4-XHsb?a8^lKeTKSH-?_=MmdYq;+I9d{i%=Jb98Wo3~gHp8)-;<{iI9kygGm<3s7J za{9VYGBPqkZ*MOH0|Ul;aE=>H;ad)J<;s=GSS)r_NU`o_Uv!f)$=Iba5~B%Doj%34 z-+XIt>X->J=9F)_Xf?(p%jNeS@GLn_cc-V;B=D}{yGe}O!eJl=3_9$~9sQ*GKkH)C Uw!?IYy#N3J07*qoM6N<$f)br{CIA2c diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png deleted file mode 100644 index 6b2e4ddc2348254824b75b9d14b03c5eb28430fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2341 zcmV+=3EK9FP)@28X)BXK4_L%0;0i^ZWhi?r}hV z*v}W9YceC#urMm2nF3QtEQ$hS=7ZC_J@-hGib}X~Arr76AbjgV-n) zZOk#|FV0?U@^GH1!GXc+9LG(?HrsGyW@J8-zcRn1udlabe0<`LT3u023Q3x&0OeyT zj%C@U-5R$X0v(3q36f(l!~@Av!9wVpeE^!G=9HZ?glraA)#mOQ&8@9>escD+t- z7@HWIuvS~k@7}%J9bu|b=|>^bq9I|0n+q1Is;b`p<^KJjvLPAx1f{7l-mxTtt@R#w&$3LrIAn^Ia@)&gLaSbpWo)pH!@@rqHE z(PVqSh&!Ttz2Ho0x0{p=N+!WMl*Yo`(>c zj*N`BcJHqLacODk@f$a8XzX@709=ud(bm@1ym8Z}@AH0_!eX)D*s)`;0O*}5E(!W) z8kTZKDX=`2mUoblkeDc@b`*Sa0l*=A;Og(UyZZZo0H9jZ>`Q4M-QC?My1Tna0Bi+t z2|%l~ytA{j#mjL%tyY^%2!YXPgvn&uF+4oHS3X~t)O0;PEnWX$`az(suI@)pr}Kn-9Iw~wvDs{AH*VVaviLNjqM{yS)jg9`=H{L)`Pfq~AdDdKnAmlS3WV*Szxp@FqvL~)6 ztFpk5q=#zj_0BuL-uT{;BUd?&n^Cc=>pEV0?WeeP>((gv>z9DE?a0W;g;S?aecS1D z?gp?PD)(ilPoF*%2uuekrMP$R9vlt_02DIk9gOAKP>8f>xSc47OhIXLbMx9WXU^;? zDk|EOot>@qcsy`8dU5mSFuAf~eaG^>{o7E?>U<8vvUDm;p=z_$PqANWw%5m&^4Bm&IsVOO_tgK82NKK^I7zmN3XfiIotOgdJ+1Kn|p-{IDwDTF9QpsBmat?uzrs(B^It-^E93X3?xCx@ z3-|r^zW^VC`lA;(RK6a-jK?AK7!v?40`NWQiNp~{9C5@EM+E#Ie<;2-)aFSU00000 LNkvXXu0mjfRQGr7 diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png deleted file mode 100644 index e29c667f3455f4afa92f63ba8de4ce3c4b53a866..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1784 zcmV+f?nK~v}qSZF0fhEyVF|mpGAYx!?s%GY?F1qL{o1(s$SZj9$F?o=Z*azEe z5&XkG>C)YIg^qROn{~A<2#Zi#vx3;hAVph<+Lq2Fo%#82&$)ljJ(IbU%ox}07l!0} z&)jo=|IY9He&-Hc!4+IVBcbdISEbV}PmhjH-+SMEkF~eAUk?hDpajI+QiUrehy;&V z4WF5r`TPF;`=2V8%cWWrYql@!*zwFO5B=f6XVd9)tM?og5;{4@$pa%K<+t2=^POFt zn{GUE^jKa*!ZR<6lzrj;`|o|syIlc-LIO`9%)CFt8?V2?#fuk{KBH!2G8y*l*~6wy zT_|8Uo82xbH9fueiBc(<=VBfk8y%fCmID*39xs(j?0R+=mo8nZivp?U6m7q0Bb~mA zRB9bdOG^MOEG$qg78xA8$9pu8$=p29xvBG}V@HlA@?4XBK>(%jV=K%Hg#yK55#RuD zt*7zIJ{vb~{O!T~!5{PaJo)^)y!Wp|jE#={)r_AzcW!4i%!O<=H!?mx{*R`nrkF#j zDuz-7D_NT>YM{%&4&aAslP`zP&d#3J*0y$K(w9JYPxnAbkki33K(AF{IGg>`_{7A4 z<|@j?6%#>B0BUoIQP9Ihpf0hf5JZ$A06AdVHKsyx<+8&gFN{wdSk7~C#n7Qg;HYqD z))QWs3!uXD_qoQxTm-V&;gN}n%F4wQ^CM5WdEFt}YS#STbAvG#A0Ez*j8BX^!?Hs1 zFe_GByxvfV(g9}8FJ?~z5T(V5^W1QDPGZf>gQ8mb8rebl;A0E?NkAQ^a zGW>=cI-bDVy4;FIG9zZMZU{tq8^3FQdrWj@bjW!t@O=2*3LevoN?E+#@UtC3#tIKx zW3lf>vK!sudn)NlUz%)t>&>@*clPYrZT9q4JFq0apwxpa zrj&%LM4jy;%gW-emEx#8yl+YkJXES$d|Ytz7&~+dL}~Pm=CUa`y}wBvqTAIdg{d=g-F`tQVA0bai#n*4CyYBz&x7^EezE zv%u=+Bj5=7L&gZWHvZ{Ib!T+Wv(R$4h+=G>r7)Qg{uJYHN{Vs>^G;0xd(;Pb|4 z6oEQ^{P;g}x!j1^#Lb58^CaoszY%;F8@{x-NV!}Fc#@w-DF9hqEY3t%Ql&yFfK#zZ zGF7n-2v!eIh(HvZ#}ThQtexEEYl9$p zdCJ&44#$BhwT5%Uu@aG-B~`q*Tw9k#JCk@nk*K87HHyB+uBO)pri1q@`pB_(h=<_z zn6FTXuc6btc8JP>fd2me4JbDFub6a4ZzC3pWs!1_cJx1M zoEd{-ZCxq)JCH7hlXTEu&&|#D^z__rl<_AONTpH?4Gpam%BWj*4|-K@JdPedTu^$4Ew_1;NZ5Ym!|UTn%4!{MnU}8Idqt!4|=Ev_l=p1&&|y- zHa13~Q1~10Ohv^2GMUVmTeoh_v|rQivh-zy!}ah)HSPt9pgi6QY&N`1nfbYS4!w7X zLZJY#3wYh7PZ-;PMS_+2OaGh%+QJGfmlEy(9sxE~v%(sp4Ez^(6YqyLKHRTre+Br} aD)tkwJPu`6Ec-S90000yBjY@lTK?(Xe}yLVYEo0pr= zD#QQVvv=<~&u{N}&U5a07H;T+INX�v)R0;sIYMD(p#66WSO%Tr>3PYNSu{8 z8yQAMd;{nH)z{P2-R*X{T!)SvJ#ygUg^MFI;deY~2Gi+u`n&G>-n#Yc%b!`aDEAH_ zK$caLGV#WeB>C-j`<|U|zWL(GFTZS`8I1pyCa@)omlRb!UG=Ih$CgiI@&XxIVVKky z!t%TCzSrE;)U?It^9{}fOsu8^CWH_b6%TK%tbC+Sr_<{rvy42A$gBkj83K}o!C*w_ zl(Ec}*VXaBz(DV|ZQC}sx3@e0FJOAT-mrP|mgY70lvOAbi`*_`!od(Ow;QL^iQDbL z>m6Wtcm#vNKuSsq3l=P-pkM_hCB<8>XAJ7jNfq6gfE{M%11R}^0pNb%w}5vFe!l>kdU|?vUaxo6%9SgH5Xvu<^6iwH z0)aq^$K!cV8&YKM<6SH*E!|+V+45!8s~o1jp@F`$X8}F~3V=fzeTRVOTU%Q5#p{O$52|abALU7XIUVN_{q?m!zE(9lIvN?u zBqt~9@4Ij9pQmFqE|}SD$;->jy#tgzDijLR{QB#PXV}2ZKmjm%Vf2-qJ9i#cQi$T< zo_p4m0V!H-#sw=XDqJhe%H|6)pML7*tP+B6p*{TK>VpqHs0@cg$jTm$)n?VFrKLTs z)m~h%Wy_Y8#3s!nN7?`+;CZdi#?#Qy(20L^bexP6Fe6n#JoWeYACE3FVBWlW z0C#G!byYASgfN**iAt6iNPz%8pAW9V+KScC(9k(qmLuyLX3a_hSggg?Rl#IgR>K?s zys7MtTva@Ve1)yQ%r{esPEO{l2&>0h)sil@ZH#H7ueH#h5a%6@_@^Tml1 zYNhi&5YS?G+$`tydQYXNr&r!`OU@j%ZMJ-Q{;elZp6obv>Qtu|hp|$7DYxH#dzo5F z$zhqg`a1gi`vHCd^k}g?Vft=wZ}*gyt*M+ndzLyKN=iz8G%zr5rmL&VHSW#hC@~>1 zsivmpjneOzmaCr$1bg@I<(+rl0r&){(Q1D}Fu&j5bNu-6!n^NYontU4(W2Mub;ZRc z52mEtl-kkJaWoJJj7`O<&1TErv17;Ex7~JInOgCx+t>N?{WzVDa5yZl1I|nxNztS> z+;iv7z2Db&_VME4l0>2A4gqblp_Ws90;ZUgepJ%@C`~8>3vy+{jz4VDEp7_l# ztEzsVHg|4Dw0lYvkF0D97PF<>>2&@&91e%If=#6pd}qn3RUg)F-D)r>s{IMiSQ_hT2 zt^L})UT=GAYwLrOBpJS&n@fVx7`Y^3BMa31FV6VHi4(l|(n};IC1EyOh$#`H-?dmQ zc@~Rhsk5zZUpO2NT}v>4&qJY5tHZs4h&8ex(Nl8)hh$V;}OOk}s;pEjn zyh>A36TQ8?IP4CrHY=HCbJT%AD3Oi=mu0amZJQ$8bPoO!foFkV0XNP6&V2Hh-XAN>OZbar%b{PWMbG!TX*VR2xTwHwh@J8Tp9Nxce zdt-TUl3_U$Cz^ABO+W>(bQ0h9qqG?Q1pE!<(VC0~Mgm5!*R2w4;N{=EB<;{0?1Y{8C1&#tAqtt+3mC0x{?5?Y;SKzKV3B4TJPN(zj zhK7a>!Ql9CGYvLfnT$r`?z%`=s#1@wlmWCk+FI%x>er74cTL}jH7FDghxgbWj^#G1 zHP>u5U%AJjJc&%RIoD>hEq6E^d*UUOuNxRd;2aLea;w#vdquVsBDy7*&E{OI)fyLW z#^0@GNC8Qb)-^OV9B6B6i+-rI<}RZe%CS^bUuQ5-;p~p(R+}}~Y*AW_ZnvB2>T0#`cn;Wwa<1@=lgVH-wlp?2 z$}hkCvYeQZAfx1zL%>x-@tY)*(P(T*xXz$dnNa?faER*&N|lN05bB1mTm1)mZLGb( S3=G}?0000A0%^ diff --git a/openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png deleted file mode 100644 index 479c44f19e3c0c824c0491b4c176123eb1547197..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1415 zcmV;21$g?2P)KAK4h#sKNeKn0cL4TArF8tM(uGMt}G2RbOnLZ=zHx zSMSNqJt>xm{eAib042}P^L*}=0s!LuM`SWtz*40$z45-m)Ya8xnAi~>9v-dHXnvPU zrQFeD$Nn7X>l<6YVZ*ypsgyrFJnX)F`EtjVD_1(6JbB_AbB(#>tXvo~H%7i@^_n@c zF|h%4cj~(K74B;+DlS?a8yzdB0sH{rv+@mgOV>#pu|m zr|EuE&%%U-K_-*2f6JDv#mwHld1u4I!lGJRTWz;**Y)^B0D_vDs}`ft*bX2Yz##yw z02}~D0Camio*U)mWs5pH%{G>0CEgMeFoYm4KdKiV!w6y45F4riicMTaI zJ32ZZewmT6-DEN?gD)+BNF@>hUmzR-s6Y9xprB^Shsh~6TbENTmbj%-sm$kmLMXgt zd^N7uH{4XI_Gefumd7C>A#atJm){Hu3Jz;*)ITMDe7>-xxVSm~{RMNp-lflMJcWC+ z^x2qBvvy!$V5qRLaQ(O0*~LLKgB7hUZT9Wkx7{HCz_M(xTCHwYC=@eZ_kzBNTc277 zGdE|}_1JrwCtikdg<`=E2M^Y9GMTX5WE{M7@sg31v%&y*fPiH=ft+InLb2G-Un=2A zB>sG{SmY-Z3K*Y5y}k9_U4o$@himKBZMWOn+S&l*OnHLh;^NXuN=km_^ZCp{wffdC z=g+GEumHI6Q3gN+Kng&@Fg#IIRAf-nlBAGzYuCNwC-7rH0k_LdcjxY|Z>VoD0@w}U zFWzJ!tJV6@Y&H)kCnqmUNJxm_@pwo~Oq3)gC51#rMh*-O4OQCh_M-qU0Qi+sdakFZ z=h%Y>53VVfDp$!lIZFsZsjBpkT3u}ifIFMbe8#iwJ8a@k87>nR~P0fXy zw{D#n931RtSvD{xCT7tGNlBlF&YCrc0yuu+M9Y!Ghg$%Y1DJSJpDP3aSy@?CX=!OI zynQ8@koy;Zc9_b_%J#CdvL*m$0i2o~Z%XMVL$BALi;j*?3k?ko27nv2wU*)0Q8z;f zal2f!%Vrxk7z{mOVPXCZ!;pLT?%4{9iW)s0PZfY8)5U!*ghGM8T7A%@(`o7H)2E#r z#}xqB58${+BtBPBQPHi{YN%yH=;8DwV2}$KwSu z48tobD!QoCYU!CXX9fai%%}!X3P3nroM}tv!^p_U-v)!>#4c^AT z{j&Wp&BzC<)!N+N-rg4*8~c&N;c%6fmNwPb*P8+4188~U3|@dl06zivaeV&Y!av`O VU4IwAq!s`G002ovPDHLkV1g(>ppyUq diff --git a/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py b/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py deleted file mode 100644 index 368ee64e32..0000000000 --- a/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py +++ /dev/null @@ -1,4196 +0,0 @@ -# ------------------------------------------------- -# KnobScripter by Adrian Pueyo -# Complete python script editor for Nuke -# adrianpueyo.com, 2016-2019 -import string -import traceback -from webbrowser import open as openUrl -from threading import Event, Thread -import platform -import subprocess -from functools import partial -import re -import sys -from nukescripts import panels -import json -import os -import nuke -version = "2.3 wip" -date = "Aug 12 2019" -# ------------------------------------------------- - - -# Symlinks on windows... -if os.name == "nt": - def symlink_ms(source, link_name): - import ctypes - csl = ctypes.windll.kernel32.CreateSymbolicLinkW - csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) - csl.restype = ctypes.c_ubyte - flags = 1 if os.path.isdir(source) else 0 - try: - if csl(link_name, source.replace('/', '\\'), flags) == 0: - raise ctypes.WinError() - except: - pass - os.symlink = symlink_ms - -try: - if nuke.NUKE_VERSION_MAJOR < 11: - from PySide import QtCore, QtGui, QtGui as QtWidgets - from PySide.QtCore import Qt - else: - from PySide2 import QtWidgets, QtGui, QtCore - from PySide2.QtCore import Qt -except ImportError: - from Qt import QtCore, QtGui, QtWidgets - -KS_DIR = os.path.dirname(__file__) -icons_path = KS_DIR + "/icons/" -DebugMode = False -AllKnobScripters = [] # All open instances at a given time - -PrefsPanel = "" -SnippetEditPanel = "" - -nuke.tprint('KnobScripter v{}, built {}.\nCopyright (c) 2016-2019 Adrian Pueyo. All Rights Reserved.'.format(version, date)) - - -class KnobScripter(QtWidgets.QWidget): - - def __init__(self, node="", knob="knobChanged"): - super(KnobScripter, self).__init__() - - # Autosave the other knobscripters and add this one - for ks in AllKnobScripters: - try: - ks.autosave() - except: - pass - if self not in AllKnobScripters: - AllKnobScripters.append(self) - - self.nodeMode = (node != "") - if node == "": - self.node = nuke.toNode("root") - else: - self.node = node - - self.isPane = False - self.knob = knob - # For the option to also display the knob labels on the knob dropdown - self.show_labels = False - self.unsavedKnobs = {} - self.modifiedKnobs = set() - self.scrollPos = {} - self.cursorPos = {} - self.fontSize = 10 - self.font = "Monospace" - self.tabSpaces = 4 - self.windowDefaultSize = [500, 300] - self.color_scheme = "sublime" # Can be nuke or sublime - self.pinned = 1 - self.toLoadKnob = True - self.frw_open = False # Find replace widget closed by default - self.icon_size = 17 - self.btn_size = 24 - self.qt_icon_size = QtCore.QSize(self.icon_size, self.icon_size) - self.qt_btn_size = QtCore.QSize(self.btn_size, self.btn_size) - self.origConsoleText = "" - self.nukeSE = self.findSE() - self.nukeSEOutput = self.findSEOutput(self.nukeSE) - self.nukeSEInput = self.findSEInput(self.nukeSE) - self.nukeSERunBtn = self.findSERunBtn(self.nukeSE) - - self.scripts_dir = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Scripts")) - self.current_folder = "scripts" - self.folder_index = 0 - self.current_script = "Untitled.py" - self.current_script_modified = False - self.script_index = 0 - self.toAutosave = False - - # Load prefs - self.prefs_txt = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Prefs.txt")) - self.loadedPrefs = self.loadPrefs() - if self.loadedPrefs != []: - try: - if "font_size" in self.loadedPrefs: - self.fontSize = self.loadedPrefs['font_size'] - self.windowDefaultSize = [ - self.loadedPrefs['window_default_w'], self.loadedPrefs['window_default_h']] - self.tabSpaces = self.loadedPrefs['tab_spaces'] - self.pinned = self.loadedPrefs['pin_default'] - if "font" in self.loadedPrefs: - self.font = self.loadedPrefs['font'] - if "color_scheme" in self.loadedPrefs: - self.color_scheme = self.loadedPrefs['color_scheme'] - if "show_labels" in self.loadedPrefs: - self.show_labels = self.loadedPrefs['show_labels'] - except TypeError: - log("KnobScripter: Failed to load preferences.") - - # Load snippets - self.snippets_txt_path = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_Snippets.txt")) - self.snippets = self.loadSnippets(maxDepth=5) - - # Current state of script (loaded when exiting node mode) - self.state_txt_path = os.path.expandvars( - os.path.expanduser("~/.nuke/KnobScripter_State.txt")) - - # Init UI - self.initUI() - - # Talk to Nuke's Script Editor - self.setSEOutputEvent() # Make the output windowS listen! - self.clearConsole() - - def initUI(self): - ''' Initializes the tool UI''' - # ------------------- - # 1. MAIN WINDOW - # ------------------- - self.resize(self.windowDefaultSize[0], self.windowDefaultSize[1]) - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.fullName(), self.knob)) - self.setObjectName("com.adrianpueyo.knobscripter") - self.move(QtGui.QCursor().pos() - QtCore.QPoint(32, 74)) - - # --------------------- - # 2. TOP BAR - # --------------------- - # --- - # 2.1. Left buttons - self.change_btn = QtWidgets.QToolButton() - # self.exit_node_btn.setIcon(QtGui.QIcon(KS_DIR+"/KnobScripter/icons/icons8-delete-26.png")) - self.change_btn.setIcon(QtGui.QIcon(icons_path + "icon_pick.png")) - self.change_btn.setIconSize(self.qt_icon_size) - self.change_btn.setFixedSize(self.qt_btn_size) - self.change_btn.setToolTip( - "Change to node if selected. Otherwise, change to Script Mode.") - self.change_btn.clicked.connect(self.changeClicked) - - # --- - # 2.2.A. Node mode UI - self.exit_node_btn = QtWidgets.QToolButton() - self.exit_node_btn.setIcon(QtGui.QIcon( - icons_path + "icon_exitnode.png")) - self.exit_node_btn.setIconSize(self.qt_icon_size) - self.exit_node_btn.setFixedSize(self.qt_btn_size) - self.exit_node_btn.setToolTip( - "Exit the node, and change to Script Mode.") - self.exit_node_btn.clicked.connect(self.exitNodeMode) - self.current_node_label_node = QtWidgets.QLabel(" Node:") - self.current_node_label_name = QtWidgets.QLabel(self.node.fullName()) - self.current_node_label_name.setStyleSheet("font-weight:bold;") - self.current_knob_label = QtWidgets.QLabel("Knob: ") - self.current_knob_dropdown = QtWidgets.QComboBox() - self.current_knob_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.updateKnobDropdown() - self.current_knob_dropdown.currentIndexChanged.connect( - lambda: self.loadKnobValue(False, updateDict=True)) - - # Layout - self.node_mode_bar_layout = QtWidgets.QHBoxLayout() - self.node_mode_bar_layout.addWidget(self.exit_node_btn) - self.node_mode_bar_layout.addSpacing(2) - self.node_mode_bar_layout.addWidget(self.current_node_label_node) - self.node_mode_bar_layout.addWidget(self.current_node_label_name) - self.node_mode_bar_layout.addSpacing(2) - self.node_mode_bar_layout.addWidget(self.current_knob_dropdown) - self.node_mode_bar = QtWidgets.QWidget() - self.node_mode_bar.setLayout(self.node_mode_bar_layout) - - self.node_mode_bar_layout.setContentsMargins(0, 0, 0, 0) - - # --- - # 2.2.B. Script mode UI - self.script_label = QtWidgets.QLabel("Script: ") - - self.current_folder_dropdown = QtWidgets.QComboBox() - self.current_folder_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.current_folder_dropdown.currentIndexChanged.connect( - self.folderDropdownChanged) - # self.current_folder_dropdown.setEditable(True) - # self.current_folder_dropdown.lineEdit().setReadOnly(True) - # self.current_folder_dropdown.lineEdit().setAlignment(Qt.AlignRight) - - self.current_script_dropdown = QtWidgets.QComboBox() - self.current_script_dropdown.setSizeAdjustPolicy( - QtWidgets.QComboBox.AdjustToContents) - self.updateFoldersDropdown() - self.updateScriptsDropdown() - self.current_script_dropdown.currentIndexChanged.connect( - self.scriptDropdownChanged) - - # Layout - self.script_mode_bar_layout = QtWidgets.QHBoxLayout() - self.script_mode_bar_layout.addWidget(self.script_label) - self.script_mode_bar_layout.addSpacing(2) - self.script_mode_bar_layout.addWidget(self.current_folder_dropdown) - self.script_mode_bar_layout.addWidget(self.current_script_dropdown) - self.script_mode_bar = QtWidgets.QWidget() - self.script_mode_bar.setLayout(self.script_mode_bar_layout) - - self.script_mode_bar_layout.setContentsMargins(0, 0, 0, 0) - - # --- - # 2.3. File-system buttons - # Refresh dropdowns - self.refresh_btn = QtWidgets.QToolButton() - self.refresh_btn.setIcon(QtGui.QIcon(icons_path + "icon_refresh.png")) - self.refresh_btn.setIconSize(QtCore.QSize(50, 50)) - self.refresh_btn.setIconSize(self.qt_icon_size) - self.refresh_btn.setFixedSize(self.qt_btn_size) - self.refresh_btn.setToolTip("Refresh the dropdowns.\nShortcut: F5") - self.refresh_btn.setShortcut('F5') - self.refresh_btn.clicked.connect(self.refreshClicked) - - # Reload script - self.reload_btn = QtWidgets.QToolButton() - self.reload_btn.setIcon(QtGui.QIcon(icons_path + "icon_download.png")) - self.reload_btn.setIconSize(QtCore.QSize(50, 50)) - self.reload_btn.setIconSize(self.qt_icon_size) - self.reload_btn.setFixedSize(self.qt_btn_size) - self.reload_btn.setToolTip( - "Reload the current script. Will overwrite any changes made to it.\nShortcut: Ctrl+R") - self.reload_btn.setShortcut('Ctrl+R') - self.reload_btn.clicked.connect(self.reloadClicked) - - # Save script - self.save_btn = QtWidgets.QToolButton() - self.save_btn.setIcon(QtGui.QIcon(icons_path + "icon_save.png")) - self.save_btn.setIconSize(QtCore.QSize(50, 50)) - self.save_btn.setIconSize(self.qt_icon_size) - self.save_btn.setFixedSize(self.qt_btn_size) - self.save_btn.setToolTip( - "Save the script into the selected knob or python file.\nShortcut: Ctrl+S") - self.save_btn.setShortcut('Ctrl+S') - self.save_btn.clicked.connect(self.saveClicked) - - # Layout - self.top_file_bar_layout = QtWidgets.QHBoxLayout() - self.top_file_bar_layout.addWidget(self.refresh_btn) - self.top_file_bar_layout.addWidget(self.reload_btn) - self.top_file_bar_layout.addWidget(self.save_btn) - - # --- - # 2.4. Right Side buttons - - # Run script - self.run_script_button = QtWidgets.QToolButton() - self.run_script_button.setIcon( - QtGui.QIcon(icons_path + "icon_run.png")) - self.run_script_button.setIconSize(self.qt_icon_size) - # self.run_script_button.setIconSize(self.qt_icon_size) - self.run_script_button.setFixedSize(self.qt_btn_size) - self.run_script_button.setToolTip( - "Execute the current selection on the KnobScripter, or the whole script if no selection.\nShortcut: Ctrl+Enter") - self.run_script_button.clicked.connect(self.runScript) - - # Clear console - self.clear_console_button = QtWidgets.QToolButton() - self.clear_console_button.setIcon( - QtGui.QIcon(icons_path + "icon_clearConsole.png")) - self.clear_console_button.setIconSize(QtCore.QSize(50, 50)) - self.clear_console_button.setIconSize(self.qt_icon_size) - self.clear_console_button.setFixedSize(self.qt_btn_size) - self.clear_console_button.setToolTip( - "Clear the text in the console window.\nShortcut: Click Backspace on the console.") - self.clear_console_button.clicked.connect(self.clearConsole) - - # FindReplace button - self.find_button = QtWidgets.QToolButton() - self.find_button.setIcon(QtGui.QIcon(icons_path + "icon_search.png")) - self.find_button.setIconSize(self.qt_icon_size) - self.find_button.setFixedSize(self.qt_btn_size) - self.find_button.setToolTip( - "Call the snippets by writing the shortcut and pressing Tab.\nShortcut: Ctrl+F") - self.find_button.setShortcut('Ctrl+F') - #self.find_button.setMaximumWidth(self.find_button.fontMetrics().boundingRect("Find").width() + 20) - self.find_button.setCheckable(True) - self.find_button.setFocusPolicy(QtCore.Qt.NoFocus) - self.find_button.clicked[bool].connect(self.toggleFRW) - if self.frw_open: - self.find_button.toggle() - - # Snippets - self.snippets_button = QtWidgets.QToolButton() - self.snippets_button.setIcon( - QtGui.QIcon(icons_path + "icon_snippets.png")) - self.snippets_button.setIconSize(QtCore.QSize(50, 50)) - self.snippets_button.setIconSize(self.qt_icon_size) - self.snippets_button.setFixedSize(self.qt_btn_size) - self.snippets_button.setToolTip( - "Call the snippets by writing the shortcut and pressing Tab.") - self.snippets_button.clicked.connect(self.openSnippets) - - # PIN - ''' - self.pin_button = QtWidgets.QPushButton("P") - self.pin_button.setCheckable(True) - if self.pinned: - self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - self.pin_button.toggle() - self.pin_button.setToolTip("Toggle 'Always On Top'. Keeps the KnobScripter on top of all other windows.") - self.pin_button.setFocusPolicy(QtCore.Qt.NoFocus) - self.pin_button.setFixedSize(self.qt_btn_size) - self.pin_button.clicked[bool].connect(self.pin) - ''' - - # Prefs - self.createPrefsMenu() - self.prefs_button = QtWidgets.QPushButton() - self.prefs_button.setIcon(QtGui.QIcon(icons_path + "icon_prefs.png")) - self.prefs_button.setIconSize(self.qt_icon_size) - self.prefs_button.setFixedSize( - QtCore.QSize(self.btn_size + 10, self.btn_size)) - # self.prefs_button.clicked.connect(self.openPrefs) - self.prefs_button.setMenu(self.prefsMenu) - self.prefs_button.setStyleSheet("text-align:left;padding-left:2px;") - #self.prefs_button.setMaximumWidth(self.prefs_button.fontMetrics().boundingRect("Prefs").width() + 12) - - # Layout - self.top_right_bar_layout = QtWidgets.QHBoxLayout() - self.top_right_bar_layout.addWidget(self.run_script_button) - self.top_right_bar_layout.addWidget(self.clear_console_button) - self.top_right_bar_layout.addWidget(self.find_button) - # self.top_right_bar_layout.addWidget(self.snippets_button) - # self.top_right_bar_layout.addWidget(self.pin_button) - # self.top_right_bar_layout.addSpacing(10) - self.top_right_bar_layout.addWidget(self.prefs_button) - - # --- - # Layout - self.top_layout = QtWidgets.QHBoxLayout() - self.top_layout.setContentsMargins(0, 0, 0, 0) - # self.top_layout.setSpacing(10) - self.top_layout.addWidget(self.change_btn) - self.top_layout.addWidget(self.node_mode_bar) - self.top_layout.addWidget(self.script_mode_bar) - self.node_mode_bar.setVisible(False) - # self.top_layout.addSpacing(10) - self.top_layout.addLayout(self.top_file_bar_layout) - self.top_layout.addStretch() - self.top_layout.addLayout(self.top_right_bar_layout) - - # ---------------------- - # 3. SCRIPTING SECTION - # ---------------------- - # Splitter - self.splitter = QtWidgets.QSplitter(Qt.Vertical) - - # Output widget - self.script_output = ScriptOutputWidget(parent=self) - self.script_output.setReadOnly(1) - self.script_output.setAcceptRichText(0) - self.script_output.setTabStopWidth( - self.script_output.tabStopWidth() / 4) - self.script_output.setFocusPolicy(Qt.ClickFocus) - self.script_output.setAutoFillBackground(0) - self.script_output.installEventFilter(self) - - # Script Editor - self.script_editor = KnobScripterTextEditMain(self, self.script_output) - self.script_editor.setMinimumHeight(30) - self.script_editor.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.script_editor.textChanged.connect(self.setModified) - self.highlighter = KSScriptEditorHighlighter( - self.script_editor.document(), self) - self.script_editor.cursorPositionChanged.connect(self.setTextSelection) - self.script_editor_font = QtGui.QFont() - self.script_editor_font.setFamily(self.font) - self.script_editor_font.setStyleHint(QtGui.QFont.Monospace) - self.script_editor_font.setFixedPitch(True) - self.script_editor_font.setPointSize(self.fontSize) - self.script_editor.setFont(self.script_editor_font) - self.script_editor.setTabStopWidth( - self.tabSpaces * QtGui.QFontMetrics(self.script_editor_font).width(' ')) - - # Add input and output to splitter - self.splitter.addWidget(self.script_output) - self.splitter.addWidget(self.script_editor) - self.splitter.setStretchFactor(0, 0) - - # FindReplace widget - self.frw = FindReplaceWidget(self) - self.frw.setVisible(self.frw_open) - - # --- - # Layout - self.scripting_layout = QtWidgets.QVBoxLayout() - self.scripting_layout.setContentsMargins(0, 0, 0, 0) - self.scripting_layout.setSpacing(0) - self.scripting_layout.addWidget(self.splitter) - self.scripting_layout.addWidget(self.frw) - - # --------------- - # MASTER LAYOUT - # --------------- - self.master_layout = QtWidgets.QVBoxLayout() - self.master_layout.setSpacing(5) - self.master_layout.setContentsMargins(8, 8, 8, 8) - self.master_layout.addLayout(self.top_layout) - self.master_layout.addLayout(self.scripting_layout) - # self.master_layout.addLayout(self.bottom_layout) - self.setLayout(self.master_layout) - - # ---------------- - # MAIN WINDOW UI - # ---------------- - size_policy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) - self.setSizePolicy(size_policy) - self.setMinimumWidth(160) - - if self.pinned: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - - # Set default values based on mode - if self.nodeMode: - self.current_knob_dropdown.blockSignals(True) - self.node_mode_bar.setVisible(True) - self.script_mode_bar.setVisible(False) - self.setCurrentKnob(self.knob) - self.loadKnobValue(check=False) - self.setKnobModified(False) - self.current_knob_dropdown.blockSignals(False) - self.splitter.setSizes([0, 1]) - else: - self.exitNodeMode() - self.script_editor.setFocus() - - # Preferences submenus - def createPrefsMenu(self): - - # Actions - self.echoAct = QtWidgets.QAction("Echo python commands", self, checkable=True, - statusTip="Toggle nuke's 'Echo all python commands to ScriptEditor'", triggered=self.toggleEcho) - if nuke.toNode("preferences").knob("echoAllCommands").value(): - self.echoAct.toggle() - self.pinAct = QtWidgets.QAction("Always on top", self, checkable=True, - statusTip="Keeps the KnobScripter window always on top or not.", triggered=self.togglePin) - if self.pinned: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.pinAct.toggle() - self.helpAct = QtWidgets.QAction( - "&Help", self, statusTip="Open the KnobScripter help in your browser.", shortcut="F1", triggered=self.showHelp) - self.nukepediaAct = QtWidgets.QAction( - "Show in Nukepedia", self, statusTip="Open the KnobScripter download page on Nukepedia.", triggered=self.showInNukepedia) - self.githubAct = QtWidgets.QAction( - "Show in GitHub", self, statusTip="Open the KnobScripter repo on GitHub.", triggered=self.showInGithub) - self.snippetsAct = QtWidgets.QAction( - "Snippets", self, statusTip="Open the Snippets editor.", triggered=self.openSnippets) - self.snippetsAct.setIcon(QtGui.QIcon(icons_path + "icon_snippets.png")) - # self.snippetsAct = QtWidgets.QAction("Keywords", self, statusTip="Add custom keywords.", triggered=self.openSnippets) #TODO THIS - self.prefsAct = QtWidgets.QAction( - "Preferences", self, statusTip="Open the Preferences panel.", triggered=self.openPrefs) - self.prefsAct.setIcon(QtGui.QIcon(icons_path + "icon_prefs.png")) - - # Menus - self.prefsMenu = QtWidgets.QMenu("Preferences") - self.prefsMenu.addAction(self.echoAct) - self.prefsMenu.addAction(self.pinAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.nukepediaAct) - self.prefsMenu.addAction(self.githubAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.helpAct) - self.prefsMenu.addSeparator() - self.prefsMenu.addAction(self.snippetsAct) - self.prefsMenu.addAction(self.prefsAct) - - def initEcho(self): - ''' Initializes the echo chechable QAction based on nuke's state ''' - echo_knob = nuke.toNode("preferences").knob("echoAllCommands") - self.echoAct.setChecked(echo_knob.value()) - - def toggleEcho(self): - ''' Toggle the "Echo python commands" from Nuke ''' - echo_knob = nuke.toNode("preferences").knob("echoAllCommands") - echo_knob.setValue(self.echoAct.isChecked()) - - def togglePin(self): - ''' Toggle "always on top" based on the submenu button ''' - self.pin(self.pinAct.isChecked()) - - def showInNukepedia(self): - openUrl("http://www.nukepedia.com/python/ui/knobscripter") - - def showInGithub(self): - openUrl("https://github.com/adrianpueyo/KnobScripter") - - def showHelp(self): - openUrl("https://vimeo.com/adrianpueyo/knobscripter2") - - # Node Mode - - def updateKnobDropdown(self): - ''' Populate knob dropdown list ''' - self.current_knob_dropdown.clear() # First remove all items - defaultKnobs = ["knobChanged", "onCreate", "onScriptLoad", "onScriptSave", "onScriptClose", "onDestroy", - "updateUI", "autolabel", "beforeRender", "beforeFrameRender", "afterFrameRender", "afterRender"] - permittedKnobClasses = ["PyScript_Knob", "PythonCustomKnob"] - counter = 0 - for i in self.node.knobs(): - if i not in defaultKnobs and self.node.knob(i).Class() in permittedKnobClasses: - if self.show_labels: - i_full = "{} ({})".format(self.node.knob(i).label(), i) - else: - i_full = i - - if i in self.unsavedKnobs.keys(): - self.current_knob_dropdown.addItem(i_full + "(*)", i) - else: - self.current_knob_dropdown.addItem(i_full, i) - - counter += 1 - if counter > 0: - self.current_knob_dropdown.insertSeparator(counter) - counter += 1 - self.current_knob_dropdown.insertSeparator(counter) - counter += 1 - for i in self.node.knobs(): - if i in defaultKnobs: - if i in self.unsavedKnobs.keys(): - self.current_knob_dropdown.addItem(i + "(*)", i) - else: - self.current_knob_dropdown.addItem(i, i) - counter += 1 - return - - def loadKnobValue(self, check=True, updateDict=False): - ''' Get the content of the knob value and populate the editor ''' - if self.toLoadKnob == False: - return - dropdown_value = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) # knobChanged... - try: - obtained_knobValue = str(self.node[dropdown_value].value()) - obtained_scrollValue = 0 - edited_knobValue = self.script_editor.toPlainText() - except: - error_message = QtWidgets.QMessageBox.information( - None, "", "Unable to find %s.%s" % (self.node.name(), dropdown_value)) - error_message.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - error_message.exec_() - return - # If there were changes to the previous knob, update the dictionary - if updateDict == True: - self.unsavedKnobs[self.knob] = edited_knobValue - self.scrollPos[self.knob] = self.script_editor.verticalScrollBar( - ).value() - prev_knob = self.knob # knobChanged... - - self.knob = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) # knobChanged... - - if check and obtained_knobValue != edited_knobValue: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The Script Editor has been modified.") - msgBox.setInformativeText( - "Do you want to overwrite the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - self.setCurrentKnob(prev_knob) - return - # If order comes from a dropdown update, update value from dictionary if possible, otherwise update normally - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.name(), self.knob)) - if updateDict: - if self.knob in self.unsavedKnobs: - if self.unsavedKnobs[self.knob] == obtained_knobValue: - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(False) - else: - obtained_knobValue = self.unsavedKnobs[self.knob] - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(True) - else: - self.script_editor.setPlainText(obtained_knobValue) - self.setKnobModified(False) - - if self.knob in self.scrollPos: - obtained_scrollValue = self.scrollPos[self.knob] - else: - self.script_editor.setPlainText(obtained_knobValue) - - cursor = self.script_editor.textCursor() - self.script_editor.setTextCursor(cursor) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - return - - def loadAllKnobValues(self): - ''' Load all knobs button's function ''' - if len(self.unsavedKnobs) >= 1: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Do you want to reload all python and callback knobs?") - msgBox.setInformativeText( - "Unsaved changes on this editor will be lost.") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - self.unsavedKnobs = {} - return - - def saveKnobValue(self, check=True): - ''' Save the text from the editor to the node's knobChanged knob ''' - dropdown_value = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()) - try: - obtained_knobValue = str(self.node[dropdown_value].value()) - self.knob = dropdown_value - except: - error_message = QtWidgets.QMessageBox.information( - None, "", "Unable to find %s.%s" % (self.node.name(), dropdown_value)) - error_message.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - error_message.exec_() - return - edited_knobValue = self.script_editor.toPlainText() - if check and obtained_knobValue != edited_knobValue: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("Do you want to overwrite %s.%s?" % - (self.node.name(), dropdown_value)) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - self.node[dropdown_value].setValue(edited_knobValue) - self.setKnobModified( - modified=False, knob=dropdown_value, changeTitle=True) - nuke.tcl("modified 1") - if self.knob in self.unsavedKnobs: - del self.unsavedKnobs[self.knob] - return - - def saveAllKnobValues(self, check=True): - ''' Save all knobs button's function ''' - if self.updateUnsavedKnobs() > 0 and check: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Do you want to save all modified python and callback knobs?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - saveErrors = 0 - savedCount = 0 - for k in self.unsavedKnobs.copy(): - try: - self.node.knob(k).setValue(self.unsavedKnobs[k]) - del self.unsavedKnobs[k] - savedCount += 1 - nuke.tcl("modified 1") - except: - saveErrors += 1 - if saveErrors > 0: - errorBox = QtWidgets.QMessageBox() - errorBox.setText("Error saving %s knob%s." % - (str(saveErrors), int(saveErrors > 1) * "s")) - errorBox.setIcon(QtWidgets.QMessageBox.Warning) - errorBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - errorBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = errorBox.exec_() - else: - log("KnobScripter: %s knobs saved" % str(savedCount)) - return - - def setCurrentKnob(self, knobToSet): - ''' Set current knob ''' - KnobDropdownItems = [] - for i in range(self.current_knob_dropdown.count()): - if self.current_knob_dropdown.itemData(i) is not None: - KnobDropdownItems.append( - self.current_knob_dropdown.itemData(i)) - else: - KnobDropdownItems.append("---") - if knobToSet in KnobDropdownItems: - index = KnobDropdownItems.index(knobToSet) - self.current_knob_dropdown.setCurrentIndex(index) - return - - def updateUnsavedKnobs(self, first_time=False): - ''' Clear unchanged knobs from the dict and return the number of unsaved knobs ''' - if not self.node: - # Node has been deleted, so simply return 0. Who cares. - return 0 - edited_knobValue = self.script_editor.toPlainText() - self.unsavedKnobs[self.knob] = edited_knobValue - if len(self.unsavedKnobs) > 0: - for k in self.unsavedKnobs.copy(): - if self.node.knob(k): - if str(self.node.knob(k).value()) == str(self.unsavedKnobs[k]): - del self.unsavedKnobs[k] - else: - del self.unsavedKnobs[k] - # Set appropriate knobs modified... - knobs_dropdown = self.current_knob_dropdown - all_knobs = [knobs_dropdown.itemData(i) - for i in range(knobs_dropdown.count())] - for key in all_knobs: - if key in self.unsavedKnobs.keys(): - self.setKnobModified( - modified=True, knob=key, changeTitle=False) - else: - self.setKnobModified( - modified=False, knob=key, changeTitle=False) - - return len(self.unsavedKnobs) - - def setKnobModified(self, modified=True, knob="", changeTitle=True): - ''' Sets the current knob modified, title and whatever else we need ''' - if knob == "": - knob = self.knob - if modified: - self.modifiedKnobs.add(knob) - else: - self.modifiedKnobs.discard(knob) - - if changeTitle: - title_modified_string = " [modified]" - windowTitle = self.windowTitle().split(title_modified_string)[0] - if modified == True: - windowTitle += title_modified_string - self.setWindowTitle(windowTitle) - - try: - knobs_dropdown = self.current_knob_dropdown - kd_index = knobs_dropdown.currentIndex() - kd_data = knobs_dropdown.itemData(kd_index) - if self.show_labels and i not in defaultKnobs: - kd_data = "{} ({})".format( - self.node.knob(kd_data).label(), kd_data) - if modified == False: - knobs_dropdown.setItemText(kd_index, kd_data) - else: - knobs_dropdown.setItemText(kd_index, kd_data + "(*)") - except: - pass - - # Script Mode - def updateFoldersDropdown(self): - ''' Populate folders dropdown list ''' - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.clear() # First remove all items - defaultFolders = ["scripts"] - scriptFolders = [] - counter = 0 - for f in defaultFolders: - self.makeScriptFolder(f) - self.current_folder_dropdown.addItem(f + "/", f) - counter += 1 - - try: - scriptFolders = sorted([f for f in os.listdir(self.scripts_dir) if os.path.isdir( - os.path.join(self.scripts_dir, f))]) # Accepts symlinks!!! - except: - log("Couldn't read any script folders.") - - for f in scriptFolders: - fname = f.split("/")[-1] - if fname in defaultFolders: - continue - self.current_folder_dropdown.addItem(fname + "/", fname) - counter += 1 - - # print scriptFolders - if counter > 0: - self.current_folder_dropdown.insertSeparator(counter) - counter += 1 - # self.current_folder_dropdown.insertSeparator(counter) - #counter += 1 - self.current_folder_dropdown.addItem("New", "create new") - self.current_folder_dropdown.addItem("Open...", "open in browser") - self.current_folder_dropdown.addItem("Add custom", "add custom path") - self.folder_index = self.current_folder_dropdown.currentIndex() - self.current_folder = self.current_folder_dropdown.itemData( - self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - def updateScriptsDropdown(self): - ''' Populate py scripts dropdown list ''' - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.clear() # First remove all items - QtWidgets.QApplication.processEvents() - log("# Updating scripts dropdown...") - log("scripts dir:" + self.scripts_dir) - log("current folder:" + self.current_folder) - log("previous current script:" + self.current_script) - #current_folder = self.current_folder_dropdown.itemData(self.current_folder_dropdown.currentIndex()) - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder) - defaultScripts = ["Untitled.py"] - found_scripts = [] - counter = 0 - # All files and folders inside of the folder - dir_list = os.listdir(current_folder_path) - try: - found_scripts = sorted([f for f in dir_list if f.endswith(".py")]) - found_temp_scripts = [ - f for f in dir_list if f.endswith(".py.autosave")] - except: - log("Couldn't find any scripts in the selected folder.") - if not len(found_scripts): - for s in defaultScripts: - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(s + "(*)", s) - else: - self.current_script_dropdown.addItem(s, s) - counter += 1 - else: - for s in defaultScripts: - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(s + "(*)", s) - elif s in found_scripts: - self.current_script_dropdown.addItem(s, s) - for s in found_scripts: - if s in defaultScripts: - continue - sname = s.split("/")[-1] - if s + ".autosave" in found_temp_scripts: - self.current_script_dropdown.addItem(sname + "(*)", sname) - else: - self.current_script_dropdown.addItem(sname, sname) - counter += 1 - # else: #Add the found scripts to the dropdown - if counter > 0: - counter += 1 - self.current_script_dropdown.insertSeparator(counter) - counter += 1 - self.current_script_dropdown.insertSeparator(counter) - self.current_script_dropdown.addItem("New", "create new") - self.current_script_dropdown.addItem("Duplicate", "create duplicate") - self.current_script_dropdown.addItem("Delete", "delete script") - self.current_script_dropdown.addItem("Open", "open in browser") - #self.script_index = self.current_script_dropdown.currentIndex() - self.script_index = 0 - self.current_script = self.current_script_dropdown.itemData( - self.script_index) - log("Finished updating scripts dropdown.") - log("current_script:" + self.current_script) - self.current_script_dropdown.blockSignals(False) - return - - def makeScriptFolder(self, name="scripts"): - folder_path = os.path.join(self.scripts_dir, name) - if not os.path.exists(folder_path): - try: - os.makedirs(folder_path) - return True - except: - print "Couldn't create the scripting folders.\nPlease check your OS write permissions." - return False - - def makeScriptFile(self, name="Untitled.py", folder="scripts", empty=True): - script_path = os.path.join(self.scripts_dir, self.current_folder, name) - if not os.path.isfile(script_path): - try: - self.current_script_file = open(script_path, 'w') - return True - except: - print "Couldn't create the scripting folders.\nPlease check your OS write permissions." - return False - - def setCurrentFolder(self, folderName): - ''' Set current folder ON THE DROPDOWN ONLY''' - folderList = [self.current_folder_dropdown.itemData( - i) for i in range(self.current_folder_dropdown.count())] - if folderName in folderList: - index = folderList.index(folderName) - self.current_folder_dropdown.setCurrentIndex(index) - self.current_folder = folderName - self.folder_index = self.current_folder_dropdown.currentIndex() - self.current_folder = self.current_folder_dropdown.itemData( - self.folder_index) - return - - def setCurrentScript(self, scriptName): - ''' Set current script ON THE DROPDOWN ONLY ''' - scriptList = [self.current_script_dropdown.itemData( - i) for i in range(self.current_script_dropdown.count())] - if scriptName in scriptList: - index = scriptList.index(scriptName) - self.current_script_dropdown.setCurrentIndex(index) - self.current_script = scriptName - self.script_index = self.current_script_dropdown.currentIndex() - self.current_script = self.current_script_dropdown.itemData( - self.script_index) - return - - def loadScriptContents(self, check=False, pyOnly=False, folder=""): - ''' Get the contents of the selected script and populate the editor ''' - log("# About to load script contents now.") - obtained_scrollValue = 0 - obtained_cursorPosValue = [0, 0] # Position, anchor - if folder == "": - folder = self.current_folder - script_path = os.path.join( - self.scripts_dir, folder, self.current_script) - script_path_temp = script_path + ".autosave" - if (self.current_folder + "/" + self.current_script) in self.scrollPos: - obtained_scrollValue = self.scrollPos[self.current_folder + - "/" + self.current_script] - if (self.current_folder + "/" + self.current_script) in self.cursorPos: - obtained_cursorPosValue = self.cursorPos[self.current_folder + - "/" + self.current_script] - - # 1: If autosave exists and pyOnly is false, load it - if os.path.isfile(script_path_temp) and not pyOnly: - log("Loading .py.autosave file\n---") - with open(script_path_temp, 'r') as script: - content = script.read() - self.script_editor.setPlainText(content) - self.setScriptModified(True) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - - # 2: Try to load the .py as first priority, if it exists - elif os.path.isfile(script_path): - log("Loading .py file\n---") - with open(script_path, 'r') as script: - content = script.read() - current_text = self.script_editor.toPlainText().encode("utf8") - if check and current_text != content and current_text.strip() != "": - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The script has been modified.") - msgBox.setInformativeText( - "Do you want to overwrite the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - # Clear trash - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.setScriptModified(False) - self.script_editor.setPlainText(content) - self.script_editor.verticalScrollBar().setValue(obtained_scrollValue) - self.setScriptModified(False) - self.loadScriptState() - self.setScriptState() - - # 3: If .py doesn't exist... only then stick to the autosave - elif os.path.isfile(script_path_temp): - with open(script_path_temp, 'r') as script: - content = script.read() - - msgBox = QtWidgets.QMessageBox() - msgBox.setText("The .py file hasn't been found.") - msgBox.setInformativeText( - "Do you want to clear the current code on this editor?") - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return - - # Clear trash - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.script_editor.setPlainText("") - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - self.loadScriptState() - self.setScriptState() - - else: - content = "" - self.script_editor.setPlainText(content) - self.setScriptModified(False) - if self.current_folder + "/" + self.current_script in self.scrollPos: - del self.scrollPos[self.current_folder + - "/" + self.current_script] - if self.current_folder + "/" + self.current_script in self.cursorPos: - del self.cursorPos[self.current_folder + - "/" + self.current_script] - - self.setWindowTitle("KnobScripter - %s/%s" % - (self.current_folder, self.current_script)) - return - - def saveScriptContents(self, temp=True): - ''' Save the current contents of the editor into the python file. If temp == True, saves a .py.autosave file ''' - log("\n# About to save script contents now.") - log("Temp mode is: " + str(temp)) - log("self.current_folder: " + self.current_folder) - log("self.current_script: " + self.current_script) - script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - script_path_temp = script_path + ".autosave" - orig_content = "" - content = self.script_editor.toPlainText().encode('utf8') - - if temp == True: - if os.path.isfile(script_path): - with open(script_path, 'r') as script: - orig_content = script.read() - # If script path doesn't exist and autosave does but the script is empty... - elif content == "" and os.path.isfile(script_path_temp): - os.remove(script_path_temp) - return - if content != orig_content: - with open(script_path_temp, 'w') as script: - script.write(content) - else: - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Nothing to save") - return - else: - with open(script_path, 'w') as script: - script.write(self.script_editor.toPlainText().encode('utf8')) - # Clear trash - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - self.setScriptModified(False) - self.saveScrollValue() - self.saveCursorPosValue() - log("Saved " + script_path + "\n---") - return - - def deleteScript(self, check=True, folder=""): - ''' Get the contents of the selected script and populate the editor ''' - log("# About to delete the .py and/or autosave script now.") - if folder == "": - folder = self.current_folder - script_path = os.path.join( - self.scripts_dir, folder, self.current_script) - script_path_temp = script_path + ".autosave" - if check: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("You're about to delete this script.") - msgBox.setInformativeText( - "Are you sure you want to delete {}?".format(self.current_script)) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) - msgBox.setIcon(QtWidgets.QMessageBox.Question) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.No) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.No: - return False - - if os.path.isfile(script_path_temp): - os.remove(script_path_temp) - log("Removed " + script_path_temp) - - if os.path.isfile(script_path): - os.remove(script_path) - log("Removed " + script_path) - - return True - - def folderDropdownChanged(self): - '''Executed when the current folder dropdown is changed''' - self.saveScriptState() - log("# folder dropdown changed") - folders_dropdown = self.current_folder_dropdown - fd_value = folders_dropdown.currentText() - fd_index = folders_dropdown.currentIndex() - fd_data = folders_dropdown.itemData(fd_index) - if fd_data == "create new": - panel = FileNameDialog(self, mode="folder") - # panel.setWidth(260) - # panel.addSingleLineInput("Name:","") - if panel.exec_(): - # Accepted - folder_name = panel.text - if os.path.isdir(os.path.join(self.scripts_dir, folder_name)): - self.messageBox("Folder already exists.") - self.setCurrentFolder(self.current_folder) - if self.makeScriptFolder(name=folder_name): - self.saveScriptContents(temp=True) - # Success creating the folder - self.current_folder = folder_name - self.updateFoldersDropdown() - self.setCurrentFolder(folder_name) - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - else: - self.messageBox("There was a problem creating the folder.") - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex( - self.folder_index) - self.current_folder_dropdown.blockSignals(False) - else: - # Canceled/rejected - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - elif fd_data == "open in browser": - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder) - self.openInFileBrowser(current_folder_path) - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - return - - elif fd_data == "add custom path": - folder_path = nuke.getFilename('Select custom folder.') - if folder_path is not None: - if folder_path.endswith("/"): - aliasName = folder_path.split("/")[-2] - else: - aliasName = folder_path.split("/")[-1] - if not os.path.isdir(folder_path): - self.messageBox( - "Folder not found. Please try again with the full path to a folder.") - elif not len(aliasName): - self.messageBox( - "Folder with the same name already exists. Please delete or rename it first.") - else: - # All good - os.symlink(folder_path, os.path.join( - self.scripts_dir, aliasName)) - self.saveScriptContents(temp=True) - self.current_folder = aliasName - self.updateFoldersDropdown() - self.setCurrentFolder(aliasName) - self.updateScriptsDropdown() - self.loadScriptContents(check=False) - self.script_editor.setFocus() - return - self.current_folder_dropdown.blockSignals(True) - self.current_folder_dropdown.setCurrentIndex(self.folder_index) - self.current_folder_dropdown.blockSignals(False) - else: - # 1: Save current script as temp if needed - self.saveScriptContents(temp=True) - # 2: Set the new folder in the variables - self.current_folder = fd_data - self.folder_index = fd_index - # 3: Update the scripts dropdown - self.updateScriptsDropdown() - # 4: Load the current script! - self.loadScriptContents() - self.script_editor.setFocus() - - self.loadScriptState() - self.setScriptState() - - return - - def scriptDropdownChanged(self): - '''Executed when the current script dropdown is changed. Should only be called by the manual dropdown change. Not by other functions.''' - self.saveScriptState() - scripts_dropdown = self.current_script_dropdown - sd_value = scripts_dropdown.currentText() - sd_index = scripts_dropdown.currentIndex() - sd_data = scripts_dropdown.itemData(sd_index) - if sd_data == "create new": - self.current_script_dropdown.blockSignals(True) - panel = FileNameDialog(self, mode="script") - if panel.exec_(): - # Accepted - script_name = panel.text + ".py" - script_path = os.path.join( - self.scripts_dir, self.current_folder, script_name) - log(script_name) - log(script_path) - if os.path.isfile(script_path): - self.messageBox("Script already exists.") - self.current_script_dropdown.setCurrentIndex( - self.script_index) - if self.makeScriptFile(name=script_name, folder=self.current_folder): - # Success creating the folder - self.saveScriptContents(temp=True) - self.updateScriptsDropdown() - if self.current_script != "Untitled.py": - self.script_editor.setPlainText("") - self.current_script = script_name - self.setCurrentScript(script_name) - self.saveScriptContents(temp=False) - # self.loadScriptContents() - else: - self.messageBox("There was a problem creating the script.") - self.current_script_dropdown.setCurrentIndex( - self.script_index) - else: - # Canceled/rejected - self.current_script_dropdown.setCurrentIndex(self.script_index) - return - self.current_script_dropdown.blockSignals(False) - - elif sd_data == "create duplicate": - self.current_script_dropdown.blockSignals(True) - current_folder_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - current_script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - - current_name = self.current_script - if self.current_script.endswith(".py"): - current_name = current_name[:-3] - - test_name = current_name - while True: - test_name += "_copy" - new_script_path = os.path.join( - self.scripts_dir, self.current_folder, test_name + ".py") - if not os.path.isfile(new_script_path): - break - - script_name = test_name + ".py" - - if self.makeScriptFile(name=script_name, folder=self.current_folder): - # Success creating the folder - self.saveScriptContents(temp=True) - self.updateScriptsDropdown() - # self.script_editor.setPlainText("") - self.current_script = script_name - self.setCurrentScript(script_name) - self.script_editor.setFocus() - else: - self.messageBox("There was a problem duplicating the script.") - self.current_script_dropdown.setCurrentIndex(self.script_index) - - self.current_script_dropdown.blockSignals(False) - - elif sd_data == "open in browser": - current_script_path = os.path.join( - self.scripts_dir, self.current_folder, self.current_script) - self.openInFileBrowser(current_script_path) - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.setCurrentIndex(self.script_index) - self.current_script_dropdown.blockSignals(False) - return - - elif sd_data == "delete script": - if self.deleteScript(): - self.updateScriptsDropdown() - self.loadScriptContents() - else: - self.current_script_dropdown.blockSignals(True) - self.current_script_dropdown.setCurrentIndex(self.script_index) - self.current_script_dropdown.blockSignals(False) - - else: - self.saveScriptContents() - self.current_script = sd_data - self.script_index = sd_index - self.setCurrentScript(self.current_script) - self.loadScriptContents() - self.script_editor.setFocus() - self.loadScriptState() - self.setScriptState() - return - - def setScriptModified(self, modified=True): - ''' Sets self.current_script_modified, title and whatever else we need ''' - self.current_script_modified = modified - title_modified_string = " [modified]" - windowTitle = self.windowTitle().split(title_modified_string)[0] - if modified == True: - windowTitle += title_modified_string - self.setWindowTitle(windowTitle) - try: - scripts_dropdown = self.current_script_dropdown - sd_index = scripts_dropdown.currentIndex() - sd_data = scripts_dropdown.itemData(sd_index) - if modified == False: - scripts_dropdown.setItemText(sd_index, sd_data) - else: - scripts_dropdown.setItemText(sd_index, sd_data + "(*)") - except: - pass - - def openInFileBrowser(self, path=""): - OS = platform.system() - if not os.path.exists(path): - path = KS_DIR - if OS == "Windows": - os.startfile(path) - elif OS == "Darwin": - subprocess.Popen(["open", path]) - else: - subprocess.Popen(["xdg-open", path]) - - def loadScriptState(self): - ''' - Loads the last state of the script from a file inside the SE directory's root. - SAVES self.scroll_pos, self.cursor_pos, self.last_open_script - ''' - self.state_dict = {} - if not os.path.isfile(self.state_txt_path): - return False - else: - with open(self.state_txt_path, "r") as f: - self.state_dict = json.load(f) - - log("Loading script state into self.state_dict, self.scrollPos, self.cursorPos") - log(self.state_dict) - - if "scroll_pos" in self.state_dict: - self.scrollPos = self.state_dict["scroll_pos"] - if "cursor_pos" in self.state_dict: - self.cursorPos = self.state_dict["cursor_pos"] - - def setScriptState(self): - ''' - Sets the already script state from self.state_dict into the current script if applicable - ''' - script_fullname = self.current_folder + "/" + self.current_script - - if "scroll_pos" in self.state_dict: - if script_fullname in self.state_dict["scroll_pos"]: - self.script_editor.verticalScrollBar().setValue( - int(self.state_dict["scroll_pos"][script_fullname])) - - if "cursor_pos" in self.state_dict: - if script_fullname in self.state_dict["cursor_pos"]: - cursor = self.script_editor.textCursor() - cursor.setPosition(int( - self.state_dict["cursor_pos"][script_fullname][1]), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(int( - self.state_dict["cursor_pos"][script_fullname][0]), QtGui.QTextCursor.KeepAnchor) - self.script_editor.setTextCursor(cursor) - - if 'splitter_sizes' in self.state_dict: - self.splitter.setSizes(self.state_dict['splitter_sizes']) - - def setLastScript(self): - if 'last_folder' in self.state_dict and 'last_script' in self.state_dict: - self.updateFoldersDropdown() - self.setCurrentFolder(self.state_dict['last_folder']) - self.updateScriptsDropdown() - self.setCurrentScript(self.state_dict['last_script']) - self.loadScriptContents() - self.script_editor.setFocus() - - def saveScriptState(self): - ''' Stores the current state of the script into a file inside the SE directory's root ''' - log("About to save script state...") - ''' - # self.state_dict = {} - if os.path.isfile(self.state_txt_path): - with open(self.state_txt_path, "r") as f: - self.state_dict = json.load(f) - - if "scroll_pos" in self.state_dict: - self.scrollPos = self.state_dict["scroll_pos"] - if "cursor_pos" in self.state_dict: - self.cursorPos = self.state_dict["cursor_pos"] - - ''' - self.loadScriptState() - - # Overwrite current values into the scriptState - self.saveScrollValue() - self.saveCursorPosValue() - - self.state_dict['scroll_pos'] = self.scrollPos - self.state_dict['cursor_pos'] = self.cursorPos - self.state_dict['last_folder'] = self.current_folder - self.state_dict['last_script'] = self.current_script - self.state_dict['splitter_sizes'] = self.splitter.sizes() - - with open(self.state_txt_path, "w") as f: - state = json.dump(self.state_dict, f, sort_keys=True, indent=4) - return state - - # Autosave background loop - def autosave(self): - if self.toAutosave: - # Save the script... - self.saveScriptContents() - self.toAutosave = False - self.saveScriptState() - log("autosaving...") - return - - # Global stuff - def setTextSelection(self): - self.highlighter.selected_text = self.script_editor.textCursor().selection().toPlainText() - return - - def eventFilter(self, object, event): - if event.type() == QtCore.QEvent.KeyPress: - return QtWidgets.QWidget.eventFilter(self, object, event) - else: - return QtWidgets.QWidget.eventFilter(self, object, event) - - def resizeEvent(self, res_event): - w = self.frameGeometry().width() - self.current_node_label_node.setVisible(w > 460) - self.script_label.setVisible(w > 460) - return super(KnobScripter, self).resizeEvent(res_event) - - def changeClicked(self, newNode=""): - ''' Change node ''' - try: - print "Changing from " + self.node.name() - except: - self.node = None - if not len(nuke.selectedNodes()): - self.exitNodeMode() - return - nuke.menu("Nuke").findItem( - "Edit/Node/Update KnobScripter Context").invoke() - selection = knobScripterSelectedNodes - if self.nodeMode: # Only update the number of unsaved knobs if we were already in node mode - if self.node is not None: - updatedCount = self.updateUnsavedKnobs() - else: - updatedCount = 0 - else: - updatedCount = 0 - self.autosave() - if newNode != "" and nuke.exists(newNode): - selection = [newNode] - elif not len(selection): - node_dialog = ChooseNodeDialog(self) - if node_dialog.exec_(): - # Accepted - selection = [nuke.toNode(node_dialog.name)] - else: - return - - # Change to node mode... - self.node_mode_bar.setVisible(True) - self.script_mode_bar.setVisible(False) - if not self.nodeMode: - self.saveScriptContents() - self.toAutosave = False - self.saveScriptState() - self.splitter.setSizes([0, 1]) - self.nodeMode = True - - # If already selected, pass - if self.node is not None and selection[0].fullName() == self.node.fullName(): - self.messageBox("Please select a different node first!") - return - elif updatedCount > 0: - msgBox = QtWidgets.QMessageBox() - msgBox.setText( - "Save changes to %s knob%s before changing the node?" % (str(updatedCount), int(updatedCount > 1) * "s")) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.Yes: - self.saveAllKnobValues(check=False) - elif reply == QtWidgets.QMessageBox.Cancel: - return - if len(selection) > 1: - self.messageBox( - "More than one node selected.\nChanging knobChanged editor to %s" % selection[0].fullName()) - # Reinitialise everything, wooo! - self.current_knob_dropdown.blockSignals(True) - self.node = selection[0] - - self.script_editor.setPlainText("") - self.unsavedKnobs = {} - self.scrollPos = {} - self.setWindowTitle("KnobScripter - %s %s" % - (self.node.fullName(), self.knob)) - self.current_node_label_name.setText(self.node.fullName()) - - self.toLoadKnob = False - self.updateKnobDropdown() # onee - # self.current_knob_dropdown.repaint() - # self.current_knob_dropdown.setMinimumWidth(self.current_knob_dropdown.minimumSizeHint().width()) - self.toLoadKnob = True - self.setCurrentKnob(self.knob) - self.loadKnobValue(False) - self.script_editor.setFocus() - self.setKnobModified(False) - self.current_knob_dropdown.blockSignals(False) - # self.current_knob_dropdown.setMinimumContentsLength(80) - return - - def exitNodeMode(self): - self.nodeMode = False - self.setWindowTitle("KnobScripter - Script Mode") - self.node_mode_bar.setVisible(False) - self.script_mode_bar.setVisible(True) - self.node = nuke.toNode("root") - # self.updateFoldersDropdown() - # self.updateScriptsDropdown() - self.splitter.setSizes([1, 1]) - self.loadScriptState() - self.setLastScript() - - self.loadScriptContents(check=False) - self.setScriptState() - - def clearConsole(self): - self.origConsoleText = self.nukeSEOutput.document().toPlainText().encode("utf8") - self.script_output.setPlainText("") - - def toggleFRW(self, frw_pressed): - self.frw_open = frw_pressed - self.frw.setVisible(self.frw_open) - if self.frw_open: - self.frw.find_lineEdit.setFocus() - self.frw.find_lineEdit.selectAll() - else: - self.script_editor.setFocus() - return - - def openSnippets(self): - ''' Whenever the 'snippets' button is pressed... open the panel ''' - global SnippetEditPanel - if SnippetEditPanel == "": - SnippetEditPanel = SnippetsPanel(self) - - if not SnippetEditPanel.isVisible(): - SnippetEditPanel.reload() - - if SnippetEditPanel.show(): - self.snippets = self.loadSnippets(maxDepth=5) - SnippetEditPanel = "" - - def loadSnippets(self, path="", maxDepth=5, depth=0): - ''' - Load prefs recursive. When maximum recursion depth, ignores paths. - ''' - max_depth = maxDepth - cur_depth = depth - if path == "": - path = self.snippets_txt_path - if not os.path.isfile(path): - return {} - else: - loaded_snippets = {} - with open(path, "r") as f: - file = json.load(f) - for i, (key, val) in enumerate(file.items()): - if re.match(r"\[custom-path-[0-9]+\]$", key): - if cur_depth < max_depth: - new_dict = self.loadSnippets( - path=val, maxDepth=max_depth, depth=cur_depth + 1) - loaded_snippets.update(new_dict) - else: - loaded_snippets[key] = val - return loaded_snippets - - def messageBox(self, the_text=""): - ''' Just a simple message box ''' - if self.isPane: - msgBox = QtWidgets.QMessageBox() - else: - msgBox = QtWidgets.QMessageBox(self) - msgBox.setText(the_text) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.exec_() - - def openPrefs(self): - ''' Open the preferences panel ''' - global PrefsPanel - if PrefsPanel == "": - PrefsPanel = KnobScripterPrefs(self) - - if PrefsPanel.show(): - PrefsPanel = "" - - def loadPrefs(self): - ''' Load prefs ''' - if not os.path.isfile(self.prefs_txt): - return [] - else: - with open(self.prefs_txt, "r") as f: - prefs = json.load(f) - return prefs - - def runScript(self): - ''' Run the current script... ''' - self.script_editor.runScript() - - def saveScrollValue(self): - ''' Save scroll values ''' - if self.nodeMode: - self.scrollPos[self.knob] = self.script_editor.verticalScrollBar( - ).value() - else: - self.scrollPos[self.current_folder + "/" + - self.current_script] = self.script_editor.verticalScrollBar().value() - - def saveCursorPosValue(self): - ''' Save cursor pos and anchor values ''' - self.cursorPos[self.current_folder + "/" + self.current_script] = [ - self.script_editor.textCursor().position(), self.script_editor.textCursor().anchor()] - - def closeEvent(self, close_event): - if self.nodeMode: - updatedCount = self.updateUnsavedKnobs() - if updatedCount > 0: - msgBox = QtWidgets.QMessageBox() - msgBox.setText("Save changes to %s knob%s before closing?" % ( - str(updatedCount), int(updatedCount > 1) * "s")) - msgBox.setStandardButtons( - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel) - msgBox.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) - msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) - reply = msgBox.exec_() - if reply == QtWidgets.QMessageBox.Yes: - self.saveAllKnobValues(check=False) - close_event.accept() - return - elif reply == QtWidgets.QMessageBox.Cancel: - close_event.ignore() - return - else: - close_event.accept() - else: - self.autosave() - if self in AllKnobScripters: - AllKnobScripters.remove(self) - close_event.accept() - - # Landing functions - - def refreshClicked(self): - ''' Function to refresh the dropdowns ''' - if self.nodeMode: - knob = self.current_knob_dropdown.itemData( - self.current_knob_dropdown.currentIndex()).encode('UTF8') - self.current_knob_dropdown.blockSignals(True) - self.current_knob_dropdown.clear() # First remove all items - self.updateKnobDropdown() - availableKnobs = [] - for i in range(self.current_knob_dropdown.count()): - if self.current_knob_dropdown.itemData(i) is not None: - availableKnobs.append( - self.current_knob_dropdown.itemData(i).encode('UTF8')) - if knob in availableKnobs: - self.setCurrentKnob(knob) - self.current_knob_dropdown.blockSignals(False) - else: - folder = self.current_folder - script = self.current_script - self.autosave() - self.updateFoldersDropdown() - self.setCurrentFolder(folder) - self.updateScriptsDropdown() - self.setCurrentScript(script) - self.script_editor.setFocus() - - def reloadClicked(self): - if self.nodeMode: - self.loadKnobValue() - else: - log("Node mode is off") - self.loadScriptContents(check=True, pyOnly=True) - - def saveClicked(self): - if self.nodeMode: - self.saveKnobValue(False) - else: - self.saveScriptContents(temp=False) - - def setModified(self): - if self.nodeMode: - self.setKnobModified(True) - elif not self.current_script_modified: - self.setScriptModified(True) - if not self.nodeMode: - self.toAutosave = True - - def pin(self, pressed): - if pressed: - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.pinned = True - self.show() - else: - self.setWindowFlags(self.windowFlags() & ~ - QtCore.Qt.WindowStaysOnTopHint) - self.pinned = False - self.show() - - def findSE(self): - for widget in QtWidgets.QApplication.allWidgets(): - if "Script Editor" in widget.windowTitle(): - return widget - - # FunctiosaveScrollValuens for Nuke's Script Editor - def findScriptEditors(self): - script_editors = [] - for widget in QtWidgets.QApplication.allWidgets(): - if "Script Editor" in widget.windowTitle() and len(widget.children()) > 5: - script_editors.append(widget) - return script_editors - - def findSEInput(self, se): - return se.children()[-1].children()[0] - - def findSEOutput(self, se): - return se.children()[-1].children()[1] - - def findSERunBtn(self, se): - for btn in se.children(): - try: - if "Run the current script" in btn.toolTip(): - return btn - except: - pass - return False - - def setSEOutputEvent(self): - nukeScriptEditors = self.findScriptEditors() - # Take the console from the first script editor found... - self.origConsoleText = self.nukeSEOutput.document().toPlainText().encode("utf8") - for se in nukeScriptEditors: - se_output = self.findSEOutput(se) - se_output.textChanged.connect( - partial(consoleChanged, se_output, self)) - consoleChanged(se_output, self) # Initialise. - - -class KnobScripterPane(KnobScripter): - def __init__(self, node="", knob="knobChanged"): - super(KnobScripterPane, self).__init__() - self.isPane = True - - def showEvent(self, the_event): - try: - killPaneMargins(self) - except: - pass - return KnobScripter.showEvent(self, the_event) - - def hideEvent(self, the_event): - self.autosave() - return KnobScripter.hideEvent(self, the_event) - - -def consoleChanged(self, ks): - ''' This will be called every time the ScriptEditor Output text is changed ''' - try: - if ks: # KS exists - ksOutput = ks.script_output # The console TextEdit widget - ksText = self.document().toPlainText().encode("utf8") - # The text from the console that will be omitted - origConsoleText = ks.origConsoleText - if ksText.startswith(origConsoleText): - ksText = ksText[len(origConsoleText):] - else: - ks.origConsoleText = "" - ksOutput.setPlainText(ksText) - ksOutput.verticalScrollBar().setValue(ksOutput.verticalScrollBar().maximum()) - except: - pass - - -def killPaneMargins(widget_object): - if widget_object: - target_widgets = set() - target_widgets.add(widget_object.parentWidget().parentWidget()) - target_widgets.add(widget_object.parentWidget( - ).parentWidget().parentWidget().parentWidget()) - - for widget_layout in target_widgets: - try: - widget_layout.layout().setContentsMargins(0, 0, 0, 0) - except: - pass - - -def debug(lev=0): - ''' Convenience function to set the KnobScripter on debug mode''' - # levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL] - # for handler in logging.root.handlers[:]: - # logging.root.removeHandler(handler) - # logging.basicConfig(level=levels[lev]) - # Changed to a shitty way for now - global DebugMode - DebugMode = True - - -def log(text): - ''' Display a debug info message. Yes, in a stupid way. I know.''' - global DebugMode - if DebugMode: - print(text) - - -# --------------------------------------------------------------------- -# Dialogs -# --------------------------------------------------------------------- -class FileNameDialog(QtWidgets.QDialog): - ''' - Dialog for creating new... (mode = "folder", "script" or "knob"). - ''' - - def __init__(self, parent=None, mode="folder", text=""): - if parent.isPane: - super(FileNameDialog, self).__init__() - else: - super(FileNameDialog, self).__init__(parent) - #self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - self.mode = mode - self.text = text - - title = "Create new {}.".format(self.mode) - self.setWindowTitle(title) - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel("Name: ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.text) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.button_box.button( - QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def nameChanged(self): - txt = self.name_lineEdit.text() - m = r"[\w]*$" - if self.mode == "knob": # Knobs can't start with a number... - m = r"[a-zA-Z_]+" + m - - if re.match(m, txt) or txt == "": - self.text = txt - else: - self.name_lineEdit.setText(self.text) - - self.button_box.button( - QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - return - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -class TextInputDialog(QtWidgets.QDialog): - ''' - Simple dialog for a text input. - ''' - - def __init__(self, parent=None, name="", text="", title=""): - if parent.isPane: - super(TextInputDialog, self).__init__() - else: - super(TextInputDialog, self).__init__(parent) - #self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) - - self.name = name # title of textinput - self.text = text # default content of textinput - - self.setWindowTitle(title) - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel(self.name + ": ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.text) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - #self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(self.text != "") - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def nameChanged(self): - self.text = self.name_lineEdit.text() - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -class ChooseNodeDialog(QtWidgets.QDialog): - ''' - Dialog for selecting a node by its name. Only admits nodes that exist (including root, preferences...) - ''' - - def __init__(self, parent=None, name=""): - if parent.isPane: - super(ChooseNodeDialog, self).__init__() - else: - super(ChooseNodeDialog, self).__init__(parent) - - self.name = name # Name of node (will be "" by default) - self.allNodes = [] - - self.setWindowTitle("Enter the node's name...") - - self.initUI() - - def initUI(self): - # Widgets - self.name_label = QtWidgets.QLabel("Name: ") - self.name_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.name_lineEdit = QtWidgets.QLineEdit() - self.name_lineEdit.setText(self.name) - self.name_lineEdit.textChanged.connect(self.nameChanged) - - self.allNodes = self.getAllNodes() - completer = QtWidgets.QCompleter(self.allNodes, self) - completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) - self.name_lineEdit.setCompleter(completer) - - # Buttons - self.button_box = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( - nuke.exists(self.name)) - self.button_box.accepted.connect(self.clickedOk) - self.button_box.rejected.connect(self.clickedCancel) - - # Layout - self.master_layout = QtWidgets.QVBoxLayout() - self.name_layout = QtWidgets.QHBoxLayout() - self.name_layout.addWidget(self.name_label) - self.name_layout.addWidget(self.name_lineEdit) - self.master_layout.addLayout(self.name_layout) - self.master_layout.addWidget(self.button_box) - self.setLayout(self.master_layout) - - self.name_lineEdit.setFocus() - self.setMinimumWidth(250) - - def getAllNodes(self): - self.allNodes = [n.fullName() for n in nuke.allNodes( - recurseGroups=True)] # if parent is in current context?? - self.allNodes.extend(["root", "preferences"]) - return self.allNodes - - def nameChanged(self): - self.name = self.name_lineEdit.text() - self.button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( - self.name in self.allNodes) - - def clickedOk(self): - self.accept() - return - - def clickedCancel(self): - self.reject() - return - - -# ------------------------------------------------------------------------------------------------------ -# Script Editor Widget -# Wouter Gilsing built an incredibly useful python script editor for his Hotbox Manager, so I had it -# really easy for this part! -# Starting from his script editor, I changed the style and added the sublime-like functionality. -# I think this bit of code has the potential to get used in many nuke tools. -# Credit to him: http://www.woutergilsing.com/ -# Originally used on W_Hotbox v1.5: http://www.nukepedia.com/python/ui/w_hotbox -# ------------------------------------------------------------------------------------------------------ -class KnobScripterTextEdit(QtWidgets.QPlainTextEdit): - # Signal that will be emitted when the user has changed the text - userChangedEvent = QtCore.Signal() - - def __init__(self, knobScripter=""): - super(KnobScripterTextEdit, self).__init__() - - self.knobScripter = knobScripter - self.selected_text = "" - - # Setup line numbers - if self.knobScripter != "": - self.tabSpaces = self.knobScripter.tabSpaces - else: - self.tabSpaces = 4 - self.lineNumberArea = KSLineNumberArea(self) - self.blockCountChanged.connect(self.updateLineNumberAreaWidth) - self.updateRequest.connect(self.updateLineNumberArea) - self.updateLineNumberAreaWidth() - - # Highlight line - self.cursorPositionChanged.connect(self.highlightCurrentLine) - - # -------------------------------------------------------------------------------------------------- - # This is adapted from an original version by Wouter Gilsing. - # Extract from his original comments: - # While researching the implementation of line number, I had a look at Nuke's Blinkscript node. [..] - # thefoundry.co.uk/products/nuke/developers/100/pythonreference/nukescripts.blinkscripteditor-pysrc.html - # I stripped and modified the useful bits of the line number related parts of the code [..] - # Credits to theFoundry for writing the blinkscripteditor, best example code I could wish for. - # -------------------------------------------------------------------------------------------------- - - def lineNumberAreaWidth(self): - digits = 1 - maxNum = max(1, self.blockCount()) - while (maxNum >= 10): - maxNum /= 10 - digits += 1 - - space = 7 + self.fontMetrics().width('9') * digits - return space - - def updateLineNumberAreaWidth(self): - self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) - - def updateLineNumberArea(self, rect, dy): - - if (dy): - self.lineNumberArea.scroll(0, dy) - else: - self.lineNumberArea.update( - 0, rect.y(), self.lineNumberArea.width(), rect.height()) - - if (rect.contains(self.viewport().rect())): - self.updateLineNumberAreaWidth() - - def resizeEvent(self, event): - QtWidgets.QPlainTextEdit.resizeEvent(self, event) - - cr = self.contentsRect() - self.lineNumberArea.setGeometry(QtCore.QRect( - cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) - - def lineNumberAreaPaintEvent(self, event): - - if self.isReadOnly(): - return - - painter = QtGui.QPainter(self.lineNumberArea) - painter.fillRect(event.rect(), QtGui.QColor(36, 36, 36)) # Number bg - - block = self.firstVisibleBlock() - blockNumber = block.blockNumber() - top = int(self.blockBoundingGeometry( - block).translated(self.contentOffset()).top()) - bottom = top + int(self.blockBoundingRect(block).height()) - currentLine = self.document().findBlock( - self.textCursor().position()).blockNumber() - - painter.setPen(self.palette().color(QtGui.QPalette.Text)) - - painterFont = QtGui.QFont() - painterFont.setFamily("Courier") - painterFont.setStyleHint(QtGui.QFont.Monospace) - painterFont.setFixedPitch(True) - if self.knobScripter != "": - painterFont.setPointSize(self.knobScripter.fontSize) - painter.setFont(self.knobScripter.script_editor_font) - - while (block.isValid() and top <= event.rect().bottom()): - - textColor = QtGui.QColor(110, 110, 110) # Numbers - - if blockNumber == currentLine and self.hasFocus(): - textColor = QtGui.QColor(255, 170, 0) # Number highlighted - - painter.setPen(textColor) - - number = "%s" % str(blockNumber + 1) - painter.drawText(-3, top, self.lineNumberArea.width(), - self.fontMetrics().height(), QtCore.Qt.AlignRight, number) - - # Move to the next block - block = block.next() - top = bottom - bottom = top + int(self.blockBoundingRect(block).height()) - blockNumber += 1 - - def keyPressEvent(self, event): - ''' - Custom actions for specific keystrokes - ''' - key = event.key() - ctrl = bool(event.modifiers() & Qt.ControlModifier) - alt = bool(event.modifiers() & Qt.AltModifier) - shift = bool(event.modifiers() & Qt.ShiftModifier) - pre_scroll = self.verticalScrollBar().value() - #modifiers = QtWidgets.QApplication.keyboardModifiers() - #ctrl = (modifiers == Qt.ControlModifier) - #shift = (modifiers == Qt.ShiftModifier) - - up_arrow = 16777235 - down_arrow = 16777237 - - # if Tab convert to Space - if key == 16777217: - self.indentation('indent') - - # if Shift+Tab remove indent - elif key == 16777218: - self.indentation('unindent') - - # if BackSpace try to snap to previous indent level - elif key == 16777219: - if not self.unindentBackspace(): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - # COOL BEHAVIORS SIMILAR TO SUBLIME GO NEXT! - cursor = self.textCursor() - cpos = cursor.position() - apos = cursor.anchor() - text_before_cursor = self.toPlainText()[:min(cpos, apos)] - text_after_cursor = self.toPlainText()[max(cpos, apos):] - text_all = self.toPlainText() - to_line_start = text_before_cursor[::-1].find("\n") - if to_line_start == -1: - # Position of the start of the line that includes the cursor selection start - linestart_pos = 0 - else: - linestart_pos = len(text_before_cursor) - to_line_start - - to_line_end = text_after_cursor.find("\n") - if to_line_end == -1: - # Position of the end of the line that includes the cursor selection end - lineend_pos = len(text_all) - else: - lineend_pos = max(cpos, apos) + to_line_end - - text_before_lines = text_all[:linestart_pos] - text_after_lines = text_all[lineend_pos:] - if len(text_after_lines) and text_after_lines.startswith("\n"): - text_after_lines = text_after_lines[1:] - text_lines = text_all[linestart_pos:lineend_pos] - - if cursor.hasSelection(): - selection = cursor.selection().toPlainText() - else: - selection = "" - if key == Qt.Key_ParenLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # ( - cursor.insertText("(" + selection + ")") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # ) - elif key == Qt.Key_ParenRight and text_after_cursor.startswith(")"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == Qt.Key_BracketLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # [ - cursor.insertText("[" + selection + "]") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # ] - elif key in [Qt.Key_BracketRight, 43] and text_after_cursor.startswith("]"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == Qt.Key_BraceLeft and (len(selection) > 0 or re.match(r"[\s)}\];]+", text_after_cursor) or not len(text_after_cursor)): # { - cursor.insertText("{" + selection + "}") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - # } - elif key in [199, Qt.Key_BraceRight] and text_after_cursor.startswith("}"): - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - self.setTextCursor(cursor) - elif key == 34: # " - if len(selection) > 0: - cursor.insertText('"' + selection + '"') - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - # and not re.search(r"(?:[\s)\]]+|$)",text_before_cursor): - elif text_after_cursor.startswith('"') and '"' in text_before_cursor.split("\n")[-1]: - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - # If chars after cursor, act normal - elif not re.match(r"(?:[\s)\]]+|$)", text_after_cursor): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # If chars before cursor, act normal - elif not re.search(r"[\s.({\[,]$", text_before_cursor) and text_before_cursor != "": - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - cursor.insertText('"' + selection + '"') - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - elif key == 39: # ' - if len(selection) > 0: - cursor.insertText("'" + selection + "'") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - # and not re.search(r"(?:[\s)\]]+|$)",text_before_cursor): - elif text_after_cursor.startswith("'") and "'" in text_before_cursor.split("\n")[-1]: - cursor.movePosition(QtGui.QTextCursor.NextCharacter) - # If chars after cursor, act normal - elif not re.match(r"(?:[\s)\]]+|$)", text_after_cursor): - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # If chars before cursor, act normal - elif not re.search(r"[\s.({\[,]$", text_before_cursor) and text_before_cursor != "": - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - else: - cursor.insertText("'" + selection + "'") - cursor.setPosition(apos + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - elif key == 35 and len(selection): # (yes, a hash) - # If there's a selection, insert a hash at the start of each line.. how the fuck? - if selection != "": - selection_split = selection.split("\n") - if all(i.startswith("#") for i in selection_split): - selection_commented = "\n".join( - [s[1:] for s in selection_split]) # Uncommented - else: - selection_commented = "#" + "\n#".join(selection_split) - cursor.insertText(selection_commented) - if apos > cpos: - cursor.setPosition( - apos + len(selection_commented) - len(selection), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos, QtGui.QTextCursor.KeepAnchor) - else: - cursor.setPosition(apos, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection_commented) - len(selection), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - elif key == 68 and ctrl and shift: # Ctrl+Shift+D, to duplicate text or line/s - - if not len(selection): - self.setPlainText( - text_before_lines + text_lines + "\n" + text_lines + "\n" + text_after_lines) - cursor.setPosition( - apos + len(text_lines) + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(text_lines) + 1, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - else: - if text_before_cursor.endswith("\n") and not selection.startswith("\n"): - cursor.insertText(selection + "\n" + selection) - cursor.setPosition( - apos + len(selection) + 1, QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection) + 1, QtGui.QTextCursor.KeepAnchor) - else: - cursor.insertText(selection + selection) - cursor.setPosition( - apos + len(selection), QtGui.QTextCursor.MoveAnchor) - cursor.setPosition( - cpos + len(selection), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # Ctrl+Shift+Up, to move the selected line/s up - elif key == up_arrow and ctrl and shift and len(text_before_lines): - prev_line_start_distance = text_before_lines[:-1][::-1].find( - "\n") - if prev_line_start_distance == -1: - prev_line_start_pos = 0 # Position of the start of the previous line - else: - prev_line_start_pos = len( - text_before_lines) - 1 - prev_line_start_distance - prev_line = text_before_lines[prev_line_start_pos:] - - text_before_prev_line = text_before_lines[:prev_line_start_pos] - - if prev_line.endswith("\n"): - prev_line = prev_line[:-1] - - if len(text_after_lines): - text_after_lines = "\n" + text_after_lines - - self.setPlainText( - text_before_prev_line + text_lines + "\n" + prev_line + text_after_lines) - cursor.setPosition(apos - len(prev_line) - 1, - QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos - len(prev_line) - 1, - QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - return - - elif key == down_arrow and ctrl and shift: # Ctrl+Shift+Up, to move the selected line/s up - if not len(text_after_lines): - text_after_lines = "" - next_line_end_distance = text_after_lines.find("\n") - if next_line_end_distance == -1: - next_line_end_pos = len(text_all) - else: - next_line_end_pos = next_line_end_distance - next_line = text_after_lines[:next_line_end_pos] - text_after_next_line = text_after_lines[next_line_end_pos:] - - self.setPlainText(text_before_lines + next_line + - "\n" + text_lines + text_after_next_line) - cursor.setPosition(apos + len(next_line) + 1, - QtGui.QTextCursor.MoveAnchor) - cursor.setPosition(cpos + len(next_line) + 1, - QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - self.verticalScrollBar().setValue(pre_scroll) - self.scrollToCursor() - return - - # If up key and nothing happens, go to start - elif key == up_arrow and not len(text_before_lines): - if not shift: - cursor.setPosition(0, QtGui.QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - else: - cursor.setPosition(0, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # If up key and nothing happens, go to start - elif key == down_arrow and not len(text_after_lines): - if not shift: - cursor.setPosition( - len(text_all), QtGui.QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - else: - cursor.setPosition( - len(text_all), QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - - # if enter or return, match indent level - elif key in [16777220, 16777221]: - self.indentNewLine() - else: - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - - self.scrollToCursor() - - def scrollToCursor(self): - self.cursor = self.textCursor() - # Does nothing, but makes the scroll go to the right place... - self.cursor.movePosition(QtGui.QTextCursor.NoMove) - self.setTextCursor(self.cursor) - - def getCursorInfo(self): - - self.cursor = self.textCursor() - - self.firstChar = self.cursor.selectionStart() - self.lastChar = self.cursor.selectionEnd() - - self.noSelection = False - if self.firstChar == self.lastChar: - self.noSelection = True - - self.originalPosition = self.cursor.position() - self.cursorBlockPos = self.cursor.positionInBlock() - - def unindentBackspace(self): - ''' - #snap to previous indent level - ''' - self.getCursorInfo() - - if not self.noSelection or self.cursorBlockPos == 0: - return False - - # check text in front of cursor - textInFront = self.document().findBlock( - self.firstChar).text()[:self.cursorBlockPos] - - # check whether solely spaces - if textInFront != ' ' * self.cursorBlockPos: - return False - - # snap to previous indent level - spaces = len(textInFront) - for space in range(spaces - ((spaces - 1) / self.tabSpaces) * self.tabSpaces - 1): - self.cursor.deletePreviousChar() - - def indentNewLine(self): - - # in case selection covers multiple line, make it one line first - self.insertPlainText('') - - self.getCursorInfo() - - # check how many spaces after cursor - text = self.document().findBlock(self.firstChar).text() - - textInFront = text[:self.cursorBlockPos] - - if len(textInFront) == 0: - self.insertPlainText('\n') - return - - indentLevel = 0 - for i in textInFront: - if i == ' ': - indentLevel += 1 - else: - break - - indentLevel /= self.tabSpaces - - # find out whether textInFront's last character was a ':' - # if that's the case add another indent. - # ignore any spaces at the end, however also - # make sure textInFront is not just an indent - if textInFront.count(' ') != len(textInFront): - while textInFront[-1] == ' ': - textInFront = textInFront[:-1] - - if textInFront[-1] == ':': - indentLevel += 1 - - # new line - self.insertPlainText('\n') - # match indent - self.insertPlainText(' ' * (self.tabSpaces * indentLevel)) - - def indentation(self, mode): - - pre_scroll = self.verticalScrollBar().value() - self.getCursorInfo() - - # if nothing is selected and mode is set to indent, simply insert as many - # space as needed to reach the next indentation level. - if self.noSelection and mode == 'indent': - - remainingSpaces = self.tabSpaces - \ - (self.cursorBlockPos % self.tabSpaces) - self.insertPlainText(' ' * remainingSpaces) - return - - selectedBlocks = self.findBlocks(self.firstChar, self.lastChar) - beforeBlocks = self.findBlocks( - last=self.firstChar - 1, exclude=selectedBlocks) - afterBlocks = self.findBlocks( - first=self.lastChar + 1, exclude=selectedBlocks) - - beforeBlocksText = self.blocks2list(beforeBlocks) - selectedBlocksText = self.blocks2list(selectedBlocks, mode) - afterBlocksText = self.blocks2list(afterBlocks) - - combinedText = '\n'.join( - beforeBlocksText + selectedBlocksText + afterBlocksText) - - # make sure the line count stays the same - originalBlockCount = len(self.toPlainText().split('\n')) - combinedText = '\n'.join(combinedText.split('\n')[:originalBlockCount]) - - self.clear() - self.setPlainText(combinedText) - - if self.noSelection: - self.cursor.setPosition(self.lastChar) - - # check whether the the original selection was from top to bottom or vice versa - else: - if self.originalPosition == self.firstChar: - first = self.lastChar - last = self.firstChar - firstBlockSnap = QtGui.QTextCursor.EndOfBlock - lastBlockSnap = QtGui.QTextCursor.StartOfBlock - else: - first = self.firstChar - last = self.lastChar - firstBlockSnap = QtGui.QTextCursor.StartOfBlock - lastBlockSnap = QtGui.QTextCursor.EndOfBlock - - self.cursor.setPosition(first) - self.cursor.movePosition( - firstBlockSnap, QtGui.QTextCursor.MoveAnchor) - self.cursor.setPosition(last, QtGui.QTextCursor.KeepAnchor) - self.cursor.movePosition( - lastBlockSnap, QtGui.QTextCursor.KeepAnchor) - - self.setTextCursor(self.cursor) - self.verticalScrollBar().setValue(pre_scroll) - - def findBlocks(self, first=0, last=None, exclude=[]): - blocks = [] - if last == None: - last = self.document().characterCount() - for pos in range(first, last + 1): - block = self.document().findBlock(pos) - if block not in blocks and block not in exclude: - blocks.append(block) - return blocks - - def blocks2list(self, blocks, mode=None): - text = [] - for block in blocks: - blockText = block.text() - if mode == 'unindent': - if blockText.startswith(' ' * self.tabSpaces): - blockText = blockText[self.tabSpaces:] - self.lastChar -= self.tabSpaces - elif blockText.startswith('\t'): - blockText = blockText[1:] - self.lastChar -= 1 - - elif mode == 'indent': - blockText = ' ' * self.tabSpaces + blockText - self.lastChar += self.tabSpaces - - text.append(blockText) - - return text - - def highlightCurrentLine(self): - ''' - Highlight currently selected line - ''' - extraSelections = [] - - selection = QtWidgets.QTextEdit.ExtraSelection() - - lineColor = QtGui.QColor(62, 62, 62, 255) - - selection.format.setBackground(lineColor) - selection.format.setProperty( - QtGui.QTextFormat.FullWidthSelection, True) - selection.cursor = self.textCursor() - selection.cursor.clearSelection() - - extraSelections.append(selection) - - self.setExtraSelections(extraSelections) - self.scrollToCursor() - - def format(self, rgb, style=''): - ''' - Return a QtWidgets.QTextCharFormat with the given attributes. - ''' - color = QtGui.QColor(*rgb) - textFormat = QtGui.QTextCharFormat() - textFormat.setForeground(color) - - if 'bold' in style: - textFormat.setFontWeight(QtGui.QFont.Bold) - if 'italic' in style: - textFormat.setFontItalic(True) - if 'underline' in style: - textFormat.setUnderlineStyle(QtGui.QTextCharFormat.SingleUnderline) - - return textFormat - - -class KSLineNumberArea(QtWidgets.QWidget): - def __init__(self, scriptEditor): - super(KSLineNumberArea, self).__init__(scriptEditor) - - self.scriptEditor = scriptEditor - self.setStyleSheet("text-align: center;") - - def paintEvent(self, event): - self.scriptEditor.lineNumberAreaPaintEvent(event) - return - - -class KSScriptEditorHighlighter(QtGui.QSyntaxHighlighter): - ''' - This is also adapted from an original version by Wouter Gilsing. His comments: - - Modified, simplified version of some code found I found when researching: - wiki.python.org/moin/PyQt/Python%20syntax%20highlighting - They did an awesome job, so credits to them. I only needed to make some - modifications to make it fit my needs. - ''' - - def __init__(self, document, parent=None): - - super(KSScriptEditorHighlighter, self).__init__(document) - self.knobScripter = parent - self.script_editor = self.knobScripter.script_editor - self.selected_text = "" - self.selected_text_prev = "" - self.rules_sublime = "" - - self.styles = { - 'keyword': self.format([238, 117, 181], 'bold'), - 'string': self.format([242, 136, 135]), - 'comment': self.format([143, 221, 144]), - 'numbers': self.format([174, 129, 255]), - 'custom': self.format([255, 170, 0], 'italic'), - 'selected': self.format([255, 255, 255], 'bold underline'), - 'underline': self.format([240, 240, 240], 'underline'), - } - - self.keywords = [ - 'and', 'assert', 'break', 'class', 'continue', 'def', - 'del', 'elif', 'else', 'except', 'exec', 'finally', - 'for', 'from', 'global', 'if', 'import', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', - 'raise', 'return', 'try', 'while', 'yield', 'with', 'as' - ] - - self.operatorKeywords = [ - '=', '==', '!=', '<', '<=', '>', '>=', - '\+', '-', '\*', '/', '//', '\%', '\*\*', - '\+=', '-=', '\*=', '/=', '\%=', - '\^', '\|', '\&', '\~', '>>', '<<' - ] - - self.variableKeywords = ['int', 'str', - 'float', 'bool', 'list', 'dict', 'set'] - - self.numbers = ['True', 'False', 'None'] - self.loadAltStyles() - - self.tri_single = (QtCore.QRegExp("'''"), 1, self.styles['comment']) - self.tri_double = (QtCore.QRegExp('"""'), 2, self.styles['comment']) - - # rules - rules = [] - - rules += [(r'\b%s\b' % i, 0, self.styles['keyword']) - for i in self.keywords] - rules += [(i, 0, self.styles['keyword']) - for i in self.operatorKeywords] - rules += [(r'\b%s\b' % i, 0, self.styles['numbers']) - for i in self.numbers] - - rules += [ - - # integers - (r'\b[0-9]+\b', 0, self.styles['numbers']), - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, self.styles['string']), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, self.styles['string']), - # From '#' until a newline - (r'#[^\n]*', 0, self.styles['comment']), - ] - - # Build a QRegExp for each pattern - self.rules_nuke = [(QtCore.QRegExp(pat), index, fmt) - for (pat, index, fmt) in rules] - self.rules = self.rules_nuke - - def loadAltStyles(self): - ''' Loads other color styles apart from Nuke's default. ''' - self.styles_sublime = { - 'base': self.format([255, 255, 255]), - 'keyword': self.format([237, 36, 110]), - 'string': self.format([237, 229, 122]), - 'comment': self.format([125, 125, 125]), - 'numbers': self.format([165, 120, 255]), - 'functions': self.format([184, 237, 54]), - 'blue': self.format([130, 226, 255], 'italic'), - 'arguments': self.format([255, 170, 10], 'italic'), - 'custom': self.format([200, 200, 200], 'italic'), - 'underline': self.format([240, 240, 240], 'underline'), - 'selected': self.format([255, 255, 255], 'bold underline'), - } - - self.keywords_sublime = [ - 'and', 'assert', 'break', 'continue', - 'del', 'elif', 'else', 'except', 'exec', 'finally', - 'for', 'from', 'global', 'if', 'import', 'in', - 'is', 'lambda', 'not', 'or', 'pass', 'print', - 'raise', 'return', 'try', 'while', 'yield', 'with', 'as' - ] - self.operatorKeywords_sublime = [ - '=', '==', '!=', '<', '<=', '>', '>=', - '\+', '-', '\*', '/', '//', '\%', '\*\*', - '\+=', '-=', '\*=', '/=', '\%=', - '\^', '\|', '\&', '\~', '>>', '<<' - ] - - self.baseKeywords_sublime = [ - ',', - ] - - self.customKeywords_sublime = [ - 'nuke', - ] - - self.blueKeywords_sublime = [ - 'def', 'class', 'int', 'str', 'float', 'bool', 'list', 'dict', 'set' - ] - - self.argKeywords_sublime = [ - 'self', - ] - - self.tri_single_sublime = (QtCore.QRegExp( - "'''"), 1, self.styles_sublime['comment']) - self.tri_double_sublime = (QtCore.QRegExp( - '"""'), 2, self.styles_sublime['comment']) - self.numbers_sublime = ['True', 'False', 'None'] - - # rules - - rules = [] - # First turn everything inside parentheses orange - rules += [(r"def [\w]+[\s]*\((.*)\)", 1, - self.styles_sublime['arguments'])] - # Now restore unwanted stuff... - rules += [(i, 0, self.styles_sublime['base']) - for i in self.baseKeywords_sublime] - rules += [(r"[^\(\w),.][\s]*[\w]+", 0, self.styles_sublime['base'])] - - # Everything else - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['keyword']) - for i in self.keywords_sublime] - rules += [(i, 0, self.styles_sublime['keyword']) - for i in self.operatorKeywords_sublime] - rules += [(i, 0, self.styles_sublime['custom']) - for i in self.customKeywords_sublime] - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['blue']) - for i in self.blueKeywords_sublime] - rules += [(i, 0, self.styles_sublime['arguments']) - for i in self.argKeywords_sublime] - rules += [(r'\b%s\b' % i, 0, self.styles_sublime['numbers']) - for i in self.numbers_sublime] - - rules += [ - - # integers - (r'\b[0-9]+\b', 0, self.styles_sublime['numbers']), - # Double-quoted string, possibly containing escape sequences - (r'"[^"\\]*(\\.[^"\\]*)*"', 0, self.styles_sublime['string']), - # Single-quoted string, possibly containing escape sequences - (r"'[^'\\]*(\\.[^'\\]*)*'", 0, self.styles_sublime['string']), - # From '#' until a newline - (r'#[^\n]*', 0, self.styles_sublime['comment']), - # Function definitions - (r"def[\s]+([\w\.]+)", 1, self.styles_sublime['functions']), - # Class definitions - (r"class[\s]+([\w\.]+)", 1, self.styles_sublime['functions']), - # Class argument (which is also a class so must be green) - (r"class[\s]+[\w\.]+[\s]*\((.*)\)", - 1, self.styles_sublime['functions']), - # Function arguments also pick their style... - (r"def[\s]+[\w]+[\s]*\(([\w]+)", 1, - self.styles_sublime['arguments']), - ] - - # Build a QRegExp for each pattern - self.rules_sublime = [(QtCore.QRegExp(pat), index, fmt) - for (pat, index, fmt) in rules] - - def format(self, rgb, style=''): - ''' - Return a QtWidgets.QTextCharFormat with the given attributes. - ''' - - color = QtGui.QColor(*rgb) - textFormat = QtGui.QTextCharFormat() - textFormat.setForeground(color) - - if 'bold' in style: - textFormat.setFontWeight(QtGui.QFont.Bold) - if 'italic' in style: - textFormat.setFontItalic(True) - if 'underline' in style: - textFormat.setUnderlineStyle(QtGui.QTextCharFormat.SingleUnderline) - - return textFormat - - def highlightBlock(self, text): - ''' - Apply syntax highlighting to the given block of text. - ''' - # Do other syntax formatting - - if self.knobScripter.color_scheme: - self.color_scheme = self.knobScripter.color_scheme - else: - self.color_scheme = "nuke" - - if self.color_scheme == "nuke": - self.rules = self.rules_nuke - elif self.color_scheme == "sublime": - self.rules = self.rules_sublime - - for expression, nth, format in self.rules: - index = expression.indexIn(text, 0) - - while index >= 0: - # We actually want the index of the nth match - index = expression.pos(nth) - length = len(expression.cap(nth)) - self.setFormat(index, length, format) - index = expression.indexIn(text, index + length) - - self.setCurrentBlockState(0) - - # Multi-line strings etc. based on selected scheme - if self.color_scheme == "nuke": - in_multiline = self.match_multiline(text, *self.tri_single) - if not in_multiline: - in_multiline = self.match_multiline(text, *self.tri_double) - elif self.color_scheme == "sublime": - in_multiline = self.match_multiline(text, *self.tri_single_sublime) - if not in_multiline: - in_multiline = self.match_multiline( - text, *self.tri_double_sublime) - - # TODO if there's a selection, highlight same occurrences in the full document. If no selection but something highlighted, unhighlight full document. (do it thru regex or sth) - - def match_multiline(self, text, delimiter, in_state, style): - ''' - Check whether highlighting requires multiple lines. - ''' - # If inside triple-single quotes, start at 0 - if self.previousBlockState() == in_state: - start = 0 - add = 0 - # Otherwise, look for the delimiter on this line - else: - start = delimiter.indexIn(text) - # Move past this match - add = delimiter.matchedLength() - - # As long as there's a delimiter match on this line... - while start >= 0: - # Look for the ending delimiter - end = delimiter.indexIn(text, start + add) - # Ending delimiter on this line? - if end >= add: - length = end - start + add + delimiter.matchedLength() - self.setCurrentBlockState(0) - # No; multi-line string - else: - self.setCurrentBlockState(in_state) - length = len(text) - start + add - # Apply formatting - self.setFormat(start, length, style) - # Look for the next match - start = delimiter.indexIn(text, start + length) - - # Return True if still inside a multi-line string, False otherwise - if self.currentBlockState() == in_state: - return True - else: - return False - -# -------------------------------------------------------------------------------------- -# Script Output Widget -# The output logger works the same way as Nuke's python script editor output window -# -------------------------------------------------------------------------------------- - - -class ScriptOutputWidget(QtWidgets.QTextEdit): - def __init__(self, parent=None): - super(ScriptOutputWidget, self).__init__(parent) - self.knobScripter = parent - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - self.setMinimumHeight(20) - - def keyPressEvent(self, event): - ctrl = ((event.modifiers() and (Qt.ControlModifier)) != 0) - alt = ((event.modifiers() and (Qt.AltModifier)) != 0) - shift = ((event.modifiers() and (Qt.ShiftModifier)) != 0) - key = event.key() - if type(event) == QtGui.QKeyEvent: - # print event.key() - if key in [32]: # Space - return KnobScripter.keyPressEvent(self.knobScripter, event) - elif key in [Qt.Key_Backspace, Qt.Key_Delete]: - self.knobScripter.clearConsole() - return QtWidgets.QTextEdit.keyPressEvent(self, event) - - # def mousePressEvent(self, QMouseEvent): - # if QMouseEvent.button() == Qt.RightButton: - # self.knobScripter.clearConsole() - # QtWidgets.QTextEdit.mousePressEvent(self, QMouseEvent) - -# --------------------------------------------------------------------- -# Modified KnobScripterTextEdit to include snippets etc. -# --------------------------------------------------------------------- - - -class KnobScripterTextEditMain(KnobScripterTextEdit): - def __init__(self, knobScripter, output=None, parent=None): - super(KnobScripterTextEditMain, self).__init__(knobScripter) - self.knobScripter = knobScripter - self.script_output = output - self.nukeCompleter = None - self.currentNukeCompletion = None - - ######## - # FROM NUKE's SCRIPT EDITOR START - ######## - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) - - # Setup completer - self.nukeCompleter = QtWidgets.QCompleter(self) - self.nukeCompleter.setWidget(self) - self.nukeCompleter.setCompletionMode( - QtWidgets.QCompleter.UnfilteredPopupCompletion) - self.nukeCompleter.setCaseSensitivity(Qt.CaseSensitive) - try: - self.nukeCompleter.setModel(QtGui.QStringListModel()) - except: - self.nukeCompleter.setModel(QtCore.QStringListModel()) - - self.nukeCompleter.activated.connect(self.insertNukeCompletion) - self.nukeCompleter.highlighted.connect(self.completerHighlightChanged) - ######## - # FROM NUKE's SCRIPT EDITOR END - ######## - - def findLongestEndingMatch(self, text, dic): - ''' - If the text ends with a key in the dictionary, it returns the key and value. - If there are several matches, returns the longest one. - False if no matches. - ''' - longest = 0 # len of longest match - match_key = None - match_snippet = "" - for key, val in dic.items(): - #match = re.search(r"[\s\.({\[,;=+-]"+key+r"(?:[\s)\]\"]+|$)",text) - match = re.search(r"[\s\.({\[,;=+-]" + key + r"$", text) - if match or text == key: - if len(key) > longest: - longest = len(key) - match_key = key - match_snippet = val - if match_key is None: - return False - return match_key, match_snippet - - def placeholderToEnd(self, text, placeholder): - '''Returns distance (int) from the first occurrence of the placeholder, to the end of the string with placeholders removed''' - search = re.search(placeholder, text) - if not search: - return -1 - from_start = search.start() - total = len(re.sub(placeholder, "", text)) - to_end = total - from_start - return to_end - - def addSnippetText(self, snippet_text): - ''' Adds the selected text as a snippet (taking care of $$, $name$ etc) to the script editor ''' - cursor_placeholder_find = r"(? 1: - cursor_len = positions[1] - positions[0] - 2 - - text = re.sub(cursor_placeholder_find, "", text) - self.cursor.insertText(text) - if placeholder_to_end >= 0: - for i in range(placeholder_to_end): - self.cursor.movePosition(QtGui.QTextCursor.PreviousCharacter) - for i in range(cursor_len): - self.cursor.movePosition( - QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor) - self.setTextCursor(self.cursor) - - def keyPressEvent(self, event): - - ctrl = bool(event.modifiers() & Qt.ControlModifier) - alt = bool(event.modifiers() & Qt.AltModifier) - shift = bool(event.modifiers() & Qt.ShiftModifier) - key = event.key() - - # ADAPTED FROM NUKE's SCRIPT EDITOR: - # Get completer state - self.nukeCompleterShowing = self.nukeCompleter.popup().isVisible() - - # BEFORE ANYTHING ELSE, IF SPECIAL MODIFIERS SIMPLY IGNORE THE REST - if not self.nukeCompleterShowing and (ctrl or shift or alt): - # Bypassed! - if key not in [Qt.Key_Return, Qt.Key_Enter, Qt.Key_Tab]: - KnobScripterTextEdit.keyPressEvent(self, event) - return - - # If the completer is showing - if self.nukeCompleterShowing: - tc = self.textCursor() - # If we're hitting enter, do completion - if key in [Qt.Key_Return, Qt.Key_Enter, Qt.Key_Tab]: - if not self.currentNukeCompletion: - self.nukeCompleter.setCurrentRow(0) - self.currentNukeCompletion = self.nukeCompleter.currentCompletion() - # print str(self.nukeCompleter.completionModel[0]) - self.insertNukeCompletion(self.currentNukeCompletion) - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If you're hitting right or escape, hide the popup - elif key == Qt.Key_Right or key == Qt.Key_Escape: - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If you hit tab, escape or ctrl-space, hide the completer - elif key == Qt.Key_Tab or key == Qt.Key_Escape or (ctrl and key == Qt.Key_Space): - self.currentNukeCompletion = "" - self.nukeCompleter.popup().hide() - self.nukeCompleterShowing = False - # If none of the above, update the completion model - else: - QtWidgets.QPlainTextEdit.keyPressEvent(self, event) - # Edit completion model - colNum = tc.columnNumber() - posNum = tc.position() - inputText = self.toPlainText() - inputTextSplit = inputText.splitlines() - runningLength = 0 - currentLine = None - for line in inputTextSplit: - length = len(line) - runningLength += length - if runningLength >= posNum: - currentLine = line - break - runningLength += 1 - if currentLine: - completionPart = currentLine.split(" ")[-1] - if "(" in completionPart: - completionPart = completionPart.split("(")[-1] - self.completeNukePartUnderCursor(completionPart) - return - - if type(event) == QtGui.QKeyEvent: - if key == Qt.Key_Escape: # Close the knobscripter... - self.knobScripter.close() - elif not ctrl and not alt and not shift and event.key() == Qt.Key_Tab: - self.placeholder = "$$" - # 1. Set the cursor - self.cursor = self.textCursor() - - # 2. Save text before and after - cpos = self.cursor.position() - text_before_cursor = self.toPlainText()[:cpos] - line_before_cursor = text_before_cursor.split('\n')[-1] - text_after_cursor = self.toPlainText()[cpos:] - - # 3. Check coincidences in snippets dicts - try: # Meaning snippet found - match_key, match_snippet = self.findLongestEndingMatch( - line_before_cursor, self.knobScripter.snippets) - for i in range(len(match_key)): - self.cursor.deletePreviousChar() - # This function takes care of adding the appropriate snippet and moving the cursor... - self.addSnippetText(match_snippet) - except: # Meaning snippet not found... - # ADAPTED FROM NUKE's SCRIPT EDITOR: - tc = self.textCursor() - allCode = self.toPlainText() - colNum = tc.columnNumber() - posNum = tc.position() - - # ...and if there's text in the editor - if len(allCode.split()) > 0: - # There is text in the editor - currentLine = tc.block().text() - - # If you're not at the end of the line just add a tab - if colNum < len(currentLine): - # If there isn't a ')' directly to the right of the cursor add a tab - if currentLine[colNum:colNum + 1] != ')': - KnobScripterTextEdit.keyPressEvent(self, event) - return - # Else show the completer - else: - completionPart = currentLine[:colNum].split( - " ")[-1] - if "(" in completionPart: - completionPart = completionPart.split( - "(")[-1] - - self.completeNukePartUnderCursor( - completionPart) - - return - - # If you are at the end of the line, - else: - # If there's nothing to the right of you add a tab - if currentLine[colNum - 1:] == "" or currentLine.endswith(" "): - KnobScripterTextEdit.keyPressEvent(self, event) - return - # Else update completionPart and show the completer - completionPart = currentLine.split(" ")[-1] - if "(" in completionPart: - completionPart = completionPart.split("(")[-1] - - self.completeNukePartUnderCursor(completionPart) - return - - KnobScripterTextEdit.keyPressEvent(self, event) - elif event.key() in [Qt.Key_Enter, Qt.Key_Return]: - modifiers = QtWidgets.QApplication.keyboardModifiers() - if modifiers == QtCore.Qt.ControlModifier: - self.runScript() - else: - KnobScripterTextEdit.keyPressEvent(self, event) - else: - KnobScripterTextEdit.keyPressEvent(self, event) - - def getPyObjects(self, text): - ''' Returns a list containing all the functions, classes and variables found within the selected python text (code) ''' - matches = [] - # 1: Remove text inside triple quotes (leaving the quotes) - text_clean = '""'.join(text.split('"""')[::2]) - text_clean = '""'.join(text_clean.split("'''")[::2]) - - # 2: Remove text inside of quotes (leaving the quotes) except if \" - lines = text_clean.split("\n") - text_clean = "" - for line in lines: - line_clean = '""'.join(line.split('"')[::2]) - line_clean = '""'.join(line_clean.split("'")[::2]) - line_clean = line_clean.split("#")[0] - text_clean += line_clean + "\n" - - # 3. Split into segments (lines plus ";") - segments = re.findall(r"[^\n;]+", text_clean) - - # 4. Go case by case. - for s in segments: - # Declared vars - matches += re.findall(r"([\w\.]+)(?=[,\s\w]*=[^=]+$)", s) - # Def functions and arguments - function = re.findall(r"[\s]*def[\s]+([\w\.]+)[\s]*\([\s]*", s) - if len(function): - matches += function - args = re.split(r"[\s]*def[\s]+([\w\.]+)[\s]*\([\s]*", s) - if len(args) > 1: - args = args[-1] - matches += re.findall( - r"(?adrianpueyo.com, 2016-2019') - kspSignature.setOpenExternalLinks(True) - kspSignature.setStyleSheet('''color:#555;font-size:9px;''') - kspSignature.setAlignment(QtCore.Qt.AlignRight) - - fontLabel = QtWidgets.QLabel("Font:") - self.fontBox = QtWidgets.QFontComboBox() - self.fontBox.setCurrentFont(QtGui.QFont(self.font)) - self.fontBox.currentFontChanged.connect(self.fontChanged) - - fontSizeLabel = QtWidgets.QLabel("Font size:") - self.fontSizeBox = QtWidgets.QSpinBox() - self.fontSizeBox.setValue(self.oldFontSize) - self.fontSizeBox.setMinimum(6) - self.fontSizeBox.setMaximum(100) - self.fontSizeBox.valueChanged.connect(self.fontSizeChanged) - - windowWLabel = QtWidgets.QLabel("Width (px):") - windowWLabel.setToolTip("Default window width in pixels") - self.windowWBox = QtWidgets.QSpinBox() - self.windowWBox.setValue(self.knobScripter.windowDefaultSize[0]) - self.windowWBox.setMinimum(200) - self.windowWBox.setMaximum(4000) - self.windowWBox.setToolTip("Default window width in pixels") - - windowHLabel = QtWidgets.QLabel("Height (px):") - windowHLabel.setToolTip("Default window height in pixels") - self.windowHBox = QtWidgets.QSpinBox() - self.windowHBox.setValue(self.knobScripter.windowDefaultSize[1]) - self.windowHBox.setMinimum(100) - self.windowHBox.setMaximum(2000) - self.windowHBox.setToolTip("Default window height in pixels") - - # TODO: "Grab current dimensions" button - - tabSpaceLabel = QtWidgets.QLabel("Tab spaces:") - tabSpaceLabel.setToolTip("Number of spaces to add with the tab key.") - self.tabSpace2 = QtWidgets.QRadioButton("2") - self.tabSpace4 = QtWidgets.QRadioButton("4") - tabSpaceButtonGroup = QtWidgets.QButtonGroup(self) - tabSpaceButtonGroup.addButton(self.tabSpace2) - tabSpaceButtonGroup.addButton(self.tabSpace4) - self.tabSpace2.setChecked(self.knobScripter.tabSpaces == 2) - self.tabSpace4.setChecked(self.knobScripter.tabSpaces == 4) - - pinDefaultLabel = QtWidgets.QLabel("Always on top:") - pinDefaultLabel.setToolTip("Default mode of the PIN toggle.") - self.pinDefaultOn = QtWidgets.QRadioButton("On") - self.pinDefaultOff = QtWidgets.QRadioButton("Off") - pinDefaultButtonGroup = QtWidgets.QButtonGroup(self) - pinDefaultButtonGroup.addButton(self.pinDefaultOn) - pinDefaultButtonGroup.addButton(self.pinDefaultOff) - self.pinDefaultOn.setChecked(self.knobScripter.pinned == True) - self.pinDefaultOff.setChecked(self.knobScripter.pinned == False) - self.pinDefaultOn.clicked.connect(lambda: self.knobScripter.pin(True)) - self.pinDefaultOff.clicked.connect( - lambda: self.knobScripter.pin(False)) - - colorSchemeLabel = QtWidgets.QLabel("Color scheme:") - colorSchemeLabel.setToolTip("Syntax highlighting text style.") - self.colorSchemeSublime = QtWidgets.QRadioButton("subl") - self.colorSchemeNuke = QtWidgets.QRadioButton("nuke") - colorSchemeButtonGroup = QtWidgets.QButtonGroup(self) - colorSchemeButtonGroup.addButton(self.colorSchemeSublime) - colorSchemeButtonGroup.addButton(self.colorSchemeNuke) - colorSchemeButtonGroup.buttonClicked.connect(self.colorSchemeChanged) - self.colorSchemeSublime.setChecked( - self.knobScripter.color_scheme == "sublime") - self.colorSchemeNuke.setChecked( - self.knobScripter.color_scheme == "nuke") - - showLabelsLabel = QtWidgets.QLabel("Show labels:") - showLabelsLabel.setToolTip( - "Display knob labels on the knob dropdown\nOtherwise, shows the internal name only.") - self.showLabelsOn = QtWidgets.QRadioButton("On") - self.showLabelsOff = QtWidgets.QRadioButton("Off") - showLabelsButtonGroup = QtWidgets.QButtonGroup(self) - showLabelsButtonGroup.addButton(self.showLabelsOn) - showLabelsButtonGroup.addButton(self.showLabelsOff) - self.showLabelsOn.setChecked(self.knobScripter.pinned == True) - self.showLabelsOff.setChecked(self.knobScripter.pinned == False) - self.showLabelsOn.clicked.connect(lambda: self.knobScripter.pin(True)) - self.showLabelsOff.clicked.connect( - lambda: self.knobScripter.pin(False)) - - self.buttonBox = QtWidgets.QDialogButtonBox( - QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) - self.buttonBox.accepted.connect(self.savePrefs) - self.buttonBox.rejected.connect(self.cancelPrefs) - - # Loaded custom values - self.ksPrefs = self.knobScripter.loadPrefs() - if self.ksPrefs != []: - try: - self.fontSizeBox.setValue(self.ksPrefs['font_size']) - self.windowWBox.setValue(self.ksPrefs['window_default_w']) - self.windowHBox.setValue(self.ksPrefs['window_default_h']) - self.tabSpace2.setChecked(self.ksPrefs['tab_spaces'] == 2) - self.tabSpace4.setChecked(self.ksPrefs['tab_spaces'] == 4) - self.pinDefaultOn.setChecked(self.ksPrefs['pin_default'] == 1) - self.pinDefaultOff.setChecked(self.ksPrefs['pin_default'] == 0) - self.showLabelsOn.setChecked(self.ksPrefs['show_labels'] == 1) - self.showLabelsOff.setChecked(self.ksPrefs['show_labels'] == 0) - self.colorSchemeSublime.setChecked( - self.ksPrefs['color_scheme'] == "sublime") - self.colorSchemeNuke.setChecked( - self.ksPrefs['color_scheme'] == "nuke") - except: - pass - - # Layouts - font_layout = QtWidgets.QHBoxLayout() - font_layout.addWidget(fontLabel) - font_layout.addWidget(self.fontBox) - - fontSize_layout = QtWidgets.QHBoxLayout() - fontSize_layout.addWidget(fontSizeLabel) - fontSize_layout.addWidget(self.fontSizeBox) - - windowW_layout = QtWidgets.QHBoxLayout() - windowW_layout.addWidget(windowWLabel) - windowW_layout.addWidget(self.windowWBox) - - windowH_layout = QtWidgets.QHBoxLayout() - windowH_layout.addWidget(windowHLabel) - windowH_layout.addWidget(self.windowHBox) - - tabSpacesButtons_layout = QtWidgets.QHBoxLayout() - tabSpacesButtons_layout.addWidget(self.tabSpace2) - tabSpacesButtons_layout.addWidget(self.tabSpace4) - tabSpaces_layout = QtWidgets.QHBoxLayout() - tabSpaces_layout.addWidget(tabSpaceLabel) - tabSpaces_layout.addLayout(tabSpacesButtons_layout) - - pinDefaultButtons_layout = QtWidgets.QHBoxLayout() - pinDefaultButtons_layout.addWidget(self.pinDefaultOn) - pinDefaultButtons_layout.addWidget(self.pinDefaultOff) - pinDefault_layout = QtWidgets.QHBoxLayout() - pinDefault_layout.addWidget(pinDefaultLabel) - pinDefault_layout.addLayout(pinDefaultButtons_layout) - - showLabelsButtons_layout = QtWidgets.QHBoxLayout() - showLabelsButtons_layout.addWidget(self.showLabelsOn) - showLabelsButtons_layout.addWidget(self.showLabelsOff) - showLabels_layout = QtWidgets.QHBoxLayout() - showLabels_layout.addWidget(showLabelsLabel) - showLabels_layout.addLayout(showLabelsButtons_layout) - - colorSchemeButtons_layout = QtWidgets.QHBoxLayout() - colorSchemeButtons_layout.addWidget(self.colorSchemeSublime) - colorSchemeButtons_layout.addWidget(self.colorSchemeNuke) - colorScheme_layout = QtWidgets.QHBoxLayout() - colorScheme_layout.addWidget(colorSchemeLabel) - colorScheme_layout.addLayout(colorSchemeButtons_layout) - - self.master_layout = QtWidgets.QVBoxLayout() - self.master_layout.addWidget(kspTitle) - self.master_layout.addWidget(kspSignature) - self.master_layout.addWidget(kspLine) - self.master_layout.addLayout(font_layout) - self.master_layout.addLayout(fontSize_layout) - self.master_layout.addLayout(windowW_layout) - self.master_layout.addLayout(windowH_layout) - self.master_layout.addLayout(tabSpaces_layout) - self.master_layout.addLayout(pinDefault_layout) - self.master_layout.addLayout(showLabels_layout) - self.master_layout.addLayout(colorScheme_layout) - self.master_layout.addWidget(self.buttonBox) - self.setLayout(self.master_layout) - self.setFixedSize(self.minimumSize()) - - def savePrefs(self): - self.font = self.fontBox.currentFont().family() - ks_prefs = { - 'font_size': self.fontSizeBox.value(), - 'window_default_w': self.windowWBox.value(), - 'window_default_h': self.windowHBox.value(), - 'tab_spaces': self.tabSpaceValue(), - 'pin_default': self.pinDefaultValue(), - 'show_labels': self.showLabelsValue(), - 'font': self.font, - 'color_scheme': self.colorSchemeValue(), - } - self.knobScripter.script_editor_font.setFamily(self.font) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - self.knobScripter.font = self.font - self.knobScripter.color_scheme = self.colorSchemeValue() - self.knobScripter.tabSpaces = self.tabSpaceValue() - self.knobScripter.script_editor.tabSpaces = self.tabSpaceValue() - with open(self.prefs_txt, "w") as f: - prefs = json.dump(ks_prefs, f, sort_keys=True, indent=4) - self.accept() - self.knobScripter.highlighter.rehighlight() - self.knobScripter.show_labels = self.showLabelsValue() - if self.knobScripter.nodeMode: - self.knobScripter.refreshClicked() - return prefs - - def cancelPrefs(self): - self.knobScripter.script_editor_font.setPointSize(self.oldFontSize) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - self.knobScripter.color_scheme = self.oldScheme - self.knobScripter.highlighter.rehighlight() - self.reject() - - def fontSizeChanged(self): - self.knobScripter.script_editor_font.setPointSize( - self.fontSizeBox.value()) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - return - - def fontChanged(self): - self.font = self.fontBox.currentFont().family() - self.knobScripter.script_editor_font.setFamily(self.font) - self.knobScripter.script_editor.setFont( - self.knobScripter.script_editor_font) - return - - def colorSchemeChanged(self): - self.knobScripter.color_scheme = self.colorSchemeValue() - self.knobScripter.highlighter.rehighlight() - return - - def tabSpaceValue(self): - return 2 if self.tabSpace2.isChecked() else 4 - - def pinDefaultValue(self): - return 1 if self.pinDefaultOn.isChecked() else 0 - - def showLabelsValue(self): - return 1 if self.showLabelsOn.isChecked() else 0 - - def colorSchemeValue(self): - return "nuke" if self.colorSchemeNuke.isChecked() else "sublime" - - def closeEvent(self, event): - self.cancelPrefs() - self.close() - - -def updateContext(): - ''' - Get the current selection of nodes with their appropriate context - Doing this outside the KnobScripter -> forces context update inside groups when needed - ''' - global knobScripterSelectedNodes - knobScripterSelectedNodes = nuke.selectedNodes() - return - -# -------------------------------- -# FindReplace -# -------------------------------- - - -class FindReplaceWidget(QtWidgets.QWidget): - ''' SearchReplace Widget for the knobscripter. FindReplaceWidget(editor = QPlainTextEdit) ''' - - def __init__(self, parent): - super(FindReplaceWidget, self).__init__(parent) - - self.editor = parent.script_editor - - self.initUI() - - def initUI(self): - - # -------------- - # Find Row - # -------------- - - # Widgets - self.find_label = QtWidgets.QLabel("Find:") - # self.find_label.setSizePolicy(QtWidgets.QSizePolicy.Fixed,QtWidgets.QSizePolicy.Fixed) - self.find_label.setFixedWidth(50) - self.find_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.find_lineEdit = QtWidgets.QLineEdit() - self.find_next_button = QtWidgets.QPushButton("Next") - self.find_next_button.clicked.connect(self.find) - self.find_prev_button = QtWidgets.QPushButton("Previous") - self.find_prev_button.clicked.connect(self.findBack) - self.find_lineEdit.returnPressed.connect(self.find_next_button.click) - - # Layout - self.find_layout = QtWidgets.QHBoxLayout() - self.find_layout.addWidget(self.find_label) - self.find_layout.addWidget(self.find_lineEdit, stretch=1) - self.find_layout.addWidget(self.find_next_button) - self.find_layout.addWidget(self.find_prev_button) - - # -------------- - # Replace Row - # -------------- - - # Widgets - self.replace_label = QtWidgets.QLabel("Replace:") - # self.replace_label.setSizePolicy(QtWidgets.QSizePolicy.Fixed,QtWidgets.QSizePolicy.Fixed) - self.replace_label.setFixedWidth(50) - self.replace_label.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.replace_lineEdit = QtWidgets.QLineEdit() - self.replace_button = QtWidgets.QPushButton("Replace") - self.replace_button.clicked.connect(self.replace) - self.replace_all_button = QtWidgets.QPushButton("Replace All") - self.replace_all_button.clicked.connect( - lambda: self.replace(rep_all=True)) - self.replace_lineEdit.returnPressed.connect(self.replace_button.click) - - # Layout - self.replace_layout = QtWidgets.QHBoxLayout() - self.replace_layout.addWidget(self.replace_label) - self.replace_layout.addWidget(self.replace_lineEdit, stretch=1) - self.replace_layout.addWidget(self.replace_button) - self.replace_layout.addWidget(self.replace_all_button) - - # Info text - self.info_text = QtWidgets.QLabel("") - self.info_text.setVisible(False) - self.info_text.mousePressEvent = lambda x: self.info_text.setVisible( - False) - #f = self.info_text.font() - # f.setItalic(True) - # self.info_text.setFont(f) - # self.info_text.clicked.connect(lambda:self.info_text.setVisible(False)) - - # Divider line - line = QtWidgets.QFrame() - line.setFrameShape(QtWidgets.QFrame.HLine) - line.setFrameShadow(QtWidgets.QFrame.Sunken) - line.setLineWidth(0) - line.setMidLineWidth(1) - line.setFrameShadow(QtWidgets.QFrame.Sunken) - - # -------------- - # Main Layout - # -------------- - - self.layout = QtWidgets.QVBoxLayout() - self.layout.addSpacing(4) - self.layout.addWidget(self.info_text) - self.layout.addLayout(self.find_layout) - self.layout.addLayout(self.replace_layout) - self.layout.setSpacing(4) - try: # >n11 - self.layout.setMargin(2) - except: # 0: # If not found but there are matches, start over - cursor.movePosition(QtGui.QTextCursor.Start) - self.editor.setTextCursor(cursor) - self.editor.find(find_str, flags) - else: - cursor.insertText(rep_str) - self.editor.find( - rep_str, flags | QtGui.QTextDocument.FindBackward) - - cursor.endEditBlock() - self.replace_lineEdit.setFocus() - return - - -# -------------------------------- -# Snippets -# -------------------------------- -class SnippetsPanel(QtWidgets.QDialog): - def __init__(self, parent): - super(SnippetsPanel, self).__init__(parent) - - self.knobScripter = parent - - self.setWindowFlags(self.windowFlags() | - QtCore.Qt.WindowStaysOnTopHint) - self.setWindowTitle("Snippet editor") - - self.snippets_txt_path = self.knobScripter.snippets_txt_path - self.snippets_dict = self.loadSnippetsDict(path=self.snippets_txt_path) - #self.snippets_dict = snippets_dic - - # self.saveSnippets(snippets_dic) - - self.initUI() - self.resize(500, 300) - - def initUI(self): - self.layout = QtWidgets.QVBoxLayout() - - # First Area (Titles) - title_layout = QtWidgets.QHBoxLayout() - shortcuts_label = QtWidgets.QLabel("Shortcut") - code_label = QtWidgets.QLabel("Code snippet") - title_layout.addWidget(shortcuts_label, stretch=1) - title_layout.addWidget(code_label, stretch=2) - self.layout.addLayout(title_layout) - - # Main Scroll area - self.scroll_content = QtWidgets.QWidget() - self.scroll_layout = QtWidgets.QVBoxLayout() - - self.buildSnippetWidgets() - - self.scroll_content.setLayout(self.scroll_layout) - - # Scroll Area Properties - self.scroll = QtWidgets.QScrollArea() - self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) - self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.scroll.setWidgetResizable(True) - self.scroll.setWidget(self.scroll_content) - - self.layout.addWidget(self.scroll) - - # File knob test - #self.filePath_lineEdit = SnippetFilePath(self) - # self.filePath_lineEdit - # self.layout.addWidget(self.filePath_lineEdit) - - # Lower buttons - self.bottom_layout = QtWidgets.QHBoxLayout() - - self.add_btn = QtWidgets.QPushButton("Add snippet") - self.add_btn.setToolTip("Create empty fields for an extra snippet.") - self.add_btn.clicked.connect(self.addSnippet) - self.bottom_layout.addWidget(self.add_btn) - - self.addPath_btn = QtWidgets.QPushButton("Add custom path") - self.addPath_btn.setToolTip( - "Add a custom path to an external snippets .txt file.") - self.addPath_btn.clicked.connect(self.addCustomPath) - self.bottom_layout.addWidget(self.addPath_btn) - - self.bottom_layout.addStretch() - - self.save_btn = QtWidgets.QPushButton('OK') - self.save_btn.setToolTip( - "Save the snippets into a json file and close the panel.") - self.save_btn.clicked.connect(self.okPressed) - self.bottom_layout.addWidget(self.save_btn) - - self.cancel_btn = QtWidgets.QPushButton("Cancel") - self.cancel_btn.setToolTip("Cancel any new snippets or modifications.") - self.cancel_btn.clicked.connect(self.close) - self.bottom_layout.addWidget(self.cancel_btn) - - self.apply_btn = QtWidgets.QPushButton('Apply') - self.apply_btn.setToolTip("Save the snippets into a json file.") - self.apply_btn.setShortcut('Ctrl+S') - self.apply_btn.clicked.connect(self.applySnippets) - self.bottom_layout.addWidget(self.apply_btn) - - self.help_btn = QtWidgets.QPushButton('Help') - self.help_btn.setShortcut('F1') - self.help_btn.clicked.connect(self.showHelp) - self.bottom_layout.addWidget(self.help_btn) - - self.layout.addLayout(self.bottom_layout) - - self.setLayout(self.layout) - - def reload(self): - ''' - Clears everything without saving and redoes the widgets etc. - Only to be called if the panel isn't shown meaning it's closed. - ''' - for i in reversed(range(self.scroll_layout.count())): - self.scroll_layout.itemAt(i).widget().deleteLater() - - self.snippets_dict = self.loadSnippetsDict(path=self.snippets_txt_path) - - self.buildSnippetWidgets() - - def buildSnippetWidgets(self): - for i, (key, val) in enumerate(self.snippets_dict.items()): - if re.match(r"\[custom-path-[0-9]+\]$", key): - file_edit = SnippetFilePath(val) - self.scroll_layout.insertWidget(-1, file_edit) - else: - snippet_edit = SnippetEdit(key, val, parent=self) - self.scroll_layout.insertWidget(-1, snippet_edit) - - def loadSnippetsDict(self, path=""): - ''' Load prefs. TO REMOVE ''' - if path == "": - path = self.knobScripter.snippets_txt_path - if not os.path.isfile(self.snippets_txt_path): - return {} - else: - with open(self.snippets_txt_path, "r") as f: - self.snippets = json.load(f) - return self.snippets - - def getSnippetsAsDict(self): - dic = {} - num_snippets = self.scroll_layout.count() - path_i = 1 - for s in range(num_snippets): - se = self.scroll_layout.itemAt(s).widget() - if se.__class__.__name__ == "SnippetEdit": - key = se.shortcut_editor.text() - val = se.script_editor.toPlainText() - if key != "": - dic[key] = val - else: - path = se.filepath_lineEdit.text() - if path != "": - dic["[custom-path-{}]".format(str(path_i))] = path - path_i += 1 - return dic - - def saveSnippets(self, snippets=""): - if snippets == "": - snippets = self.getSnippetsAsDict() - with open(self.snippets_txt_path, "w") as f: - prefs = json.dump(snippets, f, sort_keys=True, indent=4) - return prefs - - def applySnippets(self): - self.saveSnippets() - self.knobScripter.snippets = self.knobScripter.loadSnippets(maxDepth=5) - self.knobScripter.loadSnippets() - - def okPressed(self): - self.applySnippets() - self.accept() - - def addSnippet(self, key="", val=""): - se = SnippetEdit(key, val, parent=self) - self.scroll_layout.insertWidget(0, se) - self.show() - return se - - def addCustomPath(self, path=""): - cpe = SnippetFilePath(path) - self.scroll_layout.insertWidget(0, cpe) - self.show() - cpe.browseSnippets() - return cpe - - def showHelp(self): - ''' Create a new snippet, auto-completed with the help ''' - help_key = "help" - help_val = """Snippets are a convenient way to have code blocks that you can call through a shortcut.\n\n1. Simply write a shortcut on the text input field on the left. You can see this one is set to "test".\n\n2. Then, write a code or whatever in this script editor. You can include $$ as the placeholder for where you'll want the mouse cursor to appear.\n\n3. Finally, click OK or Apply to save the snippets. On the main script editor, you'll be able to call any snippet by writing the shortcut (in this example: help) and pressing the Tab key.\n\nIn order to remove a snippet, simply leave the shortcut and contents blank, and save the snippets.""" - help_se = self.addSnippet(help_key, help_val) - help_se.script_editor.resize(160, 160) - - -class SnippetEdit(QtWidgets.QWidget): - ''' Simple widget containing two fields, for the snippet shortcut and content ''' - - def __init__(self, key="", val="", parent=None): - super(SnippetEdit, self).__init__(parent) - - self.knobScripter = parent.knobScripter - self.color_scheme = self.knobScripter.color_scheme - self.layout = QtWidgets.QHBoxLayout() - - self.shortcut_editor = QtWidgets.QLineEdit(self) - f = self.shortcut_editor.font() - f.setWeight(QtGui.QFont.Bold) - self.shortcut_editor.setFont(f) - self.shortcut_editor.setText(str(key)) - #self.script_editor = QtWidgets.QTextEdit(self) - self.script_editor = KnobScripterTextEdit() - self.script_editor.setMinimumHeight(100) - self.script_editor.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.highlighter = KSScriptEditorHighlighter( - self.script_editor.document(), self) - self.script_editor_font = self.knobScripter.script_editor_font - self.script_editor.setFont(self.script_editor_font) - self.script_editor.resize(90, 90) - self.script_editor.setPlainText(str(val)) - self.layout.addWidget(self.shortcut_editor, - stretch=1, alignment=Qt.AlignTop) - self.layout.addWidget(self.script_editor, stretch=2) - self.layout.setContentsMargins(0, 0, 0, 0) - - self.setLayout(self.layout) - - -class SnippetFilePath(QtWidgets.QWidget): - ''' Simple widget containing a filepath lineEdit and a button to open the file browser ''' - - def __init__(self, path="", parent=None): - super(SnippetFilePath, self).__init__(parent) - - self.layout = QtWidgets.QHBoxLayout() - - self.custompath_label = QtWidgets.QLabel(self) - self.custompath_label.setText("Custom path: ") - - self.filepath_lineEdit = QtWidgets.QLineEdit(self) - self.filepath_lineEdit.setText(str(path)) - #self.script_editor = QtWidgets.QTextEdit(self) - self.filepath_lineEdit.setStyleSheet( - 'background:#282828;color:#EEE;') # Main Colors - self.script_editor_font = QtGui.QFont() - self.script_editor_font.setFamily("Courier") - self.script_editor_font.setStyleHint(QtGui.QFont.Monospace) - self.script_editor_font.setFixedPitch(True) - self.script_editor_font.setPointSize(11) - self.filepath_lineEdit.setFont(self.script_editor_font) - - self.file_button = QtWidgets.QPushButton(self) - self.file_button.setText("Browse...") - self.file_button.clicked.connect(self.browseSnippets) - - self.layout.addWidget(self.custompath_label) - self.layout.addWidget(self.filepath_lineEdit) - self.layout.addWidget(self.file_button) - self.layout.setContentsMargins(0, 10, 0, 10) - - self.setLayout(self.layout) - - def browseSnippets(self): - ''' Opens file panel for ...snippets.txt ''' - browseLocation = nuke.getFilename('Select snippets file', '*.txt') - - if not browseLocation: - return - - self.filepath_lineEdit.setText(browseLocation) - return - - -# -------------------------------- -# Implementation -# -------------------------------- - -def showKnobScripter(knob="knobChanged"): - selection = nuke.selectedNodes() - if not len(selection): - pan = KnobScripter() - else: - pan = KnobScripter(selection[0], knob) - pan.show() - - -def addKnobScripterPanel(): - global knobScripterPanel - try: - knobScripterPanel = panels.registerWidgetAsPanel('nuke.KnobScripterPane', 'Knob Scripter', - 'com.adrianpueyo.KnobScripterPane') - knobScripterPanel.addToPane(nuke.getPaneFor('Properties.1')) - - except: - knobScripterPanel = panels.registerWidgetAsPanel( - 'nuke.KnobScripterPane', 'Knob Scripter', 'com.adrianpueyo.KnobScripterPane') - - -nuke.KnobScripterPane = KnobScripterPane -log("KS LOADED") -ksShortcut = "alt+z" -addKnobScripterPanel() -nuke.menu('Nuke').addCommand( - 'Edit/Node/Open Floating Knob Scripter', showKnobScripter, ksShortcut) -nuke.menu('Nuke').addCommand('Edit/Node/Update KnobScripter Context', - updateContext).setVisible(False) diff --git a/openpype/hosts/nuke/startup/init.py b/openpype/hosts/nuke/startup/init.py deleted file mode 100644 index d7560814bf..0000000000 --- a/openpype/hosts/nuke/startup/init.py +++ /dev/null @@ -1,4 +0,0 @@ -import nuke - -# default write mov -nuke.knobDefault('Write.mov.colorspace', 'sRGB') From c25cb9e1f5749b4dd071c873b94fd1e5be1193bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 16:03:02 +0200 Subject: [PATCH 155/195] fixed missing "parent" in fields --- openpype/client/entities.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index a56288c1e8..1bfab5ad57 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -23,7 +23,7 @@ def _get_project_connection(project_name=None): return mongodb -def _prepare_fields(fields): +def _prepare_fields(fields, ensure_fields=None): if not fields: return None @@ -33,6 +33,10 @@ def _prepare_fields(fields): } if "_id" not in output: output["_id"] = True + + if ensure_fields: + for key in ensure_fields: + output[key] = True return output @@ -655,9 +659,8 @@ def get_last_versions(project_name, subset_ids, fields=None): doc["_version_id"] for doc in conn.aggregate(_pipeline) ] - fields = _prepare_fields(fields) - if fields and "parent" not in fields: - fields.append("parent") + + fields = _prepare_fields(fields, ["parent"]) version_docs = get_versions( project_name, version_ids=version_ids, fields=fields From 67532139b989f6e831400a12c21dab33516d3004 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 Jun 2022 16:05:00 +0200 Subject: [PATCH 156/195] changed variable name to required_fields --- openpype/client/entities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 1bfab5ad57..cc4032712c 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -23,7 +23,7 @@ def _get_project_connection(project_name=None): return mongodb -def _prepare_fields(fields, ensure_fields=None): +def _prepare_fields(fields, required_fields=None): if not fields: return None @@ -34,8 +34,8 @@ def _prepare_fields(fields, ensure_fields=None): if "_id" not in output: output["_id"] = True - if ensure_fields: - for key in ensure_fields: + if required_fields: + for key in required_fields: output[key] = True return output From ca926cf3102d0ddb40d17e88c117415773314278 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 17:05:23 +0200 Subject: [PATCH 157/195] nuke: adding extract thumbnail settings --- .../defaults/project_settings/nuke.json | 3 ++ .../schemas/schema_nuke_publish.json | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 16348bec85..6c45e2a9c1 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -166,6 +166,9 @@ }, "ExtractThumbnail": { "enabled": true, + "use_rendered": true, + "bake_viewer_process": true, + "bake_viewer_input_process": true, "nodes": { "Reformat": [ [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index 04df957d67..575bfe79e7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -135,9 +135,31 @@ "label": "Enabled" }, { - "type": "raw-json", - "key": "nodes", - "label": "Nodes" + "type": "boolean", + "key": "use_rendered", + "label": "Use rendered images" + }, + { + "type": "boolean", + "key": "bake_viewer_process", + "label": "Bake viewer process" + }, + { + "type": "boolean", + "key": "bake_viewer_input_process", + "label": "Bake viewer input process" + }, + { + "type": "collapsible-wrap", + "label": "Nodes", + "collapsible": true, + "children": [ + { + "type": "raw-json", + "key": "nodes", + "label": "Nodes" + } + ] } ] }, From e21106aa9250d5317a157adcac7b38cffab7d990 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 17:06:19 +0200 Subject: [PATCH 158/195] nuke: adding new attributes to extract thumnail --- .../nuke/plugins/publish/extract_thumbnail.py | 96 +++++++++++-------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index ef6d486ca2..ce01f12a41 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -23,9 +23,13 @@ class ExtractThumbnail(openpype.api.Extractor): families = ["review"] hosts = ["nuke"] - # presets + # settings + use_rendered = False + bake_viewer_process = True + bake_viewer_input_process = True nodes = {} + def process(self, instance): if "render.farm" in instance.data["families"]: return @@ -53,48 +57,58 @@ class ExtractThumbnail(openpype.api.Extractor): "StagingDir `{0}`...".format(instance.data["stagingDir"])) temporary_nodes = [] - collection = instance.data.get("collection", None) - if collection: - # get path - fname = os.path.basename(collection.format( - "{head}{padding}{tail}")) - fhead = collection.format("{head}") + # try to connect already rendered images + if self.use_rendered: + collection = instance.data.get("collection", None) + self.log.debug("__ collection: `{}`".format(collection)) - # get first and last frame - first_frame = min(collection.indexes) - last_frame = max(collection.indexes) - else: - fname = os.path.basename(instance.data.get("path", None)) - fhead = os.path.splitext(fname)[0] + "." - first_frame = instance.data.get("frameStart", None) - last_frame = instance.data.get("frameEnd", None) + if collection: + # get path + fname = os.path.basename(collection.format( + "{head}{padding}{tail}")) + fhead = collection.format("{head}") - if "#" in fhead: - fhead = fhead.replace("#", "")[:-1] + # get first and last frame + first_frame = min(collection.indexes) + last_frame = max(collection.indexes) + else: + fname = os.path.basename(instance.data.get("path", None)) + fhead = os.path.splitext(fname)[0] + "." + first_frame = instance.data.get("frameStart", None) + last_frame = instance.data.get("frameEnd", None) - path_render = os.path.join(staging_dir, fname).replace("\\", "/") - # check if file exist otherwise connect to write node - if os.path.isfile(path_render): - rnode = nuke.createNode("Read") + self.log.debug("__ fhead: `{}`".format(fhead)) - rnode["file"].setValue(path_render) + if "#" in fhead: + fhead = fhead.replace("#", "")[:-1] - rnode["first"].setValue(first_frame) - rnode["origfirst"].setValue(first_frame) - rnode["last"].setValue(last_frame) - rnode["origlast"].setValue(last_frame) - temporary_nodes.append(rnode) - previous_node = rnode - else: - previous_node = node + path_render = os.path.join(staging_dir, fname).replace("\\", "/") + self.log.debug("__ path_render: `{}`".format(path_render)) - # get input process and connect it to baking - ipn = self.get_view_process_node() - if ipn is not None: - ipn.setInput(0, previous_node) - previous_node = ipn - temporary_nodes.append(ipn) + # check if file exist otherwise connect to write node + if os.path.isfile(path_render): + rnode = nuke.createNode("Read") + + rnode["file"].setValue(path_render) + + rnode["first"].setValue(first_frame) + rnode["origfirst"].setValue(first_frame) + rnode["last"].setValue(last_frame) + rnode["origlast"].setValue(last_frame) + temporary_nodes.append(rnode) + previous_node = rnode + else: + previous_node = node + + # bake viewer input look node into thumbnail image + if self.bake_viewer_input_process: + # get input process and connect it to baking + ipn = self.get_view_process_node() + if ipn is not None: + ipn.setInput(0, previous_node) + previous_node = ipn + temporary_nodes.append(ipn) reformat_node = nuke.createNode("Reformat") @@ -110,10 +124,12 @@ class ExtractThumbnail(openpype.api.Extractor): previous_node = reformat_node temporary_nodes.append(reformat_node) - dag_node = nuke.createNode("OCIODisplay") - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) + # bake viewer colorspace into thumbnail image + if self.bake_viewer_process: + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) # create write node write_node = nuke.createNode("Write") From 79f81b6b36bb94d077f1b3ef7e35db06e68b002f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 May 2022 21:09:47 +0200 Subject: [PATCH 159/195] nuke: refactory extract thumbnail for new settings attributes --- .../nuke/plugins/publish/extract_thumbnail.py | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index ce01f12a41..092fc07d6c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -42,11 +42,17 @@ class ExtractThumbnail(openpype.api.Extractor): self.render_thumbnail(instance) def render_thumbnail(self, instance): + first_frame = instance.data["frameStartHandle"] + last_frame = instance.data["frameEndHandle"] + + # find frame range and define middle thumb frame + mid_frame = int((last_frame - first_frame) / 2) + node = instance[0] # group node self.log.info("Creating staging dir...") if "representations" not in instance.data: - instance.data["representations"] = list() + instance.data["representations"] = [] staging_dir = os.path.normpath( os.path.dirname(instance.data['path'])) @@ -69,21 +75,19 @@ class ExtractThumbnail(openpype.api.Extractor): "{head}{padding}{tail}")) fhead = collection.format("{head}") - # get first and last frame - first_frame = min(collection.indexes) - last_frame = max(collection.indexes) + thumb_fname = list(collection)[mid_frame] else: - fname = os.path.basename(instance.data.get("path", None)) + fname = thumb_fname = os.path.basename( + instance.data.get("path", None)) fhead = os.path.splitext(fname)[0] + "." - first_frame = instance.data.get("frameStart", None) - last_frame = instance.data.get("frameEnd", None) self.log.debug("__ fhead: `{}`".format(fhead)) if "#" in fhead: fhead = fhead.replace("#", "")[:-1] - path_render = os.path.join(staging_dir, fname).replace("\\", "/") + path_render = os.path.join( + staging_dir, thumb_fname).replace("\\", "/") self.log.debug("__ path_render: `{}`".format(path_render)) # check if file exist otherwise connect to write node @@ -92,10 +96,13 @@ class ExtractThumbnail(openpype.api.Extractor): rnode["file"].setValue(path_render) - rnode["first"].setValue(first_frame) - rnode["origfirst"].setValue(first_frame) - rnode["last"].setValue(last_frame) - rnode["origlast"].setValue(last_frame) + # turn it raw if none of baking is ON + if all([ + not self.bake_viewer_input_process, + not self.bake_viewer_process + ]): + rnode["raw"].setValue(True) + temporary_nodes.append(rnode) previous_node = rnode else: @@ -144,26 +151,18 @@ class ExtractThumbnail(openpype.api.Extractor): temporary_nodes.append(write_node) tags = ["thumbnail", "publish_on_farm"] - # retime for - mid_frame = int((int(last_frame) - int(first_frame)) / 2) \ - + int(first_frame) - first_frame = int(last_frame) / 2 - last_frame = int(last_frame) / 2 - repre = { 'name': name, 'ext': "jpg", "outputName": "thumb", 'files': file, "stagingDir": staging_dir, - "frameStart": first_frame, - "frameEnd": last_frame, "tags": tags } instance.data["representations"].append(repre) # Render frames - nuke.execute(write_node.name(), int(mid_frame), int(mid_frame)) + nuke.execute(write_node.name(), mid_frame, mid_frame) self.log.debug( "representations: {}".format(instance.data["representations"])) From 75b6acb0a4af5491e5a38a287f8ead9f88381123 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Jun 2022 18:47:58 +0200 Subject: [PATCH 160/195] OP-3214 - removed wrong targets on Extractor This caused missing extractors that should be running on a farm (Slate extractor for Nuke...). Explicitly set all plugins creating jobs on DL to be triggered only on local. --- openpype/api.py | 2 -- .../plugins/publish/submit_aftereffects_deadline.py | 1 + .../plugins/publish/submit_harmony_deadline.py | 1 + .../deadline/plugins/publish/submit_maya_deadline.py | 1 + .../publish/submit_maya_remote_publish_deadline.py | 3 ++- .../deadline/plugins/publish/submit_nuke_deadline.py | 1 + .../deadline/plugins/publish/submit_publish_job.py | 1 + openpype/plugin.py | 12 ------------ 8 files changed, 7 insertions(+), 15 deletions(-) diff --git a/openpype/api.py b/openpype/api.py index e049a683c6..9ce745b653 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -44,7 +44,6 @@ from . import resources from .plugin import ( Extractor, - Integrator, ValidatePipelineOrder, ValidateContentsOrder, @@ -87,7 +86,6 @@ __all__ = [ # plugin classes "Extractor", - "Integrator", # ordering "ValidatePipelineOrder", "ValidateContentsOrder", diff --git a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index b6584f239e..de8df3dd9e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -33,6 +33,7 @@ class AfterEffectsSubmitDeadline( hosts = ["aftereffects"] families = ["render.farm"] # cannot be "render' as that is integrated use_published = True + targets = ["local"] priority = 50 chunk_size = 1000000 diff --git a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py index 912f0f4026..2cf502224f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -238,6 +238,7 @@ class HarmonySubmitDeadline( order = pyblish.api.IntegratorOrder + 0.1 hosts = ["harmony"] families = ["render.farm"] + targets = ["local"] optional = True use_published = False diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 8562c85f7d..9964e3c646 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -287,6 +287,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.1 hosts = ["maya"] families = ["renderlayer"] + targets = ["local"] use_published = True tile_assembler_plugin = "OpenPypeTileAssembler" diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 4f82818d6d..57572fcb24 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -10,7 +10,7 @@ import openpype.api import pyblish.api -class MayaSubmitRemotePublishDeadline(openpype.api.Integrator): +class MayaSubmitRemotePublishDeadline(pyblish.api.InstancePlugin): """Submit Maya scene to perform a local publish in Deadline. Publishing in Deadline can be helpful for scenes that publish very slow. @@ -31,6 +31,7 @@ class MayaSubmitRemotePublishDeadline(openpype.api.Integrator): order = pyblish.api.IntegratorOrder hosts = ["maya"] families = ["publish.farm"] + targets = ["local"] def process(self, instance): settings = get_project_settings(os.getenv("AVALON_PROJECT")) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 94c703d66d..ca68c87f9a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -23,6 +23,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): hosts = ["nuke", "nukestudio"] families = ["render.farm", "prerender.farm"] optional = True + targets = ["local"] # presets priority = 50 diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..860d9fd01b 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -103,6 +103,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.2 icon = "tractor" deadline_plugin = "OpenPype" + targets = ["local"] hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects", "harmony"] diff --git a/openpype/plugin.py b/openpype/plugin.py index 6637ad1d8b..bb9bc2ff85 100644 --- a/openpype/plugin.py +++ b/openpype/plugin.py @@ -18,16 +18,6 @@ class InstancePlugin(pyblish.api.InstancePlugin): super(InstancePlugin, cls).process(cls, *args, **kwargs) -class Integrator(InstancePlugin): - """Integrator base class. - - Wraps pyblish instance plugin. Targets set to "local" which means all - integrators should run on "local" publishes, by default. - "remote" targets could be used for integrators that should run externally. - """ - targets = ["local"] - - class Extractor(InstancePlugin): """Extractor base class. @@ -38,8 +28,6 @@ class Extractor(InstancePlugin): """ - targets = ["local"] - order = 2.0 def staging_dir(self, instance): From db1316dd688dc2bba62395aa968ab8af7b5ce552 Mon Sep 17 00:00:00 2001 From: maxpareschi Date: Wed, 15 Jun 2022 20:56:33 +0200 Subject: [PATCH 161/195] Did this the easy way and let's go! --- openpype/modules/ftrack/ftrack_module.py | 3 -- openpype/modules/ftrack/tray/ftrack_tray.py | 42 +------------------ .../defaults/system_settings/modules.json | 1 - .../module_settings/schema_ftrack.json | 5 --- 4 files changed, 1 insertion(+), 50 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 048e5ebfb1..f99e189082 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -42,9 +42,6 @@ class FtrackModule( self.ftrack_url = ftrack_url - ftrack_open_as_app = ftrack_settings["ftrack_open_as_app"] - self.ftrack_open_as_app = ftrack_open_as_app - current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index e822fd4639..2919ae22fb 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -2,10 +2,6 @@ import os import time import datetime import threading -import platform -import posixpath -import ntpath -import webbrowser from Qt import QtCore, QtWidgets, QtGui @@ -54,43 +50,7 @@ class FtrackTrayWrapper: self.widget_login.raise_() def show_ftrack_browser(self): - env_pf64 = os.environ['ProgramW6432'].replace( - ntpath.sep, posixpath.sep) - env_pf32 = os.environ['ProgramFiles(x86)'].replace( - ntpath.sep, posixpath.sep) - env_loc = os.environ['LocalAppData'].replace( - ntpath.sep, posixpath.sep) - chromium_paths_win = [ - f"{env_pf64}/Google/Chrome/Application/chrome.exe", - f"{env_pf32}/Google/Chrome/Application/chrome.exe", - f"{env_loc}/Google/Chrome/Application/chrome.exe", - f"{env_pf32}/Microsoft/Edge/Application/msedge.exe" - ] - cur_os = cur_os = platform.system().lower() - if cur_os == "windows": - is_chromium = False - for p in chromium_paths_win: - if os.path.exists(p): - is_chromium = True - chromium_path = p - break - if is_chromium and self.module.ftrack_open_as_app: - webbrowser.get(f"{chromium_path} %s").open_new( - f"--app={self.module.ftrack_url}") - else: - webbrowser.get(using="windows-default").open_new( - self.module.ftrack_url) - - else: - if self.module.ftrack_open_as_app: - try: - webbrowser.get(using='chrome').open_new( - f"--app={self.module.ftrack_url}") - except webbrowser.Error: - webbrowser.open_new(self.module.ftrack_url) - else: - webbrowser.open_new(self.module.ftrack_url) - return + QtGui.QDesktopServices.openUrl(self.module.ftrack_url) def validate(self): validation = False diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 6d09652bb9..537e287366 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -15,7 +15,6 @@ "ftrack": { "enabled": false, "ftrack_server": "", - "ftrack_open_as_app": false, "ftrack_actions_path": { "windows": [], "darwin": [], diff --git a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json index 570d856cf8..654ddf2938 100644 --- a/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json +++ b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json @@ -16,11 +16,6 @@ "key": "ftrack_server", "label": "Server" }, - { - "type": "boolean", - "key": "ftrack_open_as_app", - "label": "Open in app mode" - }, { "type": "splitter" }, From eb3e0d626565697ff3fa7029cca9a4e318a61a8d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 11:35:57 +0200 Subject: [PATCH 162/195] add new representation only if was created --- openpype/plugins/publish/extract_jpeg_exr.py | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index 3017656621..de01ebd2de 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -92,17 +92,19 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) + # adding representation + self.log.debug( + "Adding thumbnail representation: {}".format(new_repre) + ) + instance.data["representations"].append(new_repre) def _get_filtered_repres(self, instance): filtered_repres = [] From 5bdbebf4cc163d182d599561967088382ba0adc7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 11:36:03 +0200 Subject: [PATCH 163/195] create only one thumbnail --- openpype/plugins/publish/extract_jpeg_exr.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index de01ebd2de..a467728e77 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -105,6 +105,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "Adding thumbnail representation: {}".format(new_repre) ) instance.data["representations"].append(new_repre) + # There is no need to create more then one thumbnail + break def _get_filtered_repres(self, instance): filtered_repres = [] From b722062928b081f9d7a7e51ec37d7118834459c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 16 Jun 2022 12:35:06 +0200 Subject: [PATCH 164/195] Fix: Kitsu module first synchronization Add a `None` to `pop(...)` to avoid process abortion at early stage of production --- openpype/modules/kitsu/utils/update_op_with_zou.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 673a195747..08e50d959b 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -85,7 +85,7 @@ def update_op_assets( # Frame in, fallback on 0 frame_in = int(item_data.get("frame_in") or 0) item_data["frameStart"] = frame_in - item_data.pop("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 = ( @@ -94,7 +94,7 @@ def update_op_assets( else frame_in + frames_duration ) item_data["frameEnd"] = int(frame_out) - item_data.pop("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"] From edf13e877f7416c2a64e1a123003bdfffce10d64 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 14:45:06 +0200 Subject: [PATCH 165/195] removed requirement of pypeclub role in default settings --- .../defaults/system_settings/modules.json | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json index 537e287366..8cd4114cb0 100644 --- a/openpype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -59,13 +59,11 @@ "applications": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] } }, @@ -73,25 +71,21 @@ "tools_env": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] }, "avalon_mongo_id": { "write_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ], "read_security_roles": [ "API", - "Administrator", - "Pypeclub" + "Administrator" ] }, "fps": { From 8857423828377a319fd80292f9b0ca0e49e5b2ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 14:49:52 +0200 Subject: [PATCH 166/195] Changed label of custom attribute action --- .../ftrack/event_handlers_user/action_create_cust_attrs.py | 4 ++-- website/docs/module_ftrack.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py index 88dc8213bd..d04440a564 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -140,9 +140,9 @@ class CustomAttributes(BaseAction): identifier = 'create.update.attributes' #: Action label. label = "OpenPype Admin" - variant = '- Create/Update Avalon Attributes' + variant = '- Create/Update Custom Attributes' #: Action description. - description = 'Creates Avalon/Mongo ID for double check' + description = 'Creates required custom attributes in ftrack' icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") settings_key = "create_update_attributes" diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index fd9687ed9d..667782754f 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -26,7 +26,7 @@ You can only use our Ftrack Actions and publish to Ftrack if each artist is logg ### Custom Attributes After successfully connecting OpenPype with you Ftrack, you can right click on any project in Ftrack and you should see a bunch of actions available. The most important one is called `OpenPype Admin` and contains multiple options inside. -To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Avalon Attributes](manager_ftrack_actions.md#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. +To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Custom Attributes](manager_ftrack_actions.md#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. From bcb52309517350de2ad0aa91f28ab317dfffdbce Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 15:05:46 +0200 Subject: [PATCH 167/195] Fix - add default target for New Publisher --- openpype/pipeline/create/context.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2f1922c103..7931ea400a 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -829,9 +829,10 @@ class CreateContext: discover_result = publish_plugins_discover() publish_plugins = discover_result.plugins - targets = pyblish.logic.registered_targets() or ["default"] + targets = set(pyblish.logic.registered_targets()) + targets.add("default") plugins_by_targets = pyblish.logic.plugins_by_targets( - publish_plugins, targets + publish_plugins, list(targets) ) # Collect plugins that can have attribute definitions for plugin in publish_plugins: From 5b9d2f2915f59e4b98f00bdf5855a7f093a7c46a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 16:41:09 +0200 Subject: [PATCH 168/195] Fix - add AE validation scene on render.local It was missing for local rendering. --- .../aftereffects/plugins/publish/validate_scene_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py index 14e224fdc2..6fe63fc41e 100644 --- a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py @@ -54,7 +54,7 @@ class ValidateSceneSettings(OptionalPyblishPluginMixin, order = pyblish.api.ValidatorOrder label = "Validate Scene Settings" - families = ["render.farm", "render"] + families = ["render.farm", "render.local", "render"] hosts = ["aftereffects"] optional = True From ab0bc2108c5d9a954e7fb9adde3cda732ace9afe Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Jun 2022 16:44:18 +0200 Subject: [PATCH 169/195] deadline: fixing misidentification of revieables --- .../deadline/plugins/publish/submit_publish_job.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 0583c25b57..d2f709825c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -642,9 +642,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): def _solve_families(self, instance, preview=False): families = instance.get("families") - # test also instance data review attribute - preview = preview or instance.get("review") - # if we have one representation with preview tag # flag whole instance for review and for ftrack if preview: @@ -726,6 +723,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): families = ["render"] + # pass review to families if marked as review + if data.get("review"): + families.append("review") + instance_skeleton_data = { "family": "render", "subset": subset, @@ -754,10 +755,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "family": "prerender", "families": []}) - # also include review attribute if available - if "review" in data: - instance_skeleton_data["review"] = data["review"] - # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From 2890c950f5b58fd7654497aae22016d2d1df56ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 16:46:31 +0200 Subject: [PATCH 170/195] trigger open events --- openpype/tools/workfiles/files_widget.py | 45 ++++++++++++++++++++---- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 68fe8301c9..709f05b26b 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -90,7 +90,9 @@ class FilesWidget(QtWidgets.QWidget): self._task_type = None # Pype's anatomy object for current project - self.anatomy = Anatomy(legacy_io.Session["AVALON_PROJECT"]) + project_name = legacy_io.Session["AVALON_PROJECT"] + self.anatomy = Anatomy(project_name) + self.project_name = project_name # Template key used to get work template from anatomy templates self.template_key = "work" @@ -98,6 +100,7 @@ class FilesWidget(QtWidgets.QWidget): self._workfiles_root = None self._workdir_path = None self.host = registered_host() + self.host_name = os.environ["AVALON_APP"] # Whether to automatically select the latest modified # file on a refresh of the files model. @@ -385,8 +388,9 @@ class FilesWidget(QtWidgets.QWidget): return None if self._asset_doc is None: - project_name = legacy_io.active_project() - self._asset_doc = get_asset_by_id(project_name, self._asset_id) + self._asset_doc = get_asset_by_id( + self.project_name, self._asset_id + ) return self._asset_doc @@ -396,8 +400,8 @@ class FilesWidget(QtWidgets.QWidget): session = legacy_io.Session.copy() self.template_key = get_workfile_template_key( self._task_type, - session["AVALON_APP"], - project_name=session["AVALON_PROJECT"] + self.host_name, + project_name=self.project_name ) changes = compute_session_changes( session, @@ -453,8 +457,35 @@ class FilesWidget(QtWidgets.QWidget): # Save current scene, continue to open file host.save_file(current_file) + asset_name = None + asset_doc = self._get_asset_doc() + if asset_doc: + asset_name = asset_doc["name"] + + emit_event( + "workfile.open.before", + { + "filepath": filepath, + "project_name": self.project_name, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + }, + source="workfiles.tool" + ) self._enter_session() host.open_file(filepath) + emit_event( + "workfile.open.after", + { + "filepath": filepath, + "project_name": self.project_name, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + }, + source="workfiles.tool" + ) self.file_opened.emit() def save_changes_prompt(self): @@ -602,10 +633,10 @@ class FilesWidget(QtWidgets.QWidget): # Create extra folders create_workdir_extra_folders( self._workdir_path, - legacy_io.Session["AVALON_APP"], + self.host_name, self._task_type, self._task_name, - legacy_io.Session["AVALON_PROJECT"] + self.project_name ) # Trigger after save events emit_event( From ee4b635edd51523e46864e00c5ec873e477e3e3b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 16:51:55 +0200 Subject: [PATCH 171/195] trigger open workfile events in workfiles tool --- openpype/tools/workfiles/files_widget.py | 54 +++++++++++++++--------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 709f05b26b..a7e54471dc 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -1,6 +1,7 @@ import os import logging import shutil +import copy import Qt from Qt import QtWidgets, QtCore @@ -434,6 +435,21 @@ class FilesWidget(QtWidgets.QWidget): template_key=self.template_key ) + def _get_event_context_data(self): + asset_id = None + asset_name = None + asset_doc = self._get_asset_doc() + if asset_doc: + asset_id = asset_doc["_id"] + asset_name = asset_doc["name"] + return { + "project_name": self.project_name, + "asset_id": asset_id, + "asset_name": asset_name, + "task_name": self._task_name, + "host_name": self.host_name + } + def open_file(self, filepath): host = self.host if host.has_unsaved_changes(): @@ -457,33 +473,19 @@ class FilesWidget(QtWidgets.QWidget): # Save current scene, continue to open file host.save_file(current_file) - asset_name = None - asset_doc = self._get_asset_doc() - if asset_doc: - asset_name = asset_doc["name"] - + event_data_before = self._get_event_context_data() + event_data_before["filepath"] = filepath + event_data_after = copy.deepcopy(event_data_before) emit_event( "workfile.open.before", - { - "filepath": filepath, - "project_name": self.project_name, - "asset_name": asset_name, - "task_name": self._task_name, - "host_name": self.host_name - }, + event_data_before, source="workfiles.tool" ) self._enter_session() host.open_file(filepath) emit_event( "workfile.open.after", - { - "filepath": filepath, - "project_name": self.project_name, - "asset_name": asset_name, - "task_name": self._task_name, - "host_name": self.host_name - }, + event_data_after, source="workfiles.tool" ) self.file_opened.emit() @@ -598,9 +600,14 @@ class FilesWidget(QtWidgets.QWidget): src_path = self._get_selected_filepath() # Trigger before save event + event_data_before = self._get_event_context_data() + event_data_before.update({ + "filename": work_filename, + "workdir_path": self._workdir_path + }) emit_event( "workfile.save.before", - {"filename": work_filename, "workdir_path": self._workdir_path}, + event_data_before, source="workfiles.tool" ) @@ -638,10 +645,15 @@ class FilesWidget(QtWidgets.QWidget): self._task_name, self.project_name ) + event_data_after = self._get_event_context_data() + event_data_after.update({ + "filename": work_filename, + "workdir_path": self._workdir_path + }) # Trigger after save events emit_event( "workfile.save.after", - {"filename": work_filename, "workdir_path": self._workdir_path}, + event_data_after, source="workfiles.tool" ) From 94f9d6309822431dc00218687207cefcc4397b11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:05:26 +0200 Subject: [PATCH 172/195] modules have method that can be called on host installation --- openpype/modules/base.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index bca64b19f8..b9ccec13cc 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -463,6 +463,25 @@ class OpenPypeModule: pass + def on_host_install(self, host, host_name, project_name): + """Host was installed which gives option to handle in-host logic. + + It is a good option to register in-host event callbacks which are + specific for the module. The module is kept in memory for rest of + the process. + + Arguments may change in future. E.g. 'host_name' should be possible + to receive from 'host' object. + + Args: + host (ModuleType): Access to installed/registered host object. + host_name (str): Name of host. + project_name (str): Project name which is main part of host + context. + """ + + pass + def cli(self, module_click_group): """Add commands to click group. From 0c28a29650491603a04ad9f9178e7cdc81c9f73b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:05:48 +0200 Subject: [PATCH 173/195] trigger on_host_install method on host installation --- openpype/pipeline/context_tools.py | 33 ++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index c6e09cfba1..8643c3d69d 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -33,6 +33,9 @@ from . import ( _is_installed = False _registered_root = {"_": ""} _registered_host = {"_": None} +# Keep modules manager (and it's modules) in memory +# - that gives option to register modules' callbacks +_modules_manager = None log = logging.getLogger(__name__) @@ -44,6 +47,23 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +def _get_modules_manager(): + """Get or create modules manager for host installation. + + This is not meant for public usage. Reason is to keep modules + in memory of process to be able trigger their event callbacks if they + need any. + + Returns: + ModulesManager: Manager wrapping discovered modules. + """ + + global _modules_manager + if _modules_manager is None: + _modules_manager = ModulesManager() + return _modules_manager + + def register_root(path): """Register currently active root""" log.info("Registering root: %s" % path) @@ -70,10 +90,12 @@ def install_host(host): avalon host-interface. """ global _is_installed + global _modules_manager _is_installed = True legacy_io.install() + modules_manager = _get_modules_manager() missing = list() for key in ("AVALON_PROJECT", "AVALON_ASSET"): @@ -112,7 +134,14 @@ def install_host(host): else: pyblish.api.register_target("local") - install_openpype_plugins() + project_name = os.environ.get("AVALON_PROJECT") + host_name = os.environ.get("AVALON_APP") + + # Give option to handle host installation + for module in modules_manager.get_enabled_modules(): + module.on_host_install(host, host_name, project_name) + + install_openpype_plugins(project_name, host_name) def install_openpype_plugins(project_name=None, host_name=None): @@ -124,7 +153,7 @@ def install_openpype_plugins(project_name=None, host_name=None): pyblish.api.register_discovery_filter(filter_pyblish_plugins) register_loader_plugin_path(LOAD_PATH) - modules_manager = ModulesManager() + modules_manager = _get_modules_manager() publish_plugin_dirs = modules_manager.collect_plugin_paths()["publish"] for path in publish_plugin_dirs: pyblish.api.register_plugin_path(path) From 89a7d438b4b37e0903ae33f54de58bb04a03b9c4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:42:46 +0200 Subject: [PATCH 174/195] added human readable keys into taskChanged topic data --- openpype/lib/avalon_context.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 9d8a92cfe9..243e89e00d 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -797,8 +797,14 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): else: os.environ[key] = value + data = changes.copy() + # Convert env keys to human readable keys + data["project_name"] = legacy_io.Session["AVALON_PROJECT"] + data["asset_name"] = legacy_io.Session["AVALON_ASSET"] + data["task_name"] = legacy_io.Session["AVALON_TASK"] + # Emit session change - emit_event("taskChanged", changes.copy()) + emit_event("taskChanged", data) return changes From cbb876c155fb6f31356d74404f7763689e09a6d7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 16 Jun 2022 17:43:18 +0200 Subject: [PATCH 175/195] moved timer change on task change to timers manager module --- .../modules/timers_manager/timers_manager.py | 18 ++++++++++++++++++ openpype/pipeline/context_tools.py | 8 -------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py index 3f77a2b7dc..3cf1614316 100644 --- a/openpype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -7,6 +7,7 @@ from openpype_interfaces import ( ITrayService, ILaunchHookPaths ) +from openpype.lib.events import register_event_callback from openpype.pipeline import AvalonMongoDB from .exceptions import InvalidContextError @@ -422,3 +423,20 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths): } return requests.post(rest_api_url, json=data) + + def on_host_install(self, host, host_name, project_name): + self.log.debug("Installing task changed callback") + register_event_callback("taskChanged", self._on_host_task_change) + + def _on_host_task_change(self, event): + project_name = event["project_name"] + asset_name = event["asset_name"] + task_name = event["task_name"] + self.log.debug(( + "Sending message that timer should change to" + " Project: {} Asset: {} Task: {}" + ).format(project_name, asset_name, task_name)) + + self.start_timer_with_webserver( + project_name, asset_name, task_name, self.log + ) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 8643c3d69d..3e63eeba27 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -16,9 +16,7 @@ from openpype.modules import load_modules, ModulesManager from openpype.settings import get_project_settings from openpype.lib import ( Anatomy, - register_event_callback, filter_pyblish_plugins, - change_timer_to_current_context, ) from . import ( @@ -117,8 +115,6 @@ def install_host(host): register_host(host) - register_event_callback("taskChanged", _on_task_change) - def modified_emit(obj, record): """Method replacing `emit` in Pyblish's MessageHandler.""" record.msg = record.getMessage() @@ -197,10 +193,6 @@ def install_openpype_plugins(project_name=None, host_name=None): register_inventory_action(path) -def _on_task_change(): - change_timer_to_current_context() - - def uninstall_host(): """Undo all of what `install()` did""" host = registered_host() From 3da1720702a2f21350e8a9cb4f17bbca4e11db23 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Jun 2022 18:04:08 +0200 Subject: [PATCH 176/195] Fix - validate_scene_settings was failing for legacy instances Fix - renderLocal is published to 'render' folder same as render farm --- .../hosts/aftereffects/plugins/publish/collect_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index fa23bf92b0..97b3175c57 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -21,7 +21,7 @@ class AERenderInstance(RenderInstance): projectEntity = attr.ib(default=None) stagingDir = attr.ib(default=None) app_version = attr.ib(default=None) - publish_attributes = attr.ib(default=None) + publish_attributes = attr.ib(default={}) file_name = attr.ib(default=None) @@ -90,7 +90,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): subset_name = inst.data["subset"] instance = AERenderInstance( - family=family, + family="render", families=inst.data.get("families", []), version=version, time="", @@ -116,7 +116,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): toBeRenderedOn='deadline', fps=fps, app_version=app_version, - publish_attributes=inst.data.get("publish_attributes"), + publish_attributes=inst.data.get("publish_attributes", {}), file_name=render_q.file_name ) From e9168118d73cb265a4f5597b5d577197b41db4e1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 10:25:36 +0200 Subject: [PATCH 177/195] handle prerender earlier prerender --- .../deadline/plugins/publish/submit_publish_job.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index ae4ada709a..6d08e72839 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -722,14 +722,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): " This may cause issues." ).format(source)) - families = ["render"] + family = "render" + if "prerender" in instance.data["families"]: + family = "prerender" + families = [family] # pass review to families if marked as review if data.get("review"): families.append("review") instance_skeleton_data = { - "family": "render", + "family": family, "subset": subset, "families": families, "asset": asset, @@ -751,11 +754,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "useSequenceForReview": data.get("useSequenceForReview", True) } - if "prerender" in instance.data["families"]: - instance_skeleton_data.update({ - "family": "prerender", - "families": []}) - # skip locking version if we are creating v01 instance_version = instance.data.get("version") # take this if exists if instance_version != 1: From a1676d5c44de04d880996afc738d245bcd65e66d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 10:58:26 +0200 Subject: [PATCH 178/195] create representation variable all the time --- .../hosts/nuke/plugins/publish/precollect_writes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index e050fc8c52..7e50679ed5 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -72,12 +72,12 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if "representations" not in instance.data: instance.data["representations"] = list() - representation = { - 'name': ext, - 'ext': ext, - "stagingDir": output_dir, - "tags": list() - } + representation = { + 'name': ext, + 'ext': ext, + "stagingDir": output_dir, + "tags": list() + } try: collected_frames = [f for f in os.listdir(output_dir) From 7c7ae5d3dccefb409870b91ad8ef50097268c815 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jun 2022 11:38:14 +0200 Subject: [PATCH 179/195] Fix Maya 2019 support `__iter__` is only implemented in Maya 2020+ for Maya Python 2.0 iterators. --- openpype/hosts/maya/plugins/publish/collect_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 433fa9886d..6cc9b9d7e4 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -28,18 +28,18 @@ def get_all_children(nodes): dag = sel.getDagPath(0) iterator.reset(dag) - next(iterator) # ignore self + iterator.next() # ignore self while not iterator.isDone(): path = iterator.fullPathName() if path in traversed: iterator.prune() - next(iterator) + iterator.next() continue traversed.add(path) - next(iterator) + iterator.next() return list(traversed) From 5b1ab9e7d3694f127eb3457f5e8e2dab767008dd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jun 2022 11:52:35 +0200 Subject: [PATCH 180/195] Shush hound --- openpype/hosts/maya/plugins/publish/collect_instances.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 6cc9b9d7e4..ad1f794680 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -28,18 +28,19 @@ def get_all_children(nodes): dag = sel.getDagPath(0) iterator.reset(dag) - iterator.next() # ignore self + # ignore self + iterator.next() # noqa: B305 while not iterator.isDone(): path = iterator.fullPathName() if path in traversed: iterator.prune() - iterator.next() + iterator.next() # noqa: B305 continue traversed.add(path) - iterator.next() + iterator.next() # noqa: B305 return list(traversed) From b9abbcd61629d5103413399b5048ca5ad2d23545 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 12:11:00 +0200 Subject: [PATCH 181/195] fixed extract thumbnail in nuke --- .../nuke/plugins/publish/extract_thumbnail.py | 62 +++++++++---------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index 092fc07d6c..a622271855 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -65,48 +65,46 @@ class ExtractThumbnail(openpype.api.Extractor): temporary_nodes = [] # try to connect already rendered images - if self.use_rendered: - collection = instance.data.get("collection", None) - self.log.debug("__ collection: `{}`".format(collection)) + previous_node = node + collection = instance.data.get("collection", None) + self.log.debug("__ collection: `{}`".format(collection)) - if collection: - # get path - fname = os.path.basename(collection.format( - "{head}{padding}{tail}")) - fhead = collection.format("{head}") + if collection: + # get path + fname = os.path.basename(collection.format( + "{head}{padding}{tail}")) + fhead = collection.format("{head}") - thumb_fname = list(collection)[mid_frame] - else: - fname = thumb_fname = os.path.basename( - instance.data.get("path", None)) - fhead = os.path.splitext(fname)[0] + "." + thumb_fname = list(collection)[mid_frame] + else: + fname = thumb_fname = os.path.basename( + instance.data.get("path", None)) + fhead = os.path.splitext(fname)[0] + "." - self.log.debug("__ fhead: `{}`".format(fhead)) + self.log.debug("__ fhead: `{}`".format(fhead)) - if "#" in fhead: - fhead = fhead.replace("#", "")[:-1] + if "#" in fhead: + fhead = fhead.replace("#", "")[:-1] - path_render = os.path.join( - staging_dir, thumb_fname).replace("\\", "/") - self.log.debug("__ path_render: `{}`".format(path_render)) + path_render = os.path.join( + staging_dir, thumb_fname).replace("\\", "/") + self.log.debug("__ path_render: `{}`".format(path_render)) + if self.use_rendered and os.path.isfile(path_render): # check if file exist otherwise connect to write node - if os.path.isfile(path_render): - rnode = nuke.createNode("Read") + rnode = nuke.createNode("Read") - rnode["file"].setValue(path_render) + rnode["file"].setValue(path_render) - # turn it raw if none of baking is ON - if all([ - not self.bake_viewer_input_process, - not self.bake_viewer_process - ]): - rnode["raw"].setValue(True) + # turn it raw if none of baking is ON + if all([ + not self.bake_viewer_input_process, + not self.bake_viewer_process + ]): + rnode["raw"].setValue(True) - temporary_nodes.append(rnode) - previous_node = rnode - else: - previous_node = node + temporary_nodes.append(rnode) + previous_node = rnode # bake viewer input look node into thumbnail image if self.bake_viewer_input_process: From d230cb7d49170d71eeaf8b3f201b3e966817132b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 12:39:44 +0200 Subject: [PATCH 182/195] Fix - audio validator for Harmony has wrong logic Wrong condition stayed after change from asset to raise (it should be negated). --- openpype/hosts/harmony/plugins/publish/validate_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/harmony/plugins/publish/validate_audio.py b/openpype/hosts/harmony/plugins/publish/validate_audio.py index cb6b2307cd..e9b8609803 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_audio.py +++ b/openpype/hosts/harmony/plugins/publish/validate_audio.py @@ -47,6 +47,6 @@ class ValidateAudio(pyblish.api.InstancePlugin): formatting_data = { "audio_url": audio_path } - if os.path.isfile(audio_path): + if not os.path.isfile(audio_path): raise PublishXmlValidationError(self, msg, formatting_data=formatting_data) From 29def2f2679ca95fa645ef6afe16a1b48bf1b9ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 13:17:05 +0200 Subject: [PATCH 183/195] fix kwarg key --- .../hosts/webpublisher/webserver_service/webpublish_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index b1041bf6cb..457bcd7f93 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -107,7 +107,7 @@ class HiearchyEndpoint(ResourceRestApiEndpoint): "type": 1, } - asset_docs = get_assets(project_name, field=query_projection.keys()) + asset_docs = get_assets(project_name, fields=query_projection.keys()) asset_docs_by_id = { asset_doc["_id"]: asset_doc for asset_doc in asset_docs From 434e6bf78a8595eeb7c444d72711e547970b1d17 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Jun 2022 13:51:41 +0200 Subject: [PATCH 184/195] Fix - added json support to all resources encode method was missing for WebpublishRestApiResource --- .../webserver_service/webpublish_routes.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 457bcd7f93..4cb3cee8e1 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -38,18 +38,11 @@ class WebpublishApiEndpoint(ResourceRestApiEndpoint): return self.resource.dbcon -class RestApiResource: - """Resource carrying needed info and Avalon DB connection for publish.""" - def __init__(self, server_manager, executable, upload_dir, - studio_task_queue=None): - self.server_manager = server_manager - self.upload_dir = upload_dir - self.executable = executable - - if studio_task_queue is None: - studio_task_queue = collections.deque().dequeu - self.studio_task_queue = studio_task_queue +class JsonApiResource: + """Resource for json manipulation. + All resources handling sending output to REST should inherit from + """ @staticmethod def json_dump_handler(value): if isinstance(value, datetime.datetime): @@ -69,7 +62,20 @@ class RestApiResource: ).encode("utf-8") -class WebpublishRestApiResource: +class RestApiResource(JsonApiResource): + """Resource carrying needed info and Avalon DB connection for publish.""" + def __init__(self, server_manager, executable, upload_dir, + studio_task_queue=None): + self.server_manager = server_manager + self.upload_dir = upload_dir + self.executable = executable + + if studio_task_queue is None: + studio_task_queue = collections.deque().dequeu + self.studio_task_queue = studio_task_queue + + +class WebpublishRestApiResource(JsonApiResource): """Resource carrying OP DB connection for storing batch info into DB.""" def __init__(self): From a28f76374e583e6bf5d2f8490a39945a63da418f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:33:26 +0200 Subject: [PATCH 185/195] use get_openpype_username to fill data for templates filling --- openpype/lib/avalon_context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 9d8a92cfe9..c27c5f572e 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -7,7 +7,6 @@ import platform import logging import collections import functools -import getpass from bson.objectid import ObjectId @@ -19,6 +18,7 @@ from .anatomy import Anatomy from .profiles_filtering import filter_profiles from .events import emit_event from .path_templates import StringTemplate +from .local_settings import get_openpype_username legacy_io = None @@ -550,7 +550,7 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name): "asset": asset_doc["name"], "parent": parent_name, "app": host_name, - "user": getpass.getuser(), + "user": get_openpype_username(), "hierarchy": hierarchy, } From cd90fb603ea3815445b9e181374428c2a5c4a18d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:37:38 +0200 Subject: [PATCH 186/195] remove unused imports --- openpype/plugins/publish/collect_current_pype_user.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/plugins/publish/collect_current_pype_user.py b/openpype/plugins/publish/collect_current_pype_user.py index 1a52a59012..2d507ba292 100644 --- a/openpype/plugins/publish/collect_current_pype_user.py +++ b/openpype/plugins/publish/collect_current_pype_user.py @@ -1,5 +1,3 @@ -import os -import getpass import pyblish.api from openpype.lib import get_openpype_username From 1f42006f571343cf832b078d453e6804d68a3cbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 14:38:03 +0200 Subject: [PATCH 187/195] added user key to available template keys --- website/docs/admin_settings_project_anatomy.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/admin_settings_project_anatomy.md b/website/docs/admin_settings_project_anatomy.md index 6e0b49f152..106faeb806 100644 --- a/website/docs/admin_settings_project_anatomy.md +++ b/website/docs/admin_settings_project_anatomy.md @@ -68,6 +68,7 @@ We have a few required anatomy templates for OpenPype to work properly, however | `representation` | Representation name | | `frame` | Frame number for sequence files. | | `app` | Application Name | +| `user` | User's login name (can be overridden in local settings) | | `output` | | | `comment` | | From 34a79bfec9bd448986957885eaedfeae9424eab0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 Jun 2022 15:31:51 +0200 Subject: [PATCH 188/195] fix showing after plugin hide and don't ignore close events --- openpype/tools/pyblish_pype/window.py | 89 ++++++++++----------------- 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/openpype/tools/pyblish_pype/window.py b/openpype/tools/pyblish_pype/window.py index d27ec34345..78590259bc 100644 --- a/openpype/tools/pyblish_pype/window.py +++ b/openpype/tools/pyblish_pype/window.py @@ -468,10 +468,8 @@ class Window(QtWidgets.QDialog): current_page == "terminal" ) - self.state = { - "is_closing": False, - "current_page": current_page - } + self._current_page = current_page + self._hidden_for_plugin_process = False self.tabs[current_page].setChecked(True) @@ -590,14 +588,14 @@ class Window(QtWidgets.QDialog): target_page = page if direction is None: direction = -1 - elif name == self.state["current_page"]: + elif name == self._current_page: previous_page = page if direction is None: direction = 1 else: page.setVisible(False) - self.state["current_page"] = target + self._current_page = target self.slide_page(previous_page, target_page, direction) def slide_page(self, previous_page, target_page, direction): @@ -684,7 +682,7 @@ class Window(QtWidgets.QDialog): comment_visible=None, terminal_filters_visibile=None ): - target = self.state["current_page"] + target = self._current_page comment_visibility = ( not self.perspective_widget.isVisible() and not target == "terminal" @@ -845,7 +843,7 @@ class Window(QtWidgets.QDialog): def apply_log_suspend_value(self, value): self._suspend_logs = value - if self.state["current_page"] == "terminal": + if self._current_page == "terminal": self.tabs["overview"].setChecked(True) self.tabs["terminal"].setVisible(not self._suspend_logs) @@ -882,9 +880,21 @@ class Window(QtWidgets.QDialog): visibility = True if hasattr(plugin, "hide_ui_on_process") and plugin.hide_ui_on_process: visibility = False + self._hidden_for_plugin_process = not visibility - if self.isVisible() != visibility: - self.setVisible(visibility) + self._ensure_visible(visibility) + + def _ensure_visible(self, visible): + if self.isVisible() == visible: + return + + if not visible: + self.setVisible(visible) + else: + self.show() + self.raise_() + self.activateWindow() + self.showNormal() def on_plugin_action_menu_requested(self, pos): """The user right-clicked on a plug-in @@ -955,7 +965,7 @@ class Window(QtWidgets.QDialog): self.intent_box.setEnabled(True) # Refresh tab - self.on_tab_changed(self.state["current_page"]) + self.on_tab_changed(self._current_page) self.update_compatibility() self.button_suspend_logs.setEnabled(False) @@ -1027,8 +1037,9 @@ class Window(QtWidgets.QDialog): self._update_state() - if not self.isVisible(): - self.setVisible(True) + if self._hidden_for_plugin_process: + self._hidden_for_plugin_process = False + self._ensure_visible(True) def on_was_skipped(self, plugin): plugin_item = self.plugin_model.plugin_items[plugin.id] @@ -1103,8 +1114,9 @@ class Window(QtWidgets.QDialog): plugin_item, instance_item ) - if not self.isVisible(): - self.setVisible(True) + if self._hidden_for_plugin_process: + self._hidden_for_plugin_process = False + self._ensure_visible(True) # ------------------------------------------------------------------------- # @@ -1223,53 +1235,20 @@ class Window(QtWidgets.QDialog): """ - # Make it snappy, but take care to clean it all up. - # TODO(marcus): Enable GUI to return on problem, such - # as asking whether or not the user really wants to quit - # given there are things currently running. - self.hide() + self.info(self.tr("Closing..")) - if self.state["is_closing"]: + if self.controller.is_running: + self.info(self.tr("..as soon as processing is finished..")) + self.controller.stop() - # Explicitly clear potentially referenced data - self.info(self.tr("Cleaning up models..")) - self.intent_model.deleteLater() - self.plugin_model.deleteLater() - self.terminal_model.deleteLater() - self.terminal_proxy.deleteLater() - self.plugin_proxy.deleteLater() + self.info(self.tr("Cleaning up controller..")) + self.controller.cleanup() self.overview_instance_view.setModel(None) self.overview_plugin_view.setModel(None) self.terminal_view.setModel(None) - self.info(self.tr("Cleaning up controller..")) - self.controller.cleanup() - - self.info(self.tr("All clean!")) - self.info(self.tr("Good bye")) - return super(Window, self).closeEvent(event) - - self.info(self.tr("Closing..")) - - def on_problem(): - self.heads_up( - "Warning", "Had trouble closing down. " - "Please tell someone and try again." - ) - self.show() - - if self.controller.is_running: - self.info(self.tr("..as soon as processing is finished..")) - self.controller.stop() - self.finished.connect(self.close) - util.defer(200, on_problem) - return event.ignore() - - self.state["is_closing"] = True - - util.defer(200, self.close) - return event.ignore() + event.accept() def reject(self): """Handle ESC key""" From e54073608b59c4fe814a0fa3a1e49faef8e5a551 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Jun 2022 15:48:28 +0200 Subject: [PATCH 189/195] Nuke: multiple bake stream correct frame range on farm --- .../plugins/publish/submit_nuke_deadline.py | 70 ++++++++++++------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index ca68c87f9a..93fb511a34 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -55,8 +55,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self._ver = re.search(r"\d+\.\d+", context.data.get("hostVersion")) self._deadline_user = context.data.get( "deadlineUser", getpass.getuser()) - self._frame_start = int(instance.data["frameStartHandle"]) - self._frame_end = int(instance.data["frameEndHandle"]) + submit_frame_start = int(instance.data["frameStartHandle"]) + submit_frame_end = int(instance.data["frameEndHandle"]) # get output path render_path = instance.data['path'] @@ -82,13 +82,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # exception for slate workflow if "slate" in instance.data["families"]: - self._frame_start -= 1 + submit_frame_start -= 1 - response = self.payload_submit(instance, - script_path, - render_path, - node.name() - ) + response = self.payload_submit( + instance, + script_path, + render_path, + node.name(), + submit_frame_start, + submit_frame_end + ) # Store output dir for unified publisher (filesequence) instance.data["deadlineSubmissionJob"] = response.json() instance.data["outputDir"] = os.path.dirname( @@ -96,20 +99,22 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["publishJobState"] = "Suspended" if instance.data.get("bakingNukeScripts"): + # exception for slate workflow + if "slate" in instance.data["families"]: + submit_frame_start += 1 + for baking_script in instance.data["bakingNukeScripts"]: render_path = baking_script["bakeRenderPath"] script_path = baking_script["bakeScriptPath"] exe_node_name = baking_script["bakeWriteNodeName"] - # exception for slate workflow - if "slate" in instance.data["families"]: - self._frame_start += 1 - resp = self.payload_submit( instance, script_path, render_path, exe_node_name, + submit_frame_start, + submit_frame_end, response.json() ) @@ -126,13 +131,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): families.insert(0, "prerender") instance.data["families"] = families - def payload_submit(self, - instance, - script_path, - render_path, - exe_node_name, - responce_data=None - ): + def payload_submit( + self, + instance, + script_path, + render_path, + exe_node_name, + start_frame, + end_frame, + responce_data=None + ): render_dir = os.path.normpath(os.path.dirname(render_path)) script_name = os.path.basename(script_path) jobname = "%s - %s" % (script_name, instance.name) @@ -192,8 +200,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "Plugin": "Nuke", "Frames": "{start}-{end}".format( - start=self._frame_start, - end=self._frame_end + start=start_frame, + end=end_frame ), "Comment": self._comment, @@ -293,7 +301,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self.log.info(json.dumps(payload, indent=4, sort_keys=True)) # adding expectied files to instance.data - self.expected_files(instance, render_path) + self.expected_files( + instance, + render_path, + start_frame, + end_frame + ) + self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) response = requests.post(self.deadline_url, json=payload, timeout=10) @@ -339,9 +353,13 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self.log.debug("_ path: `{}`".format(path)) return path - def expected_files(self, - instance, - path): + def expected_files( + self, + instance, + path, + start_frame, + end_frame + ): """ Create expected files in instance data """ if not instance.data.get("expectedFiles"): @@ -359,7 +377,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["expectedFiles"].append(path) return - for i in range(self._frame_start, (self._frame_end + 1)): + for i in range(start_frame, (end_frame + 1)): instance.data["expectedFiles"].append( os.path.join(dir, (file % i)).replace("\\", "/")) From c368fb587f6bc89668ce07024154768f6de2ef7d Mon Sep 17 00:00:00 2001 From: OpenPype Date: Fri, 17 Jun 2022 15:29:09 +0000 Subject: [PATCH 190/195] [Automated] Bump version --- CHANGELOG.md | 33 +++++---------------------------- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb71071205..8f9eb04f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [3.11.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) @@ -22,11 +22,7 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) -- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) -- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) -- Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225) -- Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208) **🐛 Bug fixes** @@ -43,13 +39,13 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) +- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) -- Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224) **🔀 Refactored code** @@ -60,7 +56,6 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) -- Harmony: message length in 21.1 [\#3258](https://github.com/pypeclub/OpenPype/pull/3258) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -68,46 +63,28 @@ **🚀 Enhancements** -- TVPaint: Init file for TVPaint worker also handle guideline images [\#3251](https://github.com/pypeclub/OpenPype/pull/3251) +- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) - Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) -- Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216) **🐛 Bug fixes** -- Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) -- Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) - nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- Hiero: debugging frame range and other 3.10 [\#3222](https://github.com/pypeclub/OpenPype/pull/3222) -- Project Manager: Fix persistent editors on project change [\#3218](https://github.com/pypeclub/OpenPype/pull/3218) -- Deadline: instance data overwrite fix [\#3214](https://github.com/pypeclub/OpenPype/pull/3214) -- Ftrack: Push hierarchical attributes action works [\#3210](https://github.com/pypeclub/OpenPype/pull/3210) -- Standalone Publisher: Always create new representation for thumbnail [\#3203](https://github.com/pypeclub/OpenPype/pull/3203) -- Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202) +- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) **Merged pull requests:** - Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) -- Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.7...3.9.8) -**🚀 Enhancements** - -- nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206) -- Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200) - -**🐛 Bug fixes** - -- Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204) - ## [3.9.7](https://github.com/pypeclub/OpenPype/tree/3.9.7) (2022-05-11) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.6...3.9.7) diff --git a/openpype/version.py b/openpype/version.py index 2f4d180983..4c28b4a369 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.3" +__version__ = "3.11.0-nightly.4" diff --git a/pyproject.toml b/pyproject.toml index e1b5c37289..99935b3d70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.3" # OpenPype +version = "3.11.0-nightly.4" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 961c7b6c511765684696c6b5292e85193e24f31e Mon Sep 17 00:00:00 2001 From: OpenPype Date: Fri, 17 Jun 2022 15:39:35 +0000 Subject: [PATCH 191/195] [Automated] Release --- CHANGELOG.md | 10 ++++++---- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9eb04f45..686b34177c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.11.0-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...3.11.0) ### 📖 Documentation @@ -22,6 +22,7 @@ - General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268) - Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267) - Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264) +- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250) - Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247) **🐛 Bug fixes** @@ -39,10 +40,12 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) +- Hiero: add support for task tags [\#3277](https://github.com/pypeclub/OpenPype/pull/3277) - Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) - Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) @@ -56,6 +59,7 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -69,7 +73,6 @@ **🐛 Bug fixes** -- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) @@ -78,7 +81,6 @@ **Merged pull requests:** -- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) diff --git a/openpype/version.py b/openpype/version.py index 4c28b4a369..41e61b0500 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0-nightly.4" +__version__ = "3.11.0" diff --git a/pyproject.toml b/pyproject.toml index 99935b3d70..2700704530 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0-nightly.4" # OpenPype +version = "3.11.0" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 44a46382470e4e79b9e679e9395f94dacb556a70 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 18 Jun 2022 03:45:44 +0000 Subject: [PATCH 192/195] [Automated] Bump version --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++------ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 686b34177c..553102aecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,43 @@ # Changelog +## [3.11.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...HEAD) + +**🆕 New features** + +- Flame: custom export temp folder [\#3346](https://github.com/pypeclub/OpenPype/pull/3346) +- Nuke: removing third-party plugins [\#3344](https://github.com/pypeclub/OpenPype/pull/3344) + +**🚀 Enhancements** + +- Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367) +- Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354) +- Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352) +- Ftrack: Open browser from tray [\#3320](https://github.com/pypeclub/OpenPype/pull/3320) +- Enhancement: More control over thumbnail processing. [\#3259](https://github.com/pypeclub/OpenPype/pull/3259) + +**🐛 Bug fixes** + +- Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368) +- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364) +- Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363) +- Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361) +- AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) +- deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) +- General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) +- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) +- General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) +- Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) +- Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) + +**🔀 Refactored code** + +- Webpublisher: Use client query functions [\#3333](https://github.com/pypeclub/OpenPype/pull/3333) + ## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...3.11.0) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0) ### 📖 Documentation @@ -40,15 +75,12 @@ - Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281) - Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279) - General: Fix Oiio tool path resolving [\#3278](https://github.com/pypeclub/OpenPype/pull/3278) -- Hiero: add support for task tags [\#3277](https://github.com/pypeclub/OpenPype/pull/3277) - Maya: Fix udim support for e.g. uppercase \ tag [\#3266](https://github.com/pypeclub/OpenPype/pull/3266) - Nuke: bake reformat was failing on string type [\#3261](https://github.com/pypeclub/OpenPype/pull/3261) - Maya: hotfix Pxr multitexture in looks [\#3260](https://github.com/pypeclub/OpenPype/pull/3260) - Unreal: Fix Camera Loading if Layout is missing [\#3255](https://github.com/pypeclub/OpenPype/pull/3255) -- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Unreal: Fixed Animation loading in UE5 [\#3240](https://github.com/pypeclub/OpenPype/pull/3240) - Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239) -- Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238) **🔀 Refactored code** @@ -59,7 +91,6 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) -- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -69,10 +100,10 @@ - Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253) - General: updating common vendor `scriptmenu` to 1.5.2 [\#3246](https://github.com/pypeclub/OpenPype/pull/3246) -- Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226) **🐛 Bug fixes** +- nuke: use framerange issue [\#3254](https://github.com/pypeclub/OpenPype/pull/3254) - Ftrack: Chunk sizes for queries has minimal condition [\#3244](https://github.com/pypeclub/OpenPype/pull/3244) - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) @@ -81,6 +112,7 @@ **Merged pull requests:** +- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257) - Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249) ## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19) diff --git a/openpype/version.py b/openpype/version.py index 41e61b0500..8e44b02ffc 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.0" +__version__ = "3.11.1-nightly.1" diff --git a/pyproject.toml b/pyproject.toml index 2700704530..8ba829a1c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.0" # OpenPype +version = "3.11.1-nightly.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From da1fb174bca44ec40ce00ea51b034d79bcebcca9 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Mon, 20 Jun 2022 08:48:15 +0000 Subject: [PATCH 193/195] [Automated] Release --- CHANGELOG.md | 7 +++---- openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 553102aecf..48d0d8181e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [3.11.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.11.1](https://github.com/pypeclub/OpenPype/tree/3.11.1) (2022-06-20) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...HEAD) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.11.0...3.11.1) **🆕 New features** @@ -26,7 +26,6 @@ - AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358) - deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356) - General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351) -- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347) - General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345) - Deadline: added OPENPYPE\_MONGO to filter [\#3336](https://github.com/pypeclub/OpenPype/pull/3336) - Nuke: fixing farm publishing if review is disabled [\#3306](https://github.com/pypeclub/OpenPype/pull/3306) @@ -91,6 +90,7 @@ - Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318) - Maya look: skip empty file attributes [\#3274](https://github.com/pypeclub/OpenPype/pull/3274) +- Harmony: 21.1 fix [\#3248](https://github.com/pypeclub/OpenPype/pull/3248) ## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26) @@ -108,7 +108,6 @@ - Maya: renderman displays needs to be filtered [\#3242](https://github.com/pypeclub/OpenPype/pull/3242) - Ftrack: Validate that the user exists on ftrack [\#3237](https://github.com/pypeclub/OpenPype/pull/3237) - Maya: Fix support for multiple resolutions [\#3236](https://github.com/pypeclub/OpenPype/pull/3236) -- TVPaint: Look for more groups than 12 [\#3228](https://github.com/pypeclub/OpenPype/pull/3228) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 8e44b02ffc..7bf368108a 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.11.1-nightly.1" +__version__ = "3.11.1" diff --git a/pyproject.toml b/pyproject.toml index 8ba829a1c9..ae89e7d9d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.11.1-nightly.1" # OpenPype +version = "3.11.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 8b1576aed2b74dd5e01842d2a87bc04c77f755f6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 11:22:05 +0200 Subject: [PATCH 194/195] remove unused line --- openpype/pipeline/context_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 3e63eeba27..4a147c230b 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -88,7 +88,6 @@ def install_host(host): avalon host-interface. """ global _is_installed - global _modules_manager _is_installed = True From d49c2bac223f77b20710e029c6edb9caf1577d20 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Jun 2022 13:42:04 +0200 Subject: [PATCH 195/195] reverted poetry.lock changes --- poetry.lock | 197 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 83 deletions(-) diff --git a/poetry.lock b/poetry.lock index 010dd57e17..f6ccf1ffc9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,11 +161,11 @@ toml = "*" [[package]] name = "babel" -version = "2.10.1" +version = "2.9.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pytz = ">=2015.7" @@ -671,14 +671,14 @@ testing = ["colorama", "docopt", "pytest (>=3.1.0)"] [[package]] name = "jeepney" -version = "0.8.0" +version = "0.7.1" description = "Low-level, pure Python DBus protocol wrapper." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio (>=0.17)", "testpath", "trio", "async-timeout"] +test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] trio = ["trio", "async-generator"] [[package]] @@ -756,11 +756,11 @@ pymongo = "*" [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -909,11 +909,11 @@ python-versions = "*" [[package]] name = "protobuf" -version = "3.20.1" +version = "3.19.4" description = "Protocol Buffers" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.5" [[package]] name = "py" @@ -1617,11 +1617,11 @@ python-versions = ">=3.6" [[package]] name = "typing-extensions" -version = "4.2.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" [[package]] name = "uritemplate" @@ -1716,7 +1716,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "3.7.*" -content-hash = "f7150d3d8098c26a004db9850ed328efe79695e77e830721a3e04c5941850cc5" +content-hash = "bd8e0a03668c380c6e76c8cd6c71020692f4ea9f32de7a4f09433564faa9dad0" [metadata.files] acre = [] @@ -1843,8 +1843,8 @@ autopep8 = [ {file = "autopep8-1.5.7.tar.gz", hash = "sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0"}, ] babel = [ - {file = "Babel-2.10.1-py3-none-any.whl", hash = "sha256:3f349e85ad3154559ac4930c3918247d319f21910d5ce4b25d439ed8693b98d2"}, - {file = "Babel-2.10.1.tar.gz", hash = "sha256:98aeaca086133efb3e1e2aad0396987490c8425929ddbcfe0550184fdc54cd13"}, + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ {file = "bcrypt-3.2.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e"}, @@ -2202,8 +2202,8 @@ jedi = [ {file = "jedi-0.13.3.tar.gz", hash = "sha256:2bb0603e3506f708e792c7f4ad8fc2a7a9d9c2d292a358fbbd58da531695595b"}, ] jeepney = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, + {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, + {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, ] jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, @@ -2264,46 +2264,75 @@ log4mongo = [ {file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"}, ] markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -2444,30 +2473,32 @@ prefixed = [ {file = "prefixed-0.3.2.tar.gz", hash = "sha256:ca48277ba5fa8346dd4b760847da930c7b84416387c39e93affef086add2c029"}, ] protobuf = [ - {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, - {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, - {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, - {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, - {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, - {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, - {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, - {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, - {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, - {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, - {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, - {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, - {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, - {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, - {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, - {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, - {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, - {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, - {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, - {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, - {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, - {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, - {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, - {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, + {file = "protobuf-3.19.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f51d5a9f137f7a2cec2d326a74b6e3fc79d635d69ffe1b036d39fc7d75430d37"}, + {file = "protobuf-3.19.4-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09297b7972da685ce269ec52af761743714996b4381c085205914c41fcab59fb"}, + {file = "protobuf-3.19.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:072fbc78d705d3edc7ccac58a62c4c8e0cec856987da7df8aca86e647be4e35c"}, + {file = "protobuf-3.19.4-cp310-cp310-win32.whl", hash = "sha256:7bb03bc2873a2842e5ebb4801f5c7ff1bfbdf426f85d0172f7644fcda0671ae0"}, + {file = "protobuf-3.19.4-cp310-cp310-win_amd64.whl", hash = "sha256:f358aa33e03b7a84e0d91270a4d4d8f5df6921abe99a377828839e8ed0c04e07"}, + {file = "protobuf-3.19.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1c91ef4110fdd2c590effb5dca8fdbdcb3bf563eece99287019c4204f53d81a4"}, + {file = "protobuf-3.19.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c438268eebb8cf039552897d78f402d734a404f1360592fef55297285f7f953f"}, + {file = "protobuf-3.19.4-cp36-cp36m-win32.whl", hash = "sha256:835a9c949dc193953c319603b2961c5c8f4327957fe23d914ca80d982665e8ee"}, + {file = "protobuf-3.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4276cdec4447bd5015453e41bdc0c0c1234eda08420b7c9a18b8d647add51e4b"}, + {file = "protobuf-3.19.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6cbc312be5e71869d9d5ea25147cdf652a6781cf4d906497ca7690b7b9b5df13"}, + {file = "protobuf-3.19.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:54a1473077f3b616779ce31f477351a45b4fef8c9fd7892d6d87e287a38df368"}, + {file = "protobuf-3.19.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:435bb78b37fc386f9275a7035fe4fb1364484e38980d0dd91bc834a02c5ec909"}, + {file = "protobuf-3.19.4-cp37-cp37m-win32.whl", hash = "sha256:16f519de1313f1b7139ad70772e7db515b1420d208cb16c6d7858ea989fc64a9"}, + {file = "protobuf-3.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:cdc076c03381f5c1d9bb1abdcc5503d9ca8b53cf0a9d31a9f6754ec9e6c8af0f"}, + {file = "protobuf-3.19.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:69da7d39e39942bd52848438462674c463e23963a1fdaa84d88df7fbd7e749b2"}, + {file = "protobuf-3.19.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48ed3877fa43e22bcacc852ca76d4775741f9709dd9575881a373bd3e85e54b2"}, + {file = "protobuf-3.19.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd95d1dfb9c4f4563e6093a9aa19d9c186bf98fa54da5252531cc0d3a07977e7"}, + {file = "protobuf-3.19.4-cp38-cp38-win32.whl", hash = "sha256:b38057450a0c566cbd04890a40edf916db890f2818e8682221611d78dc32ae26"}, + {file = "protobuf-3.19.4-cp38-cp38-win_amd64.whl", hash = "sha256:7ca7da9c339ca8890d66958f5462beabd611eca6c958691a8fe6eccbd1eb0c6e"}, + {file = "protobuf-3.19.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36cecbabbda242915529b8ff364f2263cd4de7c46bbe361418b5ed859677ba58"}, + {file = "protobuf-3.19.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c1068287025f8ea025103e37d62ffd63fec8e9e636246b89c341aeda8a67c934"}, + {file = "protobuf-3.19.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96bd766831596d6014ca88d86dc8fe0fb2e428c0b02432fd9db3943202bf8c5e"}, + {file = "protobuf-3.19.4-cp39-cp39-win32.whl", hash = "sha256:84123274d982b9e248a143dadd1b9815049f4477dc783bf84efe6250eb4b836a"}, + {file = "protobuf-3.19.4-cp39-cp39-win_amd64.whl", hash = "sha256:3112b58aac3bac9c8be2b60a9daf6b558ca3f7681c130dcdd788ade7c9ffbdca"}, + {file = "protobuf-3.19.4-py2.py3-none-any.whl", hash = "sha256:8961c3a78ebfcd000920c9060a262f082f29838682b1f7201889300c1fbe0616"}, + {file = "protobuf-3.19.4.tar.gz", hash = "sha256:9df0c10adf3e83015ced42a9a7bd64e13d06c4cf45c340d2c63020ea04499d0a"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -2891,8 +2922,8 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] uritemplate = [ {file = "uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"},