From 361d21eff6afb403781e92fa32fc6588af0a9257 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Sun, 31 Dec 2023 14:20:37 +0200 Subject: [PATCH 001/203] =?UTF-8?q?=F0=9F=93=B8=20allow=20taking=20snapsho?= =?UTF-8?q?ots=20from=20houdini?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 70 +++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index edd50f10c1..7b09d2baf2 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -1054,3 +1054,73 @@ def add_self_publish_button(node): template = node.parmTemplateGroup() template.insertBefore((0,), button_parm) node.setParmTemplateGroup(template) + + +def getSceneViewer(): + """ + return an instance of a visible viewport. + There may be many, some could be closed, any visible are current + """ + panes = hou.ui.paneTabs() + panes = [x for x in panes if x.type() == hou.paneTabType.SceneViewer] + panes = sorted(panes, key=lambda x: x.isCurrentTab()) + if panes: + return panes[-1] + else: + return None + + +def sceneview_snapshoot( + filename="$HIP/thumbnails/$HIPNAME.$F4.jpg", + fstart=None, + fend=None + ): + """ + It takes snapshoot of your scene view fot the given frame range. + So, it's capable of generating snapshoots image sequence. + It works in different Houdini context e.g. Objects, Solaris + + Example: + from openpype.hosts.houdini.api import lib + lib.sceneview_snapshoot() + + Notes: + .png output will render poorly, so use .jpg. + + How it works: + Get the current sceneviewer (may be more than one or hidden) + and screengrab the perspective viewport to a file in the + publish location to be picked up with the publish. + + Credits: + https://www.sidefx.com/forum/topic/42808/?page=1#post-354796 + + Args: + filename (str): filename with the desired full path. + fstart (int): the frame at which snapshooting starts + fend (int): the frame at which snapshooting ends + """ + + try: + + if fstart is None: + fstart = hou.frame() + if fend is None: + fend = fstart = hou.frame() + + sceneview = getSceneViewer() + if not sceneview: + log.debug("No SceneViewers detected.") + return + viewport = sceneview.curViewport() + + # this will open an mplay window to show the result + flip_settings = sceneview.flipbookSettings().stash() + flip_settings.frameRange( (fstart, fend) ) + flip_settings.output(filename) + flip_settings.outputToMPlay(False) + sceneview.flipbook(viewport, flip_settings) + log.debug("A snap of sceneview has been saved to: {}".format(filename)) + + except Exception as e: + log.debug(e) From 5658b109259a55a93edbb30f2e0c7f35c64a6201 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Sun, 31 Dec 2023 14:31:31 +0200 Subject: [PATCH 002/203] =?UTF-8?q?=F0=9F=90=BE=20Resolve=20Hound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 7b09d2baf2..1b172595c8 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -1073,8 +1073,7 @@ def getSceneViewer(): def sceneview_snapshoot( filename="$HIP/thumbnails/$HIPNAME.$F4.jpg", fstart=None, - fend=None - ): + fend=None): """ It takes snapshoot of your scene view fot the given frame range. So, it's capable of generating snapshoots image sequence. @@ -1116,7 +1115,7 @@ def sceneview_snapshoot( # this will open an mplay window to show the result flip_settings = sceneview.flipbookSettings().stash() - flip_settings.frameRange( (fstart, fend) ) + flip_settings.frameRange((fstart, fend)) flip_settings.output(filename) flip_settings.outputToMPlay(False) sceneview.flipbook(viewport, flip_settings) From ab61e755e99773d3f4997c4fcbf27250e47d8262 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 17:22:52 +0200 Subject: [PATCH 003/203] =?UTF-8?q?=F0=9F=93=9D=20Kuba's=20and=20Big=20Roy?= =?UTF-8?q?'s=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 59 +++++++++++++++---------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 1b172595c8..db9955694d 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -1056,7 +1056,7 @@ def add_self_publish_button(node): node.setParmTemplateGroup(template) -def getSceneViewer(): +def get_scene_viewer(): """ return an instance of a visible viewport. There may be many, some could be closed, any visible are current @@ -1066,12 +1066,13 @@ def getSceneViewer(): panes = sorted(panes, key=lambda x: x.isCurrentTab()) if panes: return panes[-1] - else: - return None + + return None -def sceneview_snapshoot( - filename="$HIP/thumbnails/$HIPNAME.$F4.jpg", +def sceneview_snapshot( + sceneview, + filepath="$HIP/thumbnails/$HIPNAME.$F4.jpg", fstart=None, fend=None): """ @@ -1081,7 +1082,8 @@ def sceneview_snapshoot( Example: from openpype.hosts.houdini.api import lib - lib.sceneview_snapshoot() + sceneview = hou.ui.paneTabOfType(hou.paneTabType.SceneViewer) + lib.sceneview_snapshot(sceneview) Notes: .png output will render poorly, so use .jpg. @@ -1095,31 +1097,28 @@ def sceneview_snapshoot( https://www.sidefx.com/forum/topic/42808/?page=1#post-354796 Args: - filename (str): filename with the desired full path. - fstart (int): the frame at which snapshooting starts - fend (int): the frame at which snapshooting ends + sceneview (hou.SceneViewer): The scene view pane from which you want + to take a snapshot. + filepath (str): thumbnail filepath. + fstart (int): the frame at which snapshot starts + fend (int): the frame at which snapshot ends """ - try: + if fstart is None: + fstart = hou.frame() + if fend is None: + fend = fstart - if fstart is None: - fstart = hou.frame() - if fend is None: - fend = fstart = hou.frame() + if not isinstance(sceneview, hou.SceneViewer): + log.debug("Wrong Input. {} is not of type hou.SceneViewer." + .format(sceneview)) + return + viewport = sceneview.curViewport() - sceneview = getSceneViewer() - if not sceneview: - log.debug("No SceneViewers detected.") - return - viewport = sceneview.curViewport() - - # this will open an mplay window to show the result - flip_settings = sceneview.flipbookSettings().stash() - flip_settings.frameRange((fstart, fend)) - flip_settings.output(filename) - flip_settings.outputToMPlay(False) - sceneview.flipbook(viewport, flip_settings) - log.debug("A snap of sceneview has been saved to: {}".format(filename)) - - except Exception as e: - log.debug(e) + # this will open an mplay window to show the result + flip_settings = sceneview.flipbookSettings().stash() + flip_settings.frameRange((fstart, fend)) + flip_settings.output(filepath) + flip_settings.outputToMPlay(False) + sceneview.flipbook(viewport, flip_settings) + log.debug("A snap of sceneview has been saved to: {}".format(filepath)) From a27c7bf8fd99effd8374075b07e1f036467964a4 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 17:40:49 +0200 Subject: [PATCH 004/203] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20=20fix=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index db9955694d..7e07bce2d1 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -1076,8 +1076,8 @@ def sceneview_snapshot( fstart=None, fend=None): """ - It takes snapshoot of your scene view fot the given frame range. - So, it's capable of generating snapshoots image sequence. + It takes snapshot of your scene view fot the given frame range. + So, it's capable of generating snapshots image sequence. It works in different Houdini context e.g. Objects, Solaris Example: @@ -1121,4 +1121,4 @@ def sceneview_snapshot( flip_settings.output(filepath) flip_settings.outputToMPlay(False) sceneview.flipbook(viewport, flip_settings) - log.debug("A snap of sceneview has been saved to: {}".format(filepath)) + log.debug("A snapshot of sceneview has been saved to: {}".format(filepath)) From b6a432d39363e00894ecb0f3fc4a95586d6d14c8 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 21:50:48 +0200 Subject: [PATCH 005/203] =?UTF-8?q?=F0=9F=96=BC=EF=B8=8F=20=20Extract=20ac?= =?UTF-8?q?tive=20view=20as=20thumbnail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../publish/extract_active_view_thumbnail.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py diff --git a/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py new file mode 100644 index 0000000000..40b0dac02e --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py @@ -0,0 +1,58 @@ +import os + +import pyblish.api + +from openpype.pipeline import publish, registered_host +from openpype.hosts.houdini.api import lib +from openpype.hosts.houdini.api.pipeline import IS_HEADLESS + +import hou + + +class ExtractActiveViewThumbnail(publish.Extractor): + """Set instance thumbnail to a screengrab of current active viewport. + + This makes it so that if an instance does not have a thumbnail set yet that + it will get a thumbnail of the currently active view at the time of + publishing as a fallback. + + """ + order = pyblish.api.ExtractorOrder + 0.49 + label = "Extract Active View Thumbnail" + families = ["workfile"] + hosts = ["houdini"] + + def process(self, instance): + if IS_HEADLESS: + self.log.debug( + "Skip extraction of active view thumbnail, due to being in" + "headless mode." + ) + return + + thumbnail = instance.data.get("thumbnailPath") + self.log.debug(thumbnail) + if not thumbnail: + view_thumbnail = self.get_view_thumbnail(instance) + if not view_thumbnail: + return + + self.log.debug("Setting instance thumbnail path to: {}".format( + view_thumbnail + )) + instance.data["thumbnailPath"] = view_thumbnail + + def get_view_thumbnail(self, instance): + + host = registered_host() + current_filepath = host.get_current_workfile() + if not current_filepath: + self.log.error("No current workfile path. Thumbnail generation skipped") + return + + thumbnail_path = "{}_thumbnail.jpg".format( + current_filepath.rsplit('.', 1)[0]) + sceneview = lib.get_scene_viewer() + lib.sceneview_snapshot(sceneview, thumbnail_path) + + return thumbnail_path From 08ccdfcea8593776f6898b659bf5c3d606b89770 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 21:52:33 +0200 Subject: [PATCH 006/203] =?UTF-8?q?=F0=9F=A6=B4=20Resolve=20Hound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/publish/extract_active_view_thumbnail.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py index 40b0dac02e..b326f6918d 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py +++ b/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py @@ -1,13 +1,9 @@ -import os - import pyblish.api from openpype.pipeline import publish, registered_host from openpype.hosts.houdini.api import lib from openpype.hosts.houdini.api.pipeline import IS_HEADLESS -import hou - class ExtractActiveViewThumbnail(publish.Extractor): """Set instance thumbnail to a screengrab of current active viewport. @@ -47,7 +43,8 @@ class ExtractActiveViewThumbnail(publish.Extractor): host = registered_host() current_filepath = host.get_current_workfile() if not current_filepath: - self.log.error("No current workfile path. Thumbnail generation skipped") + self.log.error("No current workfile path. " + "Thumbnail generation skipped") return thumbnail_path = "{}_thumbnail.jpg".format( From 0e7efc5da979d3cfdaa12cdfa31b926569ff2ea0 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 4 Jan 2024 18:56:59 +0200 Subject: [PATCH 007/203] Kuba's comments - Better Code --- openpype/hosts/houdini/api/lib.py | 18 +++++++++--------- .../publish/extract_active_view_thumbnail.py | 17 ++++++----------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 7e07bce2d1..318de5f538 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -1073,8 +1073,8 @@ def get_scene_viewer(): def sceneview_snapshot( sceneview, filepath="$HIP/thumbnails/$HIPNAME.$F4.jpg", - fstart=None, - fend=None): + frame_start=None, + frame_end=None): """ It takes snapshot of your scene view fot the given frame range. So, it's capable of generating snapshots image sequence. @@ -1100,14 +1100,14 @@ def sceneview_snapshot( sceneview (hou.SceneViewer): The scene view pane from which you want to take a snapshot. filepath (str): thumbnail filepath. - fstart (int): the frame at which snapshot starts - fend (int): the frame at which snapshot ends + frame_start (int): the frame at which snapshot starts + frame_end (int): the frame at which snapshot ends """ - if fstart is None: - fstart = hou.frame() - if fend is None: - fend = fstart + if frame_start is None: + frame_start = hou.frame() + if frame_end is None: + frame_end = frame_start if not isinstance(sceneview, hou.SceneViewer): log.debug("Wrong Input. {} is not of type hou.SceneViewer." @@ -1117,7 +1117,7 @@ def sceneview_snapshot( # this will open an mplay window to show the result flip_settings = sceneview.flipbookSettings().stash() - flip_settings.frameRange((fstart, fend)) + flip_settings.frameRange((frame_start, frame_end)) flip_settings.output(filepath) flip_settings.outputToMPlay(False) sceneview.flipbook(viewport, flip_settings) diff --git a/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py b/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py index b326f6918d..82976bdf83 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py +++ b/openpype/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py @@ -1,6 +1,6 @@ import pyblish.api - -from openpype.pipeline import publish, registered_host +import tempfile +from openpype.pipeline import publish from openpype.hosts.houdini.api import lib from openpype.hosts.houdini.api.pipeline import IS_HEADLESS @@ -27,7 +27,6 @@ class ExtractActiveViewThumbnail(publish.Extractor): return thumbnail = instance.data.get("thumbnailPath") - self.log.debug(thumbnail) if not thumbnail: view_thumbnail = self.get_view_thumbnail(instance) if not view_thumbnail: @@ -40,15 +39,11 @@ class ExtractActiveViewThumbnail(publish.Extractor): def get_view_thumbnail(self, instance): - host = registered_host() - current_filepath = host.get_current_workfile() - if not current_filepath: - self.log.error("No current workfile path. " - "Thumbnail generation skipped") - return + with tempfile.NamedTemporaryFile("w", suffix=".jpg") as tmp: + thumbnail_path = tmp.name + + instance.context.data["cleanupFullPaths"].append(thumbnail_path) - thumbnail_path = "{}_thumbnail.jpg".format( - current_filepath.rsplit('.', 1)[0]) sceneview = lib.get_scene_viewer() lib.sceneview_snapshot(sceneview, thumbnail_path) From 435cd9cce1e6a95fd5074aef1282866b8c126ef8 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 17:04:36 +0200 Subject: [PATCH 008/203] =?UTF-8?q?=E2=9E=96=20Remove=20unnecessary=20comm?= =?UTF-8?q?ent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 318de5f538..bd15b8b83d 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -1115,7 +1115,6 @@ def sceneview_snapshot( return viewport = sceneview.curViewport() - # this will open an mplay window to show the result flip_settings = sceneview.flipbookSettings().stash() flip_settings.frameRange((frame_start, frame_end)) flip_settings.output(filepath) From 3ffb6db49fe161e1790c039142bc2236b492265a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 9 Jan 2024 10:01:38 +0200 Subject: [PATCH 009/203] =?UTF-8?q?=F0=9F=93=9D=20Stick=20to=20Google=20do?= =?UTF-8?q?c=20string=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index bd15b8b83d..33b74ca6b9 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -1058,7 +1058,8 @@ def add_self_publish_button(node): def get_scene_viewer(): """ - return an instance of a visible viewport. + Return an instance of a visible viewport. + There may be many, some could be closed, any visible are current """ panes = hou.ui.paneTabs() @@ -1075,8 +1076,9 @@ def sceneview_snapshot( filepath="$HIP/thumbnails/$HIPNAME.$F4.jpg", frame_start=None, frame_end=None): - """ - It takes snapshot of your scene view fot the given frame range. + """Take a snapshot of your scene view. + + It takes snapshot of your scene view for the given frame range. So, it's capable of generating snapshots image sequence. It works in different Houdini context e.g. Objects, Solaris From 62020ca29d2ec75b7e545fbf3d1a0b835dd86e98 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 4 Mar 2024 15:46:28 +0200 Subject: [PATCH 010/203] update 'sceneview_snapshot' docstring --- client/ayon_core/hosts/houdini/api/lib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/houdini/api/lib.py b/client/ayon_core/hosts/houdini/api/lib.py index b9a92fab0c..3cab61ff26 100644 --- a/client/ayon_core/hosts/houdini/api/lib.py +++ b/client/ayon_core/hosts/houdini/api/lib.py @@ -1083,9 +1083,11 @@ def sceneview_snapshot( It works in different Houdini context e.g. Objects, Solaris Example: - from openpype.hosts.houdini.api import lib - sceneview = hou.ui.paneTabOfType(hou.paneTabType.SceneViewer) - lib.sceneview_snapshot(sceneview) + This is how the function can be used:: + + from ayon_core.hosts.houdini.api import lib + sceneview = hou.ui.paneTabOfType(hou.paneTabType.SceneViewer) + lib.sceneview_snapshot(sceneview) Notes: .png output will render poorly, so use .jpg. From ab6d89c8c29c0dd883cbece387f8a9f872a3aaa3 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 4 Mar 2024 16:01:08 +0200 Subject: [PATCH 011/203] wrap lib.sceneview_snapshot() into tempfile.NamedTemporaryFile --- .../plugins/publish/extract_active_view_thumbnail.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py index 27c4493f44..392201e865 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py @@ -39,12 +39,10 @@ class ExtractActiveViewThumbnail(publish.Extractor): def get_view_thumbnail(self, instance): - with tempfile.NamedTemporaryFile("w", suffix=".jpg") as tmp: + sceneview = lib.get_scene_viewer() + with tempfile.NamedTemporaryFile("w", suffix=".jpg", delete=False) as tmp: + lib.sceneview_snapshot(sceneview, tmp.name) thumbnail_path = tmp.name instance.context.data["cleanupFullPaths"].append(thumbnail_path) - - sceneview = lib.get_scene_viewer() - lib.sceneview_snapshot(sceneview, thumbnail_path) - return thumbnail_path From 8320ab7b5138fdc57f352e708738cbce0f40c424 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 4 Mar 2024 16:46:34 +0200 Subject: [PATCH 012/203] Houdini: add a warning if no sceneview was found --- .../houdini/plugins/publish/extract_active_view_thumbnail.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py index 392201e865..1be8cb7440 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py @@ -40,6 +40,11 @@ class ExtractActiveViewThumbnail(publish.Extractor): def get_view_thumbnail(self, instance): sceneview = lib.get_scene_viewer() + if sceneview is None: + self.log.warning("Skipping Extract Active View Thumbnail" + " because no scene view was detected.") + return + with tempfile.NamedTemporaryFile("w", suffix=".jpg", delete=False) as tmp: lib.sceneview_snapshot(sceneview, tmp.name) thumbnail_path = tmp.name From d1364d2c15de6b66dcd985f5f514c669352c2179 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Mar 2024 20:31:14 +0800 Subject: [PATCH 013/203] bug fix on getting aov expected files when global aov mode disabled --- .../hosts/maya/api/lib_renderproducts.py | 112 +++++++++--------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib_renderproducts.py b/client/ayon_core/hosts/maya/api/lib_renderproducts.py index 7f26145e1d..63071c0053 100644 --- a/client/ayon_core/hosts/maya/api/lib_renderproducts.py +++ b/client/ayon_core/hosts/maya/api/lib_renderproducts.py @@ -1136,68 +1136,72 @@ class RenderProductsRedshift(ARenderProducts): light_groups_enabled = False has_beauty_aov = False colorspace = lib.get_color_management_output_transform() - for aov in aovs: - enabled = self._get_attr(aov, "enabled") - if not enabled: - continue + global_aov_enabled = bool( + self._get_attr("redshiftOptions.aovGlobalEnableMode") + ) + if global_aov_enabled: + for aov in aovs: + enabled = self._get_attr(aov, "enabled") + if not enabled: + continue - aov_type = self._get_attr(aov, "aovType") - if self.multipart and aov_type not in self.unmerged_aovs: - continue + aov_type = self._get_attr(aov, "aovType") + if self.multipart and aov_type not in self.unmerged_aovs: + continue - # Any AOVs that still get processed, like Cryptomatte - # by themselves are not multipart files. + # Any AOVs that still get processed, like Cryptomatte + # by themselves are not multipart files. - # Redshift skips rendering of masterlayer without AOV suffix - # when a Beauty AOV is rendered. It overrides the main layer. - if aov_type == "Beauty": - has_beauty_aov = True + # Redshift skips rendering of masterlayer without AOV suffix + # when a Beauty AOV is rendered. It overrides the main layer. + if aov_type == "Beauty": + has_beauty_aov = True - aov_name = self._get_attr(aov, "name") + aov_name = self._get_attr(aov, "name") - # Support light Groups - light_groups = [] - if self._get_attr(aov, "supportsLightGroups"): - all_light_groups = self._get_attr(aov, "allLightGroups") - if all_light_groups: - # All light groups is enabled - light_groups = self._get_redshift_light_groups() - else: - value = self._get_attr(aov, "lightGroupList") - # note: string value can return None when never set - if value: - selected_light_groups = value.strip().split() - light_groups = selected_light_groups + # Support light Groups + light_groups = [] + if self._get_attr(aov, "supportsLightGroups"): + all_light_groups = self._get_attr(aov, "allLightGroups") + if all_light_groups: + # All light groups is enabled + light_groups = self._get_redshift_light_groups() + else: + value = self._get_attr(aov, "lightGroupList") + # note: string value can return None when never set + if value: + selected_light_groups = value.strip().split() + light_groups = selected_light_groups - for light_group in light_groups: - aov_light_group_name = "{}_{}".format(aov_name, - light_group) - for camera in cameras: - product = RenderProduct( - productName=aov_light_group_name, - aov=aov_name, - ext=ext, - multipart=False, - camera=camera, - driver=aov, - colorspace=colorspace) - products.append(product) + for light_group in light_groups: + aov_light_group_name = "{}_{}".format(aov_name, + light_group) + for camera in cameras: + product = RenderProduct( + productName=aov_light_group_name, + aov=aov_name, + ext=ext, + multipart=False, + camera=camera, + driver=aov, + colorspace=colorspace) + products.append(product) - if light_groups: - light_groups_enabled = True + if light_groups: + light_groups_enabled = True - # Redshift AOV Light Select always renders the global AOV - # even when light groups are present so we don't need to - # exclude it when light groups are active - for camera in cameras: - product = RenderProduct(productName=aov_name, - aov=aov_name, - ext=ext, - multipart=False, - camera=camera, - driver=aov, - colorspace=colorspace) - products.append(product) + # Redshift AOV Light Select always renders the global AOV + # even when light groups are present so we don't need to + # exclude it when light groups are active + for camera in cameras: + product = RenderProduct(productName=aov_name, + aov=aov_name, + ext=ext, + multipart=False, + camera=camera, + driver=aov, + colorspace=colorspace) + products.append(product) # When a Beauty AOV is added manually, it will be rendered as # 'Beauty_other' in file name and "standard" beauty will have From 024f6f5163a72722f7bed1d2062c45cf02332d80 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Mar 2024 21:22:23 +0800 Subject: [PATCH 014/203] return products instead if global aov mode is disabled --- .../hosts/maya/api/lib_renderproducts.py | 112 +++++++++--------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib_renderproducts.py b/client/ayon_core/hosts/maya/api/lib_renderproducts.py index 63071c0053..28981e1187 100644 --- a/client/ayon_core/hosts/maya/api/lib_renderproducts.py +++ b/client/ayon_core/hosts/maya/api/lib_renderproducts.py @@ -1139,69 +1139,71 @@ class RenderProductsRedshift(ARenderProducts): global_aov_enabled = bool( self._get_attr("redshiftOptions.aovGlobalEnableMode") ) - if global_aov_enabled: - for aov in aovs: - enabled = self._get_attr(aov, "enabled") - if not enabled: - continue + if not global_aov_enabled: + return products - aov_type = self._get_attr(aov, "aovType") - if self.multipart and aov_type not in self.unmerged_aovs: - continue + for aov in aovs: + enabled = self._get_attr(aov, "enabled") + if not enabled: + continue - # Any AOVs that still get processed, like Cryptomatte - # by themselves are not multipart files. + aov_type = self._get_attr(aov, "aovType") + if self.multipart and aov_type not in self.unmerged_aovs: + continue - # Redshift skips rendering of masterlayer without AOV suffix - # when a Beauty AOV is rendered. It overrides the main layer. - if aov_type == "Beauty": - has_beauty_aov = True + # Any AOVs that still get processed, like Cryptomatte + # by themselves are not multipart files. - aov_name = self._get_attr(aov, "name") + # Redshift skips rendering of masterlayer without AOV suffix + # when a Beauty AOV is rendered. It overrides the main layer. + if aov_type == "Beauty": + has_beauty_aov = True - # Support light Groups - light_groups = [] - if self._get_attr(aov, "supportsLightGroups"): - all_light_groups = self._get_attr(aov, "allLightGroups") - if all_light_groups: - # All light groups is enabled - light_groups = self._get_redshift_light_groups() - else: - value = self._get_attr(aov, "lightGroupList") - # note: string value can return None when never set - if value: - selected_light_groups = value.strip().split() - light_groups = selected_light_groups + aov_name = self._get_attr(aov, "name") - for light_group in light_groups: - aov_light_group_name = "{}_{}".format(aov_name, - light_group) - for camera in cameras: - product = RenderProduct( - productName=aov_light_group_name, - aov=aov_name, - ext=ext, - multipart=False, - camera=camera, - driver=aov, - colorspace=colorspace) - products.append(product) + # Support light Groups + light_groups = [] + if self._get_attr(aov, "supportsLightGroups"): + all_light_groups = self._get_attr(aov, "allLightGroups") + if all_light_groups: + # All light groups is enabled + light_groups = self._get_redshift_light_groups() + else: + value = self._get_attr(aov, "lightGroupList") + # note: string value can return None when never set + if value: + selected_light_groups = value.strip().split() + light_groups = selected_light_groups - if light_groups: - light_groups_enabled = True + for light_group in light_groups: + aov_light_group_name = "{}_{}".format(aov_name, + light_group) + for camera in cameras: + product = RenderProduct( + productName=aov_light_group_name, + aov=aov_name, + ext=ext, + multipart=False, + camera=camera, + driver=aov, + colorspace=colorspace) + products.append(product) - # Redshift AOV Light Select always renders the global AOV - # even when light groups are present so we don't need to - # exclude it when light groups are active - for camera in cameras: - product = RenderProduct(productName=aov_name, - aov=aov_name, - ext=ext, - multipart=False, - camera=camera, - driver=aov, - colorspace=colorspace) - products.append(product) + if light_groups: + light_groups_enabled = True + + # Redshift AOV Light Select always renders the global AOV + # even when light groups are present so we don't need to + # exclude it when light groups are active + for camera in cameras: + product = RenderProduct(productName=aov_name, + aov=aov_name, + ext=ext, + multipart=False, + camera=camera, + driver=aov, + colorspace=colorspace) + products.append(product) # When a Beauty AOV is added manually, it will be rendered as # 'Beauty_other' in file name and "standard" beauty will have From b729bf4f30432a5a86ab7670e73b3e2b4514cc87 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Mar 2024 23:56:31 +0800 Subject: [PATCH 015/203] make sure beauty still being added as render product --- client/ayon_core/hosts/maya/api/lib_renderproducts.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/ayon_core/hosts/maya/api/lib_renderproducts.py b/client/ayon_core/hosts/maya/api/lib_renderproducts.py index 28981e1187..3bba25ee12 100644 --- a/client/ayon_core/hosts/maya/api/lib_renderproducts.py +++ b/client/ayon_core/hosts/maya/api/lib_renderproducts.py @@ -1140,6 +1140,15 @@ class RenderProductsRedshift(ARenderProducts): self._get_attr("redshiftOptions.aovGlobalEnableMode") ) if not global_aov_enabled: + beauty_name = "BeautyAux" if has_beauty_aov else "" + for camera in cameras: + products.insert(0, + RenderProduct(productName=beauty_name, + ext=ext, + multipart=self.multipart, + camera=camera, + colorspace=colorspace)) + return products for aov in aovs: From ffa395af64b066808cdffce100ad8593bf36fbd1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Mar 2024 15:10:03 +0800 Subject: [PATCH 016/203] add only beauty output when global aov disabled --- .../hosts/maya/api/lib_renderproducts.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib_renderproducts.py b/client/ayon_core/hosts/maya/api/lib_renderproducts.py index 3bba25ee12..5f3917f642 100644 --- a/client/ayon_core/hosts/maya/api/lib_renderproducts.py +++ b/client/ayon_core/hosts/maya/api/lib_renderproducts.py @@ -1133,24 +1133,24 @@ class RenderProductsRedshift(ARenderProducts): aovs = list(set(aovs) - set(ref_aovs)) products = [] - light_groups_enabled = False - has_beauty_aov = False - colorspace = lib.get_color_management_output_transform() global_aov_enabled = bool( self._get_attr("redshiftOptions.aovGlobalEnableMode") ) + colorspace = lib.get_color_management_output_transform() if not global_aov_enabled: - beauty_name = "BeautyAux" if has_beauty_aov else "" + # only beauty output for camera in cameras: products.insert(0, - RenderProduct(productName=beauty_name, - ext=ext, - multipart=self.multipart, - camera=camera, - colorspace=colorspace)) - + RenderProduct(productName="", + ext=ext, + multipart=self.multipart, + camera=camera, + colorspace=colorspace)) return products + light_groups_enabled = False + has_beauty_aov = False + for aov in aovs: enabled = self._get_attr(aov, "enabled") if not enabled: From 85dc52a2c1b9c63d29c9dd1755c2fe316cf5e2cd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:30:34 +0100 Subject: [PATCH 017/203] Fusion: Add Launch Hook to start "AYON menu" on Fusion start --- .../hooks/pre_fusion_launch_menu_hook.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py new file mode 100644 index 0000000000..825d432a3e --- /dev/null +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py @@ -0,0 +1,34 @@ +import os +from ayon_core.lib import PreLaunchHook +from ayon_core.hosts.fusion import FUSION_HOST_DIR + + +class FusionLaunchMenuHook(PreLaunchHook): + """Launch OpenPype menu on start of Fusion""" + app_groups = ["fusion"] + order = 9 + + def execute(self): + + # TODO: Make this optional via project settings + + variant = self.application.name + if variant.isnumeric(): + version = int(variant) + if version < 18: + print("Skipping launch of OpenPype menu on Fusion start " + "because Fusion version below 18.0 does not support " + "/execute argument on launch. " + f"Version detected: {version}") + return + else: + print(f"Application variant is not numeric: {variant}. " + "Validation for Fusion version 18+ for /execute " + "prelaunch argument skipped.") + + path = os.path.join(FUSION_HOST_DIR, + "deploy", + "MenuScripts", + "launch_menu.py").replace("\\", "/") + script = f"fusion:RunScript('{path}')" + self.launch_context.launch_args.extend(["/execute", script]) From 39399b1b269d63e4f0cf1cc9390a193c7e87cdc3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 21 Mar 2024 16:32:37 +0000 Subject: [PATCH 018/203] Publish and load slate frame explicitly --- .../hosts/nuke/plugins/load/load_clip.py | 34 +++++++++++-------- .../plugins/publish/extract_slate_data.py | 22 ++++++++++++ 2 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 client/ayon_core/plugins/publish/extract_slate_data.py diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index 8a41d854d9..b2044901a3 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -130,6 +130,11 @@ class LoadClip(plugin.NukeLoader): first = 1 last = first + duration + # If a slate is present, the frame range is 1 frame longer. + slate_frame = repre_entity["data"].get("slateFrame", False) + if slate_frame: + last += 1 + # Fallback to folder name when namespace is None if namespace is None: namespace = context["folder"]["name"] @@ -167,7 +172,9 @@ class LoadClip(plugin.NukeLoader): repre_entity ) - self._set_range_to_node(read_node, first, last, start_at_workfile) + self._set_range_to_node( + read_node, first, last, start_at_workfile, slate_frame + ) version_name = version_entity["version"] if version_name < 0: @@ -402,14 +409,23 @@ class LoadClip(plugin.NukeLoader): for member in members: nuke.delete(member) - def _set_range_to_node(self, read_node, first, last, start_at_workfile): + def _set_range_to_node( + self, read_node, first, last, start_at_workfile, slate_frame=False + ): read_node['origfirst'].setValue(int(first)) read_node['first'].setValue(int(first)) read_node['origlast'].setValue(int(last)) read_node['last'].setValue(int(last)) # set start frame depending on workfile or version - self._loader_shift(read_node, start_at_workfile) + if start_at_workfile: + read_node['frame_mode'].setValue("start at") + + start_frame = self.script_start + if slate_frame: + start_frame -= 1 + + read_node['frame'].setValue(str(start_frame)) def _make_retimes(self, parent_node, version_data): ''' Create all retime and timewarping nodes with copied animation ''' @@ -466,18 +482,6 @@ class LoadClip(plugin.NukeLoader): for i, n in enumerate(dependent_nodes): last_node.setInput(i, n) - def _loader_shift(self, read_node, workfile_start=False): - """ Set start frame of read node to a workfile start - - Args: - read_node (nuke.Node): The nuke's read node - workfile_start (bool): set workfile start frame if true - - """ - if workfile_start: - read_node['frame_mode'].setValue("start at") - read_node['frame'].setValue(str(self.script_start)) - def _get_node_name(self, context): folder_entity = context["folder"] product_name = context["product"]["name"] diff --git a/client/ayon_core/plugins/publish/extract_slate_data.py b/client/ayon_core/plugins/publish/extract_slate_data.py new file mode 100644 index 0000000000..0a28083f1e --- /dev/null +++ b/client/ayon_core/plugins/publish/extract_slate_data.py @@ -0,0 +1,22 @@ +import pyblish.api + +from ayon_core.pipeline import publish + + +class ExtractSlateData(publish.Extractor): + """Add slate data for integration.""" + + label = "Slate Data" + # Offset from ExtractReviewSlate and ExtractGenerateSlate. + order = pyblish.api.ExtractorOrder + 0.49 + families = ["slate", "review"] + hosts = ["nuke", "shell"] + + def process(self, instance): + for representation in instance.data.get("representations", []): + if "slate-frame" not in representation.get("tags", []): + continue + + data = representation.get("data", {}) + data["slateFrame"] = True + representation["data"] = data From d1e1f3942a7ed9cd88628b774f7421f14b07dd33 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 21 Mar 2024 17:38:43 +0000 Subject: [PATCH 019/203] Fix file sequences --- .../ayon_core/hosts/nuke/plugins/load/load_clip.py | 12 ++++++++++-- .../nuke/plugins/publish/extract_slate_frame.py | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index b2044901a3..86a6d359c3 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -130,10 +130,18 @@ class LoadClip(plugin.NukeLoader): first = 1 last = first + duration - # If a slate is present, the frame range is 1 frame longer. + # If a slate is present, the frame range is 1 frame longer for movies, + # but file sequences its the first frame that is 1 frame lower. slate_frame = repre_entity["data"].get("slateFrame", False) if slate_frame: - last += 1 + extension = "." + repre_entity["context"]["ext"] + + if extension in VIDEO_EXTENSIONS: + last += 1 + + files_count = len(repre_entity["files"]) + if extension in IMAGE_EXTENSIONS and files_count != 1: + first -= 1 # Fallback to folder name when namespace is None if namespace is None: diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py b/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py index c013da84d2..67d2bd272c 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -300,6 +300,10 @@ class ExtractSlateFrame(publish.Extractor): self.log.debug( "__ matching_repre: {}".format(pformat(matching_repre))) + data = matching_repre.get("data", {}) + data["slateFrame"] = True + matching_repre["data"] = data + self.log.info("Added slate frame to representation files") def add_comment_slate_node(self, instance, node): From 943b4d7b087b9d0e257013b5be2e65d4c70d7da7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 13:35:25 +0100 Subject: [PATCH 020/203] Also update tool on only task or asset change --- client/ayon_core/hosts/fusion/api/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/hosts/fusion/api/plugin.py b/client/ayon_core/hosts/fusion/api/plugin.py index f63b5eaec3..1af3467bf1 100644 --- a/client/ayon_core/hosts/fusion/api/plugin.py +++ b/client/ayon_core/hosts/fusion/api/plugin.py @@ -125,6 +125,8 @@ class GenericCreateSaver(Creator): product_name = data["productName"] if ( original_product_name != product_name + or tool.GetData("openpype.task") != data["task"] + or tool.GetData("openpype.asset") != data["asset"] or original_format != data["creator_attributes"]["image_format"] ): self._configure_saver_tool(data, tool, product_name) From 55b389b7a7707dd1464bb819a440f2484c52a46b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 21:36:53 +0100 Subject: [PATCH 021/203] Only launch when enabled in settings --- .../hosts/fusion/hooks/pre_fusion_launch_menu_hook.py | 6 ++++-- server_addon/fusion/server/settings.py | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py index 825d432a3e..f5a1c75a50 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py @@ -9,8 +9,10 @@ class FusionLaunchMenuHook(PreLaunchHook): order = 9 def execute(self): - - # TODO: Make this optional via project settings + # Prelaunch hook is optional + settings = self.data["project_settings"][self.host_name] + if not settings["hooks"]["FusionLaunchMenuHook"]["enabled"]: + return variant = self.application.name if variant.isnumeric(): diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index a913db16da..b44feb1f03 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -75,6 +75,12 @@ class HooksModel(BaseSettingsModel): default_factory=HookOptionalModel, title="Install PySide2" ) + FusionLaunchMenuHook: HookOptionalModel = SettingsField( + default_factory=HookOptionalModel, + title="Launch AYON Menu on Fusion Start", + description="Launch the AYON menu on Fusion application startup. " + "This is only supported for Fusion 18+" + ) class CreateSaverModel(CreateSaverPluginModel): @@ -143,6 +149,9 @@ DEFAULT_VALUES = { "hooks": { "InstallPySideToFusion": { "enabled": True + }, + "FusionLaunchMenuHook": { + "enabled": True } }, "create": { From 38c32c55af7b882cb75b161cbf581bc7c6cc2881 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 21:37:22 +0100 Subject: [PATCH 022/203] Bump fusion server addon version --- server_addon/fusion/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/fusion/server/version.py +++ b/server_addon/fusion/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From 2ae618d2cbef243e3ea9ab57197ceda813410aca Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Mar 2024 21:51:12 +0100 Subject: [PATCH 023/203] Update client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py --- .../ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py index f5a1c75a50..e70d4b844e 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py @@ -4,7 +4,7 @@ from ayon_core.hosts.fusion import FUSION_HOST_DIR class FusionLaunchMenuHook(PreLaunchHook): - """Launch OpenPype menu on start of Fusion""" + """Launch AYON menu on start of Fusion""" app_groups = ["fusion"] order = 9 From b17df02e418b2061ec6631d028e3039ce55105f5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Mar 2024 23:04:58 +0100 Subject: [PATCH 024/203] Maya: Validate Model Content improve validation message - Also fix `get_invalid` actually returning the instance node. --- .../plugins/publish/validate_model_content.py | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py index 37c78a72ee..97d602a9ad 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py @@ -28,13 +28,15 @@ class ValidateModelContent(pyblish.api.InstancePlugin, validate_top_group = True optional = False + allowed = ('mesh', 'transform', 'nurbsCurve', 'nurbsSurface', 'locator') + @classmethod def get_invalid(cls, instance): content_instance = instance.data.get("setMembers", None) if not content_instance: cls.log.error("Instance has no nodes!") - return [instance.data["name"]] + return [instance.data["instance_node"]] # All children will be included in the extracted export so we also # validate *all* descendents of the set members and we skip any @@ -46,30 +48,39 @@ class ValidateModelContent(pyblish.api.InstancePlugin, content_instance = list(set(content_instance + descendants)) # Ensure only valid node types - allowed = ('mesh', 'transform', 'nurbsCurve', 'nurbsSurface', 'locator') nodes = cmds.ls(content_instance, long=True) - valid = cmds.ls(content_instance, long=True, type=allowed) + valid = cmds.ls(content_instance, long=True, type=cls.allowed) invalid = set(nodes) - set(valid) if invalid: - cls.log.error("These nodes are not allowed: %s" % invalid) + cls.log.error( + "These nodes are not allowed: {}.\n" + "The valid node types are: {}".format(", ".join(invalid), + ", ".join(cls.allowed)) + ) return list(invalid) if not valid: - cls.log.error("No valid nodes in the instance") - return True + cls.log.error( + "No valid nodes in the model instance.\n" + "The valid node types are: {}".format(", ".join(cls.allowed)) + ) + return [instance.data["instance_node"]] # Ensure it has shapes shapes = cmds.ls(valid, long=True, shapes=True) if not shapes: cls.log.error("No shapes in the model instance") - return True + return [instance.data["instance_node"]] - # Top group - top_parents = set([x.split("|")[1] for x in content_instance]) + # Ensure single top group + top_parents = {x.split("|", 2)[1] for x in content_instance} if cls.validate_top_group and len(top_parents) != 1: - cls.log.error("Must have exactly one top group") - return top_parents + cls.log.error( + "A model instance must have exactly one top group. " + "Found top groups: {}".format(", ".join(top_parents)) + ) + return list(top_parents) def _is_visible(node): """Return whether node is visible""" @@ -101,5 +112,11 @@ class ValidateModelContent(pyblish.api.InstancePlugin, if invalid: raise PublishValidationError( title="Model content is invalid", - message="See log for more details" + message="Model content is invalid. See log for more details.", + description=( + "## Model content is invalid\n" + "Your model instance does not adhere to the rules of a " + "model.\n\n" + "See log for more details." + ) ) From a58766962356fb4d50a6219fa361c0bbb5ec9806 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 00:56:11 +0100 Subject: [PATCH 025/203] Actually report the duplicated creator class --- client/ayon_core/pipeline/create/context.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 8c6a7f1bb6..2a779370e9 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -1790,10 +1790,10 @@ class CreateContext: creator_identifier = creator_class.identifier if creator_identifier in creators: - self.log.warning(( - "Duplicated Creator identifier. " - "Using first and skipping following" - )) + self.log.warning( + "Duplicated Creator identifier. Using first and " + "skipping following: {}".format(str(creator_class)) + ) continue # Filter by host name From cb6f4dd79857ffbe62e8a99cb91d3db83f8712d5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 09:56:44 +0100 Subject: [PATCH 026/203] Turn off by default --- server_addon/fusion/server/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py index b44feb1f03..f16ae6e3e7 100644 --- a/server_addon/fusion/server/settings.py +++ b/server_addon/fusion/server/settings.py @@ -151,7 +151,7 @@ DEFAULT_VALUES = { "enabled": True }, "FusionLaunchMenuHook": { - "enabled": True + "enabled": False } }, "create": { From 07cc3bba0b61fbb4a7d8260dddd43690de2d2f0b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 11:09:28 +0100 Subject: [PATCH 027/203] Include identifier in warning log --- client/ayon_core/pipeline/create/context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 2a779370e9..bf007a8461 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -1791,8 +1791,8 @@ class CreateContext: creator_identifier = creator_class.identifier if creator_identifier in creators: self.log.warning( - "Duplicated Creator identifier. Using first and " - "skipping following: {}".format(str(creator_class)) + "Duplicate Creator identifier: '%s'. Using first Creator " + "and skipping: %s", creator_identifier, creator_class ) continue From 1c97c78a173aa3540587c2a4f2bf34d93d8e5ae6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 15:15:03 +0100 Subject: [PATCH 028/203] Report the asset names of the other ids that were found --- .../publish/validate_node_ids_related.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py index 992988dc7d..be52dedd61 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py @@ -1,9 +1,11 @@ +from collections import defaultdict import pyblish.api import ayon_core.hosts.maya.api.action from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( OptionalPyblishPluginMixin, PublishValidationError, ValidatePipelineOrder) +from ayon_api import get_folders class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, @@ -48,12 +50,12 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, @classmethod def get_invalid(cls, instance): """Return the member nodes that are invalid""" - invalid = list() - folder_id = instance.data["folderEntity"]["id"] - # We do want to check the referenced nodes as we it might be + # We do want to check the referenced nodes as it might be # part of the end product + invalid = list() + nodes_by_other_folder_ids = defaultdict(set) for node in instance: _id = lib.get_id(node) if not _id: @@ -62,5 +64,24 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, node_folder_id = _id.split(":", 1)[0] if node_folder_id != folder_id: invalid.append(node) + nodes_by_other_folder_ids[node_folder_id].add(node) + + # Log what other assets were found. + if nodes_by_other_folder_ids: + project_name = instance.context.data["projectName"] + other_folder_ids = set(nodes_by_other_folder_ids.keys()) + folder_entities = get_folders(project_name=project_name, + folder_ids=other_folder_ids, + fields=["name"]) + if folder_entities: + # Log names of other assets detected + # We disregard logging nodes/ids for asset ids where no asset + # was found in the database because ValidateNodeIdsInDatabase + # takes care of that. + folder_names = {entity["name"] for entity in folder_entities} + cls.log.error( + "Found nodes related to other assets: {}" + .format(", ".join(sorted(folder_names))) + ) return invalid From 6cfa9a164680855bbbc651ff5a925532ddbb44bc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 15:18:23 +0100 Subject: [PATCH 029/203] Report using the folder paths --- .../hosts/maya/plugins/publish/validate_node_ids_related.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py index be52dedd61..f8337ed6f0 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py @@ -72,16 +72,16 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, other_folder_ids = set(nodes_by_other_folder_ids.keys()) folder_entities = get_folders(project_name=project_name, folder_ids=other_folder_ids, - fields=["name"]) + fields=["path"]) if folder_entities: # Log names of other assets detected # We disregard logging nodes/ids for asset ids where no asset # was found in the database because ValidateNodeIdsInDatabase # takes care of that. - folder_names = {entity["name"] for entity in folder_entities} + folder_paths = {entity["path"] for entity in folder_entities} cls.log.error( "Found nodes related to other assets: {}" - .format(", ".join(sorted(folder_names))) + .format(", ".join(sorted(folder_paths))) ) return invalid From 4dd44b08b78f3a803c0e738841067096a5cea943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 27 Mar 2024 17:19:52 +0100 Subject: [PATCH 030/203] :art: add support for Maya 2025 some changes to support PySide6 there --- client/ayon_core/hosts/maya/addon.py | 8 ++++---- .../maya/vendor/python}/capture.py | 6 +----- pyproject.toml | 2 +- .../applications/server/applications.json | 20 +++++++++++++++++++ server_addon/applications/server/version.py | 2 +- 5 files changed, 27 insertions(+), 11 deletions(-) rename client/ayon_core/{vendor/python/common => hosts/maya/vendor/python}/capture.py (99%) diff --git a/client/ayon_core/hosts/maya/addon.py b/client/ayon_core/hosts/maya/addon.py index c68aa4c911..1ad0fcf4cf 100644 --- a/client/ayon_core/hosts/maya/addon.py +++ b/client/ayon_core/hosts/maya/addon.py @@ -22,15 +22,15 @@ class MayaAddon(AYONAddon, IHostAddon): if norm_path not in new_python_paths: new_python_paths.append(norm_path) + # add vendor path + new_python_paths.append( + os.path.join(MAYA_ROOT_DIR, "vendor", "python") + ) env["PYTHONPATH"] = os.pathsep.join(new_python_paths) # Set default environments envs = { "AYON_LOG_NO_COLORS": "1", - # For python module 'qtpy' - "QT_API": "PySide2", - # For python module 'Qt' - "QT_PREFERRED_BINDING": "PySide2" } for key, value in envs.items(): env[key] = value diff --git a/client/ayon_core/vendor/python/common/capture.py b/client/ayon_core/hosts/maya/vendor/python/capture.py similarity index 99% rename from client/ayon_core/vendor/python/common/capture.py rename to client/ayon_core/hosts/maya/vendor/python/capture.py index 224699f916..7a3be38f56 100644 --- a/client/ayon_core/vendor/python/common/capture.py +++ b/client/ayon_core/hosts/maya/vendor/python/capture.py @@ -12,11 +12,7 @@ import logging from maya import cmds from maya import mel -try: - from PySide2 import QtGui, QtWidgets -except ImportError: - from PySide import QtGui - QtWidgets = QtGui +from qtpy import QtGui, QtWidgets version_info = (2, 3, 0) diff --git a/pyproject.toml b/pyproject.toml index ee124ddc2d..ec38b746dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ line-ending = "auto" [tool.codespell] # Ignore words that are not in the dictionary. -ignore-words-list = "ayon,ynput" +ignore-words-list = "ayon,ynput,developpement" skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*" count = true quiet-level = 3 diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json index 85bf6f1dda..e4b72fdff9 100644 --- a/server_addon/applications/server/applications.json +++ b/server_addon/applications/server/applications.json @@ -7,6 +7,26 @@ "host_name": "maya", "environment": "{\n \"MAYA_DISABLE_CLIC_IPM\": \"Yes\",\n \"MAYA_DISABLE_CIP\": \"Yes\",\n \"MAYA_DISABLE_CER\": \"Yes\",\n \"PYMEL_SKIP_MEL_INIT\": \"Yes\",\n \"LC_ALL\": \"C\"\n}\n", "variants": [ + { + "name": "2025", + "label": "2025", + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\Maya2025\\bin\\maya.exe" + ], + "darwin": ["/Applications/Autodesk/maya2025/Maya.app"], + "linux": [ + "/usr/autodesk/maya2025/bin/maya" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MAYA_VERSION\": \"2025\"\n}", + "use_python_2": false + }, { "name": "2024", "label": "2024", diff --git a/server_addon/applications/server/version.py b/server_addon/applications/server/version.py index 9cb17e7976..c11f861afb 100644 --- a/server_addon/applications/server/version.py +++ b/server_addon/applications/server/version.py @@ -1 +1 @@ -__version__ = "0.1.8" +__version__ = "0.1.9" From 149c83e136d342b53bc667b114df43ede22915f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Mar 2024 13:57:37 +0100 Subject: [PATCH 031/203] Raise PublishValidationError --- .../publish/validate_current_renderlayer_renderable.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py index 4590c53931..332c5aff02 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py @@ -3,9 +3,11 @@ import pyblish.api from maya import cmds from ayon_core.pipeline.publish import ( context_plugin_should_run, + PublishValidationError, OptionalPyblishPluginMixin ) + class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin, OptionalPyblishPluginMixin): """Validate if current render layer has a renderable camera @@ -35,5 +37,9 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin, layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) cameras = cmds.ls(type="camera", long=True) renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable")) - assert renderable, ("Current render layer '%s' has no renderable " - "camera" % layer) + if not renderable: + raise PublishValidationError( + "Current render layer '{}' has no renderable camera".format( + layer + ) + ) From 4a69d68a40c845180baa36e6c665d608d1fe1b6c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Mar 2024 14:00:05 +0100 Subject: [PATCH 032/203] Add description to report --- .../publish/validate_current_renderlayer_renderable.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py index 332c5aff02..f02c401bf9 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py @@ -1,3 +1,5 @@ +import inspect + import pyblish.api from maya import cmds @@ -10,7 +12,7 @@ from ayon_core.pipeline.publish import ( class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin, OptionalPyblishPluginMixin): - """Validate if current render layer has a renderable camera + """Validate if current render layer has a renderable camera. There is a bug in Redshift which occurs when the current render layer at file open has no renderable camera. The error raised is as follows: @@ -41,5 +43,6 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin, raise PublishValidationError( "Current render layer '{}' has no renderable camera".format( layer - ) + ), + description=inspect.getdoc(self) ) From 03ee91cf2c2826e0ff865b2d0949c71d8ca7ee46 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Mar 2024 14:01:51 +0100 Subject: [PATCH 033/203] Only query current renderlayer if there is no renderable camera --- .../plugins/publish/validate_current_renderlayer_renderable.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py index f02c401bf9..da8468c1b6 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py @@ -36,10 +36,11 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin, if not context_plugin_should_run(self, context): return - layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) cameras = cmds.ls(type="camera", long=True) renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable")) if not renderable: + layer = cmds.editRenderLayerGlobals(query=True, + currentRenderLayer=True) raise PublishValidationError( "Current render layer '{}' has no renderable camera".format( layer From bbbea73a8d21a3d99465d54071ea1ac9a4ad14c0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Mar 2024 15:36:55 +0100 Subject: [PATCH 034/203] Optimize collect inputs by caching scene containers once --- .../houdini/plugins/publish/collect_inputs.py | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py index 7d7fabb315..3523acee06 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py @@ -1,9 +1,21 @@ +from collections import deque + import pyblish.api from ayon_core.pipeline import registered_host -def collect_input_containers(nodes): +def get_container_members(container): + node = container["node"] + # Usually the loaded containers don't have any complex references + # and the contained children should be all we need. So we disregard + # checking for .references() on the nodes. + members = set(node.allSubChildren()) + members.add(node) # include the node itself + return members + + +def collect_input_containers(containers, nodes): """Collect containers that contain any of the node in `nodes`. This will return any loaded Avalon container that contains at least one of @@ -11,30 +23,13 @@ def collect_input_containers(nodes): there are member nodes of that container. Returns: - list: Input avalon containers + list: Loaded containers that contain the `nodes` """ - - # Lookup by node ids - lookup = frozenset(nodes) - - containers = [] - host = registered_host() - for container in host.ls(): - - node = container["node"] - - # Usually the loaded containers don't have any complex references - # and the contained children should be all we need. So we disregard - # checking for .references() on the nodes. - members = set(node.allSubChildren()) - members.add(node) # include the node itself - - # If there's an intersection - if not lookup.isdisjoint(members): - containers.append(container) - - return containers + # Assume the containers have collected their cached '_members' data + # in the collector. + return [container for container in containers + if any(node in container["_members"] for node in nodes)] def iter_upstream(node): @@ -54,7 +49,7 @@ def iter_upstream(node): ) # Initialize process queue with the node's ancestors itself - queue = list(upstream) + queue = deque(upstream) collected = set(upstream) # Traverse upstream references for all nodes and yield them as we @@ -72,6 +67,10 @@ def iter_upstream(node): # Include the references' ancestors that have not been collected yet. for reference in references: + if reference in collected: + # Might have been collected in previous iteration + continue + ancestors = reference.inputAncestors( include_ref_inputs=True, follow_subnets=True ) @@ -108,13 +107,32 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): ) return - # Collect all upstream parents - nodes = list(iter_upstream(output)) - nodes.append(output) + # For large scenes the querying of "host.ls()" can be relatively slow + # e.g. up to a second. Many instances calling it easily slows this + # down. As such, we cache it so we trigger it only once. + # todo: Instead of hidden cache make "CollectContainers" plug-in + cache_key = "__cache_containers" + scene_containers = instance.context.data.get(cache_key, None) + if scene_containers is None: + # Query the scenes' containers if there's no cache yet + host = registered_host() + scene_containers = list(host.ls()) + for container in scene_containers: + # Embed the members into the container dictionary + container_members = set(get_container_members(container)) + container["_members"] = container_members + instance.context.data["__cache_containers"] = scene_containers - # Collect containers for the given set of nodes - containers = collect_input_containers(nodes) + inputs = [] + if scene_containers: + # Collect all upstream parents + nodes = list(iter_upstream(output)) + nodes.append(output) + + # Collect containers for the given set of nodes + containers = collect_input_containers(scene_containers, nodes) + + inputs = [c["representation"] for c in containers] - inputs = [c["representation"] for c in containers] instance.data["inputRepresentations"] = inputs self.log.debug("Collected inputs: %s" % inputs) From 8eab84e2dadafc1b1e00d0d33cd4bfed2d6f85ef Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Mar 2024 18:10:28 +0100 Subject: [PATCH 035/203] Maya: Load Rendersetup - allow loading unmanaged + automatically 'accept' the import + pick import mode --- .../maya/plugins/load/load_rendersetup.py | 112 ++++++++++++++---- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py index 6f20e677f0..fb1183dfcb 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py @@ -9,7 +9,9 @@ instance. import json import sys import six +import contextlib +from ayon_core.lib import BoolDef, EnumDef from ayon_core.pipeline import ( load, get_representation_path @@ -21,6 +23,31 @@ from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup +@contextlib.contextmanager +def mark_all_imported(enabled): + """Mark all imported nodes accepted by removing the `imported` attribute""" + if not enabled: + yield + return + + node_types = cmds.pluginInfo("renderSetup", query=True, dependNode=True) + + # Get node before load, then we can disable `imported` + # attribute on all new render setup layers after import + before = cmds.ls(type=node_types, long=True) + try: + yield + finally: + after = cmds.ls(type=node_types, long=True) + for node in (node for node in after if node not in before): + if cmds.attributeQuery("imported", + node=node, + exists=True): + plug = "{}.imported".format(node) + if cmds.getAttr(plug): + cmds.deleteAttr(plug) + + class RenderSetupLoader(load.LoaderPlugin): """Load json preset for RenderSetup overwriting current one.""" @@ -32,38 +59,71 @@ class RenderSetupLoader(load.LoaderPlugin): icon = "tablet" color = "orange" + options = [ + BoolDef("accept_import", + label="Accept import on load", + tooltip=( + "By default importing or pasting Render Setup collections " + "will display them italic in the Render Setup list.\nWith " + "this enabled the load will directly mark the import " + "'accepted' and remove the italic view." + ), + default=True), + BoolDef("load_managed", + label="Load Managed", + tooltip=( + "Containerize the rendersetup on load so it can be " + "'updated' later." + ), + default=True), + EnumDef("import_mode", + label="Import mode", + items={ + renderSetup.DECODE_AND_OVERWRITE: ( + "Flush existing render setup and " + "add without any namespace" + ), + renderSetup.DECODE_AND_MERGE: ( + "Merge with the existing render setup objects and " + "rename the unexpected objects" + ), + renderSetup.DECODE_AND_RENAME: ( + "Renaming all decoded render setup objects to not " + "conflict with the existing render setup" + ), + }, + default=renderSetup.DECODE_AND_OVERWRITE) + ] + def load(self, context, name, namespace, data): """Load RenderSetup settings.""" - # from ayon_core.hosts.maya.api.lib import namespaced - - folder_name = context["folder"]["name"] - namespace = namespace or lib.unique_namespace( - folder_name + "_", - prefix="_" if folder_name[0].isdigit() else "", - suffix="_", - ) path = self.filepath_from_context(context) + + accept_import = data.get("accept_import", True) + import_mode = data.get("import_mode", renderSetup.DECODE_AND_OVERWRITE) + self.log.info(">>> loading json [ {} ]".format(path)) - with open(path, "r") as file: - renderSetup.instance().decode( - json.load(file), renderSetup.DECODE_AND_OVERWRITE, None) + with mark_all_imported(accept_import): + with open(path, "r") as file: + renderSetup.instance().decode( + json.load(file), import_mode, None) - nodes = [] - null = cmds.sets(name="null_SET", empty=True) - nodes.append(null) + if data.get("load_managed", True): + self.log.info(">>> containerising [ {} ]".format(name)) + asset = context['asset']['name'] + namespace = namespace or lib.unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) - self[:] = nodes - if not nodes: - return - - self.log.info(">>> containerising [ {} ]".format(name)) - return containerise( - name=name, - namespace=namespace, - nodes=nodes, - context=context, - loader=self.__class__.__name__) + return containerise( + name=name, + namespace=namespace, + nodes=[], + context=context, + loader=self.__class__.__name__) def remove(self, container): """Remove RenderSetup settings instance.""" @@ -73,7 +133,7 @@ class RenderSetupLoader(load.LoaderPlugin): self.log.info("Removing '%s' from Maya.." % container["name"]) - container_content = cmds.sets(container_name, query=True) + container_content = cmds.sets(container_name, query=True) or [] nodes = cmds.ls(container_content, long=True) nodes.append(container_name) From 312fd21b23f680c9f2032497ae9b857b38c52f99 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Mar 2024 19:37:25 +0100 Subject: [PATCH 036/203] Expose `timecode` for formatting ffmpeg args --- .../plugins/publish/extract_review.py | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 905158c851..ac213b8669 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -32,6 +32,35 @@ from ayon_core.pipeline.publish import ( from ayon_core.pipeline.publish.lib import add_repre_files_for_cleanup +def frame_to_timecode(frame: int, fps: float) -> str: + """Convert a frame number and FPS to editorial timecode (HH:MM:SS:FF). + + Unlike `ayon_core.pipeline.editorial.frames_to_timecode` this does not + rely on the `opentimelineio` package, so it can be used across hosts that + do not have it available. + + Args: + frame (int): The frame number to be converted. + fps (float): The frames per second of the video. + + Returns: + str: The timecode in HH:MM:SS:FF format. + """ + # Calculate total seconds + total_seconds = frame / fps + + # Extract hours, minutes, and seconds + hours = int(total_seconds // 3600) + minutes = int((total_seconds % 3600) // 60) + seconds = int(total_seconds % 60) + + # Adjust for non-integer FPS by rounding the remaining frames appropriately + remaining_frames = round((total_seconds - int(total_seconds)) * fps) + + # Format and return the timecode + return f"{hours:02d}:{minutes:02d}:{seconds:02d}:{remaining_frames:02d}" + + class ExtractReview(pyblish.api.InstancePlugin): """Extracting Review mov file for Ftrack @@ -390,7 +419,16 @@ class ExtractReview(pyblish.api.InstancePlugin): # add outputName to anatomy format fill_data fill_data.update({ "output": output_name, - "ext": output_ext + "ext": output_ext, + + # By adding `timecode` as data we can use it + # in the ffmpeg arguments for `--timecode` so that editorial + # like Resolve or Premiere can detect the start frame for e.g. + # review output files + "timecode": frame_to_timecode( + frame=temp_data["frame_start_handle"], + fps=float(instance.data["fps"]) + ) }) try: # temporary until oiiotool is supported cross platform From 33fd95dd009d65097cc68f7f4f10251eff4ea874 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 10:57:27 +0100 Subject: [PATCH 037/203] Add `usd` to product type enum for Loader Product Type Filter Used in `ayon+settings://core/tools/loader/product_type_filter_profiles` --- server/settings/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/settings/tools.py b/server/settings/tools.py index 488d27e8f1..fb8430a71c 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -173,6 +173,7 @@ def _product_types_enum(): "rig", "setdress", "take", + "usd", "usdShade", "vdbcache", "vrayproxy", From 27d531536c779561ad6b0573be226eaf70e86b1b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 12:28:48 +0100 Subject: [PATCH 038/203] Fix `asset` -> `folder` --- .../ayon_core/hosts/maya/plugins/load/load_rendersetup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py index fb1183dfcb..53e8307fee 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py @@ -111,10 +111,10 @@ class RenderSetupLoader(load.LoaderPlugin): if data.get("load_managed", True): self.log.info(">>> containerising [ {} ]".format(name)) - asset = context['asset']['name'] + folder_name = context["folder"]["name"] namespace = namespace or lib.unique_namespace( - asset + "_", - prefix="_" if asset[0].isdigit() else "", + folder_name + "_", + prefix="_" if folder_name[0].isdigit() else "", suffix="_", ) From 74ec0d0acda7e8cf88ec9dd826e00a79e4dc6623 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 13:04:56 +0100 Subject: [PATCH 039/203] Remove redundant import --- client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py index 53e8307fee..7096f86e35 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py @@ -127,8 +127,6 @@ class RenderSetupLoader(load.LoaderPlugin): def remove(self, container): """Remove RenderSetup settings instance.""" - from maya import cmds - container_name = container["objectName"] self.log.info("Removing '%s' from Maya.." % container["name"]) From a4b0dbc4936da61f167912167959916138b8e2b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 13:17:49 +0100 Subject: [PATCH 040/203] Optimize `cbId` logic for large scenes --- client/ayon_core/hosts/maya/api/lib.py | 191 +++++++++++++----- client/ayon_core/hosts/maya/api/pipeline.py | 3 +- .../maya/plugins/publish/validate_node_ids.py | 9 +- .../publish/validate_node_ids_in_database.py | 11 +- 4 files changed, 153 insertions(+), 61 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index b18d3a0c33..4b9b80f0e7 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -1519,24 +1519,30 @@ def extract_alembic(file, # region ID -def get_id_required_nodes(referenced_nodes=False, nodes=None): - """Filter out any node which are locked (reference) or readOnly +def get_id_required_nodes(referenced_nodes=False, + nodes=None, + existing_ids=True): + """Return nodes that should receive a `cbId` attribute. + + This includes only mesh and curve nodes, parent transforms of the shape + nodes, file texture nodes and object sets (including shading engines). + + This filters out any node which is locked, referenced, read-only, + intermediate object. Args: - referenced_nodes (bool): set True to filter out reference nodes + referenced_nodes (bool): set True to include referenced nodes nodes (list, Optional): nodes to consider + existing_ids (bool): set True to include nodes with `cbId` attribute + Returns: nodes (set): list of filtered nodes """ - lookup = None - if nodes is None: - # Consider all nodes - nodes = cmds.ls() - else: - # Build a lookup for the only allowed nodes in output based - # on `nodes` input of the function (+ ensure long names) - lookup = set(cmds.ls(nodes, long=True)) + if nodes is not None and not nodes: + # User supplied an empty `nodes` list to check so all we can + # do is return the empty result + return set() def _node_type_exists(node_type): try: @@ -1545,63 +1551,142 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): except RuntimeError: return False + def iterate(maya_iterator): + while not maya_iterator.isDone(): + yield maya_iterator.thisNode() + maya_iterator.next() + # `readOnly` flag is obsolete as of Maya 2016 therefore we explicitly # remove default nodes and reference nodes - camera_shapes = ["frontShape", "sideShape", "topShape", "perspShape"] + default_camera_shapes = { + "frontShape", "sideShape", "topShape", "perspShape" + } - ignore = set() - if not referenced_nodes: - ignore |= set(cmds.ls(long=True, referencedNodes=True)) - - # list all defaultNodes to filter out from the rest - ignore |= set(cmds.ls(long=True, defaultNodes=True)) - ignore |= set(cmds.ls(camera_shapes, long=True)) - - # Remove Turtle from the result of `cmds.ls` if Turtle is loaded - # TODO: This should be a less specific check for a single plug-in. - if _node_type_exists("ilrBakeLayer"): - ignore |= set(cmds.ls(type="ilrBakeLayer", long=True)) - - # Establish set of nodes types to include - types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] + # The filtered types do not include transforms because we only want the + # parent transforms that have a child shape that we filtered to, so we + # include the parents here + types = ["mesh", "nurbsCurve", "nurbsSurface", "file", "objectSet"] # Check if plugin nodes are available for Maya by checking if the plugin # is loaded if cmds.pluginInfo("pgYetiMaya", query=True, loaded=True): types.append("pgYetiMaya") - # We *always* ignore intermediate shapes, so we filter them out directly - nodes = cmds.ls(nodes, type=types, long=True, noIntermediate=True) + iterator_type = OpenMaya.MIteratorType() + # This tries to be closest matching API equivalents of `types` variable + iterator_type.filterList = [ + OpenMaya.MFn.kMesh, # mesh + OpenMaya.MFn.kNurbsSurface, # nurbsSurface + OpenMaya.MFn.kNurbsCurve, # nurbsCurve + OpenMaya.MFn.kFileTexture, # file + OpenMaya.MFn.kSet, # objectSet + OpenMaya.MFn.kPluginShape # pgYetiMaya + ] + it = OpenMaya.MItDependencyNodes(iterator_type) - # The items which need to pass the id to their parent - # Add the collected transform to the nodes - dag = cmds.ls(nodes, type="dagNode", long=True) # query only dag nodes - transforms = cmds.listRelatives(dag, - parent=True, - fullPath=True) or [] + fn_dep = OpenMaya.MFnDependencyNode() + fn_dag = OpenMaya.MFnDagNode() + result = set() - nodes = set(nodes) - nodes |= set(transforms) + def _should_include_parents(obj): + """Whether to include parents of obj in output""" + if not obj.hasFn(OpenMaya.MFn.kShape): + return False - nodes -= ignore # Remove the ignored nodes - if not nodes: - return nodes + fn_dag.setObject(obj) + if fn_dag.isIntermediateObject: + return False - # Ensure only nodes from the input `nodes` are returned when a - # filter was applied on function call because we also iterated - # to parents and alike - if lookup is not None: - nodes &= lookup + # Skip default cameras + if ( + obj.hasFn(OpenMaya.MFn.kCamera) and + fn_dag.name() in default_camera_shapes + ): + return False - # Avoid locked nodes - nodes_list = list(nodes) - locked = cmds.lockNode(nodes_list, query=True, lock=True) - for node, lock in zip(nodes_list, locked): - if lock: - log.warning("Skipping locked node: %s" % node) - nodes.remove(node) + return True - return nodes + def _add_to_result_if_valid(obj): + """Add to `result` if the object should be included""" + fn_dep.setObject(obj) + if not existing_ids and fn_dep.hasAttribute("cbId"): + return + + if not referenced_nodes and fn_dep.isFromReferencedFile: + return + + if fn_dep.isDefaultNode: + return + + if fn_dep.isLocked: + return + + # Skip default cameras + if ( + obj.hasFn(OpenMaya.MFn.kCamera) and + fn_dep.name() in default_camera_shapes + ): + return + + if obj.hasFn(OpenMaya.MFn.kDagNode): + # DAG nodes + fn_dag.setObject(obj) + + # Skip intermediate objects + if fn_dag.isIntermediateObject: + return + + # DAG nodes can be instanced and thus may have multiple paths. + # We need to identify each path + paths = OpenMaya.MDagPath.getAllPathsTo(obj) + for dag in paths: + path = dag.fullPathName() + result.add(path) + else: + # Dependency node + path = fn_dep.name() + result.add(path) + + for obj in iterate(it): + # For any non-intermediate shape node always include the parent + # even if we exclude the shape itself (e.g. when locked, default) + if _should_include_parents(obj): + fn_dag.setObject(obj) + parents = [ + fn_dag.parent(index) for index in range(fn_dag.parentCount()) + ] + for parent_obj in parents: + _add_to_result_if_valid(parent_obj) + + _add_to_result_if_valid(obj) + + if not result: + return result + + # Exclude some additional types + exclude_types = [] + if _node_type_exists("ilrBakeLayer"): + # Remove Turtle from the result if Turtle is loaded + exclude_types.append("ilrBakeLayer") + + if exclude_types: + exclude_nodes = set(cmds.ls(nodes, long=True, type=exclude_types)) + if exclude_nodes: + result -= exclude_nodes + + # Filter to explicit input nodes if provided + if nodes is not None: + # The amount of input nodes to filter to can be large and querying + # many nodes can be slow in Maya. As such we want to try and reduce + # it as much as possible, so we include the type filter to try and + # reduce the result of `maya.cmds.ls` here. + nodes = set(cmds.ls(nodes, long=True, type=types + ["dagNode"])) + if nodes: + result &= nodes + else: + return set() + + return result def get_id(node): diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index 2be452a22a..464e87814c 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -580,7 +580,8 @@ def on_save(): _remove_workfile_lock() # Generate ids of the current context on nodes in the scene - nodes = lib.get_id_required_nodes(referenced_nodes=False) + nodes = lib.get_id_required_nodes(referenced_nodes=False, + existing_ids=False) for node, new_id in lib.generate_ids(nodes): lib.set_id(node, new_id, overwrite=False) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids.py index ba748a4fc4..2d6f231cb5 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids.py @@ -60,7 +60,8 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): # We do want to check the referenced nodes as it might be # part of the end product. id_nodes = lib.get_id_required_nodes(referenced_nodes=True, - nodes=instance[:]) - invalid = [n for n in id_nodes if not lib.get_id(n)] - - return invalid + nodes=instance[:], + # Exclude those with already + # existing ids + existing_ids=False) + return id_nodes diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 5ca9690fd7..58978fb538 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -43,11 +43,15 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - invalid = [] + nodes = instance[:] + if not nodes: + return # Get all id required nodes - id_required_nodes = lib.get_id_required_nodes(referenced_nodes=True, - nodes=instance[:]) + id_required_nodes = lib.get_id_required_nodes(referenced_nodes=False, + nodes=nodes) + if not id_required_nodes: + return # check ids against database ids project_name = instance.context.data["projectName"] @@ -58,6 +62,7 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): } # Get all asset IDs + invalid = [] for node in id_required_nodes: cb_id = lib.get_id(node) From 81e6d1dd896be17a1916139de8e7d40203a37b92 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 13:20:48 +0100 Subject: [PATCH 041/203] Do not error if there are references that maya considers invalid, instead ignore them --- client/ayon_core/hosts/maya/api/lib.py | 34 +++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index b18d3a0c33..4541197ab5 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -1832,6 +1832,29 @@ def apply_attributes(attributes, nodes_by_id): set_attribute(attr, value, node) +def is_valid_reference_node(reference_node): + """Return whether Maya considers the reference node a valid reference. + + Maya might report an error when using `maya.cmds.referenceQuery`: + Reference node 'reference_node' is not associated with a reference file. + + Note that this does *not* check whether the reference node points to an + existing file. Instead it only returns whether maya considers it valid + and thus is not an unassociated reference node + + Arguments: + reference_node (str): Reference node name + + Returns: + bool: Whether reference node is a valid reference + + """ + sel = OpenMaya.MSelectionList() + sel.add(reference_node) + depend_node = sel.getDependNode(0) + return OpenMaya.MFnReference(depend_node).isValidReference() + + def get_container_members(container): """Returns the members of a container. This includes the nodes from any loaded references in the container. @@ -1857,7 +1880,16 @@ def get_container_members(container): if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"): continue - reference_members = cmds.referenceQuery(ref, nodes=True, dagPath=True) + try: + reference_members = cmds.referenceQuery(ref, + nodes=True, + dagPath=True) + except RuntimeError: + # Ignore reference nodes that are not associated with a + # referenced file on which `referenceQuery` command fails + if not is_valid_reference_node(ref): + continue + raise reference_members = cmds.ls(reference_members, long=True, objectsOnly=True) From 9cbb3b136db73a422cceb2c1edc6cd2f19aa067a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 14:29:28 +0100 Subject: [PATCH 042/203] Apply playback options in one call --- client/ayon_core/hosts/maya/api/lib.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index b18d3a0c33..ac3e32db0d 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2444,12 +2444,10 @@ def set_scene_fps(fps, update=True): cmds.currentUnit(time=unit, updateAnimation=update) # Set time slider data back to previous state - cmds.playbackOptions(edit=True, minTime=start_frame) - cmds.playbackOptions(edit=True, maxTime=end_frame) - - # Set animation data - cmds.playbackOptions(edit=True, animationStartTime=animation_start) - cmds.playbackOptions(edit=True, animationEndTime=animation_end) + cmds.playbackOptions(minTime=start_frame, + maxTime=end_frame, + animationStartTime=animation_start, + animationEndTime=animation_end) cmds.currentTime(current_frame, edit=True, update=True) From f17170d43a4d2d567d72bf8fca0b8ed0d7aaba7b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 14:37:29 +0100 Subject: [PATCH 043/203] Optimize `get_related_sets` Opt-out earlier if no valid sets to perform less maya calls --- client/ayon_core/hosts/maya/api/lib.py | 36 ++++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index b18d3a0c33..2b0a4449c9 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2125,22 +2125,6 @@ def get_related_sets(node): """ - # Ignore specific suffices - ignore_suffices = ["out_SET", "controls_SET", "_INST", "_CON"] - - # Default nodes to ignore - defaults = {"defaultLightSet", "defaultObjectSet"} - - # Ids to ignore - ignored = { - AVALON_INSTANCE_ID, - AVALON_CONTAINER_ID, - AYON_INSTANCE_ID, - AYON_CONTAINER_ID, - } - - view_sets = get_isolate_view_sets() - sets = cmds.listSets(object=node, extendToShape=False) if not sets: return [] @@ -2151,6 +2135,14 @@ def get_related_sets(node): # returned by `cmds.listSets(allSets=True)` sets = cmds.ls(sets) + # Ids to ignore + ignored = { + AVALON_INSTANCE_ID, + AVALON_CONTAINER_ID, + AYON_INSTANCE_ID, + AYON_CONTAINER_ID, + } + # Ignore `avalon.container` sets = [ s for s in sets @@ -2159,6 +2151,8 @@ def get_related_sets(node): or cmds.getAttr(f"{s}.id") not in ignored ) ] + if not sets: + return sets # Exclude deformer sets (`type=2` for `maya.cmds.listSets`) deformer_sets = cmds.listSets(object=node, @@ -2168,12 +2162,20 @@ def get_related_sets(node): sets = [s for s in sets if s not in deformer_sets] # Ignore when the set has a specific suffix + ignore_suffices = ["out_SET", "controls_SET", "_INST", "_CON"] sets = [s for s in sets if not any(s.endswith(x) for x in ignore_suffices)] + # Default nodes to ignore + defaults = {"defaultLightSet", "defaultObjectSet"} + sets = [s for s in sets if s not in defaults] + + if not sets: + return sets + # Ignore viewport filter view sets (from isolate select and # viewports) + view_sets = get_isolate_view_sets() sets = [s for s in sets if s not in view_sets] - sets = [s for s in sets if s not in defaults] return sets From c6a7272e89962a090454e52c63c660e2bfc4d8dd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 14:42:34 +0100 Subject: [PATCH 044/203] Who would have known that `str.endswith` can take a `tuple`? You do! --- client/ayon_core/hosts/maya/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 2b0a4449c9..7e81ea6317 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2162,8 +2162,8 @@ def get_related_sets(node): sets = [s for s in sets if s not in deformer_sets] # Ignore when the set has a specific suffix - ignore_suffices = ["out_SET", "controls_SET", "_INST", "_CON"] - sets = [s for s in sets if not any(s.endswith(x) for x in ignore_suffices)] + ignore_suffices = ("out_SET", "controls_SET", "_INST", "_CON") + sets = [s for s in sets if not s.endswith(ignore_suffices)] # Default nodes to ignore defaults = {"defaultLightSet", "defaultObjectSet"} From f0ba7e62723a2fb0e502ae59d4ad6d52f7007b1b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 17:26:34 +0100 Subject: [PATCH 045/203] Optimize `get_all_children` logic with ignoring intermediate objects --- client/ayon_core/hosts/maya/api/lib.py | 20 ++++++++++++-- .../publish/collect_arnold_scene_source.py | 17 ++++++++---- .../maya/plugins/publish/collect_instances.py | 26 +++++++++---------- .../maya/tools/mayalookassigner/commands.py | 5 ++-- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index b18d3a0c33..5ad893de06 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -4009,17 +4009,26 @@ def len_flattened(components): return n -def get_all_children(nodes): +def get_all_children(nodes, ignore_intermediate_objects=False): """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. + Args: + nodes (iterable): List of nodes to get children for. + ignore_intermediate_objects (bool): Ignore any children that + are intermediate objects. + + Returns: + set: Children of input nodes. + """ sel = OpenMaya.MSelectionList() traversed = set() iterator = OpenMaya.MItDag(OpenMaya.MItDag.kDepthFirst) + fn_dag = OpenMaya.MFnDagNode() for node in nodes: if node in traversed: @@ -4036,6 +4045,13 @@ def get_all_children(nodes): iterator.next() # noqa: B305 while not iterator.isDone(): + if ignore_intermediate_objects: + fn_dag.setObject(iterator.currentItem()) + if fn_dag.isIntermediateObject: + iterator.prune() + iterator.next() # noqa: B305 + continue + path = iterator.fullPathName() if path in traversed: @@ -4046,7 +4062,7 @@ def get_all_children(nodes): traversed.add(path) iterator.next() # noqa: B305 - return list(traversed) + return traversed def get_capture_preset( diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 2d621353e6..dca4560334 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -46,11 +46,18 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): self.log.debug("data: {}".format(instance.data)) def get_hierarchy(self, nodes): - """Return nodes with all their children""" + """Return nodes with all their children: + + Arguments: + nodes (List[str]): List of nodes to collect children hierarchy for + + Returns: + list: Input nodes with their children hierarchy + + """ nodes = cmds.ls(nodes, long=True) if not nodes: return [] - children = get_all_children(nodes) - # Make sure nodes merged with children only - # contains unique entries - return list(set(nodes + children)) + + children = get_all_children(nodes, ignore_intermediate_objects=True) + return list(children.union(nodes)) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_instances.py b/client/ayon_core/hosts/maya/plugins/publish/collect_instances.py index 85be15bb7b..774c217cfd 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_instances.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_instances.py @@ -48,15 +48,15 @@ class CollectNewInstances(pyblish.api.InstancePlugin): # Collect members members = cmds.ls(members, long=True) or [] + # Collect full hierarchy dag_members = cmds.ls(members, type="dagNode", long=True) - children = get_all_children(dag_members) - children = cmds.ls(children, noIntermediate=True, long=True) - parents = ( - self.get_all_parents(members) - if creator_attributes.get("includeParentHierarchy", True) - else [] - ) - members_hierarchy = list(set(members + children + parents)) + children = get_all_children(dag_members, + ignore_intermediate_objects=True) + + members_hierarchy = set(members) + members_hierarchy.update(children) + if creator_attributes.get("includeParentHierarchy", True): + members_hierarchy.update(self.get_all_parents(dag_members)) instance[:] = members_hierarchy @@ -97,16 +97,16 @@ class CollectNewInstances(pyblish.api.InstancePlugin): """Get all parents by using string operations (optimization) Args: - nodes (list): the nodes which are found in the objectSet + nodes (iterable): the nodes which are found in the objectSet Returns: - list + set """ - parents = [] + parents = set() for node in nodes: splitted = node.split("|") items = ["|".join(splitted[0:i]) for i in range(2, len(splitted))] - parents.extend(items) + parents.update(items) - return list(set(parents)) + return parents diff --git a/client/ayon_core/hosts/maya/tools/mayalookassigner/commands.py b/client/ayon_core/hosts/maya/tools/mayalookassigner/commands.py index 75c82164c2..ad43a24385 100644 --- a/client/ayon_core/hosts/maya/tools/mayalookassigner/commands.py +++ b/client/ayon_core/hosts/maya/tools/mayalookassigner/commands.py @@ -49,8 +49,9 @@ def get_selected_nodes(): """Get information from current selection""" selection = cmds.ls(selection=True, long=True) - hierarchy = lib.get_all_children(selection) - return list(set(selection + hierarchy)) + hierarchy = lib.get_all_children(selection, + ignore_intermediate_objects=True) + return list(hierarchy.union(selection)) def get_all_asset_nodes(): From 32b325b5cce2a0d1280565eaf5a1847576112137 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 17:28:47 +0100 Subject: [PATCH 046/203] Cosmetics --- .../hosts/maya/plugins/publish/collect_arnold_scene_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py index dca4560334..0db89bee31 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -46,7 +46,7 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): self.log.debug("data: {}".format(instance.data)) def get_hierarchy(self, nodes): - """Return nodes with all their children: + """Return nodes with all their children. Arguments: nodes (List[str]): List of nodes to collect children hierarchy for From e73a4ef4bf11f65a20b1303ec0c2bb41535e102a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 20:16:35 +0100 Subject: [PATCH 047/203] Only collect file dependencies if enabled in settings and only collect if renderlayer family is present --- .../publish/collect_file_dependencies.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_file_dependencies.py b/client/ayon_core/hosts/maya/plugins/publish/collect_file_dependencies.py index 94fcc834e1..93b46c511b 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_file_dependencies.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_file_dependencies.py @@ -1,5 +1,3 @@ -import json - from maya import cmds import pyblish.api @@ -11,18 +9,24 @@ class CollectFileDependencies(pyblish.api.ContextPlugin): label = "Collect File Dependencies" order = pyblish.api.CollectorOrder - 0.49 hosts = ["maya"] + families = ["renderlayer"] + + @classmethod + def apply_settings(cls, project_settings, system_settings): + # Disable plug-in if not used for deadline submission anyway + settings = project_settings["deadline"]["publish"]["MayaSubmitDeadline"] # noqa + cls.enabled = settings.get("asset_dependencies", True) def process(self, context): - dependencies = [] + dependencies = set() for node in cmds.ls(type="file"): path = cmds.getAttr("{}.{}".format(node, "fileTextureName")) if path not in dependencies: - dependencies.append(path) + dependencies.add(path) for node in cmds.ls(type="AlembicNode"): path = cmds.getAttr("{}.{}".format(node, "abc_File")) if path not in dependencies: - dependencies.append(path) + dependencies.add(path) - context.data["fileDependencies"] = dependencies - self.log.debug(json.dumps(dependencies, indent=4)) + context.data["fileDependencies"] = list(dependencies) From 2bf1ca2a2df2fc5009cdd4d4d704e4a4c791b8d9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 2 Apr 2024 10:03:59 +0200 Subject: [PATCH 048/203] Validate only when redshift renderer is used --- ...validate_current_renderlayer_renderable.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py index da8468c1b6..045e22545c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py @@ -36,6 +36,12 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin, if not context_plugin_should_run(self, context): return + # This validator only makes sense when publishing renderlayer instances + # with Redshift. We skip validation if there isn't any. + if not any(self.is_active_redshift_render_instance(instance) + for instance in context): + return + cameras = cmds.ls(type="camera", long=True) renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable")) if not renderable: @@ -47,3 +53,22 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin, ), description=inspect.getdoc(self) ) + + @staticmethod + def is_active_redshift_render_instance(instance) -> bool: + """Return whether instance is an active renderlayer instance set to + render with Redshift renderer.""" + if not instance.data.get("active", True): + return False + + # Check this before families just because it's a faster check + if not instance.data.get("renderer") == "redshift": + return False + + families = set() + families.add(instance.data.get("family")) + families.update(instance.data.get("families", [])) + if "renderlayer" not in families: + return False + + return True From ec34bc463fe9e529c6affb8ba2bb95026cad3544 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 16:13:04 +0800 Subject: [PATCH 049/203] support to fix the task in validate context in maya --- .../publish/validate_instance_in_context.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py index c5a3b1659d..ec80eb6e06 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py @@ -38,17 +38,20 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, return folder_path = instance.data.get("folderPath") - context_folder_path = self.get_context_folder_path(instance) - if folder_path != context_folder_path: + task = instance.data.get("task") + context = self.get_context(instance) + if (folder_path, task) != context: + context_label = "{} > {}".format(*context) + instance_label = "{} > {}".format(folder_path, task) raise PublishValidationError( message=( - "Instance '{}' publishes to different folder than current" + "Instance '{}' publishes to different context than current" " context: {}. Current context: {}".format( - instance.name, folder_path, context_folder_path + instance.name, instance_label, context_label ) ), description=( - "## Publishing to a different folder\n" + "## Publishing to a different context data\n" "There are publish instances present which are publishing " "into a different folder than your current context.\n\n" "Usually this is not what you want but there can be cases " @@ -64,14 +67,22 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): - context_folder_path = cls.get_context_folder_path(instance) + context_folder_path, context_task = cls.get_context( + instance) instance_node = instance.data["instance_node"] cmds.setAttr( "{}.folderPath".format(instance_node), context_folder_path, type="string" ) + cmds.setAttr( + "{}.task".format(instance_node), + context_task, + type="string" + ) @staticmethod - def get_context_folder_path(instance): - return instance.context.data["folderPath"] + def get_context(instance): + """Return asset, task from publishing context data""" + context = instance.context + return context.data["folderPath"], context.data["task"] From 39d6ffa1c126f2492277591271318cde35778f59 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 16:37:35 +0800 Subject: [PATCH 050/203] update reset scene reoslution with ayon_api related function --- client/ayon_core/hosts/maya/api/lib.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index b18d3a0c33..23120f465f 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2640,13 +2640,14 @@ def reset_scene_resolution(): Returns: None """ - - folder_attributes = get_current_project_folder()["attrib"] + project_name = get_current_project_name() + project_entity = ayon_api.get_project(project_name) + project_attribs = project_entity["attrib"] # Set resolution - width = folder_attributes.get("resolutionWidth", 1920) - height = folder_attributes.get("resolutionHeight", 1080) - pixelAspect = folder_attributes.get("pixelAspect", 1) + width = project_attribs.get("resolutionWidth", 1920) + height = project_attribs.get("resolutionHeight", 1080) + pixelAspect = project_attribs.get("pixelAspect", 1) set_scene_resolution(width, height, pixelAspect) From f3324c3863598f1946525aef02cbd8abd5444637 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 2 Apr 2024 11:09:58 +0200 Subject: [PATCH 051/203] Perform one less list comprehension --- client/ayon_core/hosts/maya/api/lib.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 7e81ea6317..ef0e394cf0 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2155,20 +2155,20 @@ def get_related_sets(node): return sets # Exclude deformer sets (`type=2` for `maya.cmds.listSets`) - deformer_sets = cmds.listSets(object=node, - extendToShape=False, - type=2) or [] - deformer_sets = set(deformer_sets) # optimize lookup - sets = [s for s in sets if s not in deformer_sets] + exclude_sets = cmds.listSets(object=node, + extendToShape=False, + type=2) or [] + exclude_sets = set(exclude_sets) # optimize lookup + + # Default nodes to ignore + exclude_sets.update({"defaultLightSet", "defaultObjectSet"}) + + # Filter out the sets to exclude + sets = [s for s in sets if s not in exclude_sets] # Ignore when the set has a specific suffix ignore_suffices = ("out_SET", "controls_SET", "_INST", "_CON") sets = [s for s in sets if not s.endswith(ignore_suffices)] - - # Default nodes to ignore - defaults = {"defaultLightSet", "defaultObjectSet"} - sets = [s for s in sets if s not in defaults] - if not sets: return sets From 3d98c1ae0a3fafab4b47af27a659c4d5df13ea5a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 17:11:11 +0800 Subject: [PATCH 052/203] use taskEntity instead --- client/ayon_core/hosts/maya/api/lib.py | 21 +++++++++++++------ .../plugins/publish/validate_resolution.py | 14 ++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 23120f465f..4c25daffc7 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2640,18 +2640,27 @@ def reset_scene_resolution(): Returns: None """ - project_name = get_current_project_name() - project_entity = ayon_api.get_project(project_name) - project_attribs = project_entity["attrib"] + # Set frame start/end + project_name = get_current_project_name() + folder_path = get_current_folder_path() + task_name = get_current_task_name() + + folder_entity = ayon_api.get_folder_by_path( + project_name, folder_path, fields={"id"}) + task_entity = ayon_api.get_task_by_name( + project_name, folder_entity["id"], task_name + ) + task_attributes = task_entity["attrib"] # Set resolution - width = project_attribs.get("resolutionWidth", 1920) - height = project_attribs.get("resolutionHeight", 1080) - pixelAspect = project_attribs.get("pixelAspect", 1) + width = task_attributes.get("resolutionWidth", 1920) + height = task_attributes.get("resolutionHeight", 1080) + pixelAspect = task_attributes.get("pixelAspect", 1) set_scene_resolution(width, height, pixelAspect) + def set_context_settings( fps=True, resolution=True, diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_resolution.py b/client/ayon_core/hosts/maya/plugins/publish/validate_resolution.py index 1e5a9a944c..1398907f6c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_resolution.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_resolution.py @@ -84,15 +84,15 @@ class ValidateResolution(pyblish.api.InstancePlugin, @classmethod def get_folder_resolution(cls, instance): - folder_attributes = instance.data["folderEntity"]["attrib"] + task_attributes = instance.data["taskEntity"]["attrib"] if ( - "resolutionWidth" in folder_attributes - and "resolutionHeight" in folder_attributes - and "pixelAspect" in folder_attributes + "resolutionWidth" in task_attributes + and "resolutionHeight" in task_attributes + and "pixelAspect" in task_attributes ): - width = folder_attributes["resolutionWidth"] - height = folder_attributes["resolutionHeight"] - pixelAspect = folder_attributes["pixelAspect"] + width = task_attributes["resolutionWidth"] + height = task_attributes["resolutionHeight"] + pixelAspect = task_attributes["pixelAspect"] return int(width), int(height), float(pixelAspect) # Defaults if not found in asset document or project document From a18737523115d95c9f294e4a088d8f0d1c4df027 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 17:37:45 +0800 Subject: [PATCH 053/203] change to use task enntity for validators which are related to context setting --- client/ayon_core/hosts/max/api/lib.py | 61 +++++++++++++------ .../plugins/publish/validate_frame_range.py | 2 +- .../publish/validate_resolution_setting.py | 10 +-- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 5f13856c9b..595c2d8841 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -8,7 +8,12 @@ from typing import Any, Dict, Union import six import ayon_api -from ayon_core.pipeline import get_current_project_name, colorspace +from ayon_core.pipeline import ( + get_current_project_name, + get_current_folder_path, + get_current_task_name, + colorspace +) from ayon_core.settings import get_project_settings from ayon_core.pipeline.context_tools import ( get_current_project_folder, @@ -221,18 +226,15 @@ def reset_scene_resolution(): scene resolution can be overwritten by a folder if the folder.attrib contains any information regarding scene resolution. """ - - folder_entity = get_current_project_folder( - fields={"attrib.resolutionWidth", "attrib.resolutionHeight"} - ) - folder_attributes = folder_entity["attrib"] - width = int(folder_attributes["resolutionWidth"]) - height = int(folder_attributes["resolutionHeight"]) + task_entity = get_current_task_entity() + task_attributes = task_entity["attrib"] + width = int(task_attributes["resolutionWidth"]) + height = int(task_attributes["resolutionHeight"]) set_scene_resolution(width, height) -def get_frame_range(folder_entiy=None) -> Union[Dict[str, Any], None]: +def get_frame_range(task_entity=None) -> Union[Dict[str, Any], None]: """Get the current folder frame range and handles. Args: @@ -242,20 +244,19 @@ def get_frame_range(folder_entiy=None) -> Union[Dict[str, Any], None]: dict: with frame start, frame end, handle start, handle end. """ # Set frame start/end - if folder_entiy is None: - folder_entiy = get_current_project_folder() - - folder_attributes = folder_entiy["attrib"] - frame_start = folder_attributes.get("frameStart") - frame_end = folder_attributes.get("frameEnd") + if task_entity is None: + task_entity = get_current_task_entity() + task_attributes = task_entity["attrib"] + frame_start = task_attributes.get("frameStart") + frame_end = task_attributes.get("frameEnd") if frame_start is None or frame_end is None: return {} frame_start = int(frame_start) frame_end = int(frame_end) - handle_start = int(folder_attributes.get("handleStart", 0)) - handle_end = int(folder_attributes.get("handleEnd", 0)) + handle_start = int(task_attributes.get("handleStart", 0)) + handle_end = int(task_attributes.get("handleEnd", 0)) frame_start_handle = frame_start - handle_start frame_end_handle = frame_end + handle_end @@ -281,9 +282,9 @@ def reset_frame_range(fps: bool = True): scene frame rate in frames-per-second. """ if fps: - project_name = get_current_project_name() - project_entity = ayon_api.get_project(project_name) - fps_number = float(project_entity["attrib"].get("fps")) + task_entity = get_current_task_entity() + task_attributes = task_entity["attrib"] + fps_number = float(task_attributes.get("fps")) rt.frameRate = fps_number frame_range = get_frame_range() @@ -356,6 +357,26 @@ def get_max_version(): return max_info[7] +def get_current_task_entity(): + """Function to get current task entity data + + Returns: + dict: data of task entity + """ + project_name = get_current_project_name() + folder_path = get_current_folder_path() + task_name = get_current_task_name() + folder_entity = ayon_api.get_folder_by_path( + project_name, folder_path, fields={"id"}) + task_entity = ayon_api.get_task_by_name( + project_name, + folder_entity["id"], + task_name, + fields={"attrib"} + ) + return task_entity + + def is_headless(): """Check if 3dsMax runs in batch mode. If it returns True, it runs in 3dsbatch.exe diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_frame_range.py b/client/ayon_core/hosts/max/plugins/publish/validate_frame_range.py index 2f4ec5f86c..11b55232d5 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_frame_range.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_frame_range.py @@ -42,7 +42,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, return frame_range = get_frame_range( - instance.data["folderEntity"]) + instance.data["taskEntity"]) inst_frame_start = instance.data.get("frameStartHandle") inst_frame_end = instance.data.get("frameEndHandle") diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index f499f851f1..7f64a413cf 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -42,11 +42,11 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, "on asset or shot.") def get_folder_resolution(self, instance): - folder_entity = instance.data["folderEntity"] - if folder_entity: - folder_attributes = folder_entity["attrib"] - width = folder_attributes["resolutionWidth"] - height = folder_attributes["resolutionHeight"] + task_entity = instance.data.get("taskEntity") + if task_entity: + task_attributes = task_entity["attrib"] + width = task_attributes["resolutionWidth"] + height = task_attributes["resolutionHeight"] return int(width), int(height) # Defaults if not found in folder entity From 27e8d8d03c2789a69818aa9425df1d5e559f439b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 17:53:50 +0800 Subject: [PATCH 054/203] update the code with field with 'attrib' --- client/ayon_core/hosts/maya/api/lib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 4c25daffc7..6e15c54d12 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2649,8 +2649,11 @@ def reset_scene_resolution(): folder_entity = ayon_api.get_folder_by_path( project_name, folder_path, fields={"id"}) task_entity = ayon_api.get_task_by_name( - project_name, folder_entity["id"], task_name - ) + project_name, + folder_entity["id"], + task_name, + fields={"attrib"} + ) task_attributes = task_entity["attrib"] # Set resolution width = task_attributes.get("resolutionWidth", 1920) @@ -2660,7 +2663,6 @@ def reset_scene_resolution(): set_scene_resolution(width, height, pixelAspect) - def set_context_settings( fps=True, resolution=True, From 066262b6ad5d539930b869a76c2994894e7b83ce Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 18:02:13 +0800 Subject: [PATCH 055/203] code tweaks on the reset frame range --- client/ayon_core/hosts/max/api/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 595c2d8841..988f069dfa 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -247,16 +247,16 @@ def get_frame_range(task_entity=None) -> Union[Dict[str, Any], None]: if task_entity is None: task_entity = get_current_task_entity() task_attributes = task_entity["attrib"] - frame_start = task_attributes.get("frameStart") - frame_end = task_attributes.get("frameEnd") + frame_start = task_attributes["frameStart"] + frame_end = task_attributes["frameEnd"] if frame_start is None or frame_end is None: return {} frame_start = int(frame_start) frame_end = int(frame_end) - handle_start = int(task_attributes.get("handleStart", 0)) - handle_end = int(task_attributes.get("handleEnd", 0)) + handle_start = int(task_attributes["handleStart"]) + handle_end = int(task_attributes["handleEnd"]) frame_start_handle = frame_start - handle_start frame_end_handle = frame_end + handle_end From 30f1145c8b875e6a23b3b1c79beaacb53950d9e3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 18:14:12 +0800 Subject: [PATCH 056/203] clean up the code in regard to the change of getting task entity data --- client/ayon_core/hosts/max/api/lib.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 988f069dfa..42084f40f4 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -247,14 +247,8 @@ def get_frame_range(task_entity=None) -> Union[Dict[str, Any], None]: if task_entity is None: task_entity = get_current_task_entity() task_attributes = task_entity["attrib"] - frame_start = task_attributes["frameStart"] - frame_end = task_attributes["frameEnd"] - - if frame_start is None or frame_end is None: - return {} - - frame_start = int(frame_start) - frame_end = int(frame_end) + frame_start = int(task_attributes["frameStart"]) + frame_end = int(task_attributes["frameEnd"]) handle_start = int(task_attributes["handleStart"]) handle_end = int(task_attributes["handleEnd"]) frame_start_handle = frame_start - handle_start @@ -284,7 +278,7 @@ def reset_frame_range(fps: bool = True): if fps: task_entity = get_current_task_entity() task_attributes = task_entity["attrib"] - fps_number = float(task_attributes.get("fps")) + fps_number = float(task_attributes["fps"]) rt.frameRate = fps_number frame_range = get_frame_range() From bb91cc523e47a3aa1308149cfd2837b7d2061bf5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 18:16:01 +0800 Subject: [PATCH 057/203] update docstring for 'get_frame_range' --- client/ayon_core/hosts/max/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 42084f40f4..d8133f4db7 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -235,10 +235,10 @@ def reset_scene_resolution(): def get_frame_range(task_entity=None) -> Union[Dict[str, Any], None]: - """Get the current folder frame range and handles. + """Get the current task frame range and handles Args: - folder_entiy (dict): Folder eneity. + task_entity (dict): Task Entity. Returns: dict: with frame start, frame end, handle start, handle end. From 71bb5552f38ea72e1bab07f41281cc3e595070ae Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 12:30:33 +0200 Subject: [PATCH 058/203] BigRoy's comments - Better doc strings --- client/ayon_core/hosts/houdini/api/lib.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/api/lib.py b/client/ayon_core/hosts/houdini/api/lib.py index dfdca62b61..111b230252 100644 --- a/client/ayon_core/hosts/houdini/api/lib.py +++ b/client/ayon_core/hosts/houdini/api/lib.py @@ -1003,6 +1003,9 @@ def get_scene_viewer(): Return an instance of a visible viewport. There may be many, some could be closed, any visible are current + + Returns: + Optional[hou.SceneViewer]: A scene viewer, if any. """ panes = hou.ui.paneTabs() panes = [x for x in panes if x.type() == hou.paneTabType.SceneViewer] @@ -1045,7 +1048,9 @@ def sceneview_snapshot( Args: sceneview (hou.SceneViewer): The scene view pane from which you want to take a snapshot. - filepath (str): thumbnail filepath. + filepath (str): thumbnail filepath. it expects `$F4` token + when frame_end is bigger than frame_star other wise + each frame will override its predecessor. frame_start (int): the frame at which snapshot starts frame_end (int): the frame at which snapshot ends """ From f543e560011a6bae6346e9a611ec1cc102fbb690 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 12:36:22 +0200 Subject: [PATCH 059/203] BigRoy's comment - Better logic --- .../publish/extract_active_view_thumbnail.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py index 1be8cb7440..aedcb1da02 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_active_view_thumbnail.py @@ -27,22 +27,24 @@ class ExtractActiveViewThumbnail(publish.Extractor): return thumbnail = instance.data.get("thumbnailPath") - if not thumbnail: - view_thumbnail = self.get_view_thumbnail(instance) - if not view_thumbnail: - return + if thumbnail: + # A thumbnail was already set for this instance + return - self.log.debug("Setting instance thumbnail path to: {}".format( - view_thumbnail - )) - instance.data["thumbnailPath"] = view_thumbnail + view_thumbnail = self.get_view_thumbnail(instance) + if not view_thumbnail: + return + self.log.debug("Setting instance thumbnail path to: {}" + .format(view_thumbnail) + ) + instance.data["thumbnailPath"] = view_thumbnail def get_view_thumbnail(self, instance): sceneview = lib.get_scene_viewer() if sceneview is None: - self.log.warning("Skipping Extract Active View Thumbnail" - " because no scene view was detected.") + self.log.debug("Skipping Extract Active View Thumbnail" + " because no scene view was detected.") return with tempfile.NamedTemporaryFile("w", suffix=".jpg", delete=False) as tmp: From d68664b20b22dbf9051d78ec19ac4653e3f97b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 2 Apr 2024 14:21:12 +0200 Subject: [PATCH 060/203] :bug: fix compatibility with Qt6 --- client/ayon_core/hosts/maya/vendor/python/capture.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/vendor/python/capture.py b/client/ayon_core/hosts/maya/vendor/python/capture.py index 7a3be38f56..4ccfdb35f3 100644 --- a/client/ayon_core/hosts/maya/vendor/python/capture.py +++ b/client/ayon_core/hosts/maya/vendor/python/capture.py @@ -869,7 +869,11 @@ def _get_screen_size(): if _in_standalone(): return [0, 0] - rect = QtWidgets.QDesktopWidget().screenGeometry(-1) + try: + rect = QtWidgets.QDesktopWidget().screenGeometry(-1) + except AttributeError: + # in Qt6 it is a different call + rect = QtWidgets.QApplication.primaryScreen().availableGeometry() return [rect.width(), rect.height()] From 059191c8d47ac321f8804b55cabad9425968ddce Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 22:03:04 +0800 Subject: [PATCH 061/203] tweak on validation error message --- .../max/plugins/publish/validate_instance_in_context.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py index 963a601009..5107665235 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py @@ -38,15 +38,15 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, context_label = "{} > {}".format(*context) instance_label = "{} > {}".format(folderPath, task) message = ( - "Instance '{}' publishes to different context than current " - "context: {}. Current context: {}".format( + "Instance '{}' publishes to different context(folder or task) " + "than current context: {}. Current context: {}".format( instance.name, instance_label, context_label ) ) raise PublishValidationError( message=message, description=( - "## Publishing to a different context data\n" + "## Publishing to a different context data(folder or task)\n" "There are publish instances present which are publishing " "into a different folder path or task than your current context.\n\n" "Usually this is not what you want but there can be cases " From 4390318f208afdd6a13723275d688d3b8e5c388d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 22:04:55 +0800 Subject: [PATCH 062/203] tweak on validation error message --- .../max/plugins/publish/validate_instance_in_context.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py index 963a601009..5107665235 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py @@ -38,15 +38,15 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, context_label = "{} > {}".format(*context) instance_label = "{} > {}".format(folderPath, task) message = ( - "Instance '{}' publishes to different context than current " - "context: {}. Current context: {}".format( + "Instance '{}' publishes to different context(folder or task) " + "than current context: {}. Current context: {}".format( instance.name, instance_label, context_label ) ) raise PublishValidationError( message=message, description=( - "## Publishing to a different context data\n" + "## Publishing to a different context data(folder or task)\n" "There are publish instances present which are publishing " "into a different folder path or task than your current context.\n\n" "Usually this is not what you want but there can be cases " From 3c14b3e356e0c2cb95e44a1618849564666a310b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 2 Apr 2024 17:36:21 +0200 Subject: [PATCH 063/203] live update of applications and tools attribute --- server_addon/applications/server/__init__.py | 167 ++++++++++++++----- 1 file changed, 128 insertions(+), 39 deletions(-) diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index d5c2de3df3..a199325475 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -118,9 +118,28 @@ class ApplicationsAddon(BaseServerAddon): ) async def setup(self): - need_restart = await self.create_applications_attribute() + need_restart = await self.create_required_attributes() if need_restart: self.request_server_restart() + await self.update_enums() + + def _get_applications_def(self): + return { + "name": "applications", + "type": "list_of_strings", + "title": "Applications", + "scope": ["project"], + "enum":[], + } + + def _get_tools_def(self): + return { + "name": "tools", + "type": "list_of_strings", + "title": "Tools", + "scope": ["project", "folder", "task"], + "enum":[], + } async def create_applications_attribute(self) -> bool: """Make sure there are required attributes which ftrack addon needs. @@ -129,6 +148,85 @@ class ApplicationsAddon(BaseServerAddon): bool: 'True' if an attribute was created or updated. """ + need_restart = await self.create_required_attributes() + await self.update_enums() + return need_restart + + async def create_required_attributes(self) -> bool: + """Make sure there are required 'applications' and 'tools' attributes. + This only checks for the existence of the attributes, it does not populate + them with any data. When an attribute is added, server needs to be restarted, + while adding enum data to the attribute does not require a restart. + Returns: + bool: 'True' if an attribute was created or updated. + """ + + # keep track of the last attribute position (for adding new attributes) + apps_present = False + tools_present = False + apps_attribute_data = self._get_applications_def() + apps_attrib_name = apps_attribute_data["name"] + + tools_attribute_data = self._get_tools_def() + tools_attrib_name = tools_attribute_data["name"] + + last_index = -1 + async for row in Postgres.iterate( + "SELECT name, position FROM attributes ORDER BY position" + ): + # check if the required attributes are present + # (in that case, we don't need to add them) + # also keep track of the last attribute position + if row["name"] == apps_attrib_name: + apps_present = True + elif row["name"] == tools_attrib_name: + tools_present = True + last_index = row["position"] + + attributes_to_create = {} + if not apps_present: + attributes_to_create[apps_attrib_name] = { + "scope": apps_attribute_data["scope"], + "data": { + "title": apps_attribute_data["title"], + "type": apps_attribute_data["type"], + "enum": [], + } + } + + if not tools_present: + attributes_to_create[tools_attrib_name] = { + "scope": tools_attribute_data["scope"], + "data": { + "title": tools_attribute_data["title"], + "type": tools_attribute_data["type"], + "enum": [], + }, + } + + # when any of the required attributes are not present, add them + # and return 'True' to indicate that server needs to be restarted + needs_restart = False + for name, payload in attributes_to_create.items(): + await Postgres.execute( + "INSERT INTO attributes (name, position, scope, data) VALUES ($1, $2, $3, $4)", + name, + last_index + 1, + payload["scope"], + payload["data"], + ) + last_index += 1 + needs_restart = True + + return needs_restart + + async def update_enums(self): + """Updates applications and tools enums based on the addon settings. + This method is called when the addon is started (after we are sure that the + 'applications' and 'tools' attributes exist) and when the addon settings are + updated (using on_settings_updated method). + """ + instance = AddonLibrary.getinstance() app_defs = instance.data.get(self.name) all_applications = [] @@ -148,33 +246,32 @@ class ApplicationsAddon(BaseServerAddon): merge_groups(all_applications, app_groups) merge_groups(all_tools, studio_settings["tool_groups"]) - query = "SELECT name, position, scope, data from public.attributes" - apps_attrib_name = "applications" tools_attrib_name = "tools" apps_enum = get_enum_items_from_groups(all_applications) tools_enum = get_enum_items_from_groups(all_tools) + apps_attribute_data = { "type": "list_of_strings", "title": "Applications", - "enum": apps_enum + "enum": apps_enum, } tools_attribute_data = { "type": "list_of_strings", "title": "Tools", - "enum": tools_enum + "enum": tools_enum, } + apps_scope = ["project"] tools_scope = ["project", "folder", "task"] - apps_match_position = None apps_matches = False - tools_match_position = None tools_matches = False - position = 1 - async for row in Postgres.iterate(query): - position += 1 + + async for row in Postgres.iterate( + "SELECT name, position, scope, data from public.attributes" + ): if row["name"] == apps_attrib_name: # Check if scope is matching ftrack addon requirements if ( @@ -182,7 +279,6 @@ class ApplicationsAddon(BaseServerAddon): and row["data"].get("enum") == apps_enum ): apps_matches = True - apps_match_position = row["position"] elif row["name"] == tools_attrib_name: if ( @@ -190,45 +286,38 @@ class ApplicationsAddon(BaseServerAddon): and row["data"].get("enum") == tools_enum ): tools_matches = True - tools_match_position = row["position"] if apps_matches and tools_matches: - return False + return - postgre_query = "\n".join(( - "INSERT INTO public.attributes", - " (name, position, scope, data)", - "VALUES", - " ($1, $2, $3, $4)", - "ON CONFLICT (name)", - "DO UPDATE SET", - " scope = $3,", - " data = $4", - )) if not apps_matches: - # Reuse position from found attribute - if apps_match_position is None: - apps_match_position = position - position += 1 - await Postgres.execute( - postgre_query, - apps_attrib_name, - apps_match_position, + """ + UPDATE attributes SET + scope = $1, + data = $2 + WHERE + name = $3 + """, apps_scope, apps_attribute_data, + apps_attrib_name, ) if not tools_matches: - if tools_match_position is None: - tools_match_position = position - position += 1 - await Postgres.execute( - postgre_query, - tools_attrib_name, - tools_match_position, + """ + UPDATE attributes SET + scope = $1, + data = $2 + WHERE + name = $3 + """, tools_scope, tools_attribute_data, + tools_attrib_name, ) - return True + + async def on_settings_changed(self, *args, **kwargs): + _ = args, kwargs + await self.update_enums() \ No newline at end of file From 769bcbaf2e39b4a67d8f6504e8860b53d46b93df Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 2 Apr 2024 17:36:43 +0200 Subject: [PATCH 064/203] bump version to 0.1.9 --- server_addon/applications/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/server/version.py b/server_addon/applications/server/version.py index 9cb17e7976..c11f861afb 100644 --- a/server_addon/applications/server/version.py +++ b/server_addon/applications/server/version.py @@ -1 +1 @@ -__version__ = "0.1.8" +__version__ = "0.1.9" From 98bed9f5c610deb2f947d643573ce18bd85054da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 2 Apr 2024 18:10:03 +0200 Subject: [PATCH 065/203] Update to latest avalon core --- client/ayon_core/hosts/fusion/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/api/plugin.py b/client/ayon_core/hosts/fusion/api/plugin.py index c2ecea93fc..80167189a5 100644 --- a/client/ayon_core/hosts/fusion/api/plugin.py +++ b/client/ayon_core/hosts/fusion/api/plugin.py @@ -126,7 +126,7 @@ class GenericCreateSaver(Creator): if ( original_product_name != product_name or tool.GetData("openpype.task") != data["task"] - or tool.GetData("openpype.asset") != data["asset"] + or tool.GetData("openpype.folderPath") != data["folderPath"] or original_format != data["creator_attributes"]["image_format"] ): self._configure_saver_tool(data, tool, product_name) From 7fde8ac0bcad96fc273e309bf2f90d7f938d9e1f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 3 Apr 2024 17:23:33 +0800 Subject: [PATCH 066/203] optional validator for resolution settings in review product type --- .../hosts/max/plugins/publish/validate_resolution_setting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index 7f64a413cf..48e2e1d45d 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -15,7 +15,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, """Validate the resolution setting aligned with DB""" order = pyblish.api.ValidatorOrder - 0.01 - families = ["maxrender"] + families = ["maxrender", "review"] hosts = ["max"] label = "Validate Resolution Setting" optional = True From dd77f243ddd2eaf3ed26ca8ec66d50af6e914451 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 11:42:10 +0200 Subject: [PATCH 067/203] Allow specifying raw JSON custom tools menu for Maya. --- client/ayon_core/hosts/maya/api/menu.py | 12 +++++- .../maya/server/settings/scriptsmenu.py | 41 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/menu.py b/client/ayon_core/hosts/maya/api/menu.py index 0cb7edd40d..bc01b8b42c 100644 --- a/client/ayon_core/hosts/maya/api/menu.py +++ b/client/ayon_core/hosts/maya/api/menu.py @@ -1,4 +1,5 @@ import os +import json import logging from functools import partial @@ -214,9 +215,18 @@ def install(project_settings): ) return - config = project_settings["maya"]["scriptsmenu"]["definition"] _menu = project_settings["maya"]["scriptsmenu"]["name"] + config = project_settings["maya"]["scriptsmenu"]["definition"] + if project_settings["maya"]["scriptsmenu"].get("use_json_definition"): + data = project_settings["maya"]["scriptsmenu"]["definition_json"] + try: + config = json.loads(data) + except json.JSONDecodeError as exc: + print("Skipping studio menu, error decoding JSON definition.") + log.error(exc) + return + if not config: log.warning("Skipping studio menu, no definition found.") return diff --git a/server_addon/maya/server/settings/scriptsmenu.py b/server_addon/maya/server/settings/scriptsmenu.py index d01dff1621..fe80d816df 100644 --- a/server_addon/maya/server/settings/scriptsmenu.py +++ b/server_addon/maya/server/settings/scriptsmenu.py @@ -1,3 +1,7 @@ +import json + +from pydantic import validator +from ayon_server.exceptions import BadRequestException from ayon_server.settings import BaseSettingsModel, SettingsField @@ -15,18 +19,52 @@ class ScriptsmenuSubmodel(BaseSettingsModel): class ScriptsmenuModel(BaseSettingsModel): + """Add a custom scripts menu to Maya""" _isGroup = True name: str = SettingsField(title="Menu Name") + use_json_definition: bool = SettingsField( + title="Use Raw JSON Definition", + description="When enabled, the definition field will be ignored. " + "Instead the menu will be build from the raw JSON " + "definition below it." + ) definition: list[ScriptsmenuSubmodel] = SettingsField( default_factory=list, title="Menu Definition", description="Scriptmenu Items Definition" ) + definition_json: str = SettingsField( + "[]", title="Definition JSON", widget="textarea", + description=( + "When Use Raw JSON definition is enabled this field will be used " + "to put the direct JSON content instead of using the definition " + "menu builder UI above. For more details on the JSON format, see " + "[here](https://github.com/Colorbleed/scriptsmenu?tab=readme-ov-file#configuration)." # noqa: E501 + ) + ) + + @validator("definition_json") + def validate_json(cls, value): + if not value.strip(): + return "[]" + try: + converted_value = json.loads(value) + success = isinstance(converted_value, list) + except json.JSONDecodeError: + success = False + + if not success: + raise BadRequestException( + "The definition can't be parsed as json list object" + ) + return value + DEFAULT_SCRIPTSMENU_SETTINGS = { "name": "Custom Tools", + "use_json_definition": False, "definition": [ { "type": "action", @@ -39,5 +77,6 @@ DEFAULT_SCRIPTSMENU_SETTINGS = { "shader" ] } - ] + ], + "definition_raw": "" } From 72aeb63ef974cfc80bd1ff0d22b1f51448336939 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 14:12:13 +0200 Subject: [PATCH 068/203] reset cache of attributes on server when enums are updated --- server_addon/applications/server/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index a199325475..07e6c46cc3 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -3,6 +3,7 @@ import json import copy from ayon_server.addons import BaseServerAddon, AddonLibrary +from ayon_server.entities.core import attribute_library from ayon_server.lib.postgres import Postgres from .version import __version__ @@ -318,6 +319,9 @@ class ApplicationsAddon(BaseServerAddon): tools_attrib_name, ) + # Reset attributes cache on server + attribute_library.load() + async def on_settings_changed(self, *args, **kwargs): _ = args, kwargs await self.update_enums() \ No newline at end of file From 16bc2e601dd448a836e4289aed5e107e76803512 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 14:12:28 +0200 Subject: [PATCH 069/203] make 'update_enums' private method '_update_enums' --- server_addon/applications/server/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index 07e6c46cc3..30181285fb 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -122,7 +122,7 @@ class ApplicationsAddon(BaseServerAddon): need_restart = await self.create_required_attributes() if need_restart: self.request_server_restart() - await self.update_enums() + await self._update_enums() def _get_applications_def(self): return { @@ -150,7 +150,7 @@ class ApplicationsAddon(BaseServerAddon): """ need_restart = await self.create_required_attributes() - await self.update_enums() + await self._update_enums() return need_restart async def create_required_attributes(self) -> bool: @@ -221,7 +221,7 @@ class ApplicationsAddon(BaseServerAddon): return needs_restart - async def update_enums(self): + async def _update_enums(self): """Updates applications and tools enums based on the addon settings. This method is called when the addon is started (after we are sure that the 'applications' and 'tools' attributes exist) and when the addon settings are @@ -324,4 +324,4 @@ class ApplicationsAddon(BaseServerAddon): async def on_settings_changed(self, *args, **kwargs): _ = args, kwargs - await self.update_enums() \ No newline at end of file + await self._update_enums() From 5a4f374e921ef05e91abfb0eedfeb3fdae920b7a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 15:46:17 +0200 Subject: [PATCH 070/203] better sql queries --- server_addon/applications/server/__init__.py | 84 +++++++++----------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index 30181285fb..2668589cbe 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -163,61 +163,49 @@ class ApplicationsAddon(BaseServerAddon): """ # keep track of the last attribute position (for adding new attributes) - apps_present = False - tools_present = False apps_attribute_data = self._get_applications_def() - apps_attrib_name = apps_attribute_data["name"] - tools_attribute_data = self._get_tools_def() + + apps_attrib_name = apps_attribute_data["name"] tools_attrib_name = tools_attribute_data["name"] - last_index = -1 - async for row in Postgres.iterate( - "SELECT name, position FROM attributes ORDER BY position" - ): - # check if the required attributes are present - # (in that case, we don't need to add them) - # also keep track of the last attribute position - if row["name"] == apps_attrib_name: - apps_present = True - elif row["name"] == tools_attrib_name: - tools_present = True - last_index = row["position"] + async with Postgres.acquire() as conn, conn.transaction(): + query = "SELECT BOOL_OR(name = 'applications') AS has_applications, BOOL_OR(name = 'tools') AS has_tools FROM attributes;" + result = (await conn.fetch(query))[0] - attributes_to_create = {} - if not apps_present: - attributes_to_create[apps_attrib_name] = { - "scope": apps_attribute_data["scope"], - "data": { - "title": apps_attribute_data["title"], - "type": apps_attribute_data["type"], - "enum": [], + attributes_to_create = {} + if not result["has_applications"]: + attributes_to_create[apps_attrib_name] = { + "scope": apps_attribute_data["scope"], + "data": { + "title": apps_attribute_data["title"], + "type": apps_attribute_data["type"], + "enum": [], + } } - } - if not tools_present: - attributes_to_create[tools_attrib_name] = { - "scope": tools_attribute_data["scope"], - "data": { - "title": tools_attribute_data["title"], - "type": tools_attribute_data["type"], - "enum": [], - }, - } + if not result["has_tools"]: + attributes_to_create[tools_attrib_name] = { + "scope": tools_attribute_data["scope"], + "data": { + "title": tools_attribute_data["title"], + "type": tools_attribute_data["type"], + "enum": [], + }, + } - # when any of the required attributes are not present, add them - # and return 'True' to indicate that server needs to be restarted - needs_restart = False - for name, payload in attributes_to_create.items(): - await Postgres.execute( - "INSERT INTO attributes (name, position, scope, data) VALUES ($1, $2, $3, $4)", - name, - last_index + 1, - payload["scope"], - payload["data"], - ) - last_index += 1 - needs_restart = True + needs_restart = False + # when any of the required attributes are not present, add them + # and return 'True' to indicate that server needs to be restarted + for name, payload in attributes_to_create.items(): + insert_query = "INSERT INTO attributes (name, scope, data, position) VALUES ($1, $2, $3, (SELECT COALESCE(MAX(position), 0) + 1 FROM attributes)) ON CONFLICT DO NOTHING" + await conn.execute( + insert_query, + name, + payload["scope"], + payload["data"], + ) + needs_restart = True return needs_restart @@ -320,7 +308,7 @@ class ApplicationsAddon(BaseServerAddon): ) # Reset attributes cache on server - attribute_library.load() + await attribute_library.load() async def on_settings_changed(self, *args, **kwargs): _ = args, kwargs From 6109803c1d48a0a302e158e81724aff96821f070 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:08:09 +0200 Subject: [PATCH 071/203] Update instances using `CreateContext` to correctly update product names if needed - Also rely on task attributes for resolution and frame ranges instead of folder attributes --- client/ayon_core/hosts/maya/api/lib.py | 71 +++++++++++++++----------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 8d418813cd..52ac67f3e2 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -37,7 +37,7 @@ from ayon_core.pipeline import ( AYON_CONTAINER_ID, ) from ayon_core.lib import NumberDef -from ayon_core.pipeline.context_tools import get_current_folder_entity +from ayon_core.pipeline.context_tools import get_current_task_entity from ayon_core.pipeline.create import CreateContext from ayon_core.lib.profiles_filtering import filter_profiles @@ -2636,7 +2636,7 @@ def reset_scene_resolution(): None """ - folder_attributes = get_current_folder_entity()["attrib"] + folder_attributes = get_current_task_entity(fields={"attrib"})["attrib"] # Set resolution width = folder_attributes.get("resolutionWidth", 1920) @@ -3244,33 +3244,46 @@ def update_content_on_context_change(): """ This will update scene content to match new folder on context change """ - scene_sets = cmds.listSets(allSets=True) - folder_entity = get_current_folder_entity() - folder_attributes = folder_entity["attrib"] - new_folder_path = folder_entity["path"] - for s in scene_sets: - try: - if cmds.getAttr("{}.id".format(s)) in { - AYON_INSTANCE_ID, AVALON_INSTANCE_ID - }: - attr = cmds.listAttr(s) - print(s) - if "folderPath" in attr: - print( - " - setting folder to: [ {} ]".format(new_folder_path) - ) - cmds.setAttr( - "{}.folderPath".format(s), - new_folder_path, type="string" - ) - if "frameStart" in attr: - cmds.setAttr("{}.frameStart".format(s), - folder_attributes["frameStart"]) - if "frameEnd" in attr: - cmds.setAttr("{}.frameEnd".format(s), - folder_attributes["frameEnd"],) - except ValueError: - pass + + host = registered_host() + create_context = CreateContext(host) + folder_entity = get_current_task_entity(fields={"attrib"}) + + instance_values = { + "folderPath": create_context.get_current_folder_path(), + "task": create_context.get_current_task_name(), + } + creator_attribute_values = { + "frameStart": folder_entity["attrib"]["frameStart"], + "frameEnd": folder_entity["attrib"]["frameEnd"], + } + + has_changes = False + for instance in create_context.instances: + for key, value in instance_values.items(): + if key not in instance or instance[key] == value: + continue + + # Update instance value + print(f"Updating {instance.product_name} {key} to: {value}") + instance[key] = value + has_changes = True + + creator_attributes = instance.creator_attributes + for key, value in creator_attribute_values.items(): + if ( + key not in creator_attributes + or creator_attributes[key] == value + ): + continue + + # Update instance creator attribute value + print(f"Updating {instance.product_name} {key} to: {value}") + instance[key] = value + has_changes = True + + if has_changes: + create_context.save_changes() def show_message(title, msg): From 5acf6f3b08fc3423c4af9a7bea4fc442a50b80a8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:12:51 +0200 Subject: [PATCH 072/203] Update variable name --- client/ayon_core/hosts/maya/api/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 52ac67f3e2..8bf753693f 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2636,12 +2636,12 @@ def reset_scene_resolution(): None """ - folder_attributes = get_current_task_entity(fields={"attrib"})["attrib"] + task_attributes = get_current_task_entity(fields={"attrib"})["attrib"] # Set resolution - width = folder_attributes.get("resolutionWidth", 1920) - height = folder_attributes.get("resolutionHeight", 1080) - pixelAspect = folder_attributes.get("pixelAspect", 1) + width = task_attributes.get("resolutionWidth", 1920) + height = task_attributes.get("resolutionHeight", 1080) + pixelAspect = task_attributes.get("pixelAspect", 1) set_scene_resolution(width, height, pixelAspect) From ef0cc38d7f7b3cb959444673c9f28d36ce861338 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:13:02 +0200 Subject: [PATCH 073/203] Cosmetics --- client/ayon_core/hosts/maya/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 8bf753693f..07ae65748e 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2641,9 +2641,9 @@ def reset_scene_resolution(): # Set resolution width = task_attributes.get("resolutionWidth", 1920) height = task_attributes.get("resolutionHeight", 1080) - pixelAspect = task_attributes.get("pixelAspect", 1) + pixel_aspect = task_attributes.get("pixelAspect", 1) - set_scene_resolution(width, height, pixelAspect) + set_scene_resolution(width, height, pixel_aspect) def set_context_settings( From c48364cd0546e50d8497a3fc6e163f13d25931a7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:13:32 +0200 Subject: [PATCH 074/203] Update docstring --- client/ayon_core/hosts/maya/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 07ae65748e..72f680943f 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -2629,8 +2629,8 @@ def reset_frame_range(playback=True, render=True, fps=True): def reset_scene_resolution(): """Apply the scene resolution from the project definition - scene resolution can be overwritten by an folder if the folder.attrib - contains any information regarding scene resolution . + The scene resolution will be retrieved from the current task entity's + attributes. Returns: None From 2803b8521f701291547325053df8de9ff987f8e0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:29:52 +0200 Subject: [PATCH 075/203] Remove enforced updating of context settings (this is replaced by the "update on context change" dialog) --- client/ayon_core/hosts/maya/api/pipeline.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index 8e6e2ccd8a..eb46088ecd 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -653,10 +653,6 @@ def on_task_changed(): "Can't set project for new context because path does not exist: {}" ).format(workdir)) - with lib.suspended_refresh(): - lib.set_context_settings() - lib.update_content_on_context_change() - global _about_to_save if not lib.IS_HEADLESS and _about_to_save: # Let's prompt the user to update the context settings or not From 44e89240f199478ce7dc7e2404220e2e1b7d46d1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 17:05:17 +0200 Subject: [PATCH 076/203] Maya: Expose loaders enabled state to settings --- .../hosts/maya/plugins/load/actions.py | 5 + server_addon/maya/server/settings/loaders.py | 111 +++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/load/actions.py b/client/ayon_core/hosts/maya/plugins/load/actions.py index a98fe97692..982f007251 100644 --- a/client/ayon_core/hosts/maya/plugins/load/actions.py +++ b/client/ayon_core/hosts/maya/plugins/load/actions.py @@ -125,6 +125,11 @@ class ImportMayaLoader(ayon_core.hosts.maya.api.plugin.Loader): ) ] + @classmethod + def apply_settings(cls, project_settings): + super(ImportMayaLoader, cls).apply_settings(project_settings) + cls.enabled = cls.load_settings["import_loader"].get("enabled", True) + def load(self, context, name=None, namespace=None, data=None): import maya.cmds as cmds diff --git a/server_addon/maya/server/settings/loaders.py b/server_addon/maya/server/settings/loaders.py index 418a7046ae..4e949f616a 100644 --- a/server_addon/maya/server/settings/loaders.py +++ b/server_addon/maya/server/settings/loaders.py @@ -2,6 +2,10 @@ from ayon_server.settings import BaseSettingsModel, SettingsField from ayon_server.types import ColorRGB_float, ColorRGBA_uint8 +class LoaderEnabledModel(BaseSettingsModel): + enabled: bool = SettingsField(title="Enabled") + + class ColorsSetting(BaseSettingsModel): model: ColorRGBA_uint8 = SettingsField( (209, 132, 30, 1.0), title="Model:") @@ -94,6 +98,7 @@ class ReferenceLoaderModel(BaseSettingsModel): class ImportLoaderModel(BaseSettingsModel): + enabled: bool = SettingsField(title="Enabled") namespace: str = SettingsField(title="Namespace") group_name: str = SettingsField(title="Group name") @@ -113,6 +118,89 @@ class LoadersModel(BaseSettingsModel): title="Import Loader" ) + # Enable/disable loaders + ArnoldStandinLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Arnold Standin Loader" + ) + AssemblyLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Assembly Loader" + ) + AudioLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Audio Loader" + ) + GpuCacheLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="GPU Cache Loader" + ) + FileNodeLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="File Node (Image) Loader" + ) + ImagePlaneLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Image Plane Loader" + ) + LookLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Look Loader" + ) + MatchmoveLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Matchmove Loader" + ) + MultiverseUsdLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Multiverse USD Loader" + ) + MultiverseUsdOverLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Multiverse USD Override Loader" + ) + RedshiftProxyLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Redshift Proxy Loader" + ) + RenderSetupLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Render Setup Loader" + ) + LoadVDBtoArnold: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="VDB to Arnold Loader" + ) + LoadVDBtoRedShift: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="VDB to Redshift Loader" + ) + LoadVDBtoVRay: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="VDB to V-Ray Loader" + ) + VRayProxyLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Vray Proxy Loader" + ) + VRaySceneLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="VrayScene Loader" + ) + XgenLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Xgen Loader" + ) + YetiCacheLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Yeti Cache Loader" + ) + YetiRigLoader: LoaderEnabledModel = SettingsField( + default_factory=LoaderEnabledModel, + title="Yeti Rig Loader" + ) + + DEFAULT_LOADERS_SETTING = { "colors": { "model": [209, 132, 30, 1.0], @@ -154,8 +242,29 @@ DEFAULT_LOADERS_SETTING = { "display_handle": True }, "import_loader": { + "enabled": True, "namespace": "{folder[name]}_{product[name]}_##_", "group_name": "_GRP", "display_handle": True - } + }, + "ArnoldStandinLoader": {"enabled": True}, + "AssemblyLoader": {"enabled": True}, + "AudioLoader": {"enabled": True}, + "FileNodeLoader": {"enabled": True}, + "GpuCacheLoader": {"enabled": True}, + "ImagePlaneLoader": {"enabled": True}, + "LookLoader": {"enabled": True}, + "MatchmoveLoader": {"enabled": True}, + "MultiverseUsdLoader": {"enabled": True}, + "MultiverseUsdOverLoader": {"enabled": True}, + "RedshiftProxyLoader": {"enabled": True}, + "RenderSetupLoader": {"enabled": True}, + "LoadVDBtoArnold": {"enabled": True}, + "LoadVDBtoRedShift": {"enabled": True}, + "LoadVDBtoVRay": {"enabled": True}, + "VRayProxyLoader": {"enabled": True}, + "VRaySceneLoader": {"enabled": True}, + "XgenLoader": {"enabled": True}, + "YetiCacheLoader": {"enabled": True}, + "YetiRigLoader": {"enabled": True}, } From fdd3da9b0093fe94e23dca690d85659618b09919 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 17:08:19 +0200 Subject: [PATCH 077/203] Bump maya server addon version --- server_addon/maya/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index 1a4f79a972..e332ce4b6e 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.13" +__version__ = "0.1.14" From 0f77c170cf64dd9cbaa1e16a806c5055acae5ea9 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Wed, 3 Apr 2024 23:43:11 +0800 Subject: [PATCH 078/203] Update client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py --- .../hosts/max/plugins/publish/validate_instance_in_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py index 5107665235..001c8d35be 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py @@ -46,7 +46,7 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, raise PublishValidationError( message=message, description=( - "## Publishing to a different context data(folder or task)\n" + "## Publishing to a different context folder or task\n" "There are publish instances present which are publishing " "into a different folder path or task than your current context.\n\n" "Usually this is not what you want but there can be cases " From 8ebb52603aa6dcf109a6fad31bc6cf7648afba10 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Wed, 3 Apr 2024 23:43:19 +0800 Subject: [PATCH 079/203] Update client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py Co-authored-by: Roy Nieterau --- .../hosts/max/plugins/publish/validate_instance_in_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py index 001c8d35be..cecfd5fd12 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_instance_in_context.py @@ -38,7 +38,7 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, context_label = "{} > {}".format(*context) instance_label = "{} > {}".format(folderPath, task) message = ( - "Instance '{}' publishes to different context(folder or task) " + "Instance '{}' publishes to different folder or task " "than current context: {}. Current context: {}".format( instance.name, instance_label, context_label ) From 42e7e32264e034fc35af8b4c75e4b1bd8c12634d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 19:47:38 +0200 Subject: [PATCH 080/203] Cache the result of the folder ids query so we don't query per instance --- .../publish/validate_node_ids_in_database.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 5ca9690fd7..85850d413b 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -50,12 +50,7 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): nodes=instance[:]) # check ids against database ids - project_name = instance.context.data["projectName"] - folder_entities = ayon_api.get_folders(project_name, fields={"id"}) - folder_ids = { - folder_entity["id"] - for folder_entity in folder_entities - } + folder_ids = cls.get_project_folder_ids(context=instance.context) # Get all asset IDs for node in id_required_nodes: @@ -71,3 +66,22 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): invalid.append(node) return invalid + + @classmethod + def get_project_folder_ids(cls, context): + # We query the database only for the first instance instead of + # per instance by storing a cache in the context + key = "__cache_project_folders_ids" + if key in context.data: + return context.data[key] + + # check ids against database + project_name = context.data["projectName"] + folder_entities = ayon_api.get_folders(project_name, fields={"id"}) + folder_ids = { + folder_entity["id"] + for folder_entity in folder_entities + } + + context.data[key] = folder_ids + return folder_ids From 8daf9d1d39b4779f82b97c2f5e7b12791642f820 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 19:50:02 +0200 Subject: [PATCH 081/203] Opt-out early if there's nothing to validate anyway --- .../maya/plugins/publish/validate_node_ids_in_database.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 85850d413b..a26a4b6843 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -43,16 +43,17 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - invalid = [] - # Get all id required nodes id_required_nodes = lib.get_id_required_nodes(referenced_nodes=True, nodes=instance[:]) + if not id_required_nodes: + return [] # check ids against database ids folder_ids = cls.get_project_folder_ids(context=instance.context) # Get all asset IDs + invalid = [] for node in id_required_nodes: cb_id = lib.get_id(node) From ed99521224f1278974d556b9fbb7a253848f2215 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 19:50:09 +0200 Subject: [PATCH 082/203] Add docstring --- .../plugins/publish/validate_node_ids_in_database.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py index a26a4b6843..814202c66c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -70,6 +70,15 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): @classmethod def get_project_folder_ids(cls, context): + """Return all folder ids in the current project. + + Arguments: + context (pyblish.api.Context): The publish context. + + Returns: + set[str]: All folder ids in the current project. + + """ # We query the database only for the first instance instead of # per instance by storing a cache in the context key = "__cache_project_folders_ids" From 322615906d7115ca1962f97f9deb9ac96c381ed1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 19:50:31 +0200 Subject: [PATCH 083/203] Cosmetics --- .../maya/plugins/publish/validate_node_ids_in_database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 814202c66c..94f3aad8aa 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -37,8 +37,8 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( - ("Found folder ids which are not related to " - "current project in instance: `{}`").format(instance.name)) + "Found folder ids which are not related to " + "current project in instance: `{}`".format(instance.name)) @classmethod def get_invalid(cls, instance): From a5c19963d5f54ac0007b60e531c67eb534cc7d5a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 19:56:12 +0200 Subject: [PATCH 084/203] Improve key --- .../hosts/maya/plugins/publish/validate_node_ids_in_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 94f3aad8aa..8e29edc0ce 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -81,7 +81,7 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): """ # We query the database only for the first instance instead of # per instance by storing a cache in the context - key = "__cache_project_folders_ids" + key = "__cache_project_folder_ids" if key in context.data: return context.data[key] From eb0e1c9cf7b1f40a1642a63344dd8354632c21ea Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 19:59:23 +0200 Subject: [PATCH 085/203] Opt-out even earlier if instance has no members/nodes. --- .../maya/plugins/publish/validate_node_ids_in_database.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 8e29edc0ce..bb6b590f5c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -43,9 +43,13 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): + nodes = instance[:] + if not nodes: + return + # Get all id required nodes id_required_nodes = lib.get_id_required_nodes(referenced_nodes=True, - nodes=instance[:]) + nodes=nodes) if not id_required_nodes: return [] From daa07a8a7495ac7c1c7961a6100b9fad35d6909d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 20:09:59 +0200 Subject: [PATCH 086/203] Improve report --- .../maya/plugins/publish/validate_node_ids_unique.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py index f4994922ce..735441a921 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py @@ -41,7 +41,7 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): if invalid: label = "Nodes found with non-unique folder ids" raise PublishValidationError( - message="{}: {}".format(label, invalid), + message="{}, see log".format(label), title="Non-unique folder ids on nodes", description="{}\n- {}".format(label, "\n- ".join(sorted(invalid))) @@ -70,7 +70,12 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): _iteritems = getattr(ids, "iteritems", ids.items) for _ids, members in _iteritems(): if len(members) > 1: - cls.log.error("ID found on multiple nodes: '%s'" % members) + members_text = "\n".join( + "- {}".format(member) for member in sorted(members) + ) + cls.log.error( + "ID found on multiple nodes:\n{}".format(members_text) + ) invalid.extend(members) return invalid From be9c8a531b47e1775220cafd7d7cef06c626ba33 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 20:10:19 +0200 Subject: [PATCH 087/203] Move import to top --- .../hosts/maya/plugins/publish/validate_node_ids_unique.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py index 735441a921..6c838d2b86 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py @@ -8,6 +8,8 @@ from ayon_core.pipeline.publish import ( import ayon_core.hosts.maya.api.action from ayon_core.hosts.maya.api import lib +from maya import cmds + class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): """Validate the nodes in the instance have a unique Colorbleed Id @@ -54,7 +56,6 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): # Check only non intermediate shapes # todo: must the instance itself ensure to have no intermediates? # todo: how come there are intermediates? - from maya import cmds instance_members = cmds.ls(instance, noIntermediate=True, long=True) # Collect each id with their members From b4139cb10ea8523e107993f409dbfa1cb0086ed6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 20:11:19 +0200 Subject: [PATCH 088/203] Remove Python 2 optimization --- .../hosts/maya/plugins/publish/validate_node_ids_unique.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py index 6c838d2b86..6b44a307d2 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_unique.py @@ -68,8 +68,7 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): # Take only the ids with more than one member invalid = list() - _iteritems = getattr(ids, "iteritems", ids.items) - for _ids, members in _iteritems(): + for members in ids.values(): if len(members) > 1: members_text = "\n".join( "- {}".format(member) for member in sorted(members) From 630f03ac946fd7eb7a1b5fb74ac0f15fe5ea06f4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 21:09:31 +0200 Subject: [PATCH 089/203] Fix: remove from list while iterating over a copy --- client/ayon_core/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py index 00e1855b19..3df83276e9 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py @@ -59,7 +59,7 @@ for node_type in list(FILE_NODES.keys()): if node_type not in all_node_types: FILE_NODES.pop(node_type) -for node_type in RENDER_SET_TYPES: +for node_type in list(RENDER_SET_TYPES): if node_type not in all_node_types: RENDER_SET_TYPES.remove(node_type) del all_node_types From c873416b6a01032304973f8570a60f06c3d64686 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 21:10:36 +0200 Subject: [PATCH 090/203] Use list comprehension instead (faster than `.remove`) --- client/ayon_core/hosts/maya/plugins/publish/collect_look.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py index 3df83276e9..314f0dc01f 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py @@ -59,9 +59,8 @@ for node_type in list(FILE_NODES.keys()): if node_type not in all_node_types: FILE_NODES.pop(node_type) -for node_type in list(RENDER_SET_TYPES): - if node_type not in all_node_types: - RENDER_SET_TYPES.remove(node_type) +RENDER_SET_TYPES = [node_type for node_type in RENDER_SET_TYPES + if node_type in all_node_types] del all_node_types # Cache pixar dependency node types so we can perform a type lookup against it From adf5b24bab6f75ef3d737142b7215dfc260dd8f0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 21:11:58 +0200 Subject: [PATCH 091/203] Report num materials with textures --- client/ayon_core/hosts/maya/plugins/publish/collect_look.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py index 314f0dc01f..31d3e7a2f7 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py @@ -359,6 +359,7 @@ class CollectLook(pyblish.api.InstancePlugin): "rman__surface", "rman__displacement" ] + materials = [] if look_sets: self.log.debug("Found look sets: {}".format(look_sets)) @@ -437,6 +438,8 @@ class CollectLook(pyblish.api.InstancePlugin): not in instance_lookup) self.log.debug("Collected look for %s" % instance) + self.log.info("Collected {} materials with {} " + "textures.".format(len(materials), len(resources))) def collect_sets(self, instance): """Collect all objectSets which are of importance for publishing From 912b22817a1a789c7c0499d0c553d33b10459de8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 21:34:29 +0200 Subject: [PATCH 092/203] Restructure / optimize logic + improve docstrings --- .../maya/plugins/publish/collect_look.py | 166 +++++++++--------- 1 file changed, 85 insertions(+), 81 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py index 31d3e7a2f7..0c8280ea1b 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py @@ -8,7 +8,7 @@ from maya import cmds # noqa import pyblish.api from ayon_core.hosts.maya.api import lib -SHAPE_ATTRS = ["castsShadows", +SHAPE_ATTRS = {"castsShadows", "receiveShadows", "motionBlur", "primaryVisibility", @@ -16,8 +16,7 @@ SHAPE_ATTRS = ["castsShadows", "visibleInReflections", "visibleInRefractions", "doubleSided", - "opposite"] -SHAPE_ATTRS = set(SHAPE_ATTRS) + "opposite"} def get_pxr_multitexture_file_attrs(node): @@ -108,8 +107,7 @@ def get_look_attrs(node): if cmds.objectType(node, isAType="shape"): attrs = cmds.listAttr(node, changedSinceFileOpen=True) or [] for attr in attrs: - if attr in SHAPE_ATTRS or \ - attr not in SHAPE_ATTRS and attr.startswith('ai'): + if attr in SHAPE_ATTRS or attr.startswith('ai'): result.append(attr) return result @@ -289,7 +287,6 @@ class CollectLook(pyblish.api.InstancePlugin): families = ["look"] label = "Collect Look" hosts = ["maya"] - maketx = True def process(self, instance): """Collect the Look in the instance with the correct layer settings""" @@ -301,15 +298,12 @@ class CollectLook(pyblish.api.InstancePlugin): """Collect looks. Args: - instance: Instance to collect. + instance (pyblish.api.Instance): Instance to collect. """ self.log.debug("Looking for look associations " "for %s" % instance.data['name']) - # Lookup set (optimization) - instance_lookup = set(cmds.ls(instance, long=True)) - # Discover related object sets self.log.debug("Gathering sets ...") sets = self.collect_sets(instance) @@ -350,76 +344,15 @@ class CollectLook(pyblish.api.InstancePlugin): # Collect file nodes used by shading engines (if we have any) files = [] look_sets = list(sets.keys()) - shader_attrs = [ - "surfaceShader", - "volumeShader", - "displacementShader", - "aiSurfaceShader", - "aiVolumeShader", - "rman__surface", - "rman__displacement" - ] - materials = [] if look_sets: self.log.debug("Found look sets: {}".format(look_sets)) - - # Get all material attrs for all look sets to retrieve their inputs - existing_attrs = [] - for look in look_sets: - for attr in shader_attrs: - if cmds.attributeQuery(attr, node=look, exists=True): - existing_attrs.append("{}.{}".format(look, attr)) - - materials = cmds.listConnections(existing_attrs, - source=True, - destination=False) or [] - - self.log.debug("Found materials:\n{}".format(materials)) - - self.log.debug("Found the following sets:\n{}".format(look_sets)) - # Get the entire node chain of the look sets - # history = cmds.listHistory(look_sets, allConnections=True) - # if materials list is empty, listHistory() will crash with - # RuntimeError - history = set() - if materials: - history = set( - cmds.listHistory(materials, allConnections=True)) - - # Since we retrieved history only of the connected materials - # connected to the look sets above we now add direct history - # for some of the look sets directly - # handling render attribute sets - - # Maya (at least 2024) crashes with Warning when render set type - # isn't available. cmds.ls() will return empty list - if RENDER_SET_TYPES: - render_sets = cmds.ls(look_sets, type=RENDER_SET_TYPES) - if render_sets: - history.update( - cmds.listHistory(render_sets, - future=False, - pruneDagObjects=True) - or [] - ) - - # Ensure unique entries only - history = list(history) - - files = cmds.ls(history, - # It's important only node types are passed that - # exist (e.g. for loaded plugins) because otherwise - # the result will turn back empty - type=list(FILE_NODES.keys()), - long=True) - - # Sort for log readability - files.sort() + files = self.collect_file_nodes(look_sets) self.log.debug("Collected file nodes:\n{}".format(files)) - # Collect textures if any file nodes are found + + # Collect texture resources if any file nodes are found resources = [] - for node in files: # sort for log readability + for node in files: resources.extend(self.collect_resources(node)) instance.data["resources"] = resources self.log.debug("Collected resources: {}".format(resources)) @@ -438,8 +371,78 @@ class CollectLook(pyblish.api.InstancePlugin): not in instance_lookup) self.log.debug("Collected look for %s" % instance) - self.log.info("Collected {} materials with {} " - "textures.".format(len(materials), len(resources))) + + def collect_file_nodes(self, look_sets): + """Get the entire node chain of the look sets and return file nodes + + Arguments: + look_sets (List[str]): List of sets and shading engines relevant + to the look. + + Returns: + List[str]: List of file node names. + + """ + + shader_attrs = [ + "surfaceShader", + "volumeShader", + "displacementShader", + "aiSurfaceShader", + "aiVolumeShader", + "rman__surface", + "rman__displacement" + ] + + # Get all material attrs for all look sets to retrieve their inputs + existing_attrs = [] + for look_set in look_sets: + for attr in shader_attrs: + if cmds.attributeQuery(attr, node=look_set, exists=True): + existing_attrs.append("{}.{}".format(look_set, attr)) + + materials = cmds.listConnections(existing_attrs, + source=True, + destination=False) or [] + + self.log.debug("Found materials:\n{}".format(materials)) + + # Get the entire node chain of the look sets + # history = cmds.listHistory(look_sets, allConnections=True) + # if materials list is empty, listHistory() will crash with + # RuntimeError + history = set() + if materials: + history = set(cmds.listHistory(materials, allConnections=True)) + + # Since we retrieved history only of the connected materials connected + # to the look sets above we now add direct history for some of the + # look sets directly handling render attribute sets + + # Maya (at least 2024) crashes with Warning when render set type + # isn't available. cmds.ls() will return empty list + if RENDER_SET_TYPES: + render_sets = cmds.ls(look_sets, type=RENDER_SET_TYPES) + if render_sets: + history.update( + cmds.listHistory(render_sets, + future=False, + pruneDagObjects=True) + or [] + ) + + # Get file nodes in the material history + files = cmds.ls(list(history), + # It's important only node types are passed that + # exist (e.g. for loaded plugins) because otherwise + # the result will turn back empty + type=list(FILE_NODES.keys()), + long=True) + + # Sort for log readability + files.sort() + + return files def collect_sets(self, instance): """Collect all objectSets which are of importance for publishing @@ -448,7 +451,8 @@ class CollectLook(pyblish.api.InstancePlugin): which need to be Args: - instance (list): all nodes to be published + instance (pyblish.api.Instance): publish instance containing all + nodes to be published. Returns: dict @@ -626,7 +630,7 @@ class CollectLook(pyblish.api.InstancePlugin): "source": source, # required for resources "files": files, "color_space": color_space - } # required for resources + } class CollectModelRenderSets(CollectLook): @@ -641,13 +645,13 @@ class CollectModelRenderSets(CollectLook): families = ["model"] label = "Collect Model Render Sets" hosts = ["maya"] - maketx = True def collect_sets(self, instance): """Collect all related objectSets except shadingEngines Args: - instance (list): all nodes to be published + instance (pyblish.api.Instance): publish instance containing all + nodes to be published. Returns: dict From 939794700c704ac7a5f75d7acadec008d40a121d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 21:35:58 +0200 Subject: [PATCH 093/203] Optimize query --- client/ayon_core/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py index 0c8280ea1b..9d8bfd270d 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py @@ -667,7 +667,7 @@ class CollectModelRenderSets(CollectLook): if objset in sets: continue - if "shadingEngine" in cmds.nodeType(objset, inherited=True): + if cmds.objectType(objset, isAType="shadingEngine"): continue sets[objset] = {"uuid": lib.get_id(objset), "members": list()} From 0b16bfc42ccaea7014e2dba0f806577d1bb2a3e9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 22:55:44 +0200 Subject: [PATCH 094/203] Use enum in settings to toggle between the two menu build options --- client/ayon_core/hosts/maya/api/menu.py | 13 ++++---- .../maya/server/settings/scriptsmenu.py | 31 ++++++++++++------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/menu.py b/client/ayon_core/hosts/maya/api/menu.py index bc01b8b42c..e3ef50cdc0 100644 --- a/client/ayon_core/hosts/maya/api/menu.py +++ b/client/ayon_core/hosts/maya/api/menu.py @@ -215,11 +215,12 @@ def install(project_settings): ) return - _menu = project_settings["maya"]["scriptsmenu"]["name"] + menu_settings = project_settings["maya"]["scriptsmenu"] + menu_name = menu_settings["name"] + config = menu_settings["definition"] - config = project_settings["maya"]["scriptsmenu"]["definition"] - if project_settings["maya"]["scriptsmenu"].get("use_json_definition"): - data = project_settings["maya"]["scriptsmenu"]["definition_json"] + if menu_settings.get("definition_type") == "definition_json": + data = menu_settings["definition_json"] try: config = json.loads(data) except json.JSONDecodeError as exc: @@ -233,8 +234,8 @@ def install(project_settings): # run the launcher for Maya menu studio_menu = launchformaya.main( - title=_menu.title(), - objectName=_menu.title().lower().replace(" ", "_") + title=menu_name.title(), + objectName=menu_name.title().lower().replace(" ", "_") ) # apply configuration diff --git a/server_addon/maya/server/settings/scriptsmenu.py b/server_addon/maya/server/settings/scriptsmenu.py index fe80d816df..7b0ba7d831 100644 --- a/server_addon/maya/server/settings/scriptsmenu.py +++ b/server_addon/maya/server/settings/scriptsmenu.py @@ -18,29 +18,36 @@ class ScriptsmenuSubmodel(BaseSettingsModel): ) +_definition_mode_type = [ + {"value": "definition", "label": "Menu Builder"}, + {"value": "definition_json", "label": "Raw JSON (advanced)"} +] + + class ScriptsmenuModel(BaseSettingsModel): """Add a custom scripts menu to Maya""" _isGroup = True name: str = SettingsField(title="Menu Name") - use_json_definition: bool = SettingsField( - title="Use Raw JSON Definition", - description="When enabled, the definition field will be ignored. " - "Instead the menu will be build from the raw JSON " - "definition below it." + + definition_type: str = SettingsField( + title="Define menu using", + description="Choose the way to define the custom scripts menu " + "via settings", + enum_resolver=lambda: _definition_mode_type, + conditionalEnum=True, + default="definition" ) definition: list[ScriptsmenuSubmodel] = SettingsField( default_factory=list, title="Menu Definition", description="Scriptmenu Items Definition" ) - definition_json: str = SettingsField( - "[]", title="Definition JSON", widget="textarea", + "[]", title="Menu Definition JSON", widget="textarea", description=( - "When Use Raw JSON definition is enabled this field will be used " - "to put the direct JSON content instead of using the definition " - "menu builder UI above. For more details on the JSON format, see " + "Define the custom tools menu using a JSON list. " + "For more details on the JSON format, see " "[here](https://github.com/Colorbleed/scriptsmenu?tab=readme-ov-file#configuration)." # noqa: E501 ) ) @@ -64,7 +71,7 @@ class ScriptsmenuModel(BaseSettingsModel): DEFAULT_SCRIPTSMENU_SETTINGS = { "name": "Custom Tools", - "use_json_definition": False, + "definition_type": "definition", "definition": [ { "type": "action", @@ -78,5 +85,5 @@ DEFAULT_SCRIPTSMENU_SETTINGS = { ] } ], - "definition_raw": "" + "definition_json": "[]" } From ca04b809c3e12614497739dd39818b4a4a4b8a00 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 4 Apr 2024 01:43:19 +0200 Subject: [PATCH 095/203] Compute workdir based on instance folder path and task data if it does not match current context --- client/ayon_core/hosts/fusion/api/plugin.py | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/api/plugin.py b/client/ayon_core/hosts/fusion/api/plugin.py index 80167189a5..efe8269120 100644 --- a/client/ayon_core/hosts/fusion/api/plugin.py +++ b/client/ayon_core/hosts/fusion/api/plugin.py @@ -16,6 +16,12 @@ from ayon_core.pipeline import ( AVALON_INSTANCE_ID, AYON_INSTANCE_ID, ) +from ayon_core.pipeline.workfile import get_workdir +from ayon_api import ( + get_project, + get_folder_by_path, + get_task_by_name +) class GenericCreateSaver(Creator): @@ -147,7 +153,30 @@ class GenericCreateSaver(Creator): folder_path = formatting_data["folderPath"] folder_name = folder_path.rsplit("/", 1)[-1] - workdir = os.path.normpath(os.getenv("AYON_WORKDIR")) + # If the folder path and task do not match the current context then the + # workdir is not just the `AYON_WORKDIR`. Hence, we need to actually + # compute the resulting workdir + if ( + data["folderPath"] == self.create_context.get_current_folder_path() + and data["task"] == self.create_context.get_current_task_name() + ): + workdir = os.path.normpath(os.getenv("AYON_WORKDIR")) + else: + # TODO: Optimize this logic + project_name = self.create_context.get_current_project_name() + project_entity = get_project(project_name) + folder_entity = get_folder_by_path(project_name, + data["folderPath"]) + task_entity = get_task_by_name(project_name, + folder_id=folder_entity["id"], + task_name=data["task"]) + workdir = get_workdir( + project_entity=project_entity, + folder_entity=folder_entity, + task_entity=task_entity, + host_name=self.create_context.host_name, + ) + formatting_data.update({ "workdir": workdir, "frame": "0" * frame_padding, From ccc3c1710acda86eeaf4840a88384f127c55f549 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Apr 2024 11:01:48 +0200 Subject: [PATCH 096/203] use 'sitesync' over 'sync_server' --- client/ayon_core/host/dirmap.py | 36 +++++++-------- client/ayon_core/hosts/nuke/api/lib.py | 18 ++++---- client/ayon_core/pipeline/anatomy/anatomy.py | 20 ++++---- .../tools/loader/models/site_sync.py | 2 +- .../ayon_core/tools/sceneinventory/control.py | 4 +- .../tools/sceneinventory/models/site_sync.py | 46 +++++++++---------- client/ayon_core/tools/sceneinventory/view.py | 6 +-- .../ayon_core/tools/sceneinventory/window.py | 2 +- 8 files changed, 67 insertions(+), 67 deletions(-) diff --git a/client/ayon_core/host/dirmap.py b/client/ayon_core/host/dirmap.py index effafb6261..2e24877d28 100644 --- a/client/ayon_core/host/dirmap.py +++ b/client/ayon_core/host/dirmap.py @@ -36,23 +36,23 @@ class HostDirmap(object): host_name, project_name, project_settings=None, - sync_module=None + sitesync_addon=None ): self.host_name = host_name self.project_name = project_name self._project_settings = project_settings - self._sync_module = sync_module + self._sitesync_addon = sitesync_addon # to limit reinit of Modules - self._sync_module_discovered = sync_module is not None + self._sitesync_addon_discovered = sitesync_addon is not None self._log = None @property - def sync_module(self): - if not self._sync_module_discovered: - self._sync_module_discovered = True + def sitesync_addon(self): + if not self._sitesync_addon_discovered: + self._sitesync_addon_discovered = True manager = AddonsManager() - self._sync_module = manager.get("sync_server") - return self._sync_module + self._sitesync_addon = manager.get("sitesync") + return self._sitesync_addon @property def project_settings(self): @@ -158,25 +158,25 @@ class HostDirmap(object): """ project_name = self.project_name - sync_module = self.sync_module + sitesync_addon = self.sitesync_addon mapping = {} if ( - sync_module is None - or not sync_module.enabled - or project_name not in sync_module.get_enabled_projects() + sitesync_addon is None + or not sitesync_addon.enabled + or project_name not in sitesync_addon.get_enabled_projects() ): return mapping - active_site = sync_module.get_local_normalized_site( - sync_module.get_active_site(project_name)) - remote_site = sync_module.get_local_normalized_site( - sync_module.get_remote_site(project_name)) + active_site = sitesync_addon.get_local_normalized_site( + sitesync_addon.get_active_site(project_name)) + remote_site = sitesync_addon.get_local_normalized_site( + sitesync_addon.get_remote_site(project_name)) self.log.debug( "active {} - remote {}".format(active_site, remote_site) ) if active_site == "local" and active_site != remote_site: - sync_settings = sync_module.get_sync_project_setting( + sync_settings = sitesync_addon.get_sync_project_setting( project_name, exclude_locals=False, cached=False) @@ -194,7 +194,7 @@ class HostDirmap(object): self.log.debug("remote overrides {}".format(remote_overrides)) current_platform = platform.system().lower() - remote_provider = sync_module.get_provider_for_site( + remote_provider = sitesync_addon.get_provider_for_site( project_name, remote_site ) # dirmap has sense only with regular disk provider, in the workfile diff --git a/client/ayon_core/hosts/nuke/api/lib.py b/client/ayon_core/hosts/nuke/api/lib.py index 4fcba8d2d4..78cbe85097 100644 --- a/client/ayon_core/hosts/nuke/api/lib.py +++ b/client/ayon_core/hosts/nuke/api/lib.py @@ -2627,11 +2627,11 @@ class NukeDirmap(HostDirmap): class DirmapCache: - """Caching class to get settings and sync_module easily and only once.""" + """Caching class to get settings and sitesync easily and only once.""" _project_name = None _project_settings = None - _sync_module_discovered = False - _sync_module = None + _sitesync_addon_discovered = False + _sitesync_addon = None _mapping = None @classmethod @@ -2647,11 +2647,11 @@ class DirmapCache: return cls._project_settings @classmethod - def sync_module(cls): - if not cls._sync_module_discovered: - cls._sync_module_discovered = True - cls._sync_module = AddonsManager().get("sync_server") - return cls._sync_module + def sitesync_addon(cls): + if not cls._sitesync_addon_discovered: + cls._sitesync_addon_discovered = True + cls._sitesync_addon = AddonsManager().get("sitesync") + return cls._sitesync_addon @classmethod def mapping(cls): @@ -2673,7 +2673,7 @@ def dirmap_file_name_filter(file_name): "nuke", DirmapCache.project_name(), DirmapCache.project_settings(), - DirmapCache.sync_module(), + DirmapCache.sitesync_addon(), ) if not DirmapCache.mapping(): DirmapCache.set_mapping(dirmap_processor.get_mappings()) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 0d250116bd..73dd215233 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -447,7 +447,7 @@ class CacheItem: class Anatomy(BaseAnatomy): - _sync_server_addon_cache = CacheItem() + _sitesync_addon_cache = CacheItem() _project_cache = collections.defaultdict(CacheItem) _default_site_id_cache = collections.defaultdict(CacheItem) _root_overrides_cache = collections.defaultdict( @@ -482,13 +482,13 @@ class Anatomy(BaseAnatomy): return copy.deepcopy(project_cache.data) @classmethod - def get_sync_server_addon(cls): - if cls._sync_server_addon_cache.is_outdated: + def get_sitesync_addon(cls): + if cls._sitesync_addon_cache.is_outdated: manager = AddonsManager() - cls._sync_server_addon_cache.update_data( - manager.get_enabled_addon("sync_server") + cls._sitesync_addon_cache.update_data( + manager.get_enabled_addon("sitesync") ) - return cls._sync_server_addon_cache.data + return cls._sitesync_addon_cache.data @classmethod def _get_studio_roots_overrides(cls, project_name): @@ -525,8 +525,8 @@ class Anatomy(BaseAnatomy): """ # First check if sync server is available and enabled - sync_server = cls.get_sync_server_addon() - if sync_server is None or not sync_server.enabled: + sitesync_addon = cls.get_sitesync_addon() + if sitesync_addon is None or not sitesync_addon.enabled: # QUESTION is ok to force 'studio' when site sync is not enabled? site_name = "studio" @@ -535,7 +535,7 @@ class Anatomy(BaseAnatomy): project_cache = cls._default_site_id_cache[project_name] if project_cache.is_outdated: project_cache.update_data( - sync_server.get_active_site_type(project_name) + sitesync_addon.get_active_site_type(project_name) ) site_name = project_cache.data @@ -549,7 +549,7 @@ class Anatomy(BaseAnatomy): ) else: # Ask sync server to get roots overrides - roots_overrides = sync_server.get_site_root_overrides( + roots_overrides = sitesync.get_site_root_overrides( project_name, site_name ) site_cache.update_data(roots_overrides) diff --git a/client/ayon_core/tools/loader/models/site_sync.py b/client/ayon_core/tools/loader/models/site_sync.py index a589cf7fbe..fd4470894d 100644 --- a/client/ayon_core/tools/loader/models/site_sync.py +++ b/client/ayon_core/tools/loader/models/site_sync.py @@ -57,7 +57,7 @@ class SiteSyncModel: ) manager = AddonsManager() - self._site_sync_addon = manager.get("sync_server") + self._site_sync_addon = manager.get("sitesync") def reset(self): self._site_icons = None diff --git a/client/ayon_core/tools/sceneinventory/control.py b/client/ayon_core/tools/sceneinventory/control.py index 77f4d60b22..eae0f066f4 100644 --- a/client/ayon_core/tools/sceneinventory/control.py +++ b/client/ayon_core/tools/sceneinventory/control.py @@ -89,8 +89,8 @@ class SceneInventoryController: return [] # Site Sync methods - def is_sync_server_enabled(self): - return self._site_sync_model.is_sync_server_enabled() + def is_sitesync_enabled(self): + return self._site_sync_model.is_sitesync_enabled() def get_sites_information(self): return self._site_sync_model.get_sites_information() diff --git a/client/ayon_core/tools/sceneinventory/models/site_sync.py b/client/ayon_core/tools/sceneinventory/models/site_sync.py index 7f09f2b25b..fb49161e6e 100644 --- a/client/ayon_core/tools/sceneinventory/models/site_sync.py +++ b/client/ayon_core/tools/sceneinventory/models/site_sync.py @@ -9,30 +9,30 @@ class SiteSyncModel: def __init__(self, controller): self._controller = controller - self._sync_server_module = NOT_SET - self._sync_server_enabled = None + self._sitesync_addon = NOT_SET + self._sitesync_enabled = None self._active_site = NOT_SET self._remote_site = NOT_SET self._active_site_provider = NOT_SET self._remote_site_provider = NOT_SET def reset(self): - self._sync_server_module = NOT_SET - self._sync_server_enabled = None + self._sitesync_addon = NOT_SET + self._sitesync_enabled = None self._active_site = NOT_SET self._remote_site = NOT_SET self._active_site_provider = NOT_SET self._remote_site_provider = NOT_SET - def is_sync_server_enabled(self): + def is_sitesync_enabled(self): """Site sync is enabled. Returns: bool: Is enabled or not. """ - self._cache_sync_server_module() - return self._sync_server_enabled + self._cache_sitesync_addon() + return self._sitesync_enabled def get_site_provider_icons(self): """Icon paths per provider. @@ -41,9 +41,9 @@ class SiteSyncModel: dict[str, str]: Path by provider name. """ - if not self.is_sync_server_enabled(): + if not self.is_sitesync_enabled(): return {} - site_sync_addon = self._get_sync_server_module() + site_sync_addon = self._get_sitesync_addon() return site_sync_addon.get_site_icons() def get_sites_information(self): @@ -65,11 +65,11 @@ class SiteSyncModel: } for repre_id in representation_ids } - if not self.is_sync_server_enabled(): + if not self.is_sitesync_enabled(): return output project_name = self._controller.get_current_project_name() - site_sync = self._get_sync_server_module() + site_sync = self._get_sitesync_addon() repre_entities = ayon_api.get_representations( project_name, representation_ids ) @@ -95,7 +95,7 @@ class SiteSyncModel: """ project_name = self._controller.get_current_project_name() - site_sync = self._get_sync_server_module() + site_sync = self._get_sitesync_addon() active_site = self._get_active_site() remote_site = self._get_remote_site() progress = self.get_representations_site_progress( @@ -119,18 +119,18 @@ class SiteSyncModel: project_name, repre_id, site, force=True ) - def _get_sync_server_module(self): - self._cache_sync_server_module() - return self._sync_server_module + def _get_sitesync_addon(self): + self._cache_sitesync_addon() + return self._sitesync_addon - def _cache_sync_server_module(self): - if self._sync_server_module is not NOT_SET: - return self._sync_server_module + def _cache_sitesync_addon(self): + if self._sitesync_addon is not NOT_SET: + return self._sitesync_addon manager = AddonsManager() - site_sync = manager.get("sync_server") + site_sync = manager.get("sitesync") sync_enabled = site_sync is not None and site_sync.enabled - self._sync_server_module = site_sync - self._sync_server_enabled = sync_enabled + self._sitesync_addon = site_sync + self._sitesync_enabled = sync_enabled def _get_active_site(self): if self._active_site is NOT_SET: @@ -157,8 +157,8 @@ class SiteSyncModel: remote_site = None active_site_provider = None remote_site_provider = None - if self.is_sync_server_enabled(): - site_sync = self._get_sync_server_module() + if self.is_sitesync_enabled(): + site_sync = self._get_sitesync_addon() project_name = self._controller.get_current_project_name() active_site = site_sync.get_active_site(project_name) remote_site = site_sync.get_remote_site(project_name) diff --git a/client/ayon_core/tools/sceneinventory/view.py b/client/ayon_core/tools/sceneinventory/view.py index d576bdc139..5cbd4daf70 100644 --- a/client/ayon_core/tools/sceneinventory/view.py +++ b/client/ayon_core/tools/sceneinventory/view.py @@ -311,9 +311,9 @@ class SceneInventoryView(QtWidgets.QTreeView): menu.addAction(remove_action) - self._handle_sync_server(menu, repre_ids) + self._handle_sitesync(menu, repre_ids) - def _handle_sync_server(self, menu, repre_ids): + def _handle_sitesync(self, menu, repre_ids): """Adds actions for download/upload when SyncServer is enabled Args: @@ -324,7 +324,7 @@ class SceneInventoryView(QtWidgets.QTreeView): (OptionMenu) """ - if not self._controller.is_sync_server_enabled(): + if not self._controller.is_sitesync_enabled(): return menu.addSeparator() diff --git a/client/ayon_core/tools/sceneinventory/window.py b/client/ayon_core/tools/sceneinventory/window.py index 9584524edd..555db3a17c 100644 --- a/client/ayon_core/tools/sceneinventory/window.py +++ b/client/ayon_core/tools/sceneinventory/window.py @@ -70,7 +70,7 @@ class SceneInventoryWindow(QtWidgets.QDialog): view = SceneInventoryView(controller, self) view.setModel(proxy) - sync_enabled = controller.is_sync_server_enabled() + sync_enabled = controller.is_sitesync_enabled() view.setColumnHidden(model.active_site_col, not sync_enabled) view.setColumnHidden(model.remote_site_col, not sync_enabled) From 8e107744eb31d770e34e192b2f78d7090d5bc86b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 4 Apr 2024 10:30:43 +0100 Subject: [PATCH 097/203] Account for no nodes in container --- client/ayon_core/hosts/maya/api/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/api/workfile_template_builder.py b/client/ayon_core/hosts/maya/api/workfile_template_builder.py index cb25a722f0..75386d7e64 100644 --- a/client/ayon_core/hosts/maya/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/maya/api/workfile_template_builder.py @@ -286,7 +286,7 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): if not container: return - roots = cmds.sets(container, q=True) + roots = cmds.sets(container, q=True) or [] ref_node = None try: ref_node = get_reference_node(roots) From e1b22cbe8d6cf56ee8949fd54abf1b78b6bd0a22 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Apr 2024 11:44:17 +0200 Subject: [PATCH 098/203] use same variable naming in rest of codebase --- client/ayon_core/tools/loader/abstract.py | 2 +- client/ayon_core/tools/loader/control.py | 22 +++---- .../tools/loader/models/site_sync.py | 60 +++++++++---------- .../tools/loader/ui/products_model.py | 2 +- .../tools/loader/ui/products_widget.py | 20 +++---- .../tools/loader/ui/repres_widget.py | 14 ++--- .../ayon_core/tools/sceneinventory/control.py | 14 ++--- .../tools/sceneinventory/models/site_sync.py | 28 ++++----- 8 files changed, 81 insertions(+), 81 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 33add0213b..7a7d335092 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -871,7 +871,7 @@ class FrontendLoaderController(_BaseLoaderController): # Site sync functions @abstractmethod - def is_site_sync_enabled(self, project_name=None): + def is_sitesync_enabled(self, project_name=None): """Is site sync enabled. Site sync addon can be enabled but can be disabled per project. diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index d8562f50ca..0c9bb369c7 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -113,7 +113,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): self._products_model = ProductsModel(self) self._loader_actions_model = LoaderActionsModel(self) self._thumbnails_model = ThumbnailsModel() - self._site_sync_model = SiteSyncModel(self) + self._sitesync_model = SiteSyncModel(self) @property def log(self): @@ -149,7 +149,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): self._loader_actions_model.reset() self._projects_model.reset() self._thumbnails_model.reset() - self._site_sync_model.reset() + self._sitesync_model.reset() self._projects_model.refresh() @@ -240,7 +240,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): project_name, representation_ids) ) - action_items.extend(self._site_sync_model.get_site_sync_action_items( + action_items.extend(self._sitesync_model.get_sitesync_action_items( project_name, representation_ids) ) @@ -254,8 +254,8 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): version_ids, representation_ids ): - if self._site_sync_model.is_site_sync_action(identifier): - self._site_sync_model.trigger_action_item( + if self._sitesync_model.is_sitesync_action(identifier): + self._sitesync_model.trigger_action_item( identifier, project_name, representation_ids @@ -368,24 +368,24 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): self._loaded_products_cache.update_data(product_ids) return self._loaded_products_cache.get_data() - def is_site_sync_enabled(self, project_name=None): - return self._site_sync_model.is_site_sync_enabled(project_name) + def is_sitesync_enabled(self, project_name=None): + return self._sitesync_model.is_sitesync_enabled(project_name) def get_active_site_icon_def(self, project_name): - return self._site_sync_model.get_active_site_icon_def(project_name) + return self._sitesync_model.get_active_site_icon_def(project_name) def get_remote_site_icon_def(self, project_name): - return self._site_sync_model.get_remote_site_icon_def(project_name) + return self._sitesync_model.get_remote_site_icon_def(project_name) def get_version_sync_availability(self, project_name, version_ids): - return self._site_sync_model.get_version_sync_availability( + return self._sitesync_model.get_version_sync_availability( project_name, version_ids ) def get_representations_sync_status( self, project_name, representation_ids ): - return self._site_sync_model.get_representations_sync_status( + return self._sitesync_model.get_representations_sync_status( project_name, representation_ids ) diff --git a/client/ayon_core/tools/loader/models/site_sync.py b/client/ayon_core/tools/loader/models/site_sync.py index fd4470894d..987510905b 100644 --- a/client/ayon_core/tools/loader/models/site_sync.py +++ b/client/ayon_core/tools/loader/models/site_sync.py @@ -36,7 +36,7 @@ class SiteSyncModel: self._controller = controller self._site_icons = None - self._site_sync_enabled_cache = NestedCacheItem( + self._sitesync_enabled_cache = NestedCacheItem( levels=1, lifetime=self.lifetime ) self._active_site_cache = NestedCacheItem( @@ -57,17 +57,17 @@ class SiteSyncModel: ) manager = AddonsManager() - self._site_sync_addon = manager.get("sitesync") + self._sitesync_addon = manager.get("sitesync") def reset(self): self._site_icons = None - self._site_sync_enabled_cache.reset() + self._sitesync_enabled_cache.reset() self._active_site_cache.reset() self._remote_site_cache.reset() self._version_availability_cache.reset() self._repre_status_cache.reset() - def is_site_sync_enabled(self, project_name=None): + def is_sitesync_enabled(self, project_name=None): """Site sync is enabled for a project. Returns false if site sync addon is not available or enabled @@ -82,13 +82,13 @@ class SiteSyncModel: bool: Site sync is enabled. """ - if not self._is_site_sync_addon_enabled(): + if not self._is_sitesync_addon_enabled(): return False - cache = self._site_sync_enabled_cache[project_name] + cache = self._sitesync_enabled_cache[project_name] if not cache.is_valid: enabled = True if project_name: - enabled = self._site_sync_addon.is_project_enabled( + enabled = self._sitesync_addon.is_project_enabled( project_name, single=True ) cache.update_data(enabled) @@ -107,8 +107,8 @@ class SiteSyncModel: cache = self._active_site_cache[project_name] if not cache.is_valid: site_name = None - if project_name and self._is_site_sync_addon_enabled(): - site_name = self._site_sync_addon.get_active_site(project_name) + if project_name and self._is_sitesync_addon_enabled(): + site_name = self._sitesync_addon.get_active_site(project_name) cache.update_data(site_name) return cache.get_data() @@ -125,8 +125,8 @@ class SiteSyncModel: cache = self._remote_site_cache[project_name] if not cache.is_valid: site_name = None - if project_name and self._is_site_sync_addon_enabled(): - site_name = self._site_sync_addon.get_remote_site(project_name) + if project_name and self._is_sitesync_addon_enabled(): + site_name = self._sitesync_addon.get_remote_site(project_name) cache.update_data(site_name) return cache.get_data() @@ -140,7 +140,7 @@ class SiteSyncModel: Union[dict[str, Any], None]: Site icon definition. """ - if not project_name or not self.is_site_sync_enabled(project_name): + if not project_name or not self.is_sitesync_enabled(project_name): return None active_site = self.get_active_site(project_name) return self._get_site_icon_def(project_name, active_site) @@ -155,14 +155,14 @@ class SiteSyncModel: Union[dict[str, Any], None]: Site icon definition. """ - if not project_name or not self.is_site_sync_enabled(project_name): + if not project_name or not self.is_sitesync_enabled(project_name): return None remote_site = self.get_remote_site(project_name) return self._get_site_icon_def(project_name, remote_site) def _get_site_icon_def(self, project_name, site_name): # use different icon for studio even if provider is 'local_drive' - if site_name == self._site_sync_addon.DEFAULT_SITE: + if site_name == self._sitesync_addon.DEFAULT_SITE: provider = "studio" else: provider = self._get_provider_for_site(project_name, site_name) @@ -179,7 +179,7 @@ class SiteSyncModel: dict[str, tuple[int, int]] """ - if not self.is_site_sync_enabled(project_name): + if not self.is_sitesync_enabled(project_name): return { version_id: _default_version_availability() for version_id in version_ids @@ -217,7 +217,7 @@ class SiteSyncModel: dict[str, tuple[float, float]] """ - if not self.is_site_sync_enabled(project_name): + if not self.is_sitesync_enabled(project_name): return { repre_id: _default_repre_status() for repre_id in representation_ids @@ -242,7 +242,7 @@ class SiteSyncModel: output[repre_id] = repre_cache.get_data() return output - def get_site_sync_action_items(self, project_name, representation_ids): + def get_sitesync_action_items(self, project_name, representation_ids): """ Args: @@ -253,7 +253,7 @@ class SiteSyncModel: list[ActionItem]: Actions that can be shown in loader. """ - if not self.is_site_sync_enabled(project_name): + if not self.is_sitesync_enabled(project_name): return [] repres_status = self.get_representations_sync_status( @@ -289,7 +289,7 @@ class SiteSyncModel: return action_items - def is_site_sync_action(self, identifier): + def is_sitesync_action(self, identifier): """Should be `identifier` handled by SiteSync. Args: @@ -353,22 +353,22 @@ class SiteSyncModel: ) elif identifier == REMOVE_IDENTIFIER: - self._site_sync_addon.remove_site( + self._sitesync_addon.remove_site( project_name, repre_id, active_site, remove_local_files=True ) - def _is_site_sync_addon_enabled(self): + def _is_sitesync_addon_enabled(self): """ Returns: bool: Site sync addon is enabled. """ - if self._site_sync_addon is None: + if self._sitesync_addon is None: return False - return self._site_sync_addon.enabled + return self._sitesync_addon.enabled def _get_provider_for_site(self, project_name, site_name): """Provider for a site. @@ -381,9 +381,9 @@ class SiteSyncModel: Union[str, None]: Provider name. """ - if not self._is_site_sync_addon_enabled(): + if not self._is_sitesync_addon_enabled(): return None - return self._site_sync_addon.get_provider_for_site( + return self._sitesync_addon.get_provider_for_site( project_name, site_name ) @@ -398,7 +398,7 @@ class SiteSyncModel: return None if self._site_icons is None: - self._site_icons = self._site_sync_addon.get_site_icons() + self._site_icons = self._sitesync_addon.get_site_icons() return self._site_icons.get(provider) def _refresh_version_availability(self, project_name, version_ids): @@ -406,7 +406,7 @@ class SiteSyncModel: return project_cache = self._version_availability_cache[project_name] - avail_by_id = self._site_sync_addon.get_version_availability( + avail_by_id = self._sitesync_addon.get_version_availability( project_name, version_ids, self.get_active_site(project_name), @@ -425,7 +425,7 @@ class SiteSyncModel: return project_cache = self._repre_status_cache[project_name] status_by_repre_id = ( - self._site_sync_addon.get_representations_sync_state( + self._sitesync_addon.get_representations_sync_state( project_name, representation_ids, self.get_active_site(project_name), @@ -496,7 +496,7 @@ class SiteSyncModel: ) def _add_site(self, project_name, repre_entity, site_name, product_type): - self._site_sync_addon.add_site( + self._sitesync_addon.add_site( project_name, repre_entity["id"], site_name, force=True ) @@ -513,7 +513,7 @@ class SiteSyncModel: try: print("Adding {} to linked representation: {}".format( site_name, link_repre_id)) - self._site_sync_addon.add_site( + self._sitesync_addon.add_site( project_name, link_repre_id, site_name, diff --git a/client/ayon_core/tools/loader/ui/products_model.py b/client/ayon_core/tools/loader/ui/products_model.py index c51172849a..41342ba0df 100644 --- a/client/ayon_core/tools/loader/ui/products_model.py +++ b/client/ayon_core/tools/loader/ui/products_model.py @@ -73,7 +73,7 @@ class ProductsModel(QtGui.QStandardItemModel): published_time_col = column_labels.index("Time") folders_label_col = column_labels.index("Folder") in_scene_col = column_labels.index("In scene") - site_sync_avail_col = column_labels.index("Availability") + sitesync_avail_col = column_labels.index("Availability") def __init__(self, controller): super(ProductsModel, self).__init__() diff --git a/client/ayon_core/tools/loader/ui/products_widget.py b/client/ayon_core/tools/loader/ui/products_widget.py index 3025ec18bd..d9f027153e 100644 --- a/client/ayon_core/tools/loader/ui/products_widget.py +++ b/client/ayon_core/tools/loader/ui/products_widget.py @@ -139,9 +139,9 @@ class ProductsWidget(QtWidgets.QWidget): products_view.setItemDelegateForColumn( products_model.in_scene_col, in_scene_delegate) - site_sync_delegate = SiteSyncDelegate() + sitesync_delegate = SiteSyncDelegate() products_view.setItemDelegateForColumn( - products_model.site_sync_avail_col, site_sync_delegate) + products_model.sitesync_avail_col, sitesync_delegate) main_layout = QtWidgets.QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) @@ -176,7 +176,7 @@ class ProductsWidget(QtWidgets.QWidget): self._version_delegate = version_delegate self._time_delegate = time_delegate self._in_scene_delegate = in_scene_delegate - self._site_sync_delegate = site_sync_delegate + self._sitesync_delegate = sitesync_delegate self._selected_project_name = None self._selected_folder_ids = set() @@ -192,8 +192,8 @@ class ProductsWidget(QtWidgets.QWidget): products_model.in_scene_col, not controller.is_loaded_products_supported() ) - self._set_site_sync_visibility( - self._controller.is_site_sync_enabled() + self._set_sitesync_visibility( + self._controller.is_sitesync_enabled() ) def set_name_filter(self, name): @@ -229,10 +229,10 @@ class ProductsWidget(QtWidgets.QWidget): def refresh(self): self._refresh_model() - def _set_site_sync_visibility(self, site_sync_enabled): + def _set_sitesync_visibility(self, sitesync_enabled): self._products_view.setColumnHidden( - self._products_model.site_sync_avail_col, - not site_sync_enabled + self._products_model.sitesync_avail_col, + not sitesync_enabled ) def _fill_version_editor(self): @@ -395,10 +395,10 @@ class ProductsWidget(QtWidgets.QWidget): def _on_folders_selection_change(self, event): project_name = event["project_name"] - site_sync_enabled = self._controller.is_site_sync_enabled( + sitesync_enabled = self._controller.is_sitesync_enabled( project_name ) - self._set_site_sync_visibility(site_sync_enabled) + self._set_sitesync_visibility(sitesync_enabled) self._selected_project_name = project_name self._selected_folder_ids = event["folder_ids"] self._refresh_model() diff --git a/client/ayon_core/tools/loader/ui/repres_widget.py b/client/ayon_core/tools/loader/ui/repres_widget.py index 3b6b8f94bf..d19ad306a3 100644 --- a/client/ayon_core/tools/loader/ui/repres_widget.py +++ b/client/ayon_core/tools/loader/ui/repres_widget.py @@ -307,8 +307,8 @@ class RepresentationsWidget(QtWidgets.QWidget): self._repre_model = repre_model self._repre_proxy_model = repre_proxy_model - self._set_site_sync_visibility( - self._controller.is_site_sync_enabled() + self._set_sitesync_visibility( + self._controller.is_sitesync_enabled() ) self._set_multiple_folders_selected(False) @@ -320,19 +320,19 @@ class RepresentationsWidget(QtWidgets.QWidget): def _on_project_change(self, event): self._selected_project_name = event["project_name"] - site_sync_enabled = self._controller.is_site_sync_enabled( + sitesync_enabled = self._controller.is_sitesync_enabled( self._selected_project_name ) - self._set_site_sync_visibility(site_sync_enabled) + self._set_sitesync_visibility(sitesync_enabled) - def _set_site_sync_visibility(self, site_sync_enabled): + def _set_sitesync_visibility(self, sitesync_enabled): self._repre_view.setColumnHidden( self._repre_model.active_site_column, - not site_sync_enabled + not sitesync_enabled ) self._repre_view.setColumnHidden( self._repre_model.remote_site_column, - not site_sync_enabled + not sitesync_enabled ) def _set_multiple_folders_selected(self, selected_multiple_folders): diff --git a/client/ayon_core/tools/sceneinventory/control.py b/client/ayon_core/tools/sceneinventory/control.py index eae0f066f4..592113455c 100644 --- a/client/ayon_core/tools/sceneinventory/control.py +++ b/client/ayon_core/tools/sceneinventory/control.py @@ -28,7 +28,7 @@ class SceneInventoryController: self._current_folder_id = None self._current_folder_set = False - self._site_sync_model = SiteSyncModel(self) + self._sitesync_model = SiteSyncModel(self) # Switch dialog requirements self._hierarchy_model = HierarchyModel(self) self._event_system = self._create_event_system() @@ -47,7 +47,7 @@ class SceneInventoryController: self._current_folder_id = None self._current_folder_set = False - self._site_sync_model.reset() + self._sitesync_model.reset() self._hierarchy_model.reset() def get_current_context(self): @@ -90,21 +90,21 @@ class SceneInventoryController: # Site Sync methods def is_sitesync_enabled(self): - return self._site_sync_model.is_sitesync_enabled() + return self._sitesync_model.is_sitesync_enabled() def get_sites_information(self): - return self._site_sync_model.get_sites_information() + return self._sitesync_model.get_sites_information() def get_site_provider_icons(self): - return self._site_sync_model.get_site_provider_icons() + return self._sitesync_model.get_site_provider_icons() def get_representations_site_progress(self, representation_ids): - return self._site_sync_model.get_representations_site_progress( + return self._sitesync_model.get_representations_site_progress( representation_ids ) def resync_representations(self, representation_ids, site_type): - return self._site_sync_model.resync_representations( + return self._sitesync_model.resync_representations( representation_ids, site_type ) diff --git a/client/ayon_core/tools/sceneinventory/models/site_sync.py b/client/ayon_core/tools/sceneinventory/models/site_sync.py index fb49161e6e..1a1f08bf02 100644 --- a/client/ayon_core/tools/sceneinventory/models/site_sync.py +++ b/client/ayon_core/tools/sceneinventory/models/site_sync.py @@ -43,8 +43,8 @@ class SiteSyncModel: if not self.is_sitesync_enabled(): return {} - site_sync_addon = self._get_sitesync_addon() - return site_sync_addon.get_site_icons() + sitesync_addon = self._get_sitesync_addon() + return sitesync_addon.get_site_icons() def get_sites_information(self): return { @@ -69,7 +69,7 @@ class SiteSyncModel: return output project_name = self._controller.get_current_project_name() - site_sync = self._get_sitesync_addon() + sitesync_addon = self._get_sitesync_addon() repre_entities = ayon_api.get_representations( project_name, representation_ids ) @@ -78,7 +78,7 @@ class SiteSyncModel: for repre_entity in repre_entities: repre_output = output[repre_entity["id"]] - result = site_sync.get_progress_for_repre( + result = sitesync_addon.get_progress_for_repre( repre_entity, active_site, remote_site ) repre_output["active_site"] = result[active_site] @@ -95,7 +95,7 @@ class SiteSyncModel: """ project_name = self._controller.get_current_project_name() - site_sync = self._get_sitesync_addon() + sitesync_addon = self._get_sitesync_addon() active_site = self._get_active_site() remote_site = self._get_remote_site() progress = self.get_representations_site_progress( @@ -115,7 +115,7 @@ class SiteSyncModel: site = remote_site if check_progress == 1: - site_sync.add_site( + sitesync_addon.add_site( project_name, repre_id, site, force=True ) @@ -127,9 +127,9 @@ class SiteSyncModel: if self._sitesync_addon is not NOT_SET: return self._sitesync_addon manager = AddonsManager() - site_sync = manager.get("sitesync") - sync_enabled = site_sync is not None and site_sync.enabled - self._sitesync_addon = site_sync + sitesync_addon = manager.get("sitesync") + sync_enabled = sitesync_addon is not None and sitesync_addon.enabled + self._sitesync_addon = sitesync_addon self._sitesync_enabled = sync_enabled def _get_active_site(self): @@ -158,18 +158,18 @@ class SiteSyncModel: active_site_provider = None remote_site_provider = None if self.is_sitesync_enabled(): - site_sync = self._get_sitesync_addon() + sitesync_addon = self._get_sitesync_addon() project_name = self._controller.get_current_project_name() - active_site = site_sync.get_active_site(project_name) - remote_site = site_sync.get_remote_site(project_name) + active_site = sitesync_addon.get_active_site(project_name) + remote_site = sitesync_addon.get_remote_site(project_name) active_site_provider = "studio" remote_site_provider = "studio" if active_site != "studio": - active_site_provider = site_sync.get_provider_for_site( + active_site_provider = sitesync_addon.get_provider_for_site( project_name, active_site ) if remote_site != "studio": - remote_site_provider = site_sync.get_provider_for_site( + remote_site_provider = sitesync_addon.get_provider_for_site( project_name, remote_site ) From 7670a9ce4627db2c44f048495ad474930b8f25bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Apr 2024 11:47:16 +0200 Subject: [PATCH 099/203] rename files too --- client/ayon_core/tools/loader/models/__init__.py | 2 +- .../ayon_core/tools/loader/models/{site_sync.py => sitesync.py} | 0 client/ayon_core/tools/sceneinventory/models/__init__.py | 2 +- .../tools/sceneinventory/models/{site_sync.py => sitesync.py} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename client/ayon_core/tools/loader/models/{site_sync.py => sitesync.py} (100%) rename client/ayon_core/tools/sceneinventory/models/{site_sync.py => sitesync.py} (100%) diff --git a/client/ayon_core/tools/loader/models/__init__.py b/client/ayon_core/tools/loader/models/__init__.py index 8e640659a0..10fd3da4d3 100644 --- a/client/ayon_core/tools/loader/models/__init__.py +++ b/client/ayon_core/tools/loader/models/__init__.py @@ -1,7 +1,7 @@ from .selection import SelectionModel from .products import ProductsModel from .actions import LoaderActionsModel -from .site_sync import SiteSyncModel +from .sitesync import SiteSyncModel __all__ = ( diff --git a/client/ayon_core/tools/loader/models/site_sync.py b/client/ayon_core/tools/loader/models/sitesync.py similarity index 100% rename from client/ayon_core/tools/loader/models/site_sync.py rename to client/ayon_core/tools/loader/models/sitesync.py diff --git a/client/ayon_core/tools/sceneinventory/models/__init__.py b/client/ayon_core/tools/sceneinventory/models/__init__.py index c861d3c1a0..f840a45aa8 100644 --- a/client/ayon_core/tools/sceneinventory/models/__init__.py +++ b/client/ayon_core/tools/sceneinventory/models/__init__.py @@ -1,4 +1,4 @@ -from .site_sync import SiteSyncModel +from .sitesync import SiteSyncModel __all__ = ( diff --git a/client/ayon_core/tools/sceneinventory/models/site_sync.py b/client/ayon_core/tools/sceneinventory/models/sitesync.py similarity index 100% rename from client/ayon_core/tools/sceneinventory/models/site_sync.py rename to client/ayon_core/tools/sceneinventory/models/sitesync.py From 905587a00f88fc5e6038059b2fb6fe3d2c6ea67e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Apr 2024 12:32:39 +0200 Subject: [PATCH 100/203] remove start.py or ayon executable instead of script --- client/ayon_core/cli.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index ab8e9f47f4..2f01f71a98 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -11,7 +11,7 @@ import acre from ayon_core import AYON_CORE_ROOT from ayon_core.addon import AddonsManager from ayon_core.settings import get_general_environments -from ayon_core.lib import initialize_ayon_connection +from ayon_core.lib import initialize_ayon_connection, is_running_from_build from .cli_commands import Commands @@ -167,16 +167,29 @@ def run(script): if not script: print("Error: missing path to script file.") + return + + # Remove first argument if it is the same as AYON executable + # - Forwards compatibility with future AYON versions. + # - Current AYON launcher keep the arguments with first argument but + # future versions might remove it. + first_arg = sys.argv[0] + if is_running_from_build(): + comp_path = os.path.join(os.environ["AYON_ROOT"], "start.py") else: + comp_path = os.getenv("AYON_EXECUTABLE") + # Normalize paths for comparison + first_arg = os.path.normpath(first_arg).lower() + comp_path = os.path.normpath(comp_path).lower() + if first_arg == comp_path: + sys.argv.pop(0) - args = sys.argv - args.remove("run") - args.remove(script) - sys.argv = args + # Remove 'run' command from sys.argv + sys.argv.remove("run") - args_string = " ".join(args[1:]) - print(f"... running: {script} {args_string}") - runpy.run_path(script, run_name="__main__", ) + args_string = " ".join(sys.argv[1:]) + print(f"... running: {script} {args_string}") + runpy.run_path(script, run_name="__main__", ) @main_cli.command() From f7ea2cc525924794e2e12f4e6b039fc606808b8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:42:37 +0200 Subject: [PATCH 101/203] typos and cosmetics Co-authored-by: Roy Nieterau --- client/ayon_core/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index 2f01f71a98..40227b63b8 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -170,8 +170,8 @@ def run(script): return # Remove first argument if it is the same as AYON executable - # - Forwards compatibility with future AYON versions. - # - Current AYON launcher keep the arguments with first argument but + # - Forward compatibility with future AYON versions. + # - Current AYON launcher keeps the arguments with first argument but # future versions might remove it. first_arg = sys.argv[0] if is_running_from_build(): @@ -189,7 +189,7 @@ def run(script): args_string = " ".join(sys.argv[1:]) print(f"... running: {script} {args_string}") - runpy.run_path(script, run_name="__main__", ) + runpy.run_path(script, run_name="__main__") @main_cli.command() From 041933e96bba36f001de2194b160b311d1da4110 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Apr 2024 12:49:52 +0200 Subject: [PATCH 102/203] use pathlib instead --- client/ayon_core/cli.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index 40227b63b8..80fbb57340 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -4,6 +4,7 @@ import os import sys import code import traceback +from pathlib import Path import click import acre @@ -178,10 +179,8 @@ def run(script): comp_path = os.path.join(os.environ["AYON_ROOT"], "start.py") else: comp_path = os.getenv("AYON_EXECUTABLE") - # Normalize paths for comparison - first_arg = os.path.normpath(first_arg).lower() - comp_path = os.path.normpath(comp_path).lower() - if first_arg == comp_path: + # Compare paths and remove first argument if it is the same as AYON + if Path(first_arg).resolve() == Path(comp_path).resolve(): sys.argv.pop(0) # Remove 'run' command from sys.argv From a4758f501c11cc3d1e8bfc2d8ea41e129d877713 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Apr 2024 14:19:16 +0200 Subject: [PATCH 103/203] bump version to '0.3.0' --- client/ayon_core/version.py | 2 +- package.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index f3ad9713d5..7061e3c126 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON core addon version.""" -__version__ = "0.3.0-dev.1" +__version__ = "0.3.0" diff --git a/package.py b/package.py index 470bbf256b..4b511da60d 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "0.3.0-dev.1" +version = "0.3.0" client_dir = "ayon_core" From f42ee6049006ca803f90f54db613c9a5dc24b132 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:40:00 +0200 Subject: [PATCH 104/203] bump version to '0.3.1-dev.1' --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 7061e3c126..a60de0493a 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON core addon version.""" -__version__ = "0.3.0" +__version__ = "0.3.1-dev.1" diff --git a/package.py b/package.py index 4b511da60d..79450d029f 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "0.3.0" +version = "0.3.1-dev.1" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index bfdc3cca8c..3c9ff4ea0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "0.3.0" +version = "0.3.1" description = "" authors = ["Ynput Team "] readme = "README.md" From 368444a7f25c2d9a6d044f605a860677f268d2d8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 22:21:11 +0800 Subject: [PATCH 105/203] use cmds.setattr instead of self._get_attr() --- client/ayon_core/hosts/maya/api/lib_renderproducts.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib_renderproducts.py b/client/ayon_core/hosts/maya/api/lib_renderproducts.py index 5f3917f642..6b73639040 100644 --- a/client/ayon_core/hosts/maya/api/lib_renderproducts.py +++ b/client/ayon_core/hosts/maya/api/lib_renderproducts.py @@ -1133,9 +1133,11 @@ class RenderProductsRedshift(ARenderProducts): aovs = list(set(aovs) - set(ref_aovs)) products = [] + # global_aov_enabled = bool( + # self._get_attr("redshiftOptions.aovGlobalEnableMode") + # ) global_aov_enabled = bool( - self._get_attr("redshiftOptions.aovGlobalEnableMode") - ) + cmds.getAttr("redshiftOptions.aovGlobalEnableMode")) colorspace = lib.get_color_management_output_transform() if not global_aov_enabled: # only beauty output From 4a04bd1397a9a5e564c6b5d7e7f7262b53588ccc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:35:49 +0200 Subject: [PATCH 106/203] fix update of context change --- client/ayon_core/tools/workfiles/control.py | 38 ++++++++++----------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/client/ayon_core/tools/workfiles/control.py b/client/ayon_core/tools/workfiles/control.py index 7fa7af1662..2cdcd37d53 100644 --- a/client/ayon_core/tools/workfiles/control.py +++ b/client/ayon_core/tools/workfiles/control.py @@ -659,16 +659,7 @@ class BaseWorkfileController( folder_id != self.get_current_folder_id() or task_name != self.get_current_task_name() ): - folder_entity = ayon_api.get_folder_by_id( - event_data["project_name"], - event_data["folder_id"], - ) - task_entity = ayon_api.get_task_by_name( - event_data["project_name"], - event_data["folder_id"], - event_data["task_name"] - ) - change_current_context(folder_entity, task_entity) + self._change_current_context(project_name, folder_id, task_id) self._host_open_workfile(filepath) @@ -710,16 +701,8 @@ class BaseWorkfileController( folder_id != self.get_current_folder_id() or task_name != self.get_current_task_name() ): - folder_entity = ayon_api.get_folder_by_id( - project_name, folder["id"] - ) - task_entity = ayon_api.get_task_by_name( - project_name, folder["id"], task_name - ) - change_current_context( - folder_entity, - task_entity, - template_key=template_key + self._change_current_context( + project_name, folder_id, task_id, template_key ) # Save workfile @@ -745,3 +728,18 @@ class BaseWorkfileController( # Trigger after save events emit_event("workfile.save.after", event_data, source="workfiles.tool") self.reset() + + def _change_current_context( + self, project_name, folder_id, task_id, template_key=None + ): + # Change current context + folder_entity = self.get_folder_entity(project_name, folder_id) + task_entity = self.get_task_entity(project_name, task_id) + change_current_context( + folder_entity, + task_entity, + template_key=template_key + ) + self._current_folder_id = folder_entity["id"] + self._current_folder_path = folder_entity["path"] + self._current_task_name = task_entity["name"] From 1e0c115a5ad52375b63060250e32fd8a95037f82 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 4 Apr 2024 16:38:00 +0200 Subject: [PATCH 107/203] Improve artist-facing report --- .../plugins/publish/validate_model_content.py | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py index 97d602a9ad..015f4c11ed 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py @@ -1,3 +1,5 @@ +import inspect + from maya import cmds import pyblish.api @@ -14,8 +16,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Adheres to the content of 'model' product type - - Must have one top group. (configurable) - - Must only contain: transforms, meshes and groups + See `get_description` for more details. """ @@ -35,7 +36,8 @@ class ValidateModelContent(pyblish.api.InstancePlugin, content_instance = instance.data.get("setMembers", None) if not content_instance: - cls.log.error("Instance has no nodes!") + cls.log.error("Model instance has no nodes. " + "It is not allowed to be empty") return [instance.data["instance_node"]] # All children will be included in the extracted export so we also @@ -53,10 +55,13 @@ class ValidateModelContent(pyblish.api.InstancePlugin, invalid = set(nodes) - set(valid) if invalid: + # List as bullet points + invalid_bullets = "\n".join(f"- {node}" for node in invalid) + cls.log.error( - "These nodes are not allowed: {}.\n" - "The valid node types are: {}".format(", ".join(invalid), - ", ".join(cls.allowed)) + "These nodes are not allowed:\n{}\n" + "The valid node types are: {}".format( + invalid_bullets, ", ".join(cls.allowed)) ) return list(invalid) @@ -113,10 +118,20 @@ class ValidateModelContent(pyblish.api.InstancePlugin, raise PublishValidationError( title="Model content is invalid", message="Model content is invalid. See log for more details.", - description=( - "## Model content is invalid\n" - "Your model instance does not adhere to the rules of a " - "model.\n\n" - "See log for more details." - ) + description=self.get_description() ) + + @classmethod + def get_description(cls): + return inspect.cleandoc(f""" + ### Model content is invalid + + Your model instance does not adhere to the rules of a + model product type: + + - Must have at least one visible shape in it, like a mesh. + - Must have one root node. When exporting multiple meshes they + must be inside a group. + - May only contain the following node types: + {", ".join(cls.allowed)} + """) From 2701d4bf8aeddb4da0841b49d7d06451ee5f06d8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:46:15 +0200 Subject: [PATCH 108/203] removed unnecessary `reset` call --- client/ayon_core/tools/workfiles/control.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/tools/workfiles/control.py b/client/ayon_core/tools/workfiles/control.py index 2cdcd37d53..3048e6be94 100644 --- a/client/ayon_core/tools/workfiles/control.py +++ b/client/ayon_core/tools/workfiles/control.py @@ -727,7 +727,6 @@ class BaseWorkfileController( # Trigger after save events emit_event("workfile.save.after", event_data, source="workfiles.tool") - self.reset() def _change_current_context( self, project_name, folder_id, task_id, template_key=None From fca484dd01cb84f1b14f23d1d50690955eeae57a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 4 Apr 2024 17:11:26 +0200 Subject: [PATCH 109/203] Improve log readability --- .../hosts/maya/plugins/publish/validate_model_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py index 015f4c11ed..1001bee91f 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py @@ -59,7 +59,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin, invalid_bullets = "\n".join(f"- {node}" for node in invalid) cls.log.error( - "These nodes are not allowed:\n{}\n" + "These nodes are not allowed:\n{}\n\n" "The valid node types are: {}".format( invalid_bullets, ", ".join(cls.allowed)) ) From dc21231c291ebffb532151806e2e696ac3510fdc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 23:16:12 +0800 Subject: [PATCH 110/203] exposes asString as arguments for getting attributes for renderlayer & renderlayersetup --- client/ayon_core/hosts/maya/api/lib.py | 9 +++++---- client/ayon_core/hosts/maya/api/lib_renderproducts.py | 10 ++++------ client/ayon_core/hosts/maya/api/lib_rendersetup.py | 6 +++--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 060c16056c..89582bac8b 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -3129,7 +3129,7 @@ def load_capture_preset(data): return options -def get_attr_in_layer(attr, layer): +def get_attr_in_layer(attr, layer, as_string=True): """Return attribute value in specified renderlayer. Same as cmds.getAttr but this gets the attribute's value in a @@ -3156,7 +3156,8 @@ def get_attr_in_layer(attr, layer): try: if cmds.mayaHasRenderSetup(): from . import lib_rendersetup - return lib_rendersetup.get_attr_in_layer(attr, layer) + return lib_rendersetup.get_attr_in_layer( + attr, layer, as_string=as_string) except AttributeError: pass @@ -3173,7 +3174,7 @@ def get_attr_in_layer(attr, layer): type="renderLayer") or [] connections = filter(lambda x: x.endswith(".plug"), connections) if not connections: - return cmds.getAttr(attr) + return cmds.getAttr(attr, asString=as_string) # Some value types perform a conversion when assigning # TODO: See if there's a maya method to allow this conversion @@ -3215,7 +3216,7 @@ def get_attr_in_layer(attr, layer): value *= conversion return value - return cmds.getAttr(attr) + return cmds.getAttr(attr, asString=as_string) def fix_incompatible_containers(): diff --git a/client/ayon_core/hosts/maya/api/lib_renderproducts.py b/client/ayon_core/hosts/maya/api/lib_renderproducts.py index 6b73639040..832d1c21c2 100644 --- a/client/ayon_core/hosts/maya/api/lib_renderproducts.py +++ b/client/ayon_core/hosts/maya/api/lib_renderproducts.py @@ -297,7 +297,7 @@ class ARenderProducts: """ return self._get_attr("defaultRenderGlobals", attribute) - def _get_attr(self, node_attr, attribute=None): + def _get_attr(self, node_attr, attribute=None, as_string=True): """Return the value of the attribute in the renderlayer For readability this allows passing in the attribute in two ways. @@ -317,7 +317,7 @@ class ARenderProducts: else: plug = "{}.{}".format(node_attr, attribute) - return lib.get_attr_in_layer(plug, layer=self.layer) + return lib.get_attr_in_layer(plug, layer=self.layer, as_string=as_string) @staticmethod def extract_separator(file_prefix): @@ -1133,11 +1133,9 @@ class RenderProductsRedshift(ARenderProducts): aovs = list(set(aovs) - set(ref_aovs)) products = [] - # global_aov_enabled = bool( - # self._get_attr("redshiftOptions.aovGlobalEnableMode") - # ) global_aov_enabled = bool( - cmds.getAttr("redshiftOptions.aovGlobalEnableMode")) + self._get_attr("redshiftOptions.aovGlobalEnableMode", as_string=False) + ) colorspace = lib.get_color_management_output_transform() if not global_aov_enabled: # only beauty output diff --git a/client/ayon_core/hosts/maya/api/lib_rendersetup.py b/client/ayon_core/hosts/maya/api/lib_rendersetup.py index c2b5ec843c..6dca8eb6dd 100644 --- a/client/ayon_core/hosts/maya/api/lib_rendersetup.py +++ b/client/ayon_core/hosts/maya/api/lib_rendersetup.py @@ -77,7 +77,7 @@ def get_rendersetup_layer(layer): if conn.endswith(".legacyRenderLayer")), None) -def get_attr_in_layer(node_attr, layer): +def get_attr_in_layer(node_attr, layer, as_string=True): """Return attribute value in Render Setup layer. This will only work for attributes which can be @@ -124,7 +124,7 @@ def get_attr_in_layer(node_attr, layer): node = history_overrides[-1] if history_overrides else override node_attr_ = node + ".original" - return get_attribute(node_attr_, asString=True) + return get_attribute(node_attr_, asString=as_string) layer = get_rendersetup_layer(layer) rs = renderSetup.instance() @@ -144,7 +144,7 @@ def get_attr_in_layer(node_attr, layer): # we will let it error out. rs.switchToLayer(current_layer) - return get_attribute(node_attr, asString=True) + return get_attribute(node_attr, asString=as_string) overrides = get_attr_overrides(node_attr, layer) default_layer_value = get_default_layer_value(node_attr) From 8573a0f6cf63103284466d84c1c48ea6067e9fb0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 4 Apr 2024 17:51:00 +0200 Subject: [PATCH 111/203] Improve Creator error for new users --- .../hosts/maya/plugins/create/create_render.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/create/create_render.py b/client/ayon_core/hosts/maya/plugins/create/create_render.py index 213d5b543e..e5a8d4dbd8 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_render.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_render.py @@ -40,8 +40,15 @@ class CreateRenderlayer(plugin.RenderlayerCreator): def create(self, product_name, instance_data, pre_create_data): # Only allow a single render instance to exist if self._get_singleton_node(): - raise CreatorError("A Render instance already exists - only " - "one can be configured.") + raise CreatorError( + "A Render instance already exists - only one can be " + "configured.\n\n" + "To render multiple render layers, create extra Render Setup " + "Layers via Maya's Render Setup UI.\n" + "Then refresh the publisher to detect the new layers for " + "rendering.\n\n" + "With a render instance present all Render Setup layers in " + "your workfile are renderable instances.") # Apply default project render settings on create if self.render_settings.get("apply_render_settings"): From 55070d9528fd693ee6353d113d7b8e4ed3bc3838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Thu, 4 Apr 2024 23:54:57 +0200 Subject: [PATCH 112/203] Fix typos --- .../ayon_core/hosts/hiero/plugins/create/create_shot_clip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/hiero/plugins/create/create_shot_clip.py b/client/ayon_core/hosts/hiero/plugins/create/create_shot_clip.py index 2985a81317..1fc808fdd1 100644 --- a/client/ayon_core/hosts/hiero/plugins/create/create_shot_clip.py +++ b/client/ayon_core/hosts/hiero/plugins/create/create_shot_clip.py @@ -137,7 +137,7 @@ class CreateShotClip(phiero.Creator): "value": ["", "main", "bg", "fg", "bg", "animatic"], "type": "QComboBox", - "label": "pRODUCT Name", + "label": "Product Name", "target": "ui", "toolTip": "chose product name pattern, if is selected, name of track layer will be used", # noqa "order": 0}, @@ -159,7 +159,7 @@ class CreateShotClip(phiero.Creator): "type": "QCheckBox", "label": "Include audio", "target": "tag", - "toolTip": "Process productS with corresponding audio", # noqa + "toolTip": "Process products with corresponding audio", # noqa "order": 3}, "sourceResolution": { "value": False, From 26b7a53656062625fed2c62f1573ad0166a6dbc3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 5 Apr 2024 15:10:36 +0800 Subject: [PATCH 113/203] add docstring and make sure the backward compatible is converting to string value as before --- client/ayon_core/hosts/maya/api/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 89582bac8b..91f60824b6 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -3147,6 +3147,7 @@ def get_attr_in_layer(attr, layer, as_string=True): Args: attr (str): attribute name, ex. "node.attribute" layer (str): layer name + as_string (bool): whether attribute should convert to a string value Returns: The return value from `maya.cmds.getAttr` @@ -3165,7 +3166,7 @@ def get_attr_in_layer(attr, layer, as_string=True): current_layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) if layer == current_layer: - return cmds.getAttr(attr) + return cmds.getAttr(attr, asString=as_string) connections = cmds.listConnections(attr, plugs=True, @@ -3174,7 +3175,7 @@ def get_attr_in_layer(attr, layer, as_string=True): type="renderLayer") or [] connections = filter(lambda x: x.endswith(".plug"), connections) if not connections: - return cmds.getAttr(attr, asString=as_string) + return cmds.getAttr(attr) # Some value types perform a conversion when assigning # TODO: See if there's a maya method to allow this conversion From ba9350e945e05c9b5af2315fb0e39c339a8e05c4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 5 Apr 2024 12:00:27 +0200 Subject: [PATCH 114/203] Update client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- .../ayon_core/hosts/houdini/plugins/publish/collect_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py b/client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py index 3523acee06..6cf6bbf430 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/collect_inputs.py @@ -121,7 +121,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): # Embed the members into the container dictionary container_members = set(get_container_members(container)) container["_members"] = container_members - instance.context.data["__cache_containers"] = scene_containers + instance.context.data[cache_key] = scene_containers inputs = [] if scene_containers: From 1d286e7341c48e71715246f9603eb67a80fc61b3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 5 Apr 2024 20:08:51 +0800 Subject: [PATCH 115/203] refactor repair action --- .../publish/validate_instance_in_context.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py index ec80eb6e06..4e43e8811f 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py @@ -4,16 +4,19 @@ from __future__ import absolute_import import pyblish.api import ayon_core.hosts.maya.api.action +from ayon_core.pipeline import registered_host from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, PublishValidationError, OptionalPyblishPluginMixin ) +from ayon_core.pipeline.create import CreateContext from maya import cmds + class ValidateInstanceInContext(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Validator to check if instance asset match context asset. @@ -70,16 +73,20 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, context_folder_path, context_task = cls.get_context( instance) instance_node = instance.data["instance_node"] - cmds.setAttr( - "{}.folderPath".format(instance_node), - context_folder_path, - type="string" - ) - cmds.setAttr( - "{}.task".format(instance_node), - context_task, - type="string" - ) + host = registered_host() + create_context = CreateContext(host) + + instance_values = { + "folderPath": context_folder_path, + "task": context_task + } + + for instance in create_context.instances: + if instance["productName"] == instance_node: + for key, value in instance_values.items(): + instance[key] = value + + create_context.save_changes() @staticmethod def get_context(instance): From 287e5753f33410b892895510c5f566b7d1e31033 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 5 Apr 2024 21:30:12 +0800 Subject: [PATCH 116/203] use the instance data for getting create-context data instead --- .../publish/validate_instance_in_context.py | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py b/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py index 4e43e8811f..e6f4b908bb 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_instance_in_context.py @@ -4,17 +4,12 @@ from __future__ import absolute_import import pyblish.api import ayon_core.hosts.maya.api.action -from ayon_core.pipeline import registered_host from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, PublishValidationError, OptionalPyblishPluginMixin ) -from ayon_core.pipeline.create import CreateContext - -from maya import cmds - class ValidateInstanceInContext(pyblish.api.InstancePlugin, @@ -72,20 +67,14 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, def repair(cls, instance): context_folder_path, context_task = cls.get_context( instance) - instance_node = instance.data["instance_node"] - host = registered_host() - create_context = CreateContext(host) - - instance_values = { - "folderPath": context_folder_path, - "task": context_task - } - - for instance in create_context.instances: - if instance["productName"] == instance_node: - for key, value in instance_values.items(): - instance[key] = value + create_context = instance.context.data["create_context"] + instance_id = instance.data["instance_id"] + created_instance = create_context.get_instance_by_id( + instance_id + ) + created_instance["folderPath"] = context_folder_path + created_instance["task"] = context_task create_context.save_changes() @staticmethod From 9f7fe606f7f045005aa3d481682e1252ac67ab02 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 5 Apr 2024 21:44:50 +0800 Subject: [PATCH 117/203] refactor original resolution setting for validate review resolution setting --- .../publish/validate_resolution_setting.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index 48e2e1d45d..49beecbabc 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -15,7 +15,7 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, """Validate the resolution setting aligned with DB""" order = pyblish.api.ValidatorOrder - 0.01 - families = ["maxrender", "review"] + families = ["maxrender"] hosts = ["max"] label = "Validate Resolution Setting" optional = True @@ -25,8 +25,10 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, if not self.is_active(instance.data): return width, height = self.get_folder_resolution(instance) - current_width = rt.renderWidth - current_height = rt.renderHeight + current_width, current_height = ( + self.get_current_resolution(instance) + ) + if current_width != width and current_height != height: raise PublishValidationError("Resolution Setting " "not matching resolution " @@ -41,7 +43,11 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, "not matching resolution set " "on asset or shot.") - def get_folder_resolution(self, instance): + def get_current_resolution(self, instance): + return rt.renderWidth, rt.renderHeight + + @classmethod + def get_folder_resolution(cls, instance): task_entity = instance.data.get("taskEntity") if task_entity: task_attributes = task_entity["attrib"] @@ -55,3 +61,23 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): reset_scene_resolution() + + +class ValidateReviewResolutionSetting(ValidateResolutionSetting): + families = ["review"] + label = "Validate Review Animation Resolution Setting" + optional = True + actions = [RepairAction] + + def get_current_resolution(self, instance): + current_width = instance.data["review_width"] + current_height = instance.data["review_height"] + return current_width, current_height + + @classmethod + def repair(cls, instance): + context_width, context_height = ( + cls.get_folder_resolution(instance) + ) + instance.data["review_width"] = context_width + instance.data["review_height"] = context_height From 51569b17dbe3dc1113b316e148a45cbd9d3ee841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Fri, 5 Apr 2024 22:38:23 +0200 Subject: [PATCH 118/203] Pass env var used to login to ayon-shotgrid addon --- .../deadline/plugins/publish/submit_publish_cache_job.py | 2 +- .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- .../plugins/publish/create_publish_royalrender_job.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index 910b2e46db..4e4657d886 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -67,7 +67,7 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, "FTRACK_SERVER", "AYON_APP_NAME", "AYON_USERNAME", - "OPENPYPE_SG_USER", + "AYON_SG_USERNAME", "KITSU_LOGIN", "KITSU_PWD" ] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index af5839d0cf..8def9cc63c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -130,7 +130,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "FTRACK_SERVER", "AYON_APP_NAME", "AYON_USERNAME", - "OPENPYPE_SG_USER", + "AYON_SG_USERNAME", "KITSU_LOGIN", "KITSU_PWD" ] diff --git a/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py b/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py index 662913cadf..f3287b7638 100644 --- a/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py +++ b/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py @@ -65,7 +65,7 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin, "FTRACK_SERVER", "AYON_APP_NAME", "AYON_USERNAME", - "OPENPYPE_SG_USER", + "AYON_SG_USERNAME", ] priority = 50 From 78074e4d16324041d40091fe3412de6198e2959d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Fri, 5 Apr 2024 22:38:29 +0200 Subject: [PATCH 119/203] Fix typo --- .../hosts/hiero/plugins/publish/extract_thumbnail.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/hiero/plugins/publish/extract_thumbnail.py b/client/ayon_core/hosts/hiero/plugins/publish/extract_thumbnail.py index bcaf5308d9..3599a830d2 100644 --- a/client/ayon_core/hosts/hiero/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/hosts/hiero/plugins/publish/extract_thumbnail.py @@ -4,12 +4,12 @@ import pyblish.api from ayon_core.pipeline import publish -class ExtractThumnail(publish.Extractor): +class ExtractThumbnail(publish.Extractor): """ - Extractor for track item's tumnails + Extractor for track item's tumbnails """ - label = "Extract Thumnail" + label = "Extract Thumbnail" order = pyblish.api.ExtractorOrder families = ["plate", "take"] hosts = ["hiero"] @@ -48,7 +48,7 @@ class ExtractThumnail(publish.Extractor): self.log.debug( "__ thumb_path: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) - self.log.info("Thumnail was generated to: {}".format(thumb_path)) + self.log.info("Thumbnail was generated to: {}".format(thumb_path)) thumb_representation = { 'files': thumb_file, 'stagingDir': staging_dir, From a08b1786b0891a3cd4e6bb5d8d557f3bf749b7c4 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 8 Apr 2024 08:47:50 +0100 Subject: [PATCH 120/203] Account for multiple slate frames. --- .../hosts/nuke/plugins/load/load_clip.py | 23 ++++++++----------- .../plugins/publish/extract_slate_frame.py | 2 +- .../plugins/publish/extract_slate_data.py | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index 86a6d359c3..062a5295ed 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -132,16 +132,15 @@ class LoadClip(plugin.NukeLoader): # If a slate is present, the frame range is 1 frame longer for movies, # but file sequences its the first frame that is 1 frame lower. - slate_frame = repre_entity["data"].get("slateFrame", False) - if slate_frame: - extension = "." + repre_entity["context"]["ext"] + slate_frames = repre_entity["data"].get("slateFrames", 0) + extension = "." + repre_entity["context"]["ext"] - if extension in VIDEO_EXTENSIONS: - last += 1 + if extension in VIDEO_EXTENSIONS: + last += slate_frames - files_count = len(repre_entity["files"]) - if extension in IMAGE_EXTENSIONS and files_count != 1: - first -= 1 + files_count = len(repre_entity["files"]) + if extension in IMAGE_EXTENSIONS and files_count != 1: + first -= slate_frames # Fallback to folder name when namespace is None if namespace is None: @@ -181,7 +180,7 @@ class LoadClip(plugin.NukeLoader): ) self._set_range_to_node( - read_node, first, last, start_at_workfile, slate_frame + read_node, first, last, start_at_workfile, slate_frames ) version_name = version_entity["version"] @@ -418,7 +417,7 @@ class LoadClip(plugin.NukeLoader): nuke.delete(member) def _set_range_to_node( - self, read_node, first, last, start_at_workfile, slate_frame=False + self, read_node, first, last, start_at_workfile, slate_frames=0 ): read_node['origfirst'].setValue(int(first)) read_node['first'].setValue(int(first)) @@ -429,9 +428,7 @@ class LoadClip(plugin.NukeLoader): if start_at_workfile: read_node['frame_mode'].setValue("start at") - start_frame = self.script_start - if slate_frame: - start_frame -= 1 + start_frame = self.script_start - slate_frames read_node['frame'].setValue(str(start_frame)) diff --git a/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py b/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py index 67d2bd272c..627888ac92 100644 --- a/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/client/ayon_core/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -301,7 +301,7 @@ class ExtractSlateFrame(publish.Extractor): "__ matching_repre: {}".format(pformat(matching_repre))) data = matching_repre.get("data", {}) - data["slateFrame"] = True + data["slateFrames"] = 1 matching_repre["data"] = data self.log.info("Added slate frame to representation files") diff --git a/client/ayon_core/plugins/publish/extract_slate_data.py b/client/ayon_core/plugins/publish/extract_slate_data.py index 0a28083f1e..750fb5d60a 100644 --- a/client/ayon_core/plugins/publish/extract_slate_data.py +++ b/client/ayon_core/plugins/publish/extract_slate_data.py @@ -18,5 +18,5 @@ class ExtractSlateData(publish.Extractor): continue data = representation.get("data", {}) - data["slateFrame"] = True + data["slateFrames"] = 1 representation["data"] = data From 825add6c2cf51be99b8a1454e357f1ca7acfa891 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 10:26:40 +0200 Subject: [PATCH 121/203] Fix support for non-unique top group names --- .../hosts/maya/plugins/publish/validate_model_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py index 1001bee91f..bbc644c3db 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_model_content.py @@ -79,7 +79,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin, return [instance.data["instance_node"]] # Ensure single top group - top_parents = {x.split("|", 2)[1] for x in content_instance} + top_parents = {"|" + x.split("|", 2)[1] for x in content_instance} if cls.validate_top_group and len(top_parents) != 1: cls.log.error( "A model instance must have exactly one top group. " From 738f0924b5a92d3346f18ddae2e7959df8172423 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 9 Apr 2024 12:27:56 +0200 Subject: [PATCH 122/203] Update client/ayon_core/hosts/maya/plugins/publish/collect_look.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- client/ayon_core/hosts/maya/plugins/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py index 9d8bfd270d..a3a32bc0cb 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_look.py @@ -413,7 +413,7 @@ class CollectLook(pyblish.api.InstancePlugin): # RuntimeError history = set() if materials: - history = set(cmds.listHistory(materials, allConnections=True)) + history.update(cmds.listHistory(materials, allConnections=True)) # Since we retrieved history only of the connected materials connected # to the look sets above we now add direct history for some of the From c193e2e6144c259ee7dbcefaebfe9800c2a0bd3b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 13:19:12 +0200 Subject: [PATCH 123/203] Fix delivery - fixes #351 --- client/ayon_core/plugins/load/delivery.py | 27 +++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index f8184c8567..5fadf7ea3c 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -91,9 +91,15 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): longest_key = max(self.templates.keys(), key=len) dropdown.setMinimumContentsLength(len(longest_key)) - template_label = QtWidgets.QLabel() - template_label.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) - template_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + template_dir_label = QtWidgets.QLabel() + template_dir_label.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) + template_dir_label.setTextInteractionFlags( + QtCore.Qt.TextSelectableByMouse) + + template_file_label = QtWidgets.QLabel() + template_file_label.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) + template_file_label.setTextInteractionFlags( + QtCore.Qt.TextSelectableByMouse) renumber_frame = QtWidgets.QCheckBox() @@ -123,7 +129,8 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): input_layout.addRow("Selected representations", selected_label) input_layout.addRow("Delivery template", dropdown) - input_layout.addRow("Template value", template_label) + input_layout.addRow("Template directory", template_dir_label) + input_layout.addRow("Template file", template_file_label) input_layout.addRow("Renumber Frame", renumber_frame) input_layout.addRow("Renumber start frame", first_frame_start) input_layout.addRow("Root", root_line_edit) @@ -151,7 +158,8 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): layout.addWidget(text_area) self.selected_label = selected_label - self.template_label = template_label + self.template_dir_label = template_dir_label + self.template_file_label = template_file_label self.dropdown = dropdown self.first_frame_start = first_frame_start self.renumber_frame = renumber_frame @@ -282,10 +290,10 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): """Adds list of delivery templates from Anatomy to dropdown.""" templates = {} for template_name, value in anatomy.templates["delivery"].items(): - path_template = value["path"] + directory_template = value["directory"] if ( - not isinstance(path_template, str) - or not path_template.startswith('{root') + not isinstance(directory_template, str) + or not directory_template.startswith('{root') ): continue @@ -350,7 +358,8 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): name = self.dropdown.currentText() template_value = self.templates.get(name) if template_value: - self.template_label.setText(template_value) + self.template_dir_label.setText(template_value["directory"]) + self.template_file_label.setText(template_value["file"]) self.btn_delivery.setEnabled(bool(self._get_selected_repres())) def _update_progress(self, uploaded): From 010655007e1a71a92c783ec6d6c7e1c66c215ec6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 13:56:17 +0200 Subject: [PATCH 124/203] Update client/ayon_core/plugins/load/delivery.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/load/delivery.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 5fadf7ea3c..d685649496 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -129,8 +129,8 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): input_layout.addRow("Selected representations", selected_label) input_layout.addRow("Delivery template", dropdown) - input_layout.addRow("Template directory", template_dir_label) - input_layout.addRow("Template file", template_file_label) + input_layout.addRow("Directory template", template_dir_label) + input_layout.addRow("File template", template_file_label) input_layout.addRow("Renumber Frame", renumber_frame) input_layout.addRow("Renumber start frame", first_frame_start) input_layout.addRow("Root", root_line_edit) From e70651bd3f94742a588883cd20fa98db74bff0f0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 13:57:38 +0200 Subject: [PATCH 125/203] Skip string check --- client/ayon_core/plugins/load/delivery.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index d685649496..f363c6418f 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -291,10 +291,7 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): templates = {} for template_name, value in anatomy.templates["delivery"].items(): directory_template = value["directory"] - if ( - not isinstance(directory_template, str) - or not directory_template.startswith('{root') - ): + if not directory_template.startswith('{root'): continue templates[template_name] = value From 30460440539306de5f1d2ba6f5b26260feec9b99 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 13:57:49 +0200 Subject: [PATCH 126/203] Cosmetics --- client/ayon_core/plugins/load/delivery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index f363c6418f..7ad054b6b9 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -291,7 +291,7 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): templates = {} for template_name, value in anatomy.templates["delivery"].items(): directory_template = value["directory"] - if not directory_template.startswith('{root'): + if not directory_template.startswith("{root"): continue templates[template_name] = value From ecfb972332fe628dc822d6513344705c50f7e121 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 14:13:10 +0200 Subject: [PATCH 127/203] Log a warning --- client/ayon_core/plugins/load/delivery.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index 7ad054b6b9..e2a283a516 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -292,6 +292,10 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): for template_name, value in anatomy.templates["delivery"].items(): directory_template = value["directory"] if not directory_template.startswith("{root"): + self.log.warning( + "Skipping template '%s' because directory template does " + "not start with `{root`", template_name + ) continue templates[template_name] = value From 8ec3c80c5fe59f0fa65b0e1aaa80bd2843c628a4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 14:13:49 +0200 Subject: [PATCH 128/203] Log the directory template for better log message --- client/ayon_core/plugins/load/delivery.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index e2a283a516..cd358846ea 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -294,7 +294,8 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): if not directory_template.startswith("{root"): self.log.warning( "Skipping template '%s' because directory template does " - "not start with `{root`", template_name + "not start with `{root` in value: %s", + template_name, directory_template ) continue From 17e7c9ff00aebb94f5198a9dcf30407f41a5f301 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:23:45 +0200 Subject: [PATCH 129/203] change 'representations' attribute on loader plugin to 'set' --- client/ayon_core/pipeline/load/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 064af4ddc1..3f47ec421e 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -24,7 +24,7 @@ class LoaderPlugin(list): """ product_types = set() - representations = [] + representations = set() extensions = {"*"} order = 0 is_multiple_contexts_compatible = False From 43b4be851ede5043729446de171d30b0fd717640 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:26:28 +0200 Subject: [PATCH 130/203] use 'set' for 'representations' attribute --- .../hosts/aftereffects/plugins/load/load_background.py | 2 +- .../ayon_core/hosts/aftereffects/plugins/load/load_file.py | 2 +- .../ayon_core/hosts/blender/plugins/load/import_workfile.py | 4 ++-- client/ayon_core/hosts/blender/plugins/load/load_abc.py | 2 +- client/ayon_core/hosts/blender/plugins/load/load_action.py | 2 +- .../ayon_core/hosts/blender/plugins/load/load_animation.py | 2 +- client/ayon_core/hosts/blender/plugins/load/load_audio.py | 2 +- client/ayon_core/hosts/blender/plugins/load/load_blend.py | 2 +- .../ayon_core/hosts/blender/plugins/load/load_blendscene.py | 2 +- .../ayon_core/hosts/blender/plugins/load/load_camera_abc.py | 2 +- .../ayon_core/hosts/blender/plugins/load/load_camera_fbx.py | 2 +- client/ayon_core/hosts/blender/plugins/load/load_fbx.py | 2 +- .../hosts/blender/plugins/load/load_layout_json.py | 2 +- client/ayon_core/hosts/blender/plugins/load/load_look.py | 2 +- client/ayon_core/hosts/flame/plugins/load/load_clip.py | 2 +- .../ayon_core/hosts/flame/plugins/load/load_clip_batch.py | 2 +- client/ayon_core/hosts/fusion/plugins/load/actions.py | 4 ++-- client/ayon_core/hosts/fusion/plugins/load/load_alembic.py | 2 +- client/ayon_core/hosts/fusion/plugins/load/load_fbx.py | 2 +- client/ayon_core/hosts/fusion/plugins/load/load_sequence.py | 2 +- client/ayon_core/hosts/fusion/plugins/load/load_usd.py | 2 +- client/ayon_core/hosts/fusion/plugins/load/load_workfile.py | 2 +- client/ayon_core/hosts/harmony/api/README.md | 2 +- client/ayon_core/hosts/harmony/plugins/load/load_audio.py | 2 +- .../ayon_core/hosts/harmony/plugins/load/load_background.py | 2 +- .../hosts/harmony/plugins/load/load_imagesequence.py | 2 +- client/ayon_core/hosts/harmony/plugins/load/load_palette.py | 2 +- .../ayon_core/hosts/harmony/plugins/load/load_template.py | 2 +- .../hosts/harmony/plugins/load/load_template_workfile.py | 4 ++-- client/ayon_core/hosts/hiero/plugins/load/load_clip.py | 2 +- client/ayon_core/hosts/hiero/plugins/load/load_effects.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/actions.py | 4 ++-- client/ayon_core/hosts/houdini/plugins/load/load_alembic.py | 2 +- .../hosts/houdini/plugins/load/load_alembic_archive.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/load_ass.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py | 4 ++-- client/ayon_core/hosts/houdini/plugins/load/load_camera.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/load_fbx.py | 2 +- .../ayon_core/hosts/houdini/plugins/load/load_filepath.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/load_hda.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/load_image.py | 2 +- .../hosts/houdini/plugins/load/load_redshift_proxy.py | 2 +- .../ayon_core/hosts/houdini/plugins/load/load_usd_layer.py | 2 +- .../hosts/houdini/plugins/load/load_usd_reference.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/load_vdb.py | 2 +- client/ayon_core/hosts/houdini/plugins/load/show_usdview.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_max_scene.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_model.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_model_fbx.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_model_obj.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_model_usd.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_pointcache.py | 2 +- .../hosts/max/plugins/load/load_pointcache_ornatrix.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_pointcloud.py | 2 +- .../ayon_core/hosts/max/plugins/load/load_redshift_proxy.py | 2 +- client/ayon_core/hosts/max/plugins/load/load_tycache.py | 2 +- client/ayon_core/hosts/maya/plugins/load/_load_animation.py | 4 ++-- client/ayon_core/hosts/maya/plugins/load/actions.py | 6 +++--- .../hosts/maya/plugins/load/load_arnold_standin.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_assembly.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_audio.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_gpucache.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_image.py | 2 +- .../ayon_core/hosts/maya/plugins/load/load_image_plane.py | 4 ++-- client/ayon_core/hosts/maya/plugins/load/load_look.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_matchmove.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py | 2 +- .../hosts/maya/plugins/load/load_multiverse_usd.py | 2 +- .../hosts/maya/plugins/load/load_multiverse_usd_over.py | 2 +- .../hosts/maya/plugins/load/load_redshift_proxy.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_reference.py | 4 ++-- .../ayon_core/hosts/maya/plugins/load/load_rendersetup.py | 2 +- .../ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py | 2 +- .../hosts/maya/plugins/load/load_vdb_to_redshift.py | 2 +- .../ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_xgen.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py | 2 +- client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/actions.py | 4 ++-- client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_clip.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_effects.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_image.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_model.py | 2 +- client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py | 2 +- .../hosts/nuke/plugins/load/load_script_precomp.py | 2 +- client/ayon_core/hosts/photoshop/api/README.md | 2 +- client/ayon_core/hosts/photoshop/plugins/load/load_image.py | 2 +- .../photoshop/plugins/load/load_image_from_sequence.py | 2 +- .../hosts/photoshop/plugins/load/load_reference.py | 2 +- client/ayon_core/hosts/resolve/plugins/load/load_clip.py | 2 +- .../hosts/substancepainter/plugins/load/load_mesh.py | 2 +- client/ayon_core/hosts/tvpaint/plugins/load/load_image.py | 2 +- .../hosts/tvpaint/plugins/load/load_reference_image.py | 2 +- client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py | 2 +- .../ayon_core/hosts/tvpaint/plugins/load/load_workfile.py | 2 +- .../hosts/unreal/plugins/load/load_alembic_animation.py | 2 +- .../ayon_core/hosts/unreal/plugins/load/load_animation.py | 2 +- client/ayon_core/hosts/unreal/plugins/load/load_camera.py | 2 +- .../hosts/unreal/plugins/load/load_geometrycache_abc.py | 2 +- client/ayon_core/hosts/unreal/plugins/load/load_layout.py | 2 +- .../hosts/unreal/plugins/load/load_layout_existing.py | 2 +- .../hosts/unreal/plugins/load/load_skeletalmesh_abc.py | 2 +- .../hosts/unreal/plugins/load/load_skeletalmesh_fbx.py | 2 +- .../hosts/unreal/plugins/load/load_staticmesh_abc.py | 2 +- .../hosts/unreal/plugins/load/load_staticmesh_fbx.py | 2 +- client/ayon_core/hosts/unreal/plugins/load/load_uasset.py | 4 ++-- .../ayon_core/hosts/unreal/plugins/load/load_yeticache.py | 2 +- client/ayon_core/plugins/load/copy_file.py | 2 +- client/ayon_core/plugins/load/copy_file_path.py | 2 +- client/ayon_core/plugins/load/delete_old_versions.py | 2 +- client/ayon_core/plugins/load/delivery.py | 2 +- client/ayon_core/plugins/load/open_file.py | 2 +- client/ayon_core/plugins/load/push_to_library.py | 2 +- 123 files changed, 135 insertions(+), 135 deletions(-) diff --git a/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py b/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py index 7c9bd2fcfd..5685011d5f 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py +++ b/client/ayon_core/hosts/aftereffects/plugins/load/load_background.py @@ -21,7 +21,7 @@ class BackgroundLoader(api.AfterEffectsLoader): """ label = "Load JSON Background" product_types = {"background"} - representations = ["json"] + representations = {"json"} def load(self, context, name=None, namespace=None, data=None): stub = self.get_stub() diff --git a/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py b/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py index 79e791af7b..4b81201722 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py +++ b/client/ayon_core/hosts/aftereffects/plugins/load/load_file.py @@ -20,7 +20,7 @@ class FileLoader(api.AfterEffectsLoader): "review", "audio", } - representations = ["*"] + representations = {"*"} def load(self, context, name=None, namespace=None, data=None): stub = self.get_stub() diff --git a/client/ayon_core/hosts/blender/plugins/load/import_workfile.py b/client/ayon_core/hosts/blender/plugins/load/import_workfile.py index 3aa73a5143..d2e58c7752 100644 --- a/client/ayon_core/hosts/blender/plugins/load/import_workfile.py +++ b/client/ayon_core/hosts/blender/plugins/load/import_workfile.py @@ -43,7 +43,7 @@ class AppendBlendLoader(plugin.AssetLoader): so you could also use it as a new base. """ - representations = ["blend"] + representations = {"blend"} product_types = {"workfile"} label = "Append Workfile" @@ -68,7 +68,7 @@ class ImportBlendLoader(plugin.AssetLoader): so you could also use it as a new base. """ - representations = ["blend"] + representations = {"blend"} product_types = {"workfile"} label = "Import Workfile" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_abc.py b/client/ayon_core/hosts/blender/plugins/load/load_abc.py index 938ae6106b..c074b5ed13 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_abc.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_abc.py @@ -27,7 +27,7 @@ class CacheModelLoader(plugin.AssetLoader): At least for now it only supports Alembic files. """ product_types = {"model", "pointcache", "animation"} - representations = ["abc"] + representations = {"abc"} label = "Load Alembic" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_action.py b/client/ayon_core/hosts/blender/plugins/load/load_action.py index 4161f8bff3..8135df042a 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_action.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_action.py @@ -25,7 +25,7 @@ class BlendActionLoader(plugin.AssetLoader): """ product_types = {"action"} - representations = ["blend"] + representations = {"blend"} label = "Link Action" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_animation.py b/client/ayon_core/hosts/blender/plugins/load/load_animation.py index effb91c48c..c9f3b33a6f 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_animation.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_animation.py @@ -17,7 +17,7 @@ class BlendAnimationLoader(plugin.AssetLoader): """ product_types = {"animation"} - representations = ["blend"] + representations = {"blend"} label = "Link Animation" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_audio.py b/client/ayon_core/hosts/blender/plugins/load/load_audio.py index db83c4bca2..3d2f412e2b 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_audio.py @@ -21,7 +21,7 @@ class AudioLoader(plugin.AssetLoader): """Load audio in Blender.""" product_types = {"audio"} - representations = ["wav"] + representations = {"wav"} label = "Load Audio" icon = "volume-up" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_blend.py b/client/ayon_core/hosts/blender/plugins/load/load_blend.py index 1984193a30..f9377d615c 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_blend.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_blend.py @@ -21,7 +21,7 @@ class BlendLoader(plugin.AssetLoader): """Load assets from a .blend file.""" product_types = {"model", "rig", "layout", "camera"} - representations = ["blend"] + representations = {"blend"} label = "Append Blend" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py b/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py index 627941752f..f91d828d83 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_blendscene.py @@ -19,7 +19,7 @@ class BlendSceneLoader(plugin.AssetLoader): """Load assets from a .blend file.""" product_types = {"blendScene"} - representations = ["blend"] + representations = {"blend"} label = "Append Blend" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py b/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py index e677fc3e58..6178578081 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_camera_abc.py @@ -24,7 +24,7 @@ class AbcCameraLoader(plugin.AssetLoader): """ product_types = {"camera"} - representations = ["abc"] + representations = {"abc"} label = "Load Camera (ABC)" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_camera_fbx.py b/client/ayon_core/hosts/blender/plugins/load/load_camera_fbx.py index 14d61a6395..a510d42850 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_camera_fbx.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_camera_fbx.py @@ -24,7 +24,7 @@ class FbxCameraLoader(plugin.AssetLoader): """ product_types = {"camera"} - representations = ["fbx"] + representations = {"fbx"} label = "Load Camera (FBX)" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_fbx.py b/client/ayon_core/hosts/blender/plugins/load/load_fbx.py index 0042482284..e323d49dea 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_fbx.py @@ -24,7 +24,7 @@ class FbxModelLoader(plugin.AssetLoader): """ product_types = {"model", "rig"} - representations = ["fbx"] + representations = {"fbx"} label = "Load FBX" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py b/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py index 7a3da1882e..bea997108b 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py @@ -27,7 +27,7 @@ class JsonLayoutLoader(plugin.AssetLoader): """Load layout published from Unreal.""" product_types = {"layout"} - representations = ["json"] + representations = {"json"} label = "Load Layout" icon = "code-fork" diff --git a/client/ayon_core/hosts/blender/plugins/load/load_look.py b/client/ayon_core/hosts/blender/plugins/load/load_look.py index ce677a8471..75401f94ec 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_look.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_look.py @@ -24,7 +24,7 @@ class BlendLookLoader(plugin.AssetLoader): """ product_types = {"look"} - representations = ["json"] + representations = {"json"} label = "Load Look" icon = "code-fork" diff --git a/client/ayon_core/hosts/flame/plugins/load/load_clip.py b/client/ayon_core/hosts/flame/plugins/load/load_clip.py index f528caeb29..40ab9c038b 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip.py @@ -18,7 +18,7 @@ class LoadClip(opfapi.ClipLoader): """ product_types = {"render2d", "source", "plate", "render", "review"} - representations = ["*"] + representations = {"*"} extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) ) diff --git a/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py b/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py index 9bdd467d63..1b23a8b465 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py @@ -17,7 +17,7 @@ class LoadClipBatch(opfapi.ClipLoader): """ product_types = {"render2d", "source", "plate", "render", "review"} - representations = ["*"] + representations = {"*"} extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) ) diff --git a/client/ayon_core/hosts/fusion/plugins/load/actions.py b/client/ayon_core/hosts/fusion/plugins/load/actions.py index 9600479680..95400ea41c 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/actions.py +++ b/client/ayon_core/hosts/fusion/plugins/load/actions.py @@ -17,7 +17,7 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin): "pointcache", "render", } - representations = ["*"] + representations = {"*"} extensions = {"*"} label = "Set frame range" @@ -54,7 +54,7 @@ class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin): "pointcache", "render", } - representations = ["*"] + representations = {"*"} label = "Set frame range (with handles)" order = 12 diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py b/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py index ae2175964d..312362caca 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_alembic.py @@ -13,7 +13,7 @@ class FusionLoadAlembicMesh(load.LoaderPlugin): """Load Alembic mesh into Fusion""" product_types = {"pointcache", "model"} - representations = ["*"] + representations = {"*"} extensions = {"abc"} label = "Load alembic mesh" diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py b/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py index 68b7cdacd1..a84e7e0914 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_fbx.py @@ -13,7 +13,7 @@ class FusionLoadFBXMesh(load.LoaderPlugin): """Load FBX mesh into Fusion""" product_types = {"*"} - representations = ["*"] + representations = {"*"} extensions = { "3ds", "amc", diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py b/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py index f0a8233377..7c70b54e48 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_sequence.py @@ -137,7 +137,7 @@ class FusionLoadSequence(load.LoaderPlugin): "image", "online", } - representations = ["*"] + representations = {"*"} extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) ) diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_usd.py b/client/ayon_core/hosts/fusion/plugins/load/load_usd.py index 2f8eeb4c66..309b0c094c 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_usd.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_usd.py @@ -17,7 +17,7 @@ class FusionLoadUSD(load.LoaderPlugin): """ product_types = {"*"} - representations = ["*"] + representations = {"*"} extensions = {"usd", "usda", "usdz"} label = "Load USD" diff --git a/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py b/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py index fd2fa7c08b..818fbcb187 100644 --- a/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/fusion/plugins/load/load_workfile.py @@ -15,7 +15,7 @@ class FusionLoadWorkfile(load.LoaderPlugin): """Load the content of a workfile into Fusion""" product_types = {"workfile"} - representations = ["*"] + representations = {"*"} extensions = {"comp"} label = "Load Workfile" diff --git a/client/ayon_core/hosts/harmony/api/README.md b/client/ayon_core/hosts/harmony/api/README.md index 7ac185638a..b8d1dbc100 100644 --- a/client/ayon_core/hosts/harmony/api/README.md +++ b/client/ayon_core/hosts/harmony/api/README.md @@ -590,7 +590,7 @@ class ImageSequenceLoader(load.LoaderPlugin): "reference", "review", } - representations = ["*"] + representations = {"*"} extensions = {"jpeg", "png", "jpg"} def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_audio.py b/client/ayon_core/hosts/harmony/plugins/load/load_audio.py index 00f3ac77ec..d23f3ed034 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_audio.py @@ -36,7 +36,7 @@ class ImportAudioLoader(load.LoaderPlugin): """Import audio.""" product_types = {"shot", "audio"} - representations = ["wav"] + representations = {"wav"} label = "Import Audio" def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_background.py b/client/ayon_core/hosts/harmony/plugins/load/load_background.py index 74bc5a4fd8..dad6ac2f22 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_background.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_background.py @@ -234,7 +234,7 @@ class BackgroundLoader(load.LoaderPlugin): Stores the imported asset in a container named after the asset. """ product_types = {"background"} - representations = ["json"] + representations = {"json"} def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py b/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py index bf4b87a03e..f81ebca9af 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_imagesequence.py @@ -28,7 +28,7 @@ class ImageSequenceLoader(load.LoaderPlugin): "reference", "review", } - representations = ["*"] + representations = {"*"} extensions = {"jpeg", "png", "jpg"} def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_palette.py b/client/ayon_core/hosts/harmony/plugins/load/load_palette.py index d5fbeb323b..24f4b4e8d4 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_palette.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_palette.py @@ -12,7 +12,7 @@ class ImportPaletteLoader(load.LoaderPlugin): """Import palettes.""" product_types = {"palette", "harmony.palette"} - representations = ["plt"] + representations = {"plt"} label = "Import Palette" def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_template.py b/client/ayon_core/hosts/harmony/plugins/load/load_template.py index 48064f2b75..96dadb0375 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_template.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_template.py @@ -24,7 +24,7 @@ class TemplateLoader(load.LoaderPlugin): """ product_types = {"template", "workfile"} - representations = ["*"] + representations = {"*"} label = "Load Template" icon = "gift" diff --git a/client/ayon_core/hosts/harmony/plugins/load/load_template_workfile.py b/client/ayon_core/hosts/harmony/plugins/load/load_template_workfile.py index c7132ce373..fa5ffe5105 100644 --- a/client/ayon_core/hosts/harmony/plugins/load/load_template_workfile.py +++ b/client/ayon_core/hosts/harmony/plugins/load/load_template_workfile.py @@ -14,7 +14,7 @@ class ImportTemplateLoader(load.LoaderPlugin): """Import templates.""" product_types = {"harmony.template", "workfile"} - representations = ["*"] + representations = {"*"} label = "Import Template" def load(self, context, name=None, namespace=None, data=None): @@ -61,5 +61,5 @@ class ImportWorkfileLoader(ImportTemplateLoader): """Import workfiles.""" product_types = {"workfile"} - representations = ["zip"] + representations = {"zip"} label = "Import Workfile" diff --git a/client/ayon_core/hosts/hiero/plugins/load/load_clip.py b/client/ayon_core/hosts/hiero/plugins/load/load_clip.py index 72d7e03a9a..715e8c508e 100644 --- a/client/ayon_core/hosts/hiero/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/hiero/plugins/load/load_clip.py @@ -16,7 +16,7 @@ class LoadClip(phiero.SequenceLoader): """ product_types = {"render2d", "source", "plate", "render", "review"} - representations = ["*"] + representations = {"*"} extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) ) diff --git a/client/ayon_core/hosts/hiero/plugins/load/load_effects.py b/client/ayon_core/hosts/hiero/plugins/load/load_effects.py index fd6b8ed694..92aa2de325 100644 --- a/client/ayon_core/hosts/hiero/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/hiero/plugins/load/load_effects.py @@ -15,7 +15,7 @@ class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" product_types = {"effect"} - representations = ["*"] + representations = {"*"} extension = {"json"} label = "Load Effects" diff --git a/client/ayon_core/hosts/houdini/plugins/load/actions.py b/client/ayon_core/hosts/houdini/plugins/load/actions.py index fbd89ab9c2..3e9cc35504 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/actions.py +++ b/client/ayon_core/hosts/houdini/plugins/load/actions.py @@ -15,7 +15,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): "vdbcache", "usd", } - representations = ["abc", "vdb", "usd"] + representations = {"abc", "vdb", "usd"} label = "Set frame range" order = 11 @@ -52,7 +52,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): "vdbcache", "usd", } - representations = ["abc", "vdb", "usd"] + representations = {"abc", "vdb", "usd"} label = "Set frame range (with handles)" order = 12 diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py index 37657cbdff..1bb9043cd0 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py @@ -11,7 +11,7 @@ class AbcLoader(load.LoaderPlugin): product_types = {"model", "animation", "pointcache", "gpuCache"} label = "Load Alembic" - representations = ["*"] + representations = {"*"} extensions = {"abc"} order = -10 icon = "code-fork" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_alembic_archive.py b/client/ayon_core/hosts/houdini/plugins/load/load_alembic_archive.py index 39928fd952..a231bd9993 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_alembic_archive.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_alembic_archive.py @@ -11,7 +11,7 @@ class AbcArchiveLoader(load.LoaderPlugin): product_types = {"model", "animation", "pointcache", "gpuCache"} label = "Load Alembic as Archive" - representations = ["*"] + representations = {"*"} extensions = {"abc"} order = -5 icon = "code-fork" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_ass.py b/client/ayon_core/hosts/houdini/plugins/load/load_ass.py index fd0e8f4604..6e0922e305 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_ass.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_ass.py @@ -13,7 +13,7 @@ class AssLoader(load.LoaderPlugin): product_types = {"ass"} label = "Load Arnold Procedural" - representations = ["ass"] + representations = {"ass"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py b/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py index fd8071c0de..a318b71963 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_bgeo.py @@ -14,9 +14,9 @@ class BgeoLoader(load.LoaderPlugin): label = "Load bgeo" product_types = {"model", "pointcache", "bgeo"} - representations = [ + representations = { "bgeo", "bgeosc", "bgeogz", - "bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"] + "bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py index 7cb4542d0c..b7912f88f1 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_camera.py @@ -89,7 +89,7 @@ class CameraLoader(load.LoaderPlugin): product_types = {"camera"} label = "Load Camera (abc)" - representations = ["abc"] + representations = {"abc"} order = -10 icon = "code-fork" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py b/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py index a0c5e0c934..398019a3bd 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_fbx.py @@ -17,7 +17,7 @@ class FbxLoader(load.LoaderPlugin): order = -10 product_types = {"*"} - representations = ["*"] + representations = {"*"} extensions = {"fbx"} def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_filepath.py b/client/ayon_core/hosts/houdini/plugins/load/load_filepath.py index f107190f96..d189a027fd 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_filepath.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_filepath.py @@ -22,7 +22,7 @@ class FilePathLoader(load.LoaderPlugin): icon = "link" color = "white" product_types = {"*"} - representations = ["*"] + representations = {"*"} def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_hda.py b/client/ayon_core/hosts/houdini/plugins/load/load_hda.py index df77783a34..10fc03be03 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_hda.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_hda.py @@ -12,7 +12,7 @@ class HdaLoader(load.LoaderPlugin): product_types = {"hda"} label = "Load Hda" - representations = ["hda"] + representations = {"hda"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_image.py b/client/ayon_core/hosts/houdini/plugins/load/load_image.py index 0429b1c3fe..dfbd3c11eb 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_image.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_image.py @@ -54,7 +54,7 @@ class ImageLoader(load.LoaderPlugin): "online", } label = "Load Image (COP2)" - representations = ["*"] + representations = {"*"} order = -10 icon = "code-fork" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_redshift_proxy.py b/client/ayon_core/hosts/houdini/plugins/load/load_redshift_proxy.py index a6556619fc..f09856a970 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_redshift_proxy.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_redshift_proxy.py @@ -15,7 +15,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): product_types = {"redshiftproxy"} label = "Load Redshift Proxy" - representations = ["rs"] + representations = {"rs"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_usd_layer.py b/client/ayon_core/hosts/houdini/plugins/load/load_usd_layer.py index 19950e2c98..4e6954c531 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_usd_layer.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_usd_layer.py @@ -14,7 +14,7 @@ class USDSublayerLoader(load.LoaderPlugin): "usdCamera", } label = "Sublayer USD" - representations = ["usd", "usda", "usdlc", "usdnc", "abc"] + representations = {"usd", "usda", "usdlc", "usdnc", "abc"} order = 1 icon = "code-fork" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_usd_reference.py b/client/ayon_core/hosts/houdini/plugins/load/load_usd_reference.py index 25f98c7c7c..7e82a6abd0 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_usd_reference.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_usd_reference.py @@ -14,7 +14,7 @@ class USDReferenceLoader(load.LoaderPlugin): "usdCamera", } label = "Reference USD" - representations = ["usd", "usda", "usdlc", "usdnc", "abc"] + representations = {"usd", "usda", "usdlc", "usdnc", "abc"} order = -8 icon = "code-fork" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py b/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py index 5b7e022e73..506f6140bf 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py @@ -9,7 +9,7 @@ class SopUsdImportLoader(load.LoaderPlugin): label = "Load USD to SOPs" product_types = {"*"} - representations = ["usd"] + representations = {"usd"} order = -6 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py b/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py index d9808020d7..0008f0d5f8 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_vdb.py @@ -13,7 +13,7 @@ class VdbLoader(load.LoaderPlugin): product_types = {"vdbcache"} label = "Load VDB" - representations = ["vdb"] + representations = {"vdb"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py b/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py index 9506d9dd0c..0158a6b963 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py +++ b/client/ayon_core/hosts/houdini/plugins/load/show_usdview.py @@ -10,7 +10,7 @@ class ShowInUsdview(load.LoaderPlugin): """Open USD file in usdview""" label = "Show in usdview" - representations = ["*"] + representations = {"*"} product_types = {"*"} extensions = {"usd", "usda", "usdlc", "usdnc", "abc"} order = 15 diff --git a/client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py b/client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py index e1de6b98f9..6f1e9988c5 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py +++ b/client/ayon_core/hosts/max/plugins/load/load_camera_fbx.py @@ -19,7 +19,7 @@ class FbxLoader(load.LoaderPlugin): """Fbx Loader.""" product_types = {"camera"} - representations = ["fbx"] + representations = {"fbx"} order = -9 icon = "code-fork" color = "white" diff --git a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py index 1c2c5317cc..4f982dd5ba 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_max_scene.py +++ b/client/ayon_core/hosts/max/plugins/load/load_max_scene.py @@ -78,7 +78,7 @@ class MaxSceneLoader(load.LoaderPlugin): "model", } - representations = ["max"] + representations = {"max"} order = -8 icon = "code-fork" color = "green" diff --git a/client/ayon_core/hosts/max/plugins/load/load_model.py b/client/ayon_core/hosts/max/plugins/load/load_model.py index 00e675d69c..1070fce2bd 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model.py @@ -16,7 +16,7 @@ class ModelAbcLoader(load.LoaderPlugin): product_types = {"model"} label = "Load Model with Alembic" - representations = ["abc"] + representations = {"abc"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py b/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py index 4b87c60de0..82cad71c3e 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_fbx.py @@ -18,7 +18,7 @@ class FbxModelLoader(load.LoaderPlugin): """Fbx Model Loader.""" product_types = {"model"} - representations = ["fbx"] + representations = {"fbx"} order = -9 icon = "code-fork" color = "white" diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_obj.py b/client/ayon_core/hosts/max/plugins/load/load_model_obj.py index 2330dbfc24..38f2cdf43c 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_obj.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_obj.py @@ -20,7 +20,7 @@ class ObjLoader(load.LoaderPlugin): """Obj Loader.""" product_types = {"model"} - representations = ["obj"] + representations = {"obj"} order = -9 icon = "code-fork" color = "white" diff --git a/client/ayon_core/hosts/max/plugins/load/load_model_usd.py b/client/ayon_core/hosts/max/plugins/load/load_model_usd.py index bde23e119e..2b946eb2aa 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_model_usd.py +++ b/client/ayon_core/hosts/max/plugins/load/load_model_usd.py @@ -24,7 +24,7 @@ class ModelUSDLoader(load.LoaderPlugin): product_types = {"model"} label = "Load Model(USD)" - representations = ["usda"] + representations = {"usda"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/max/plugins/load/load_pointcache.py b/client/ayon_core/hosts/max/plugins/load/load_pointcache.py index 7f515ac6a5..0743b3bb34 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcache.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcache.py @@ -20,7 +20,7 @@ class AbcLoader(load.LoaderPlugin): product_types = {"camera", "animation", "pointcache"} label = "Load Alembic" - representations = ["abc"] + representations = {"abc"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py b/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py index 31d3f02ec0..2efb7c7f62 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcache_ornatrix.py @@ -23,7 +23,7 @@ class OxAbcLoader(load.LoaderPlugin): product_types = {"camera", "animation", "pointcache"} label = "Load Alembic with Ornatrix" - representations = ["abc"] + representations = {"abc"} order = -10 icon = "code-fork" color = "orange" diff --git a/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py b/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py index c0000c7a79..0e79882fc5 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py +++ b/client/ayon_core/hosts/max/plugins/load/load_pointcloud.py @@ -18,7 +18,7 @@ class PointCloudLoader(load.LoaderPlugin): """Point Cloud Loader.""" product_types = {"pointcloud"} - representations = ["prt"] + representations = {"prt"} order = -8 icon = "code-fork" color = "green" diff --git a/client/ayon_core/hosts/max/plugins/load/load_redshift_proxy.py b/client/ayon_core/hosts/max/plugins/load/load_redshift_proxy.py index ff6811bd1b..22d42390d9 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_redshift_proxy.py +++ b/client/ayon_core/hosts/max/plugins/load/load_redshift_proxy.py @@ -24,7 +24,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): label = "Load Redshift Proxy" product_types = {"redshiftproxy"} - representations = ["rs"] + representations = {"rs"} order = -9 icon = "code-fork" color = "white" diff --git a/client/ayon_core/hosts/max/plugins/load/load_tycache.py b/client/ayon_core/hosts/max/plugins/load/load_tycache.py index 0244e4e6fc..7a5296d933 100644 --- a/client/ayon_core/hosts/max/plugins/load/load_tycache.py +++ b/client/ayon_core/hosts/max/plugins/load/load_tycache.py @@ -17,7 +17,7 @@ class TyCacheLoader(load.LoaderPlugin): """TyCache Loader.""" product_types = {"tycache"} - representations = ["tyc"] + representations = {"tyc"} order = -8 icon = "code-fork" color = "green" diff --git a/client/ayon_core/hosts/maya/plugins/load/_load_animation.py b/client/ayon_core/hosts/maya/plugins/load/_load_animation.py index 884bdd7538..393f6b0115 100644 --- a/client/ayon_core/hosts/maya/plugins/load/_load_animation.py +++ b/client/ayon_core/hosts/maya/plugins/load/_load_animation.py @@ -51,7 +51,7 @@ class AbcLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): "camera", "pointcache", } - representations = ["abc"] + representations = {"abc"} label = "Reference animation" order = -10 @@ -81,7 +81,7 @@ class FbxLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): "animation", "camera", } - representations = ["fbx"] + representations = {"fbx"} label = "Reference animation" order = -10 diff --git a/client/ayon_core/hosts/maya/plugins/load/actions.py b/client/ayon_core/hosts/maya/plugins/load/actions.py index a98fe97692..3e21d600ac 100644 --- a/client/ayon_core/hosts/maya/plugins/load/actions.py +++ b/client/ayon_core/hosts/maya/plugins/load/actions.py @@ -19,7 +19,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): "proxyAbc", "pointcache", } - representations = ["abc"] + representations = {"abc"} label = "Set frame range" order = 11 @@ -54,7 +54,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): "proxyAbc", "pointcache", } - representations = ["abc"] + representations = {"abc"} label = "Set frame range (with handles)" order = 12 @@ -94,7 +94,7 @@ class ImportMayaLoader(ayon_core.hosts.maya.api.plugin.Loader): so you could also use it as a new base. """ - representations = ["ma", "mb", "obj"] + representations = {"ma", "mb", "obj"} product_types = { "model", "pointcache", diff --git a/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py b/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py index 7170c30422..4b7d2f42ab 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py @@ -31,7 +31,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): product_types = { "ass", "animation", "model", "proxyAbc", "pointcache", "usd" } - representations = ["ass", "abc", "usda", "usdc", "usd"] + representations = {"ass", "abc", "usda", "usdc", "usd"} label = "Load as Arnold standin" order = -5 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_assembly.py b/client/ayon_core/hosts/maya/plugins/load/load_assembly.py index a0cbf91905..0fcbc6bd07 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_assembly.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_assembly.py @@ -13,7 +13,7 @@ from ayon_core.hosts.maya.api import setdress class AssemblyLoader(load.LoaderPlugin): product_types = {"assembly"} - representations = ["json"] + representations = {"json"} label = "Load Set Dress" order = -9 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_audio.py b/client/ayon_core/hosts/maya/plugins/load/load_audio.py index 0a40993fcd..228189f1a1 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_audio.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_audio.py @@ -13,7 +13,7 @@ class AudioLoader(load.LoaderPlugin): product_types = {"audio"} label = "Load audio" - representations = ["wav"] + representations = {"wav"} icon = "volume-up" color = "orange" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py b/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py index 9689282ae9..9832d2d657 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py @@ -14,7 +14,7 @@ class GpuCacheLoader(load.LoaderPlugin): """Load Alembic as gpuCache""" product_types = {"model", "animation", "proxyAbc", "pointcache"} - representations = ["abc", "gpu_cache"] + representations = {"abc", "gpu_cache"} label = "Load Gpu Cache" order = -5 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_image.py b/client/ayon_core/hosts/maya/plugins/load/load_image.py index 3641655d49..5b0858ce70 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_image.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_image.py @@ -93,7 +93,7 @@ class FileNodeLoader(load.LoaderPlugin): product_types = {"image", "plate", "render"} label = "Load file node" - representations = ["exr", "tif", "png", "jpg"] + representations = {"exr", "tif", "png", "jpg"} icon = "image" color = "orange" order = 2 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_image_plane.py b/client/ayon_core/hosts/maya/plugins/load/load_image_plane.py index b298d5b892..15c7654c52 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_image_plane.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_image_plane.py @@ -89,7 +89,7 @@ class ImagePlaneLoader(load.LoaderPlugin): product_types = {"image", "plate", "render"} label = "Load imagePlane" - representations = ["mov", "exr", "preview", "png", "jpg"] + representations = {"mov", "exr", "preview", "png", "jpg"} icon = "image" color = "orange" @@ -171,7 +171,7 @@ class ImagePlaneLoader(load.LoaderPlugin): plug = "{}.{}".format(image_plane_shape, attr) cmds.setAttr(plug, value) - movie_representations = ["mov", "preview"] + movie_representations = {"mov", "preview"} if context["representation"]["name"] in movie_representations: cmds.setAttr(image_plane_shape + ".type", 2) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_look.py b/client/ayon_core/hosts/maya/plugins/load/load_look.py index f126a1df26..af0e000dd2 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_look.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_look.py @@ -18,7 +18,7 @@ class LookLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Specific loader for lookdev""" product_types = {"look"} - representations = ["ma"] + representations = {"ma"} label = "Reference look" order = -10 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py b/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py index 05da173bb7..b19b14b1aa 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_matchmove.py @@ -9,7 +9,7 @@ class MatchmoveLoader(load.LoaderPlugin): """ product_types = {"matchmove"} - representations = ["py", "mel"] + representations = {"py", "mel"} defaults = ["Camera", "Object", "Mocap"] label = "Run matchmove script" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py b/client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py index cd73c26de1..628a25e574 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_maya_usd.py @@ -17,7 +17,7 @@ class MayaUsdLoader(load.LoaderPlugin): """Read USD data in a Maya USD Proxy""" product_types = {"model", "usd", "pointcache", "animation"} - representations = ["usd", "usda", "usdc", "usdz", "abc"] + representations = {"usd", "usda", "usdc", "usdz", "abc"} label = "Load USD to Maya Proxy" order = -1 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd.py b/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd.py index 984d14dff3..f32c76481d 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd.py @@ -28,7 +28,7 @@ class MultiverseUsdLoader(load.LoaderPlugin): "pointcache", "animation", } - representations = ["usd", "usda", "usdc", "usdz", "abc"] + representations = {"usd", "usda", "usdc", "usdz", "abc"} label = "Load USD to Multiverse" order = -10 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd_over.py b/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd_over.py index dc5bc6ec1c..b23fa48f07 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd_over.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_multiverse_usd_over.py @@ -20,7 +20,7 @@ class MultiverseUsdOverLoader(load.LoaderPlugin): """Reference file""" product_types = {"mvUsdOverride"} - representations = ["usda", "usd", "udsz"] + representations = {"usda", "usd", "udsz"} label = "Load Usd Override into Compound" order = -10 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py b/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py index 0f91d9048a..7760d4081c 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py @@ -23,7 +23,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): """Load Redshift proxy""" product_types = {"redshiftproxy"} - representations = ["rs"] + representations = {"rs"} label = "Import Redshift Proxy" order = -10 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_reference.py b/client/ayon_core/hosts/maya/plugins/load/load_reference.py index de18b2b0ec..847591bd11 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_reference.py @@ -107,7 +107,7 @@ class ReferenceLoader(plugin.ReferenceLoader): "matchmove", } - representations = ["ma", "abc", "fbx", "mb"] + representations = {"ma", "abc", "fbx", "mb"} label = "Reference" order = -10 @@ -269,7 +269,7 @@ class MayaUSDReferenceLoader(ReferenceLoader): label = "Reference Maya USD" product_types = {"usd"} - representations = ["usd"] + representations = {"usd"} extensions = {"usd", "usda", "usdc"} options = ReferenceLoader.options + [ diff --git a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py index 7096f86e35..d5685b2c4c 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_rendersetup.py @@ -52,7 +52,7 @@ class RenderSetupLoader(load.LoaderPlugin): """Load json preset for RenderSetup overwriting current one.""" product_types = {"rendersetup"} - representations = ["json"] + representations = {"json"} defaults = ['Main'] label = "Load RenderSetup template" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py index 0780a7b3e7..5b0c78fd6f 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py @@ -13,7 +13,7 @@ class LoadVDBtoArnold(load.LoaderPlugin): """Load OpenVDB for Arnold in aiVolume""" product_types = {"vdbcache"} - representations = ["vdb"] + representations = {"vdb"} label = "Load VDB to Arnold" icon = "cloud" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py index 3fa490f405..e345a7bf6f 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -19,7 +19,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin): """ product_types = {"vdbcache"} - representations = ["vdb"] + representations = {"vdb"} label = "Load VDB to RedShift" icon = "cloud" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py index 7b87c21f38..d6d1c948b0 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py @@ -78,7 +78,7 @@ class LoadVDBtoVRay(load.LoaderPlugin): """Load OpenVDB in a V-Ray Volume Grid""" product_types = {"vdbcache"} - representations = ["vdb"] + representations = {"vdb"} label = "Load VDB to VRay" icon = "cloud" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py index 895a4a4127..14d645021c 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py @@ -28,7 +28,7 @@ class VRayProxyLoader(load.LoaderPlugin): """Load VRay Proxy with Alembic or VrayMesh.""" product_types = {"vrayproxy", "model", "pointcache", "animation"} - representations = ["vrmesh", "abc"] + representations = {"vrmesh", "abc"} label = "Import VRay Proxy" order = -10 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py index 36a25e2af1..ea3215da97 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py @@ -18,7 +18,7 @@ class VRaySceneLoader(load.LoaderPlugin): """Load Vray scene""" product_types = {"vrayscene_layer"} - representations = ["vrscene"] + representations = {"vrscene"} label = "Import VRay Scene" order = -10 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_xgen.py b/client/ayon_core/hosts/maya/plugins/load/load_xgen.py index 880efd82e1..e2664439b0 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_xgen.py @@ -21,7 +21,7 @@ class XgenLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): """Load Xgen as reference""" product_types = {"xgen"} - representations = ["ma", "mb"] + representations = {"ma", "mb"} label = "Reference Xgen" icon = "code-fork" diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py index a5cd04b0f4..caea6b7a72 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py @@ -37,7 +37,7 @@ class YetiCacheLoader(load.LoaderPlugin): """Load Yeti Cache with one or more Yeti nodes""" product_types = {"yeticache", "yetiRig"} - representations = ["fur"] + representations = {"fur"} label = "Load Yeti Cache" order = -9 diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py index 74e33c5866..bf9525bae3 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py @@ -8,7 +8,7 @@ class YetiRigLoader(plugin.ReferenceLoader): """This loader will load Yeti rig.""" product_types = {"yetiRig"} - representations = ["ma"] + representations = {"ma"} label = "Load Yeti Rig" order = -9 diff --git a/client/ayon_core/hosts/nuke/plugins/load/actions.py b/client/ayon_core/hosts/nuke/plugins/load/actions.py index a1b3697ef1..53cb03087b 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/actions.py +++ b/client/ayon_core/hosts/nuke/plugins/load/actions.py @@ -19,7 +19,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): "yeticache", "pointcache", } - representations = ["*"] + representations = {"*"} extensions = {"*"} label = "Set frame range" @@ -53,7 +53,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin): "yeticache", "pointcache", } - representations = ["*"] + representations = {"*"} label = "Set frame range (with handles)" order = 12 diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py b/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py index e48dbf5e2f..7d823919dc 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_backdrop.py @@ -22,7 +22,7 @@ class LoadBackdropNodes(load.LoaderPlugin): """Loading Published Backdrop nodes (workfile, nukenodes)""" product_types = {"workfile", "nukenodes"} - representations = ["*"] + representations = {"*"} extensions = {"nk"} label = "Import Nuke Nodes" diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py b/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py index 70b736b1c8..14c54c3adc 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_camera_abc.py @@ -21,7 +21,7 @@ class AlembicCameraLoader(load.LoaderPlugin): """ product_types = {"camera"} - representations = ["*"] + representations = {"*"} extensions = {"abc"} label = "Load Alembic Camera" diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py index 062a5295ed..df8f2ab018 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_clip.py @@ -42,7 +42,7 @@ class LoadClip(plugin.NukeLoader): "prerender", "review", } - representations = ["*"] + representations = {"*"} extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) ) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py b/client/ayon_core/hosts/nuke/plugins/load/load_effects.py index f17b179d1b..a87c81295a 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_effects.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_effects.py @@ -19,7 +19,7 @@ class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" product_types = {"effect"} - representations = ["*"] + representations = {"*"} extensions = {"json"} label = "Load Effects - nodes" diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py b/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py index 6b58977a95..8fa1347598 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_effects_ip.py @@ -20,7 +20,7 @@ class LoadEffectsInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" product_types = {"effect"} - representations = ["*"] + representations = {"*"} extensions = {"json"} label = "Load Effects - Input Process" diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py index 6709648ffb..95f85bacfc 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo.py @@ -22,7 +22,7 @@ class LoadGizmo(load.LoaderPlugin): """Loading nuke Gizmo""" product_types = {"gizmo"} - representations = ["*"] + representations = {"*"} extensions = {"nk"} label = "Load Gizmo" diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py index 3017fa5fc9..3112e27811 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -24,7 +24,7 @@ class LoadGizmoInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" product_types = {"gizmo"} - representations = ["*"] + representations = {"*"} extensions = {"nk"} label = "Load Gizmo - Input Process" diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_image.py b/client/ayon_core/hosts/nuke/plugins/load/load_image.py index 9d3f9ceea0..d825b621fc 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_image.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_image.py @@ -32,7 +32,7 @@ class LoadImage(load.LoaderPlugin): "review", "image", } - representations = ["*"] + representations = {"*"} extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS ) diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py b/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py index 73c21376b4..beebd0458f 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_matchmove.py @@ -8,7 +8,7 @@ class MatchmoveLoader(load.LoaderPlugin): """ product_types = {"matchmove"} - representations = ["*"] + representations = {"*"} extensions = {"py"} defaults = ["Camera", "Object"] diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_model.py b/client/ayon_core/hosts/nuke/plugins/load/load_model.py index 971c36b6cf..0326e0a4fc 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_model.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_model.py @@ -19,7 +19,7 @@ class AlembicModelLoader(load.LoaderPlugin): """ product_types = {"model", "pointcache", "animation"} - representations = ["*"] + representations = {"*"} extensions = {"abc"} label = "Load Alembic" diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py b/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py index 82addcdfc0..c369030b65 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_ociolook.py @@ -21,7 +21,7 @@ class LoadOcioLookNodes(load.LoaderPlugin): """Loading Ocio look to the nuke.Node graph""" product_types = {"ociolook"} - representations = ["*"] + representations = {"*"} extensions = {"json"} label = "Load OcioLook [nodes]" diff --git a/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py b/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py index ccc4164355..3e554f9d3b 100644 --- a/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py +++ b/client/ayon_core/hosts/nuke/plugins/load/load_script_precomp.py @@ -17,7 +17,7 @@ class LinkAsGroup(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" product_types = {"workfile", "nukenodes"} - representations = ["*"] + representations = {"*"} extensions = {"nk"} label = "Load Precomp" diff --git a/client/ayon_core/hosts/photoshop/api/README.md b/client/ayon_core/hosts/photoshop/api/README.md index c936c1ec1f..b391131a42 100644 --- a/client/ayon_core/hosts/photoshop/api/README.md +++ b/client/ayon_core/hosts/photoshop/api/README.md @@ -207,7 +207,7 @@ class ImageLoader(load.LoaderPlugin): """ families = ["image"] - representations = ["*"] + representations = {"*"} def load(self, context, name=None, namespace=None, data=None): path = self.filepath_from_context(context) diff --git a/client/ayon_core/hosts/photoshop/plugins/load/load_image.py b/client/ayon_core/hosts/photoshop/plugins/load/load_image.py index 72df2706b5..d71067615e 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_image.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_image.py @@ -12,7 +12,7 @@ class ImageLoader(photoshop.PhotoshopLoader): """ product_types = {"image", "render"} - representations = ["*"] + representations = {"*"} def load(self, context, name=None, namespace=None, data=None): stub = self.get_stub() diff --git a/client/ayon_core/hosts/photoshop/plugins/load/load_image_from_sequence.py b/client/ayon_core/hosts/photoshop/plugins/load/load_image_from_sequence.py index 73e8c3683c..dd14543f3e 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_image_from_sequence.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_image_from_sequence.py @@ -25,7 +25,7 @@ class ImageFromSequenceLoader(photoshop.PhotoshopLoader): """ product_types = {"render"} - representations = ["*"] + representations = {"*"} options = [] def load(self, context, name=None, namespace=None, data=None): diff --git a/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py b/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py index 7cd34690f7..b563faff82 100644 --- a/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/photoshop/plugins/load/load_reference.py @@ -15,7 +15,7 @@ class ReferenceLoader(photoshop.PhotoshopLoader): """ product_types = {"image", "render"} - representations = ["*"] + representations = {"*"} def load(self, context, name=None, namespace=None, data=None): stub = self.get_stub() diff --git a/client/ayon_core/hosts/resolve/plugins/load/load_clip.py b/client/ayon_core/hosts/resolve/plugins/load/load_clip.py index c7bced5e8e..2ce1c43957 100644 --- a/client/ayon_core/hosts/resolve/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/resolve/plugins/load/load_clip.py @@ -20,7 +20,7 @@ class LoadClip(plugin.TimelineItemLoader): product_types = {"render2d", "source", "plate", "render", "review"} - representations = ["*"] + representations = {"*"} extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) ) diff --git a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py index d940d7b05c..01cb65dd5c 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -18,7 +18,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" product_types = {"*"} - representations = ["abc", "fbx", "obj", "gltf", "usd", "usda", "usdc"] + representations = {"abc", "fbx", "obj", "gltf", "usd", "usda", "usdc"} label = "Load mesh" order = -10 diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py index e954b72f12..aad8f92d26 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_image.py @@ -7,7 +7,7 @@ class ImportImage(plugin.Loader): """Load image or image sequence to TVPaint as new layer.""" product_types = {"render", "image", "background", "plate", "review"} - representations = ["*"] + representations = {"*"} label = "Import Image" order = 1 diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py index ec671b0cb1..a7fcb9f4a4 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_reference_image.py @@ -18,7 +18,7 @@ class LoadImage(plugin.Loader): """Load image or image sequence to TVPaint as new layer.""" product_types = {"render", "image", "background", "plate", "review"} - representations = ["*"] + representations = {"*"} label = "Load Image" order = 1 diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py index d8c6a7a430..7e8c8022d6 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_sound.py @@ -23,7 +23,7 @@ class ImportSound(plugin.Loader): """ product_types = {"audio", "review", "plate"} - representations = ["*"] + representations = {"*"} label = "Import Sound" order = 1 diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py index 8663dac997..407671e14d 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py @@ -25,7 +25,7 @@ class LoadWorkfile(plugin.Loader): """Load workfile.""" product_types = {"workfile"} - representations = ["tvpp"] + representations = {"tvpp"} label = "Load Workfile" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py b/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py index 64d684939c..a12f4f41b4 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_alembic_animation.py @@ -16,7 +16,7 @@ class AnimationAlembicLoader(plugin.Loader): product_types = {"animation"} label = "Import Alembic Animation" - representations = ["abc"] + representations = {"abc"} icon = "cube" color = "orange" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_animation.py b/client/ayon_core/hosts/unreal/plugins/load/load_animation.py index 59b9f66b78..f6a612ce53 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_animation.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_animation.py @@ -22,7 +22,7 @@ class AnimationFBXLoader(plugin.Loader): product_types = {"animation"} label = "Import FBX Animation" - representations = ["fbx"] + representations = {"fbx"} icon = "cube" color = "orange" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_camera.py b/client/ayon_core/hosts/unreal/plugins/load/load_camera.py index 285834c911..681c83c6a1 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_camera.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_camera.py @@ -30,7 +30,7 @@ class CameraLoader(plugin.Loader): product_types = {"camera"} label = "Load Camera" - representations = ["fbx"] + representations = {"fbx"} icon = "cube" color = "orange" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_geometrycache_abc.py b/client/ayon_core/hosts/unreal/plugins/load/load_geometrycache_abc.py index 44c308069b..ae7d41192a 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_geometrycache_abc.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_geometrycache_abc.py @@ -21,7 +21,7 @@ class PointCacheAlembicLoader(plugin.Loader): product_types = {"model", "pointcache"} label = "Import Alembic Point Cache" - representations = ["abc"] + representations = {"abc"} icon = "cube" color = "orange" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py index b0f09ee8b0..49d95c6459 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py @@ -41,7 +41,7 @@ class LayoutLoader(plugin.Loader): """Load Layout from a JSON file""" product_types = {"layout"} - representations = ["json"] + representations = {"json"} label = "Load Layout" icon = "code-fork" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py b/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py index 56e36f6185..f9d438367b 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout_existing.py @@ -22,7 +22,7 @@ class ExistingLayoutLoader(plugin.Loader): """ product_types = {"layout"} - representations = ["json"] + representations = {"json"} label = "Load Layout on Existing Scene" icon = "code-fork" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_abc.py b/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_abc.py index 64b1810080..dfc5d58708 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_abc.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_abc.py @@ -20,7 +20,7 @@ class SkeletalMeshAlembicLoader(plugin.Loader): product_types = {"pointcache", "skeletalMesh"} label = "Import Alembic Skeletal Mesh" - representations = ["abc"] + representations = {"abc"} icon = "cube" color = "orange" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py b/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py index f61f0dbc3f..513404ab98 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_skeletalmesh_fbx.py @@ -20,7 +20,7 @@ class SkeletalMeshFBXLoader(plugin.Loader): product_types = {"rig", "skeletalMesh"} label = "Import FBX Skeletal Mesh" - representations = ["fbx"] + representations = {"fbx"} icon = "cube" color = "orange" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_abc.py b/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_abc.py index 256cb9e8bc..0bf6ce9eaa 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_abc.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_abc.py @@ -20,7 +20,7 @@ class StaticMeshAlembicLoader(plugin.Loader): product_types = {"model", "staticMesh"} label = "Import Alembic Static Mesh" - representations = ["abc"] + representations = {"abc"} icon = "cube" color = "orange" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_fbx.py b/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_fbx.py index 0ec4b1b4f8..b7bb57ac23 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_fbx.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_staticmesh_fbx.py @@ -20,7 +20,7 @@ class StaticMeshFBXLoader(plugin.Loader): product_types = {"model", "staticMesh"} label = "Import FBX Static Mesh" - representations = ["fbx"] + representations = {"fbx"} icon = "cube" color = "orange" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py b/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py index 89ef357c89..63f23ecc11 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_uasset.py @@ -17,7 +17,7 @@ class UAssetLoader(plugin.Loader): product_types = {"uasset"} label = "Load UAsset" - representations = ["uasset"] + representations = {"uasset"} icon = "cube" color = "orange" @@ -166,6 +166,6 @@ class UMapLoader(UAssetLoader): product_types = {"uasset"} label = "Load Level" - representations = ["umap"] + representations = {"umap"} extension = "umap" diff --git a/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py b/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py index 21715a24c6..708fc83745 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_yeticache.py @@ -17,7 +17,7 @@ class YetiLoader(plugin.Loader): product_types = {"yeticacheUE"} label = "Import Yeti" - representations = ["abc"] + representations = {"abc"} icon = "pagelines" color = "orange" diff --git a/client/ayon_core/plugins/load/copy_file.py b/client/ayon_core/plugins/load/copy_file.py index 5e6daa866b..08dad03be3 100644 --- a/client/ayon_core/plugins/load/copy_file.py +++ b/client/ayon_core/plugins/load/copy_file.py @@ -5,7 +5,7 @@ from ayon_core.pipeline import load class CopyFile(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" - representations = ["*"] + representations = {"*"} product_types = {"*"} label = "Copy File" diff --git a/client/ayon_core/plugins/load/copy_file_path.py b/client/ayon_core/plugins/load/copy_file_path.py index ecde3bc55f..fdf31b5e02 100644 --- a/client/ayon_core/plugins/load/copy_file_path.py +++ b/client/ayon_core/plugins/load/copy_file_path.py @@ -5,7 +5,7 @@ from ayon_core.pipeline import load class CopyFilePath(load.LoaderPlugin): """Copy published file path to clipboard""" - representations = ["*"] + representations = {"*"} product_types = {"*"} label = "Copy File Path" diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 04873d8b5c..8e04fd9827 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -26,7 +26,7 @@ # is_multiple_contexts_compatible = True # sequence_splitter = "__sequence_splitter__" # -# representations = ["*"] +# representations = {"*"} # product_types = {"*"} # tool_names = ["library_loader"] # diff --git a/client/ayon_core/plugins/load/delivery.py b/client/ayon_core/plugins/load/delivery.py index cd358846ea..c7954a18b2 100644 --- a/client/ayon_core/plugins/load/delivery.py +++ b/client/ayon_core/plugins/load/delivery.py @@ -28,7 +28,7 @@ class Delivery(load.ProductLoaderPlugin): is_multiple_contexts_compatible = True sequence_splitter = "__sequence_splitter__" - representations = ["*"] + representations = {"*"} product_types = {"*"} tool_names = ["library_loader"] diff --git a/client/ayon_core/plugins/load/open_file.py b/client/ayon_core/plugins/load/open_file.py index fc57708cd6..3b5fbbc0c9 100644 --- a/client/ayon_core/plugins/load/open_file.py +++ b/client/ayon_core/plugins/load/open_file.py @@ -19,7 +19,7 @@ class OpenFile(load.LoaderPlugin): """Open Image Sequence or Video with system default""" product_types = {"render2d"} - representations = ["*"] + representations = {"*"} label = "Open" order = -10 diff --git a/client/ayon_core/plugins/load/push_to_library.py b/client/ayon_core/plugins/load/push_to_library.py index 02d834bc95..981028d734 100644 --- a/client/ayon_core/plugins/load/push_to_library.py +++ b/client/ayon_core/plugins/load/push_to_library.py @@ -11,7 +11,7 @@ class PushToLibraryProject(load.ProductLoaderPlugin): is_multiple_contexts_compatible = True - representations = ["*"] + representations = {"*"} product_types = {"*"} label = "Push to Library project" From 84f8a61cc64d85f71441bf253a45042ec67efc4e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:28:49 +0200 Subject: [PATCH 131/203] add docstring --- client/ayon_core/pipeline/load/plugins.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 3f47ec421e..2475800cbb 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -162,6 +162,15 @@ class LoaderPlugin(list): @classmethod def get_representations(cls): + """Representation names with which is plugin compatible. + + Empty set makes the plugin incompatible with any representation. To + allow compatibility with all representations use '{"*"}'. + + Returns: + set[str]: Names with which is plugin compatible. + + """ return cls.representations @classmethod From 590f1db63e1254d6fb2be4eac1ea22fb60762afb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 10 Apr 2024 09:54:31 +0200 Subject: [PATCH 132/203] Bump Maya addon version --- server_addon/maya/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index e332ce4b6e..75b463f198 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.14" +__version__ = "0.1.15" From 3c73c1762740d7385f951b39195abe719ea96134 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:03:05 +0200 Subject: [PATCH 133/203] removed unused imports in settings --- server_addon/maya/server/settings/loaders.py | 2 +- server_addon/maya/server/settings/publish_playblast.py | 2 +- server_addon/tvpaint/server/settings/publish_plugins.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server_addon/maya/server/settings/loaders.py b/server_addon/maya/server/settings/loaders.py index 4e949f616a..f59711b1e6 100644 --- a/server_addon/maya/server/settings/loaders.py +++ b/server_addon/maya/server/settings/loaders.py @@ -1,5 +1,5 @@ from ayon_server.settings import BaseSettingsModel, SettingsField -from ayon_server.types import ColorRGB_float, ColorRGBA_uint8 +from ayon_server.types import ColorRGBA_uint8 class LoaderEnabledModel(BaseSettingsModel): diff --git a/server_addon/maya/server/settings/publish_playblast.py b/server_addon/maya/server/settings/publish_playblast.py index 39f48bacbe..d513a43e99 100644 --- a/server_addon/maya/server/settings/publish_playblast.py +++ b/server_addon/maya/server/settings/publish_playblast.py @@ -6,7 +6,7 @@ from ayon_server.settings import ( ensure_unique_names, task_types_enum, ) -from ayon_server.types import ColorRGBA_uint8, ColorRGB_float +from ayon_server.types import ColorRGBA_uint8 def hardware_falloff_enum(): diff --git a/server_addon/tvpaint/server/settings/publish_plugins.py b/server_addon/tvpaint/server/settings/publish_plugins.py index 0d978e5714..db1c7bd11a 100644 --- a/server_addon/tvpaint/server/settings/publish_plugins.py +++ b/server_addon/tvpaint/server/settings/publish_plugins.py @@ -1,5 +1,5 @@ from ayon_server.settings import BaseSettingsModel, SettingsField -from ayon_server.types import ColorRGBA_uint8, ColorRGB_uint8 +from ayon_server.types import ColorRGBA_uint8 class CollectRenderInstancesModel(BaseSettingsModel): From 640bbaadec212d35c8a68335361395034ac22cbf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:04:48 +0200 Subject: [PATCH 134/203] remove unused import in ayon core --- .../publish/validate_look_default_shaders_connections.py | 1 - client/ayon_core/hosts/nuke/api/plugin.py | 2 +- .../plugins/publish/create_publish_royalrender_job.py | 1 - client/ayon_core/pipeline/anatomy/templates.py | 1 - client/ayon_core/pipeline/context_tools.py | 2 -- client/ayon_core/pipeline/schema/__init__.py | 1 - client/ayon_core/plugins/actions/open_file_explorer.py | 2 -- 7 files changed, 1 insertion(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/client/ayon_core/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index d8a9222c36..cfd4156124 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -2,7 +2,6 @@ from maya import cmds import pyblish.api from ayon_core.pipeline.publish import ( - ValidateContentsOrder, RepairContextAction, PublishValidationError ) diff --git a/client/ayon_core/hosts/nuke/api/plugin.py b/client/ayon_core/hosts/nuke/api/plugin.py index 6aa098c558..5b97fab0c2 100644 --- a/client/ayon_core/hosts/nuke/api/plugin.py +++ b/client/ayon_core/hosts/nuke/api/plugin.py @@ -5,7 +5,7 @@ import sys import six import random import string -from collections import OrderedDict, defaultdict +from collections import defaultdict from ayon_core.settings import get_current_project_settings from ayon_core.lib import ( diff --git a/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py b/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py index f3287b7638..51500f84f5 100644 --- a/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py +++ b/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py @@ -3,7 +3,6 @@ import os import attr import json -import re import pyblish.api diff --git a/client/ayon_core/pipeline/anatomy/templates.py b/client/ayon_core/pipeline/anatomy/templates.py index 46cad385f0..d89b70719e 100644 --- a/client/ayon_core/pipeline/anatomy/templates.py +++ b/client/ayon_core/pipeline/anatomy/templates.py @@ -14,7 +14,6 @@ from .exceptions import ( TemplateMissingKey, AnatomyTemplateUnsolved, ) -from .roots import RootItem _PLACEHOLDER = object() diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index e9151bcd1f..33567d7280 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -1,7 +1,6 @@ """Core pipeline functionality""" import os -import types import logging import platform import uuid @@ -21,7 +20,6 @@ from .anatomy import Anatomy from .template_data import get_template_data_with_names from .workfile import ( get_workdir, - get_workfile_template_key, get_custom_workfile_template_by_string_context, ) from . import ( diff --git a/client/ayon_core/pipeline/schema/__init__.py b/client/ayon_core/pipeline/schema/__init__.py index 67cf120b59..db98a6d080 100644 --- a/client/ayon_core/pipeline/schema/__init__.py +++ b/client/ayon_core/pipeline/schema/__init__.py @@ -13,7 +13,6 @@ Resources: """ import os -import re import json import logging diff --git a/client/ayon_core/plugins/actions/open_file_explorer.py b/client/ayon_core/plugins/actions/open_file_explorer.py index 6a456c75c1..50a3107444 100644 --- a/client/ayon_core/plugins/actions/open_file_explorer.py +++ b/client/ayon_core/plugins/actions/open_file_explorer.py @@ -3,8 +3,6 @@ import platform import subprocess from string import Formatter -import ayon_api - from ayon_core.pipeline import ( Anatomy, LauncherAction, From 75bb2b7a46b13e619b853a2a6b1b7ae7969b9619 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:09:12 +0200 Subject: [PATCH 135/203] remove python 2 compatible type hint in houdini this is because ruff does not use this type hinting --- .../hosts/houdini/plugins/create/create_alembic_camera.py | 4 ++-- .../hosts/houdini/plugins/create/create_arnold_ass.py | 2 +- .../hosts/houdini/plugins/create/create_arnold_rop.py | 2 +- client/ayon_core/hosts/houdini/plugins/create/create_bgeo.py | 4 ++-- .../hosts/houdini/plugins/create/create_composite.py | 4 ++-- client/ayon_core/hosts/houdini/plugins/create/create_hda.py | 2 +- .../hosts/houdini/plugins/create/create_karma_rop.py | 3 +-- .../hosts/houdini/plugins/create/create_mantra_ifd.py | 3 +-- .../hosts/houdini/plugins/create/create_mantra_rop.py | 3 +-- client/ayon_core/hosts/houdini/plugins/create/create_usd.py | 3 +-- .../hosts/houdini/plugins/create/create_usdrender.py | 3 +-- .../hosts/houdini/plugins/create/create_vbd_cache.py | 3 +-- .../ayon_core/hosts/houdini/plugins/create/create_vray_rop.py | 4 ++-- .../hosts/houdini/plugins/publish/increment_current_file.py | 3 +-- 14 files changed, 18 insertions(+), 25 deletions(-) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_alembic_camera.py b/client/ayon_core/hosts/houdini/plugins/create/create_alembic_camera.py index b61b4cbd46..0ab5e2794e 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_alembic_camera.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_alembic_camera.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating alembic camera products.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance, CreatorError +from ayon_core.pipeline import CreatorError import hou @@ -23,7 +23,7 @@ class CreateAlembicCamera(plugin.HoudiniCreator): instance = super(CreateAlembicCamera, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) parms = { diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_ass.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_ass.py index 6d992f136a..be5604c01c 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_ass.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_ass.py @@ -29,7 +29,7 @@ class CreateArnoldAss(plugin.HoudiniCreator): instance = super(CreateArnoldAss, self).create( product_name, instance_data, - pre_create_data) # type: plugin.CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py index b7c5910a4f..f65b54a452 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_arnold_rop.py @@ -31,7 +31,7 @@ class CreateArnoldRop(plugin.HoudiniCreator): instance = super(CreateArnoldRop, self).create( product_name, instance_data, - pre_create_data) # type: plugin.CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_bgeo.py b/client/ayon_core/hosts/houdini/plugins/create/create_bgeo.py index 92c89c71cb..3749598b1d 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_bgeo.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_bgeo.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating pointcache bgeo files.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance, CreatorError +from ayon_core.pipeline import CreatorError import hou from ayon_core.lib import EnumDef, BoolDef @@ -25,7 +25,7 @@ class CreateBGEO(plugin.HoudiniCreator): instance = super(CreateBGEO, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_composite.py b/client/ayon_core/hosts/houdini/plugins/create/create_composite.py index a1104e5093..a25faf0e8e 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_composite.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_composite.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin for creating composite sequences.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance, CreatorError +from ayon_core.pipeline import CreatorError import hou @@ -25,7 +25,7 @@ class CreateCompositeSequence(plugin.HoudiniCreator): instance = super(CreateCompositeSequence, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) filepath = "{}{}".format( diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_hda.py b/client/ayon_core/hosts/houdini/plugins/create/create_hda.py index b307293dc8..d399aa5e15 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_hda.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_hda.py @@ -78,7 +78,7 @@ class CreateHDA(plugin.HoudiniCreator): instance = super(CreateHDA, self).create( product_name, instance_data, - pre_create_data) # type: plugin.CreatedInstance + pre_create_data) return instance diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py index 9eb9d80cd3..e91ddbc0ac 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_karma_rop.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin to create Karma ROP.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance from ayon_core.lib import BoolDef, EnumDef, NumberDef @@ -25,7 +24,7 @@ class CreateKarmaROP(plugin.HoudiniCreator): instance = super(CreateKarmaROP, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_ifd.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_ifd.py index bb10f3893c..e0cf035c35 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_ifd.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_ifd.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating pointcache alembics.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance from ayon_core.lib import BoolDef @@ -22,7 +21,7 @@ class CreateMantraIFD(plugin.HoudiniCreator): instance = super(CreateMantraIFD, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py index f15f49f463..64ecf428e9 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_mantra_rop.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin to create Mantra ROP.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance from ayon_core.lib import EnumDef, BoolDef @@ -28,7 +27,7 @@ class CreateMantraROP(plugin.HoudiniCreator): instance = super(CreateMantraROP, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_usd.py b/client/ayon_core/hosts/houdini/plugins/create/create_usd.py index ee05639368..700f7eefd6 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_usd.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_usd.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating USDs.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance import hou @@ -22,7 +21,7 @@ class CreateUSD(plugin.HoudiniCreator): instance = super(CreateUSD, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_usdrender.py b/client/ayon_core/hosts/houdini/plugins/create/create_usdrender.py index 0a5c8896a8..36197e349e 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_usdrender.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_usdrender.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating USD renders.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance class CreateUSDRender(plugin.HoudiniCreator): @@ -23,7 +22,7 @@ class CreateUSDRender(plugin.HoudiniCreator): instance = super(CreateUSDRender, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vbd_cache.py b/client/ayon_core/hosts/houdini/plugins/create/create_vbd_cache.py index 9ac7ebdff7..c34cd2b4b5 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vbd_cache.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vbd_cache.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating VDB Caches.""" from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance from ayon_core.lib import BoolDef import hou @@ -26,7 +25,7 @@ class CreateVDBCache(plugin.HoudiniCreator): instance = super(CreateVDBCache, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) file_path = "{}{}".format( diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py index 6b2396bffb..5ed9e848a7 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_vray_rop.py @@ -3,7 +3,7 @@ import hou from ayon_core.hosts.houdini.api import plugin -from ayon_core.pipeline import CreatedInstance, CreatorError +from ayon_core.pipeline import CreatorError from ayon_core.lib import EnumDef, BoolDef @@ -31,7 +31,7 @@ class CreateVrayROP(plugin.HoudiniCreator): instance = super(CreateVrayROP, self).create( product_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) instance_node = hou.node(instance.get("instance_node")) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py index 73145b211a..fe8fa25f10 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/increment_current_file.py @@ -3,7 +3,6 @@ import pyblish.api from ayon_core.lib import version_up from ayon_core.pipeline import registered_host from ayon_core.pipeline.publish import get_errored_plugins_from_context -from ayon_core.hosts.houdini.api import HoudiniHost from ayon_core.pipeline.publish import KnownPublishError @@ -39,7 +38,7 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): ) # Filename must not have changed since collecting - host = registered_host() # type: HoudiniHost + host = registered_host() current_file = host.current_file() if context.data["currentFile"] != current_file: raise KnownPublishError( From 22a845b8170a3022ff05493ffef2d726b4e62f9f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:09:53 +0200 Subject: [PATCH 136/203] remove python 2 compatible type hint in maya this is because ruff does not use this type hinting --- client/ayon_core/hosts/maya/api/fbx.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/fbx.py b/client/ayon_core/hosts/maya/api/fbx.py index 97e95d2ec4..939da4011b 100644 --- a/client/ayon_core/hosts/maya/api/fbx.py +++ b/client/ayon_core/hosts/maya/api/fbx.py @@ -2,8 +2,6 @@ """Tools to work with FBX.""" import logging -from pyblish.api import Instance - from maya import cmds # noqa import maya.mel as mel # noqa from ayon_core.hosts.maya.api.lib import maintained_selection @@ -146,7 +144,6 @@ class FBXExtractor: return options def set_options_from_instance(self, instance): - # type: (Instance) -> None """Sets FBX export options from data in the instance. Args: From 8a49a5533d09fb16665f0b7d7f6dcaaaa1445d35 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:10:49 +0200 Subject: [PATCH 137/203] modify RR imports in api.py added noqa F401 for ruff which does not use python 2 type hinting --- client/ayon_core/modules/royalrender/api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/modules/royalrender/api.py b/client/ayon_core/modules/royalrender/api.py index cd72014a42..a69f88c43c 100644 --- a/client/ayon_core/modules/royalrender/api.py +++ b/client/ayon_core/modules/royalrender/api.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- """Wrapper around Royal Render API.""" -import sys import os +import sys -from ayon_core.lib.local_settings import AYONSettingsRegistry -from ayon_core.lib import Logger, run_subprocess -from .rr_job import RRJob, SubmitFile, SubmitterParameter +from ayon_core.lib import Logger, run_subprocess, AYONSettingsRegistry from ayon_core.lib.vendor_bin_utils import find_tool_in_custom_paths +from .rr_job import SubmitFile +from .rr_job import RRjob, SubmitterParameter # noqa F401 + class Api: From c89eb16689e1d1beebae112a19ee0ca7d08b2089 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:11:25 +0200 Subject: [PATCH 138/203] remove python 2 compatible type hint in pyblish functions this is because ruff does not use this type hinting --- client/ayon_core/pipeline/farm/pyblish_functions.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index dadf2cbe1a..37003cbd88 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -6,13 +6,11 @@ from copy import deepcopy import attr import ayon_api -import pyblish.api import clique from ayon_core.pipeline import ( get_current_project_name, get_representation_path, - Anatomy, ) from ayon_core.lib import Logger from ayon_core.pipeline.publish import KnownPublishError @@ -137,7 +135,7 @@ def get_transferable_representations(instance): list of dicts: List of transferable representations. """ - anatomy = instance.context.data["anatomy"] # type: Anatomy + anatomy = instance.context.data["anatomy"] to_transfer = [] for representation in instance.data.get("representations", []): @@ -166,7 +164,6 @@ def get_transferable_representations(instance): def create_skeleton_instance( instance, families_transfer=None, instance_transfer=None): - # type: (pyblish.api.Instance, list, dict) -> dict """Create skeleton instance from original instance data. This will create dictionary containing skeleton @@ -191,7 +188,7 @@ def create_skeleton_instance( context = instance.context data = instance.data.copy() - anatomy = instance.context.data["anatomy"] # type: Anatomy + anatomy = instance.context.data["anatomy"] # get time related data from instance (or context) time_data = get_time_data_from_instance_or_context(instance) @@ -751,7 +748,6 @@ def get_resources(project_name, version_entity, extension=None): def create_skeleton_instance_cache(instance): - # type: (pyblish.api.Instance, list, dict) -> dict """Create skeleton instance from original instance data. This will create dictionary containing skeleton @@ -771,7 +767,7 @@ def create_skeleton_instance_cache(instance): context = instance.context data = instance.data.copy() - anatomy = instance.context.data["anatomy"] # type: Anatomy + anatomy = instance.context.data["anatomy"] # get time related data from instance (or context) time_data = get_time_data_from_instance_or_context(instance) @@ -1005,7 +1001,7 @@ def copy_extend_frames(instance, representation): start = instance.data.get("frameStart") end = instance.data.get("frameEnd") project_name = instance.context.data["project"] - anatomy = instance.context.data["anatomy"] # type: Anatomy + anatomy = instance.context.data["anatomy"] folder_entity = ayon_api.get_folder_by_path( project_name, instance.data.get("folderPath") From 87f4336aca4f01d6f212e489d424449f6b987bdf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:11:47 +0200 Subject: [PATCH 139/203] fix typehint import in deadline settings --- server_addon/deadline/server/settings/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 9537d6d550..83c7567c0d 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -1,3 +1,4 @@ +from typing import TYPE_CHECKING from pydantic import validator from ayon_server.settings import ( @@ -5,6 +6,8 @@ from ayon_server.settings import ( SettingsField, ensure_unique_names, ) +if TYPE_CHECKING: + from ayon_server.addons import BaseServerAddon from .publish_plugins import ( PublishPluginsModel, @@ -19,7 +22,7 @@ class ServerListSubmodel(BaseSettingsModel): async def defined_deadline_ws_name_enum_resolver( - addon: "BaseServerAddon", + addon: BaseServerAddon, settings_variant: str = "production", project_name: str | None = None, ) -> list[str]: From 0bf6be0fc42a600d37341ecf901e49a2b8888001 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:12:36 +0200 Subject: [PATCH 140/203] better import in fusion --- client/ayon_core/hosts/fusion/api/lib.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/fusion/api/lib.py b/client/ayon_core/hosts/fusion/api/lib.py index 03a1eeeb65..08722463e1 100644 --- a/client/ayon_core/hosts/fusion/api/lib.py +++ b/client/ayon_core/hosts/fusion/api/lib.py @@ -3,8 +3,8 @@ import sys import re import contextlib -from ayon_core.lib import Logger - +from ayon_core.lib import Logger, BoolDef, UILabelDef +from ayon_core.style import load_stylesheet from ayon_core.pipeline import registered_host from ayon_core.pipeline.create import CreateContext from ayon_core.pipeline.context_tools import get_current_folder_entity @@ -181,7 +181,6 @@ def validate_comp_prefs(comp=None, force_repair=False): from . import menu from ayon_core.tools.utils import SimplePopup - from ayon_core.style import load_stylesheet dialog = SimplePopup(parent=menu.menu) dialog.setWindowTitle("Fusion comp has invalid configuration") @@ -340,9 +339,7 @@ def prompt_reset_context(): from ayon_core.tools.attribute_defs.dialog import ( AttributeDefinitionsDialog ) - from ayon_core.style import load_stylesheet - from ayon_core.lib import BoolDef, UILabelDef - from qtpy import QtWidgets, QtCore + from qtpy import QtCore definitions = [ UILabelDef( From 1a1826dbbf10a13cd87858d7affa07b8769cf72f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:18:28 +0200 Subject: [PATCH 141/203] fix filename formatting in workflie load plugin --- client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py index 8663dac997..2ad81cabcd 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py +++ b/client/ayon_core/hosts/tvpaint/plugins/load/load_workfile.py @@ -1,6 +1,5 @@ import os -from ayon_core.lib import StringTemplate from ayon_core.pipeline import ( registered_host, get_current_context, @@ -111,8 +110,6 @@ class LoadWorkfile(plugin.Loader): data["version"] = version - filename = StringTemplate.format_strict_template( - file_template, data - ) + filename = work_template["file"].format_strict(data) path = os.path.join(work_root, filename) host.save_workfile(path) From e69ac72a7520352d03424de0e2ed1a57304116bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:19:08 +0200 Subject: [PATCH 142/203] fix used variable in blender load layout plugin --- client/ayon_core/hosts/blender/plugins/load/load_layout_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py b/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py index 7a3da1882e..f5d3aed084 100644 --- a/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py +++ b/client/ayon_core/hosts/blender/plugins/load/load_layout_json.py @@ -167,7 +167,7 @@ class JsonLayoutLoader(plugin.AssetLoader): asset_group.empty_display_type = 'SINGLE_ARROW' avalon_container.objects.link(asset_group) - self._process(libpath, asset, asset_group, None) + self._process(libpath, asset_name, asset_group, None) bpy.context.scene.collection.objects.link(asset_group) From 09e1779586f5cee1c12af1702b3c5cb07dd9c03d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:19:43 +0200 Subject: [PATCH 143/203] use correct variable for product name --- .../hosts/hiero/plugins/publish/precollect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/hiero/plugins/publish/precollect_instances.py b/client/ayon_core/hosts/hiero/plugins/publish/precollect_instances.py index 67e1f18cbf..17a5e00bfd 100644 --- a/client/ayon_core/hosts/hiero/plugins/publish/precollect_instances.py +++ b/client/ayon_core/hosts/hiero/plugins/publish/precollect_instances.py @@ -293,7 +293,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin): label += " {}".format(product_name) data.update({ - "name": "{}_{}".format(folder_path, subset), + "name": "{}_{}".format(folder_path, product_name), "label": label, "productName": product_name, "productType": product_type, From 978604f34c1d393502635b2ef92d03800740e986 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:20:07 +0200 Subject: [PATCH 144/203] remove duplicated code --- .../hosts/hiero/plugins/publish/precollect_instances.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/ayon_core/hosts/hiero/plugins/publish/precollect_instances.py b/client/ayon_core/hosts/hiero/plugins/publish/precollect_instances.py index 17a5e00bfd..b7a508f0b5 100644 --- a/client/ayon_core/hosts/hiero/plugins/publish/precollect_instances.py +++ b/client/ayon_core/hosts/hiero/plugins/publish/precollect_instances.py @@ -92,10 +92,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin): folder_path, folder_name = self._get_folder_data(tag_data) - product_name = tag_data.get("productName") - if product_name is None: - product_name = tag_data["subset"] - families = [str(f) for f in tag_data["families"]] # TODO: remove backward compatibility From a782d4bc1982e063b30d1795a17c09d4f594dd32 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:24:16 +0200 Subject: [PATCH 145/203] added constants to __all__ --- .../ayon_core/addons/applications/ayon_applications/__init__.py | 1 + client/ayon_core/hosts/unreal/api/__init__.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/client/ayon_core/addons/applications/ayon_applications/__init__.py b/client/ayon_core/addons/applications/ayon_applications/__init__.py index b4a50279ab..c9b72f9914 100644 --- a/client/ayon_core/addons/applications/ayon_applications/__init__.py +++ b/client/ayon_core/addons/applications/ayon_applications/__init__.py @@ -31,6 +31,7 @@ from .addon import ApplicationsAddon __all__ = ( + "APPLICATIONS_ADDON_ROOT", "DEFAULT_ENV_SUBGROUP", "PLATFORM_NAMES", diff --git a/client/ayon_core/hosts/unreal/api/__init__.py b/client/ayon_core/hosts/unreal/api/__init__.py index ac6a91eae9..defad8b3c9 100644 --- a/client/ayon_core/hosts/unreal/api/__init__.py +++ b/client/ayon_core/hosts/unreal/api/__init__.py @@ -30,6 +30,8 @@ from .pipeline import ( __all__ = [ "install", "uninstall", + "UnrealActorCreator", + "UnrealAssetCreator", "Loader", "ls", "publish", From ebfcd364cd51876339d7e99498fd77b506fdb5ba Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:24:33 +0200 Subject: [PATCH 146/203] don't use single char variable --- .../ayon_core/tools/utils/color_widgets/color_inputs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/utils/color_widgets/color_inputs.py b/client/ayon_core/tools/utils/color_widgets/color_inputs.py index 9c8e7b92e8..795b80fc1e 100644 --- a/client/ayon_core/tools/utils/color_widgets/color_inputs.py +++ b/client/ayon_core/tools/utils/color_widgets/color_inputs.py @@ -562,11 +562,11 @@ class HSLInputs(QtWidgets.QWidget): return self._block_changes = True - h, s, l, _ = self.color.getHsl() + hue, sat, lum, _ = self.color.getHsl() - self.input_hue.setValue(h) - self.input_sat.setValue(s) - self.input_light.setValue(l) + self.input_hue.setValue(hue) + self.input_sat.setValue(sat) + self.input_light.setValue(lum) self._block_changes = False From d1243d74e34ff18e2f5baec764a53ec0b88d4e93 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:24:41 +0200 Subject: [PATCH 147/203] define function instead of lambda --- client/ayon_core/tools/utils/widgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/utils/widgets.py b/client/ayon_core/tools/utils/widgets.py index 1d4f85246f..21cab5d682 100644 --- a/client/ayon_core/tools/utils/widgets.py +++ b/client/ayon_core/tools/utils/widgets.py @@ -578,7 +578,8 @@ class OptionalAction(QtWidgets.QWidgetAction): def set_option_tip(self, options): sep = "\n\n" if not options or not isinstance(options[0], AbstractAttrDef): - mak = (lambda opt: opt["name"] + " :\n " + opt["help"]) + def mak(opt): + return opt["name"] + " :\n " + opt["help"] self.option_tip = sep.join(mak(opt) for opt in options) return From 9cb8b3b84ca1cfc7be8aecb54852ccb55fd7ae2c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:24:55 +0200 Subject: [PATCH 148/203] comment out example implementation --- client/ayon_core/tools/publisher/control_qt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/publisher/control_qt.py b/client/ayon_core/tools/publisher/control_qt.py index ee08899cac..bef3a5af3b 100644 --- a/client/ayon_core/tools/publisher/control_qt.py +++ b/client/ayon_core/tools/publisher/control_qt.py @@ -343,8 +343,9 @@ class QtRemotePublishController(BasePublisherController): @abstractmethod def _send_instance_changes_to_client(self): - instance_changes = self._get_instance_changes_for_client() - # Implement to send 'instance_changes' value to client + # TODO Implement to send 'instance_changes' value to client + # instance_changes = self._get_instance_changes_for_client() + pass @abstractmethod def save_changes(self): From 2f9834ba3bbb68828a01851ea99599b3d6ca1719 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:25:05 +0200 Subject: [PATCH 149/203] removed duplicated methods in slates --- client/ayon_core/scripts/slates/slate_base/base.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client/ayon_core/scripts/slates/slate_base/base.py b/client/ayon_core/scripts/slates/slate_base/base.py index 35ef46769c..e1648c916a 100644 --- a/client/ayon_core/scripts/slates/slate_base/base.py +++ b/client/ayon_core/scripts/slates/slate_base/base.py @@ -82,20 +82,6 @@ class BaseObj: def main_style(self): return load_default_style() - def height(self): - raise NotImplementedError( - "Attribute `height` is not implemented for <{}>".format( - self.__clas__.__name__ - ) - ) - - def width(self): - raise NotImplementedError( - "Attribute `width` is not implemented for <{}>".format( - self.__clas__.__name__ - ) - ) - def collect_data(self): return None From 40c41f82ece7331020a185166d1437a16045b3c9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:25:18 +0200 Subject: [PATCH 150/203] ignore unused import in loader action --- client/ayon_core/modules/loader_action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/loader_action.py b/client/ayon_core/modules/loader_action.py index a0cc417b66..1e45db05dc 100644 --- a/client/ayon_core/modules/loader_action.py +++ b/client/ayon_core/modules/loader_action.py @@ -13,7 +13,7 @@ class LoaderAddon(AYONAddon, ITrayAddon): # Add library tool self._loader_imported = False try: - from ayon_core.tools.loader.ui import LoaderWindow + from ayon_core.tools.loader.ui import LoaderWindow # noqa F401 self._loader_imported = True except Exception: From 55b60fc7e224fb1d27c24d31482ed94939899eec Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:25:33 +0200 Subject: [PATCH 151/203] do not store output to unused variable --- .../hosts/traypublisher/plugins/create/create_editorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py index 843729786c..4057aee9a6 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py @@ -402,7 +402,7 @@ or updating already created. Publishing will create OTIO file. ): continue - instance = self._make_product_instance( + self._make_product_instance( otio_clip, product_type_preset, deepcopy(base_instance_data), From 7c994ee1f0d0c87784d40e0f3b1f1ef157ff51cd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:25:45 +0200 Subject: [PATCH 152/203] ignore unused variable in tray --- client/ayon_core/tools/tray/tray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/tray/tray.py b/client/ayon_core/tools/tray/tray.py index 3c6c529be8..957518afe4 100644 --- a/client/ayon_core/tools/tray/tray.py +++ b/client/ayon_core/tools/tray/tray.py @@ -552,7 +552,7 @@ class TrayStarter(QtCore.QObject): def main(): app = get_ayon_qt_app() - starter = TrayStarter(app) + starter = TrayStarter(app) # noqa F841 if not is_running_from_build() and os.name == "nt": import ctypes From 400e30f160b1190889cc61b712286cc45f95939c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:29:02 +0200 Subject: [PATCH 153/203] fix variable used in anatomy to work with site sync --- client/ayon_core/pipeline/anatomy/anatomy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 73dd215233..2aa8eeddbc 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -549,7 +549,7 @@ class Anatomy(BaseAnatomy): ) else: # Ask sync server to get roots overrides - roots_overrides = sitesync.get_site_root_overrides( + roots_overrides = sitesync_addon.get_site_root_overrides( project_name, site_name ) site_cache.update_data(roots_overrides) From eb5258012f5f30306e4afb36bcfdec8a366e595e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:34:30 +0200 Subject: [PATCH 154/203] added list of files to exclude from linting --- pyproject.toml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3c9ff4ea0a..29733983e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,20 @@ unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +exclude = [ + "client/ayon_core/hosts/unreal/integration/*", + "client/ayon_core/hosts/aftereffects/api/extension/js/libs/*", + "client/ayon_core/hosts/hiero/api/startup/*", + "client/ayon_core/modules/deadline/repository/custom/plugins/CelAction/*", + "client/ayon_core/modules/deadline/repository/custom/plugins/HarmonyAYON/*", + "client/ayon_core/modules/click_wrap.py", + "client/ayon_core/scripts/slates/__init__.py" +] + +[tool.ruff.lint.per-file-ignores] +"client/ayon_core/lib/__init__.py" = ["E402"] +"client/ayon_core/hosts/max/startup/startup.py" = ["E402"] + [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "double" From 6366998d57b1914e1552923edc8ad9ef97fdc2b3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:02:12 +0200 Subject: [PATCH 155/203] change order to match imports order Co-authored-by: Roy Nieterau --- client/ayon_core/hosts/unreal/api/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/unreal/api/__init__.py b/client/ayon_core/hosts/unreal/api/__init__.py index defad8b3c9..7e7f839f27 100644 --- a/client/ayon_core/hosts/unreal/api/__init__.py +++ b/client/ayon_core/hosts/unreal/api/__init__.py @@ -28,11 +28,11 @@ from .pipeline import ( ) __all__ = [ - "install", - "uninstall", "UnrealActorCreator", "UnrealAssetCreator", "Loader", + "install", + "uninstall", "ls", "publish", "containerise", From acdafb0a90563ffe4056beeda6ede5bbff33306b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:50:53 +0200 Subject: [PATCH 156/203] removed unused 'win32api' import --- client/ayon_core/hosts/blender/hooks/pre_pyside_install.py | 1 - client/ayon_core/hosts/fusion/hooks/pre_pyside_install.py | 1 - 2 files changed, 2 deletions(-) diff --git a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py index 8f46eea0de..de397d6542 100644 --- a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py +++ b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py @@ -139,7 +139,6 @@ class InstallPySideToBlender(PreLaunchHook): administration rights. """ try: - import win32api import win32con import win32process import win32event diff --git a/client/ayon_core/hosts/fusion/hooks/pre_pyside_install.py b/client/ayon_core/hosts/fusion/hooks/pre_pyside_install.py index ab12078c43..4678d5bac7 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_pyside_install.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_pyside_install.py @@ -85,7 +85,6 @@ class InstallPySideToFusion(PreLaunchHook): administration rights. """ try: - import win32api import win32con import win32process import win32event From ca704a81266975a3026c90d1a76f44bf43e1e6c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:19:25 +0200 Subject: [PATCH 157/203] define qt binding based on blender version --- .../hosts/blender/hooks/pre_pyside_install.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py index 8f46eea0de..b0ace518c9 100644 --- a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py +++ b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py @@ -31,7 +31,7 @@ class InstallPySideToBlender(PreLaunchHook): def inner_execute(self): # Get blender's python directory - version_regex = re.compile(r"^[2-4]\.[0-9]+$") + version_regex = re.compile(r"^([2-4])\.[0-9]+$") platform = system().lower() executable = self.launch_context.executable.executable_path @@ -42,7 +42,8 @@ class InstallPySideToBlender(PreLaunchHook): if os.path.basename(executable).lower() != expected_executable: self.log.info(( f"Executable does not lead to {expected_executable} file." - "Can't determine blender's python to check/install PySide2." + "Can't determine blender's python to check/install" + " Qt binding." )) return @@ -73,6 +74,12 @@ class InstallPySideToBlender(PreLaunchHook): return version_subfolder = version_subfolders[0] + before_blender_4 = False + if int(version_regex.match(version_subfolder).group(1)) < 4: + before_blender_4 = True + # Blender 4 has Python 3.11 which does not support 'PySide2' + # QUESTION could we always install PySide6? + qt_binding = "PySide2" if before_blender_4 else "PySide6" python_dir = os.path.join(versions_dir, version_subfolder, "python") python_lib = os.path.join(python_dir, "lib") From ff6d207d442d4b9e3e8a447e00092511225b55d6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:21:31 +0200 Subject: [PATCH 158/203] use the qt binding to be installed --- .../hosts/blender/hooks/pre_pyside_install.py | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py index b0ace518c9..694080a324 100644 --- a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py +++ b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py @@ -123,22 +123,30 @@ class InstallPySideToBlender(PreLaunchHook): return # Check if PySide2 is installed and skip if yes - if self.is_pyside_installed(python_executable): + if self.is_pyside_installed(python_executable, qt_binding): self.log.debug("Blender has already installed PySide2.") return # Install PySide2 in blender's python if platform == "windows": - result = self.install_pyside_windows(python_executable) + result = self.install_pyside_windows( + python_executable, qt_binding + ) else: - result = self.install_pyside(python_executable) + result = self.install_pyside( + python_executable, qt_binding + ) if result: - self.log.info("Successfully installed PySide2 module to blender.") + self.log.info( + f"Successfully installed {qt_binding} module to blender." + ) else: - self.log.warning("Failed to install PySide2 module to blender.") + self.log.warning( + f"Failed to install {qt_binding} module to blender." + ) - def install_pyside_windows(self, python_executable): + def install_pyside_windows(self, python_executable, qt_binding): """Install PySide2 python module to blender's python. Installation requires administration rights that's why it is required @@ -162,7 +170,7 @@ class InstallPySideToBlender(PreLaunchHook): # - use "-m pip" as module pip to install PySide2 and argument # "--ignore-installed" is to force install module to blender's # site-packages and make sure it is binary compatible - parameters = "-m pip install --ignore-installed PySide2" + parameters = f"-m pip install --ignore-installed {qt_binding}" # Execute command and ask for administrator's rights process_info = ShellExecuteEx( @@ -180,20 +188,22 @@ class InstallPySideToBlender(PreLaunchHook): except pywintypes.error: pass - def install_pyside(self, python_executable): + def install_pyside(self, python_executable, qt_binding): """Install PySide2 python module to blender's python.""" try: # Parameters - # - use "-m pip" as module pip to install PySide2 and argument + # - use "-m pip" as module pip to install qt binding and argument # "--ignore-installed" is to force install module to blender's # site-packages and make sure it is binary compatible + # TODO find out if blender 4.x on linux/darwin does install + # qt binding to correct place. args = [ python_executable, "-m", "pip", "install", "--ignore-installed", - "PySide2", + qt_binding, ] process = subprocess.Popen( args, stdout=subprocess.PIPE, universal_newlines=True @@ -210,13 +220,15 @@ class InstallPySideToBlender(PreLaunchHook): except subprocess.SubprocessError: pass - def is_pyside_installed(self, python_executable): + def is_pyside_installed(self, python_executable, qt_binding): """Check if PySide2 module is in blender's pip list. Check that PySide2 is installed directly in blender's site-packages. It is possible that it is installed in user's site-packages but that may be incompatible with blender's python. """ + + qt_binding_low = qt_binding.lower() # Get pip list from blender's python executable args = [python_executable, "-m", "pip", "list"] process = subprocess.Popen(args, stdout=subprocess.PIPE) @@ -233,6 +245,6 @@ class InstallPySideToBlender(PreLaunchHook): if not line: continue package_name = line[0:package_len].strip() - if package_name.lower() == "pyside2": + if package_name.lower() == qt_binding_low: return True return False From 99160a8832652cba7fa552f2c6f6881281345f7b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:22:15 +0200 Subject: [PATCH 159/203] fix installation of qt binding for newer blender --- .../hosts/blender/hooks/pre_pyside_install.py | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py index 694080a324..e03856c2e5 100644 --- a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py +++ b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py @@ -130,7 +130,7 @@ class InstallPySideToBlender(PreLaunchHook): # Install PySide2 in blender's python if platform == "windows": result = self.install_pyside_windows( - python_executable, qt_binding + python_executable, qt_binding, before_blender_4 ) else: result = self.install_pyside( @@ -146,7 +146,9 @@ class InstallPySideToBlender(PreLaunchHook): f"Failed to install {qt_binding} module to blender." ) - def install_pyside_windows(self, python_executable, qt_binding): + def install_pyside_windows( + self, python_executable, qt_binding, before_blender_4 + ): """Install PySide2 python module to blender's python. Installation requires administration rights that's why it is required @@ -170,7 +172,29 @@ class InstallPySideToBlender(PreLaunchHook): # - use "-m pip" as module pip to install PySide2 and argument # "--ignore-installed" is to force install module to blender's # site-packages and make sure it is binary compatible - parameters = f"-m pip install --ignore-installed {qt_binding}" + fake_exe = "fake.exe" + site_packages_prefix = os.path.dirname( + os.path.dirname(python_executable) + ) + args = [ + fake_exe, + "-m", + "pip", + "install", + "--ignore-installed", + qt_binding, + ] + if not before_blender_4: + # Define prefix for site package + # Python in blender 4.x is installing packages in AppData and + # not in blender's directory. + args.extend(["--prefix", site_packages_prefix]) + + parameters = ( + subprocess.list2cmdline(args) + .lstrip(fake_exe) + .lstrip(" ") + ) # Execute command and ask for administrator's rights process_info = ShellExecuteEx( From 066e18afe7fb784c0473b8837a9791f2a486bd7e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 14:22:24 +0200 Subject: [PATCH 160/203] do not set 'QT_PREFERRED_BINDING' for subprocess --- client/ayon_core/hosts/blender/addon.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/blender/addon.py b/client/ayon_core/hosts/blender/addon.py index b7484de243..6a4b325365 100644 --- a/client/ayon_core/hosts/blender/addon.py +++ b/client/ayon_core/hosts/blender/addon.py @@ -55,8 +55,7 @@ class BlenderAddon(AYONAddon, IHostAddon): ) # Define Qt binding if not defined - if not env.get("QT_PREFERRED_BINDING"): - env["QT_PREFERRED_BINDING"] = "PySide2" + env.pop("QT_PREFERRED_BINDING", None) def get_launch_hook_paths(self, app): if app.host_name != self.host_name: From 43a95bed75c8748673d3092260415ec93af0664d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:00:52 +0200 Subject: [PATCH 161/203] move client code of applications addon next to server codebase --- .../applications/client}/ayon_applications/__init__.py | 0 .../applications/client}/ayon_applications/addon.py | 0 .../applications/client}/ayon_applications/constants.py | 0 .../applications/client}/ayon_applications/defs.py | 0 .../applications/client}/ayon_applications/exceptions.py | 0 .../applications/client}/ayon_applications/hooks.py | 0 .../applications/client}/ayon_applications/manager.py | 0 .../client}/ayon_applications/plugins/publish/collect_app_name.py | 0 .../applications/client}/ayon_applications/utils.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/__init__.py (100%) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/addon.py (100%) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/constants.py (100%) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/defs.py (100%) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/exceptions.py (100%) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/hooks.py (100%) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/manager.py (100%) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/plugins/publish/collect_app_name.py (100%) rename {client/ayon_core/addons/applications => server_addon/applications/client}/ayon_applications/utils.py (100%) diff --git a/client/ayon_core/addons/applications/ayon_applications/__init__.py b/server_addon/applications/client/ayon_applications/__init__.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/__init__.py rename to server_addon/applications/client/ayon_applications/__init__.py diff --git a/client/ayon_core/addons/applications/ayon_applications/addon.py b/server_addon/applications/client/ayon_applications/addon.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/addon.py rename to server_addon/applications/client/ayon_applications/addon.py diff --git a/client/ayon_core/addons/applications/ayon_applications/constants.py b/server_addon/applications/client/ayon_applications/constants.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/constants.py rename to server_addon/applications/client/ayon_applications/constants.py diff --git a/client/ayon_core/addons/applications/ayon_applications/defs.py b/server_addon/applications/client/ayon_applications/defs.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/defs.py rename to server_addon/applications/client/ayon_applications/defs.py diff --git a/client/ayon_core/addons/applications/ayon_applications/exceptions.py b/server_addon/applications/client/ayon_applications/exceptions.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/exceptions.py rename to server_addon/applications/client/ayon_applications/exceptions.py diff --git a/client/ayon_core/addons/applications/ayon_applications/hooks.py b/server_addon/applications/client/ayon_applications/hooks.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/hooks.py rename to server_addon/applications/client/ayon_applications/hooks.py diff --git a/client/ayon_core/addons/applications/ayon_applications/manager.py b/server_addon/applications/client/ayon_applications/manager.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/manager.py rename to server_addon/applications/client/ayon_applications/manager.py diff --git a/client/ayon_core/addons/applications/ayon_applications/plugins/publish/collect_app_name.py b/server_addon/applications/client/ayon_applications/plugins/publish/collect_app_name.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/plugins/publish/collect_app_name.py rename to server_addon/applications/client/ayon_applications/plugins/publish/collect_app_name.py diff --git a/client/ayon_core/addons/applications/ayon_applications/utils.py b/server_addon/applications/client/ayon_applications/utils.py similarity index 100% rename from client/ayon_core/addons/applications/ayon_applications/utils.py rename to server_addon/applications/client/ayon_applications/utils.py From a17381dcaea7e16c369bdecc2baf37f8bb04dcbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:27:17 +0200 Subject: [PATCH 162/203] create ayon addons script can handle client code --- server_addon/create_ayon_addons.py | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index c2686199be..fb6b46da88 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -44,6 +44,11 @@ version = "{addon_version}" plugin_for = ["ayon_server"] """ +CLIENT_VERSION_CONTENT = '''# -*- coding: utf-8 -*- +"""Package declaring AYON core addon version.""" +__version__ = "{}" +''' + class ZipFileLongPaths(zipfile.ZipFile): """Allows longer paths in zip files. @@ -175,6 +180,45 @@ def create_addon_zip( shutil.rmtree(str(output_dir / addon_name)) +def prepare_client_code( + addon_dir: Path, + addon_output_dir: Path, + addon_version: str +): + client_dir = addon_dir / "client" + if not client_dir.exists(): + return + + # Prepare private dir in output + private_dir = addon_output_dir / "private" + private_dir.mkdir(parents=True, exist_ok=True) + + # Copy pyproject toml if available + pyproject_toml = client_dir / "pyproject.toml" + if pyproject_toml.exists(): + shutil.copy(pyproject_toml, private_dir) + + for subpath in client_dir.iterdir(): + if subpath.name == "pyproject.toml": + continue + + if subpath.is_file(): + continue + + # Update version.py with server version if 'version.py' is available + version_path = subpath / "version.py" + if version_path.exists(): + with open(version_path, "w") as stream: + stream.write(CLIENT_VERSION_CONTENT.format(addon_version)) + + zip_filepath = private_dir / "client.zip" + with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf: + # Add client code content to zip + for path, sub_path in find_files_in_subdir(str(subpath)): + sub_path = os.path.join(subpath.name, sub_path) + zipf.write(path, sub_path) + + def create_addon_package( addon_dir: Path, output_dir: Path, @@ -205,6 +249,8 @@ def create_addon_package( server_dir, addon_output_dir / "server", dirs_exist_ok=True ) + prepare_client_code(addon_dir, addon_output_dir, addon_version) + if create_zip: create_addon_zip( output_dir, addon_dir.name, addon_version, keep_source From d498eb8e42ac83d3aa0bcc130227986a4e7fbced Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:42:38 +0200 Subject: [PATCH 163/203] create pakcage.py only if is not available --- server_addon/create_ayon_addons.py | 46 +++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index fb6b46da88..2501faebe3 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -4,6 +4,8 @@ import re import shutil import argparse import zipfile +import types +import importlib import platform import collections from pathlib import Path @@ -219,13 +221,34 @@ def prepare_client_code( zipf.write(path, sub_path) +def import_filepath(path: Path, module_name: Optional[str] = None): + if not module_name: + module_name = os.path.splitext(path.name)[0] + + module = types.ModuleType(module_name) + module.__file__ = path + + # Use loader so module has full specs + module_loader = importlib.machinery.SourceFileLoader( + module_name, path + ) + module_loader.exec_module(module) + return module + + def create_addon_package( addon_dir: Path, output_dir: Path, create_zip: bool, keep_source: bool, ): - addon_version = get_addon_version(addon_dir) + src_package_py = addon_dir / "package.py" + package = None + if src_package_py.exists(): + package = import_filepath(src_package_py) + addon_version = package.version + else: + addon_version = get_addon_version(addon_dir) addon_output_dir = output_dir / addon_dir.name / addon_version if addon_output_dir.exists(): @@ -233,16 +256,19 @@ def create_addon_package( addon_output_dir.mkdir(parents=True) # Copy server content - package_py = addon_output_dir / "package.py" - addon_name = addon_dir.name - if addon_name == "royal_render": - addon_name = "royalrender" - package_py_content = PACKAGE_PY_TEMPLATE.format( - addon_name=addon_name, addon_version=addon_version - ) + dst_package_py = addon_output_dir / "package.py" + if package is not None: + shutil.copy(src_package_py, dst_package_py) + else: + addon_name = addon_dir.name + if addon_name == "royal_render": + addon_name = "royalrender" + package_py_content = PACKAGE_PY_TEMPLATE.format( + addon_name=addon_name, addon_version=addon_version + ) - with open(package_py, "w+") as pkg_py: - pkg_py.write(package_py_content) + with open(dst_package_py, "w+") as pkg_py: + pkg_py.write(package_py_content) server_dir = addon_dir / "server" shutil.copytree( From 10ba857abdebd6390ee1d406a36b97ad01be0057 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:42:49 +0200 Subject: [PATCH 164/203] applications addon has package.py --- server_addon/applications/package.py | 3 +++ server_addon/applications/server/__init__.py | 4 ---- server_addon/applications/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/applications/package.py delete mode 100644 server_addon/applications/server/version.py diff --git a/server_addon/applications/package.py b/server_addon/applications/package.py new file mode 100644 index 0000000000..0380222a53 --- /dev/null +++ b/server_addon/applications/package.py @@ -0,0 +1,3 @@ +name = "applications" +title = "Applications" +version = "0.1.9" diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index 2668589cbe..d85678b77b 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -6,7 +6,6 @@ from ayon_server.addons import BaseServerAddon, AddonLibrary from ayon_server.entities.core import attribute_library from ayon_server.lib.postgres import Postgres -from .version import __version__ from .settings import ApplicationsAddonSettings, DEFAULT_VALUES try: @@ -87,9 +86,6 @@ def get_enum_items_from_groups(groups): class ApplicationsAddon(BaseServerAddon): - name = "applications" - title = "Applications" - version = __version__ settings_model = ApplicationsAddonSettings async def get_default_settings(self): diff --git a/server_addon/applications/server/version.py b/server_addon/applications/server/version.py deleted file mode 100644 index c11f861afb..0000000000 --- a/server_addon/applications/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.9" From cad45cee4c00ea92bfcda92687fda52f4eb1e29b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:37:52 +0200 Subject: [PATCH 165/203] check for correct first argument --- client/ayon_core/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index 80fbb57340..bd47dc1aac 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -176,9 +176,9 @@ def run(script): # future versions might remove it. first_arg = sys.argv[0] if is_running_from_build(): - comp_path = os.path.join(os.environ["AYON_ROOT"], "start.py") - else: comp_path = os.getenv("AYON_EXECUTABLE") + else: + comp_path = os.path.join(os.environ["AYON_ROOT"], "start.py") # Compare paths and remove first argument if it is the same as AYON if Path(first_arg).resolve() == Path(comp_path).resolve(): sys.argv.pop(0) From 409c8ef88510cf2dc7f726c242adeac7918e08c5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:29:24 +0200 Subject: [PATCH 166/203] bump applications addon version to '0.2.0' --- server_addon/applications/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/applications/package.py b/server_addon/applications/package.py index 0380222a53..ce312ed662 100644 --- a/server_addon/applications/package.py +++ b/server_addon/applications/package.py @@ -1,3 +1,3 @@ name = "applications" title = "Applications" -version = "0.1.9" +version = "0.2.0" From ad7b558d186bcd5c27cef35c93c0f1e383ceadfc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:29:58 +0200 Subject: [PATCH 167/203] removed logic loading addons from 'addons' dir --- client/ayon_core/addon/base.py | 57 ---------------------------------- 1 file changed, 57 deletions(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 6ef838652e..8d0465db35 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -336,66 +336,9 @@ def _load_ayon_addons(openpype_modules, modules_key, log): return addons_to_skip_in_core -def _load_ayon_core_addons_dir( - ignore_addon_names, openpype_modules, modules_key, log -): - addons_dir = os.path.join(AYON_CORE_ROOT, "addons") - if not os.path.exists(addons_dir): - return - - imported_modules = [] - - # Make sure that addons which already have client code are not loaded - # from core again, with older code - filtered_paths = [] - for name in os.listdir(addons_dir): - if name in ignore_addon_names: - continue - path = os.path.join(addons_dir, name) - if os.path.isdir(path): - filtered_paths.append(path) - - for path in filtered_paths: - while path in sys.path: - sys.path.remove(path) - sys.path.insert(0, path) - - for name in os.listdir(path): - fullpath = os.path.join(path, name) - if os.path.isfile(fullpath): - basename, ext = os.path.splitext(name) - if ext != ".py": - continue - else: - basename = name - try: - module = __import__(basename, fromlist=("",)) - for attr_name in dir(module): - attr = getattr(module, attr_name) - if ( - inspect.isclass(attr) - and issubclass(attr, AYONAddon) - ): - new_import_str = "{}.{}".format(modules_key, basename) - sys.modules[new_import_str] = module - setattr(openpype_modules, basename, module) - imported_modules.append(module) - break - - except Exception: - log.error( - "Failed to import addon '{}'.".format(fullpath), - exc_info=True - ) - return imported_modules - - def _load_addons_in_core( ignore_addon_names, openpype_modules, modules_key, log ): - _load_ayon_core_addons_dir( - ignore_addon_names, openpype_modules, modules_key, log - ) # Add current directory at first place # - has small differences in import logic hosts_dir = os.path.join(AYON_CORE_ROOT, "hosts") From 4fa9cab65bc7a159ca1581340c89c9e9400f2bf1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 10 Apr 2024 18:32:23 +0200 Subject: [PATCH 168/203] implemented milestone versions so we can log that addon version is too old to be compatible with ayon core --- client/ayon_core/addon/base.py | 56 ++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 8d0465db35..3d028dba07 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -15,6 +15,7 @@ from abc import ABCMeta, abstractmethod import six import appdirs import ayon_api +from semver import VersionInfo from ayon_core import AYON_CORE_ROOT from ayon_core.lib import Logger, is_dev_mode_enabled @@ -46,6 +47,11 @@ IGNORED_HOSTS_IN_AYON = { } IGNORED_MODULES_IN_AYON = set() +# When addon was moved from ayon-core codebase +# - this is used to log the missing addon +MOVED_ADDON_MILESTONE_VERSIONS = { + "applications": VersionInfo(2, 0, 0), +} # Inherit from `object` for Python 2 hosts class _ModuleClass(object): @@ -192,6 +198,45 @@ def _get_ayon_addons_information(bundle_info): return output +def _handle_moved_addons(addon_name, milestone_version, log): + """Log message that addon version is not compatible with current core. + + The function can return path to addon client code, but that can happen + only if ayon-core is used from code (for development), but still + logs a warning. + + Args: + addon_name (str): Addon name. + milestone_version (str): Milestone addon version. + log (logging.Logger): Logger object. + + Returns: + Union[str, None]: Addon dir or None. + """ + # Handle addons which were moved out of ayon-core + # - Try to fix it by loading it directly from server addons dir in + # ayon-core repository. But that will work only if ayon-core is + # used from code. + addon_dir = os.path.join( + os.path.dirname(os.path.dirname(AYON_CORE_ROOT)), + "server_addon", + addon_name, + "client", + ) + if not os.path.exists(addon_dir): + log.error(( + "Addon '{}' is not be available." + " Please update applications addon to '{}' or higher." + ).format(addon_name, milestone_version)) + return None + + log.warning(( + "Please update '{}' addon to '{}' or higher." + " Using client code from ayon-core repository." + ).format(addon_name, milestone_version)) + return addon_dir + + def _load_ayon_addons(openpype_modules, modules_key, log): """Load AYON addons based on information from server. @@ -249,6 +294,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log): use_dev_path = dev_addon_info.get("enabled", False) addon_dir = None + milestone_version = MOVED_ADDON_MILESTONE_VERSIONS.get(addon_name) if use_dev_path: addon_dir = dev_addon_info["path"] if not addon_dir or not os.path.exists(addon_dir): @@ -257,6 +303,16 @@ def _load_ayon_addons(openpype_modules, modules_key, log): ).format(addon_name, addon_version, addon_dir)) continue + elif ( + milestone_version is not None + and VersionInfo.parse(addon_version) < milestone_version + ): + addon_dir = _handle_moved_addons( + addon_name, milestone_version, log + ) + if not addon_dir: + continue + elif addons_dir_exists: folder_name = "{}_{}".format(addon_name, addon_version) addon_dir = os.path.join(addons_dir, folder_name) From 1d459d2be26ff4b73e13f1e64c89bdb5a050877a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 10 Apr 2024 22:43:21 +0200 Subject: [PATCH 169/203] modified products model to get versions information once --- .../tools/loader/ui/products_model.py | 65 ++++++++++++++++--- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/loader/ui/products_model.py b/client/ayon_core/tools/loader/ui/products_model.py index 41342ba0df..b465679c3b 100644 --- a/client/ayon_core/tools/loader/ui/products_model.py +++ b/client/ayon_core/tools/loader/ui/products_model.py @@ -284,7 +284,13 @@ class ProductsModel(QtGui.QStandardItemModel): model_item.setData(label, QtCore.Qt.DisplayRole) return model_item - def _set_version_data_to_product_item(self, model_item, version_item): + def _set_version_data_to_product_item( + self, + model_item, + version_item, + repre_count_by_version_id=None, + sync_availability_by_version_id=None, + ): """ Args: @@ -292,6 +298,10 @@ class ProductsModel(QtGui.QStandardItemModel): from version item. version_item (VersionItem): Item from entities model with information about version. + repre_count_by_version_id (Optional[str, int]): Mapping of + representation count by version id. + sync_availability_by_version_id (Optional[str, Tuple[int, int]]): + Mapping of sync availability by version id. """ model_item.setData(version_item.version_id, VERSION_ID_ROLE) @@ -312,12 +322,20 @@ class ProductsModel(QtGui.QStandardItemModel): # TODO call site sync methods for all versions at once project_name = self._last_project_name version_id = version_item.version_id - repre_count = self._controller.get_versions_representation_count( - project_name, [version_id] - )[version_id] - active, remote = self._controller.get_version_sync_availability( - project_name, [version_id] - )[version_id] + if repre_count_by_version_id is None: + repre_count_by_version_id = ( + self._controller.get_versions_representation_count( + project_name, [version_id] + ) + ) + if sync_availability_by_version_id is None: + sync_availability_by_version_id = ( + self._controller.get_version_sync_availability( + project_name, [version_id] + ) + ) + repre_count = repre_count_by_version_id[version_id] + active, remote = sync_availability_by_version_id[version_id] model_item.setData(repre_count, REPRESENTATIONS_COUNT_ROLE) model_item.setData(active, SYNC_ACTIVE_SITE_AVAILABILITY) @@ -327,7 +345,9 @@ class ProductsModel(QtGui.QStandardItemModel): self, product_item, active_site_icon, - remote_site_icon + remote_site_icon, + repre_count_by_version_id, + sync_availability_by_version_id, ): model_item = self._items_by_id.get(product_item.product_id) versions = list(product_item.version_items.values()) @@ -357,7 +377,12 @@ class ProductsModel(QtGui.QStandardItemModel): model_item.setData(active_site_icon, ACTIVE_SITE_ICON_ROLE) model_item.setData(remote_site_icon, REMOTE_SITE_ICON_ROLE) - self._set_version_data_to_product_item(model_item, last_version) + self._set_version_data_to_product_item( + model_item, + last_version, + repre_count_by_version_id, + sync_availability_by_version_id, + ) return model_item def get_last_project_name(self): @@ -387,6 +412,24 @@ class ProductsModel(QtGui.QStandardItemModel): product_item.product_id: product_item for product_item in product_items } + last_version_id_by_product_id = {} + for product_item in product_items: + versions = list(product_item.version_items.values()) + versions.sort() + last_version = versions[-1] + last_version_id_by_product_id[product_item.product_id] = ( + last_version.version_id + ) + + version_ids = set(last_version_id_by_product_id.values()) + repre_count_by_version_id = self._controller.get_versions_representation_count( + project_name, version_ids + ) + sync_availability_by_version_id = ( + self._controller.get_version_sync_availability( + project_name, version_ids + ) + ) # Prepare product groups product_name_matches_by_group = collections.defaultdict(dict) @@ -443,6 +486,8 @@ class ProductsModel(QtGui.QStandardItemModel): product_item, active_site_icon, remote_site_icon, + repre_count_by_version_id, + sync_availability_by_version_id, ) new_items.append(item) @@ -463,6 +508,8 @@ class ProductsModel(QtGui.QStandardItemModel): product_item, active_site_icon, remote_site_icon, + repre_count_by_version_id, + sync_availability_by_version_id, ) new_merged_items.append(item) merged_product_types.add(product_item.product_type) From 93d0e28a639814d752317ad8d06d3b76d4a17cf7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:10:55 +0200 Subject: [PATCH 170/203] make sure path is string --- server_addon/create_ayon_addons.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index 2501faebe3..bfd601af07 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -225,6 +225,8 @@ def import_filepath(path: Path, module_name: Optional[str] = None): if not module_name: module_name = os.path.splitext(path.name)[0] + # Convert to string + path = str(path) module = types.ModuleType(module_name) module.__file__ = path From 065906a5ab69274f04aefdc140dc3b9a540d0254 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 12:16:45 +0200 Subject: [PATCH 171/203] Fix 'reference node is not associated with a reference file' errors --- client/ayon_core/hosts/maya/api/lib.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 2c6017148a..321bcbc0b5 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -4270,6 +4270,9 @@ def get_reference_node(members, log=None): if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"): continue + if not is_valid_reference_node(ref): + continue + references.add(ref) assert references, "No reference node found in container" @@ -4300,15 +4303,19 @@ def get_reference_node_parents(ref): list: The upstream parent reference nodes. """ - parent = cmds.referenceQuery(ref, - referenceNode=True, - parent=True) + def _get_parent(reference_node): + """Return parent reference node, but ignore invalid reference nodes""" + if not is_valid_reference_node(reference_node): + return + return cmds.referenceQuery(reference_node, + referenceNode=True, + parent=True) + + parent = _get_parent(ref) parents = [] while parent: parents.append(parent) - parent = cmds.referenceQuery(parent, - referenceNode=True, - parent=True) + parent = _get_parent(parent) return parents From f9a0b7a1ec62f42d9a96ed2524671e792195849f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 12:58:47 +0200 Subject: [PATCH 172/203] Replace instances while preserving their `instance.id` and instance.data[`instance_id`] --- .../plugins/publish/collect_render.py | 11 ++---- .../fusion/plugins/publish/collect_render.py | 16 +++----- .../publish/abstract_collect_render.py | 15 +++++++- client/ayon_core/pipeline/publish/lib.py | 38 +++++++++++++++++++ 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py index 4134e9d593..46616a2d93 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py @@ -41,7 +41,6 @@ class CollectAERender(publish.AbstractCollectRender): def get_instances(self, context): instances = [] - instances_to_remove = [] app_version = CollectAERender.get_stub().get_app_version() app_version = app_version[0:4] @@ -117,7 +116,10 @@ class CollectAERender(publish.AbstractCollectRender): fps=fps, app_version=app_version, publish_attributes=inst.data.get("publish_attributes", {}), - file_names=[item.file_name for item in render_q] + file_names=[item.file_name for item in render_q], + + # The source instance this render instance replaces + source_instance=inst ) comp = compositions_by_id.get(comp_id) @@ -144,11 +146,6 @@ class CollectAERender(publish.AbstractCollectRender): # to skip ExtractReview locally instance.families.remove("review") - instances.append(instance) - instances_to_remove.append(inst) - - for instance in instances_to_remove: - context.remove(instance) return instances def get_expected_files(self, render_instance): diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py index 36102d02cb..521a47675a 100644 --- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py @@ -37,14 +37,13 @@ class CollectFusionRender( aspect_x = comp_frame_format_prefs["AspectX"] aspect_y = comp_frame_format_prefs["AspectY"] - instances = [] - instances_to_remove = [] current_file = context.data["currentFile"] version = context.data["version"] project_entity = context.data["projectEntity"] + instances = [] for inst in context: if not inst.data.get("active", True): continue @@ -91,7 +90,10 @@ class CollectFusionRender( frameStep=1, fps=comp_frame_format_prefs.get("Rate"), app_version=comp.GetApp().Version, - publish_attributes=inst.data.get("publish_attributes", {}) + publish_attributes=inst.data.get("publish_attributes", {}), + + # The source instance this render instance replaces + source_instance=inst ) render_target = inst.data["creator_attributes"]["render_target"] @@ -114,14 +116,6 @@ class CollectFusionRender( # to skip ExtractReview locally instance.families.remove("review") - # add new instance to the list and remove the original - # instance since it is not needed anymore - instances.append(instance) - instances_to_remove.append(inst) - - for instance in instances_to_remove: - context.remove(instance) - return instances def post_collecting_action(self): diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 745632ca0a..1b482b1d5f 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -12,6 +12,7 @@ import six import pyblish.api from .publish_plugins import AbstractMetaContextPlugin +from .lib import replace_instance_in_context @attr.s @@ -81,6 +82,9 @@ class RenderInstance(object): outputDir = attr.ib(default=None) context = attr.ib(default=None) + # The source instance this render instance should replace in the context + source_instance = attr.ib(default=None, type=pyblish.api.Instance) + @frameStart.validator def check_frame_start(self, _, value): """Validate if frame start is not larger then end.""" @@ -214,8 +218,15 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): data = self.add_additional_data(data) render_instance_dict = attr.asdict(render_instance) - instance = context.create_instance(render_instance.name) - instance.data["label"] = render_instance.label + instance = pyblish.api.Instance(render_instance.name, + parent=context) + if render_instance.source_instance: + replace_instance_in_context(context, + render_instance.source_instance, + instance) + else: + context.append(instance) + instance.data.update(render_instance_dict) instance.data.update(data) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 8d3644637b..63fb0feb0a 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -934,3 +934,41 @@ def get_publish_instance_families(instance): families.discard(family) output.extend(families) return output + + +def replace_instance_in_context( + context: pyblish.api.Context, + source_instance: pyblish.api.Instance, + destination_instance: pyblish.api.Instance +): + """Replace source instance with the destination instance. + + This transfers the instance's IDs so that the new instance acts exactly + as if it was the source instance the whole time. This is required for + the publisher to correctly detect and transfer the logs relevant for the + instance. + + Returns: + bool: Whether the source instance was replaced or the destination + instance was only added because source instance did not exist + in the context. + + """ + + # Transfer the pyblish.api.Instance id + destination_instance._id = source_instance.id + + # Transfer the `instance_id` of the new publisher's instances + key = "instance_id" + if key in source_instance.data: + destination_instance.data[key] = source_instance.data[key] + + # Replace the instance at the same index in the context + for i, instance in enumerate(context): + if source_instance is instance: + context[i] = destination_instance + return True + + # Source was not found + context.append(destination_instance) + return False From 56fd07a7848a4cb0c3ee5d16d60d84e39c4333e5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 13:14:24 +0200 Subject: [PATCH 173/203] Fix replacement --- client/ayon_core/pipeline/publish/abstract_collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 1b482b1d5f..01c2ed7537 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -220,7 +220,7 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): instance = pyblish.api.Instance(render_instance.name, parent=context) - if render_instance.source_instance: + if render_instance.source_instance is not None: replace_instance_in_context(context, render_instance.source_instance, instance) From 6e812e00796c8f0a2cf3eedf5d5d00196d3984d2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 13:23:00 +0200 Subject: [PATCH 174/203] Fix replacement, and do not add new instance in context twice --- .../pipeline/publish/abstract_collect_render.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 01c2ed7537..096f1c406e 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -218,14 +218,15 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): data = self.add_additional_data(data) render_instance_dict = attr.asdict(render_instance) - instance = pyblish.api.Instance(render_instance.name, - parent=context) + instance = context.create_instance(render_instance.name) if render_instance.source_instance is not None: - replace_instance_in_context(context, - render_instance.source_instance, - instance) - else: - context.append(instance) + # remove the new instance, because we want to insert it + # at the position of the original instance + context.pop() + replace_instance_in_context( + context, + source_instance=render_instance.source_instance, + destination_instance=instance) instance.data.update(render_instance_dict) instance.data.update(data) From 5b85cc9ec3cafbe632eba90975acb0d4bde43c29 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 13:23:41 +0200 Subject: [PATCH 175/203] Tweak comment --- client/ayon_core/pipeline/publish/abstract_collect_render.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 096f1c406e..a9d5b38ada 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -221,7 +221,8 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): instance = context.create_instance(render_instance.name) if render_instance.source_instance is not None: # remove the new instance, because we want to insert it - # at the position of the original instance + # at the position of the original instance to replace the + # source instance in the context completely context.pop() replace_instance_in_context( context, From b3d9dabd62bd98455529ae4fa564c3e6de87379c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 13:24:08 +0200 Subject: [PATCH 176/203] Fix returning the instances --- client/ayon_core/hosts/fusion/plugins/publish/collect_render.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py index 521a47675a..7a2844d5db 100644 --- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py @@ -116,6 +116,8 @@ class CollectFusionRender( # to skip ExtractReview locally instance.families.remove("review") + instances.append(instance) + return instances def post_collecting_action(self): From 708fd64c793f41cb423ff7a139072e8f78d73abd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 13:29:35 +0200 Subject: [PATCH 177/203] Fix instances - I need more coffee today --- .../hosts/aftereffects/plugins/publish/collect_render.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py index 46616a2d93..c28042b6ae 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py @@ -146,6 +146,8 @@ class CollectAERender(publish.AbstractCollectRender): # to skip ExtractReview locally instance.families.remove("review") + instances.append(instance) + return instances def get_expected_files(self, render_instance): From bcfd43b9f5b906f0bece12fc5667bf0bb0f5b45a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Apr 2024 20:28:11 +0800 Subject: [PATCH 178/203] add 8k options into texture creator --- .../hosts/substancepainter/plugins/create/create_textures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/substancepainter/plugins/create/create_textures.py b/client/ayon_core/hosts/substancepainter/plugins/create/create_textures.py index f204ff7728..f46afadb5a 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/create/create_textures.py +++ b/client/ayon_core/hosts/substancepainter/plugins/create/create_textures.py @@ -144,7 +144,8 @@ class CreateTextures(Creator): 9: "512", 10: "1024", 11: "2048", - 12: "4096" + 12: "4096", + 13: "8192" }, default=None, label="Size"), From 19c655e1532236418b55d07fcb3b44386b8324a5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 14:38:28 +0200 Subject: [PATCH 179/203] Update client/ayon_core/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/publish/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 63fb0feb0a..87d01e5b97 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -937,9 +937,9 @@ def get_publish_instance_families(instance): def replace_instance_in_context( - context: pyblish.api.Context, - source_instance: pyblish.api.Instance, - destination_instance: pyblish.api.Instance + context: pyblish.api.Context, + source_instance: pyblish.api.Instance, + destination_instance: pyblish.api.Instance ): """Replace source instance with the destination instance. From e1bd0ccf540f341905a11862b8b65ce03a94272a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 14:38:33 +0200 Subject: [PATCH 180/203] Update client/ayon_core/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/publish/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 87d01e5b97..2b97feebb3 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -964,9 +964,9 @@ def replace_instance_in_context( destination_instance.data[key] = source_instance.data[key] # Replace the instance at the same index in the context - for i, instance in enumerate(context): + for idx, instance in enumerate(context): if source_instance is instance: - context[i] = destination_instance + context[idx] = destination_instance return True # Source was not found From 25a54cbdca35380195a5998f3b2f34f4561a8c66 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 14:38:38 +0200 Subject: [PATCH 181/203] Update client/ayon_core/pipeline/publish/lib.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/publish/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 2b97feebb3..ebdaca5518 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -954,7 +954,6 @@ def replace_instance_in_context( in the context. """ - # Transfer the pyblish.api.Instance id destination_instance._id = source_instance.id From e3cfb6800707eb906135feae6bf5c835b7ecccc6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 11 Apr 2024 14:42:08 +0200 Subject: [PATCH 182/203] convert only string values in prepare data --- client/ayon_core/lib/plugin_tools.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/lib/plugin_tools.py b/client/ayon_core/lib/plugin_tools.py index 5ad4da88b9..654bc7ac4a 100644 --- a/client/ayon_core/lib/plugin_tools.py +++ b/client/ayon_core/lib/plugin_tools.py @@ -94,8 +94,12 @@ def prepare_template_data(fill_pairs): output = {} for item in valid_items: keys, value = item - upper_value = value.upper() - capitalized_value = _capitalize_value(value) + # Convert only string values + if isinstance(value, str): + upper_value = value.upper() + capitalized_value = _capitalize_value(value) + else: + upper_value = capitalized_value = value first_key = keys.pop(0) if not keys: From d0b622ffc0fcbfe3c35d213b328702e51acc0aae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 15:18:11 +0200 Subject: [PATCH 183/203] Improve validations for meshes without any faces/geometry Easiest reproducable: `maya.cmds.createNode("mesh")` --- .../maya/plugins/publish/validate_mesh_ngons.py | 5 +++++ .../plugins/publish/validate_mesh_uv_set_map1.py | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_ngons.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_ngons.py index d1d7e49fa4..58d015e962 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_ngons.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_ngons.py @@ -45,6 +45,11 @@ class ValidateMeshNgons(pyblish.api.InstancePlugin, # Get all faces faces = ['{0}.f[*]'.format(node) for node in meshes] + # Skip meshes that for some reason have no faces, e.g. empty meshes + faces = cmds.ls(faces) + if not faces: + return [] + # Filter to n-sided polygon faces (ngons) invalid = lib.polyConstraint(faces, t=0x0008, # type=face diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py index a139b65169..71503f42ee 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py @@ -29,8 +29,8 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin, actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction] - @staticmethod - def get_invalid(instance): + @classmethod + def get_invalid(cls, instance): meshes = cmds.ls(instance, type='mesh', long=True) @@ -40,6 +40,11 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin, # Get existing mapping of uv sets by index indices = cmds.polyUVSet(mesh, query=True, allUVSetsIndices=True) maps = cmds.polyUVSet(mesh, query=True, allUVSets=True) + if not indices or not maps: + cls.log.warning("Mesh has no UV set: %s", mesh) + invalid.append(mesh) + continue + mapping = dict(zip(indices, maps)) # Get the uv set at index zero. @@ -68,6 +73,12 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin, # Get existing mapping of uv sets by index indices = cmds.polyUVSet(mesh, query=True, allUVSetsIndices=True) maps = cmds.polyUVSet(mesh, query=True, allUVSets=True) + if not indices or not maps: + # No UV set exist at all, create a `map1` uv set + # This may fail silently if the mesh has no geometry at all + cmds.polyUVSet(mesh, create=True, uvSet="map1") + continue + mapping = dict(zip(indices, maps)) # Ensure there is no uv set named map1 to avoid From 3c8efc0a75cb6dd3c6af2836299048b870918a8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 11 Apr 2024 15:37:10 +0200 Subject: [PATCH 184/203] define explicit version of pyside6 --- .../hosts/blender/hooks/pre_pyside_install.py | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py index e03856c2e5..a64c767582 100644 --- a/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py +++ b/client/ayon_core/hosts/blender/hooks/pre_pyside_install.py @@ -80,6 +80,9 @@ class InstallPySideToBlender(PreLaunchHook): # Blender 4 has Python 3.11 which does not support 'PySide2' # QUESTION could we always install PySide6? qt_binding = "PySide2" if before_blender_4 else "PySide6" + # Use PySide6 6.6.3 because 6.7.0 had a bug + # - 'QTextEdit' can't be added to 'QBoxLayout' + qt_binding_version = None if before_blender_4 else "6.6.3" python_dir = os.path.join(versions_dir, version_subfolder, "python") python_lib = os.path.join(python_dir, "lib") @@ -130,11 +133,16 @@ class InstallPySideToBlender(PreLaunchHook): # Install PySide2 in blender's python if platform == "windows": result = self.install_pyside_windows( - python_executable, qt_binding, before_blender_4 + python_executable, + qt_binding, + qt_binding_version, + before_blender_4, ) else: result = self.install_pyside( - python_executable, qt_binding + python_executable, + qt_binding, + qt_binding_version, ) if result: @@ -147,7 +155,11 @@ class InstallPySideToBlender(PreLaunchHook): ) def install_pyside_windows( - self, python_executable, qt_binding, before_blender_4 + self, + python_executable, + qt_binding, + qt_binding_version, + before_blender_4, ): """Install PySide2 python module to blender's python. @@ -167,6 +179,9 @@ class InstallPySideToBlender(PreLaunchHook): self.log.warning("Couldn't import \"pywin32\" modules") return + if qt_binding_version: + qt_binding = f"{qt_binding}=={qt_binding_version}" + try: # Parameters # - use "-m pip" as module pip to install PySide2 and argument @@ -212,8 +227,15 @@ class InstallPySideToBlender(PreLaunchHook): except pywintypes.error: pass - def install_pyside(self, python_executable, qt_binding): - """Install PySide2 python module to blender's python.""" + def install_pyside( + self, + python_executable, + qt_binding, + qt_binding_version, + ): + """Install Qt binding python module to blender's python.""" + if qt_binding_version: + qt_binding = f"{qt_binding}=={qt_binding_version}" try: # Parameters # - use "-m pip" as module pip to install qt binding and argument From dfc52029d6718287a642dcff81a5298c76ac8576 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 16:03:34 +0200 Subject: [PATCH 185/203] Improve validation report: better node list and add description for the report --- .../publish/validate_mesh_uv_set_map1.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py index 71503f42ee..305a58d78e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py @@ -1,3 +1,5 @@ +import inspect + from maya import cmds import pyblish.api @@ -61,8 +63,14 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin, invalid = self.get_invalid(instance) if invalid: + + invalid_list = "\n".join(f"- {node}" for node in invalid) + raise PublishValidationError( - "Meshes found without 'map1' UV set: {0}".format(invalid)) + "Meshes found without 'map1' UV set:\n" + "{0}".format(invalid_list), + description=self.get_description() + ) @classmethod def repair(cls, instance): @@ -108,3 +116,23 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin, rename=True, uvSet=original, newUVSet="map1") + + @staticmethod + def get_description(): + return inspect.cleandoc("""### Mesh found without map1 uv set + + A mesh must have a default UV set named `map1` to adhere to the default + mesh behavior of Maya meshes. + + There may be meshes that: + - Have no UV set + - Have no `map1` uv set but are using a different name + - Have a `map1` uv set, but it's not the default (first index) + + + #### Repair + + Using repair will try to make the first UV set the `map1` uv set. If it + does not exist yet it will be created or renames the current first + UV set to `map1`. + """) From 9690748d993dc2384013fc3a6714cd82e7000435 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 16:24:40 +0200 Subject: [PATCH 186/203] Do not return `bool` --- client/ayon_core/pipeline/publish/lib.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index ebdaca5518..4cf2c4cb83 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -948,11 +948,6 @@ def replace_instance_in_context( the publisher to correctly detect and transfer the logs relevant for the instance. - Returns: - bool: Whether the source instance was replaced or the destination - instance was only added because source instance did not exist - in the context. - """ # Transfer the pyblish.api.Instance id destination_instance._id = source_instance.id @@ -966,8 +961,7 @@ def replace_instance_in_context( for idx, instance in enumerate(context): if source_instance is instance: context[idx] = destination_instance - return True + return # Source was not found context.append(destination_instance) - return False From 5ad7103f5777f70d244bde459cbf7b98b880f1d9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 16:25:34 +0200 Subject: [PATCH 187/203] Raise error if source not found in context --- client/ayon_core/pipeline/publish/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 4cf2c4cb83..bf733a8620 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -963,5 +963,6 @@ def replace_instance_in_context( context[idx] = destination_instance return - # Source was not found - context.append(destination_instance) + raise ValueError( + f"Source instance {source_instance} not found in context." + ) From 79abe05637823693cc9cea73823f1adf8f6359f7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 17:03:42 +0200 Subject: [PATCH 188/203] Fix message formatting --- .../hosts/maya/plugins/publish/validate_scene_set_workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_scene_set_workspace.py b/client/ayon_core/hosts/maya/plugins/publish/validate_scene_set_workspace.py index 6e68cf5d14..c7d5de2050 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_scene_set_workspace.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_scene_set_workspace.py @@ -46,6 +46,6 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): raise PublishValidationError( "Maya workspace is not set correctly.\n\n" f"Current workfile `{scene_name}` is not inside the " - "current Maya project root directory `{root_dir}`.\n\n" + f"current Maya project root directory `{root_dir}`.\n\n" "Please use Workfile app to re-save." ) From 129d5d6b0ed2cbcb1ab673f2e315dc65bfd7471a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 17:11:39 +0200 Subject: [PATCH 189/203] Do not create a new instance, just update the existing instance --- .../publish/abstract_collect_render.py | 13 ++----- client/ayon_core/pipeline/publish/lib.py | 34 +------------------ 2 files changed, 4 insertions(+), 43 deletions(-) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index a9d5b38ada..d344433d53 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -12,7 +12,6 @@ import six import pyblish.api from .publish_plugins import AbstractMetaContextPlugin -from .lib import replace_instance_in_context @attr.s @@ -218,16 +217,10 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): data = self.add_additional_data(data) render_instance_dict = attr.asdict(render_instance) - instance = context.create_instance(render_instance.name) if render_instance.source_instance is not None: - # remove the new instance, because we want to insert it - # at the position of the original instance to replace the - # source instance in the context completely - context.pop() - replace_instance_in_context( - context, - source_instance=render_instance.source_instance, - destination_instance=instance) + instance = render_instance.source_instance + else: + instance = context.create_instance(render_instance.name) instance.data.update(render_instance_dict) instance.data.update(data) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index bf733a8620..0be72b459a 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -933,36 +933,4 @@ def get_publish_instance_families(instance): output.append(family) families.discard(family) output.extend(families) - return output - - -def replace_instance_in_context( - context: pyblish.api.Context, - source_instance: pyblish.api.Instance, - destination_instance: pyblish.api.Instance -): - """Replace source instance with the destination instance. - - This transfers the instance's IDs so that the new instance acts exactly - as if it was the source instance the whole time. This is required for - the publisher to correctly detect and transfer the logs relevant for the - instance. - - """ - # Transfer the pyblish.api.Instance id - destination_instance._id = source_instance.id - - # Transfer the `instance_id` of the new publisher's instances - key = "instance_id" - if key in source_instance.data: - destination_instance.data[key] = source_instance.data[key] - - # Replace the instance at the same index in the context - for idx, instance in enumerate(context): - if source_instance is instance: - context[idx] = destination_instance - return - - raise ValueError( - f"Source instance {source_instance} not found in context." - ) + return output \ No newline at end of file From 5f507ff85438654e42891f54daf12a327d87993e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 17:12:45 +0200 Subject: [PATCH 190/203] Update comment --- client/ayon_core/pipeline/publish/abstract_collect_render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index d344433d53..2e56adf66e 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -81,7 +81,7 @@ class RenderInstance(object): outputDir = attr.ib(default=None) context = attr.ib(default=None) - # The source instance this render instance should replace in the context + # The source instance the data of this render instance should merge into source_instance = attr.ib(default=None, type=pyblish.api.Instance) @frameStart.validator From c9dc848b70592088594c8254e256f446101548c5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 17:15:42 +0200 Subject: [PATCH 191/203] Cosmetics --- client/ayon_core/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 0be72b459a..8d3644637b 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -933,4 +933,4 @@ def get_publish_instance_families(instance): output.append(family) families.discard(family) output.extend(families) - return output \ No newline at end of file + return output From c538ed58efd1c35849455822c089dc53b2ca8731 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 17:26:15 +0200 Subject: [PATCH 192/203] Do not merge `source_instance` into the instance --- .../ayon_core/pipeline/publish/abstract_collect_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index 2e56adf66e..c50dc16380 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -217,9 +217,9 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): data = self.add_additional_data(data) render_instance_dict = attr.asdict(render_instance) - if render_instance.source_instance is not None: - instance = render_instance.source_instance - else: + # Merge into source instance if provided, otherwise create instance + instance = render_instance_dict.pop("source_instance", None) + if instance is None: instance = context.create_instance(render_instance.name) instance.data.update(render_instance_dict) From cc42c4d5a89978a3ca3be622dc28bbab0e85902f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 11 Apr 2024 17:52:38 +0200 Subject: [PATCH 193/203] Fix - use differently named variable Original `instance` variable might shadow wanted `render-like` instance if wrong order of instances get processed. --- .../deadline/plugins/publish/submit_fusion_deadline.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index bfb65708e6..e3a4cd8030 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -103,17 +103,17 @@ class FusionSubmitDeadline( # Collect all saver instances in context that are to be rendered saver_instances = [] - for instance in context: - if instance.data["productType"] != "render": + for inst in context: + if inst.data["productType"] != "render": # Allow only saver family instances continue - if not instance.data.get("publish", True): + if not inst.data.get("publish", True): # Skip inactive instances continue - self.log.debug(instance.data["name"]) - saver_instances.append(instance) + self.log.debug(inst.data["name"]) + saver_instances.append(inst) if not saver_instances: raise RuntimeError("No instances found for Deadline submission") From 6a08003ca7975dc2a72d7e66577a21303dc71362 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 18:06:27 +0200 Subject: [PATCH 194/203] Preserve image plane size on attaching - fix #411 --- .../hosts/maya/plugins/publish/extract_camera_mayaScene.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/client/ayon_core/hosts/maya/plugins/publish/extract_camera_mayaScene.py index c4af2914cd..cb3951ec0c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -299,4 +299,10 @@ def transfer_image_planes(source_cameras, target_cameras, def _attach_image_plane(camera, image_plane): cmds.imagePlane(image_plane, edit=True, detach=True) + + # Attaching to a camera resets it to identity size, so we counter that + size_x = cmds.getAttr(f"{image_plane}.sizeX") + size_y = cmds.getAttr(f"{image_plane}.sizeY") cmds.imagePlane(image_plane, edit=True, camera=camera) + cmds.setAttr(f"{image_plane}.sizeX", size_x) + cmds.setAttr(f"{image_plane}.sizeY", size_y) From 5adfd62e9a025bbced343c121ef9982160a95887 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 11 Apr 2024 21:20:53 +0100 Subject: [PATCH 195/203] Porting --- .../pipeline/farm/pyblish_functions.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 37003cbd88..eb6f8569d9 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -617,15 +617,32 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, aov_patterns = aov_filter preview = match_aov_pattern(app, aov_patterns, render_file_name) - # toggle preview on if multipart is on - if instance.data.get("multipartExr"): - log.debug("Adding preview tag because its multipartExr") - preview = True new_instance = deepcopy(skeleton) new_instance["productName"] = product_name new_instance["productGroup"] = group_name + # toggle preview on if multipart is on + # Because we cant query the multipartExr data member of each AOV we'll + # need to have hardcoded rule of excluding any renders with + # "cryptomatte" in the file name from being a multipart EXR. This issue + # happens with Redshift that forces Cryptomatte renders to be separate + # files even when the rest of the AOVs are merged into a single EXR. + # There might be an edge case where the main instance has cryptomatte + # in the name even though it's a multipart EXR. + if instance.data.get("renderer") == "redshift": + if ( + instance.data.get("multipartExr") and + "cryptomatte" not in render_file_name.lower() + ): + log.debug("Adding preview tag because it's multipartExr") + preview = True + else: + new_instance["multipartExr"] = False + elif instance.data.get("multipartExr"): + log.debug("Adding preview tag because its multipartExr") + preview = True + # explicitly disable review by user preview = preview and not do_not_add_review if preview: From 0a2a1105df3f1ced13e460bc9c4a46e6e6d7a5aa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Apr 2024 14:45:57 +0200 Subject: [PATCH 196/203] Ignore invalid uuids in query to AYON API --- .../publish/validate_node_ids_related.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py index f8337ed6f0..2930f6f7f6 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py @@ -1,3 +1,4 @@ +import uuid from collections import defaultdict import pyblish.api @@ -8,6 +9,15 @@ from ayon_core.pipeline.publish import ( from ayon_api import get_folders +def is_valid_uuid(value) -> bool: + """Return whether value is a valid UUID""" + try: + uuid.UUID(value) + except ValueError: + return False + return True + + class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Validate nodes have a related Colorbleed Id to the @@ -70,6 +80,14 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, if nodes_by_other_folder_ids: project_name = instance.context.data["projectName"] other_folder_ids = set(nodes_by_other_folder_ids.keys()) + + # Remove folder ids that are not valid UUID identifiers, these + # may be legacy OpenPype ids + other_folder_ids = {folder_id for folder_id in other_folder_ids + if is_valid_uuid(folder_id)} + if not other_folder_ids: + return invalid + folder_entities = get_folders(project_name=project_name, folder_ids=other_folder_ids, fields=["path"]) From 8984788bc35d24ec819803cf06c643f420867da9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Apr 2024 15:29:45 +0200 Subject: [PATCH 197/203] Improve validation report --- .../publish/validate_node_ids_related.py | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py index 2930f6f7f6..17eb58f421 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_related.py @@ -1,3 +1,4 @@ +import inspect import uuid from collections import defaultdict import pyblish.api @@ -20,10 +21,7 @@ def is_valid_uuid(value) -> bool: class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): - """Validate nodes have a related Colorbleed Id to the - instance.data[folderPath] - - """ + """Validate nodes have a related `cbId` to the instance.data[folderPath]""" order = ValidatePipelineOrder label = 'Node Ids Related (ID)' @@ -51,11 +49,14 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, # Ensure all nodes have a cbId invalid = self.get_invalid(instance) if invalid: + + invalid_list = "\n".join(f"- {node}" for node in sorted(invalid)) + raise PublishValidationError(( - "Nodes IDs found that are not related to folder '{}' : {}" - ).format( - instance.data["folderPath"], invalid - )) + "Nodes IDs found that are not related to folder '{}':\n{}" + ).format(instance.data["folderPath"], invalid_list), + description=self.get_description() + ) @classmethod def get_invalid(cls, instance): @@ -98,8 +99,24 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin, # takes care of that. folder_paths = {entity["path"] for entity in folder_entities} cls.log.error( - "Found nodes related to other assets: {}" - .format(", ".join(sorted(folder_paths))) + "Found nodes related to other folders:\n{}".format( + "\n".join(f"- {path}" for path in sorted(folder_paths)) + ) ) return invalid + + @staticmethod + def get_description(): + return inspect.cleandoc("""### Node IDs must match folder id + + The node ids must match the folder entity id you are publishing to. + + Usually these mismatch occurs if you are re-using nodes from another + folder or project. + + #### How to repair? + + The repair action will regenerate new ids for + the invalid nodes to match the instance's folder. + """) From 6a93e29923e75f4edb3dc501105e322927eccb44 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:01:35 +0200 Subject: [PATCH 198/203] fix applications addon version --- client/ayon_core/addon/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index 3d028dba07..21b1193b07 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -50,7 +50,7 @@ IGNORED_MODULES_IN_AYON = set() # When addon was moved from ayon-core codebase # - this is used to log the missing addon MOVED_ADDON_MILESTONE_VERSIONS = { - "applications": VersionInfo(2, 0, 0), + "applications": VersionInfo(0, 2, 0), } # Inherit from `object` for Python 2 hosts From c1cfd266f61add3a45cd0c0f4b5bb772c6044f82 Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Tue, 16 Apr 2024 16:51:54 +0200 Subject: [PATCH 199/203] Just fixing typo in "product" which broke functionality. --- .../ayon_core/hosts/maya/plugins/inventory/connect_geometry.py | 2 +- client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py | 2 +- .../ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/inventory/connect_geometry.py b/client/ayon_core/hosts/maya/plugins/inventory/connect_geometry.py index 839a4dad90..5410546a2e 100644 --- a/client/ayon_core/hosts/maya/plugins/inventory/connect_geometry.py +++ b/client/ayon_core/hosts/maya/plugins/inventory/connect_geometry.py @@ -37,7 +37,7 @@ class ConnectGeometry(InventoryAction): repre_id = container["representation"] repre_context = repre_contexts_by_id[repre_id] - product_type = repre_context["prouct"]["productType"] + product_type = repre_context["product"]["productType"] containers_by_product_type.setdefault(product_type, []) containers_by_product_type[product_type].append(container) diff --git a/client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py b/client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py index bf9e679928..166c419072 100644 --- a/client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py +++ b/client/ayon_core/hosts/maya/plugins/inventory/connect_xgen.py @@ -36,7 +36,7 @@ class ConnectXgen(InventoryAction): repre_id = container["representation"] repre_context = repre_contexts_by_id[repre_id] - product_type = repre_context["prouct"]["productType"] + product_type = repre_context["product"]["productType"] containers_by_product_type.setdefault(product_type, []) containers_by_product_type[product_type].append(container) diff --git a/client/ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py index 5916bf7b97..8f13cc6ae5 100644 --- a/client/ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/inventory/connect_yeti_rig.py @@ -39,7 +39,7 @@ class ConnectYetiRig(InventoryAction): repre_id = container["representation"] repre_context = repre_contexts_by_id[repre_id] - product_type = repre_context["prouct"]["productType"] + product_type = repre_context["product"]["productType"] containers_by_product_type.setdefault(product_type, []) containers_by_product_type[product_type].append(container) From 991eee9657e7a2e3a56f016170a9d095ce8c1637 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Apr 2024 18:25:49 +0200 Subject: [PATCH 200/203] Fix PreLaunchHook import --- .../ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py index e70d4b844e..113a1ffe59 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_launch_menu_hook.py @@ -1,5 +1,5 @@ import os -from ayon_core.lib import PreLaunchHook +from ayon_applications import PreLaunchHook from ayon_core.hosts.fusion import FUSION_HOST_DIR From 787ebed3466254af39db4734c50592399b9eb33b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 18:13:31 +0800 Subject: [PATCH 201/203] fix the bug of repair action failing to fix the validate resolution settings --- .../publish/validate_resolution_setting.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index 49beecbabc..9f2d94f6e6 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -7,7 +7,10 @@ from ayon_core.pipeline.publish import ( RepairAction, PublishValidationError ) -from ayon_core.hosts.max.api.lib import reset_scene_resolution +from ayon_core.hosts.max.api.lib import ( + reset_scene_resolution, + imprint +) class ValidateResolutionSetting(pyblish.api.InstancePlugin, @@ -79,5 +82,12 @@ class ValidateReviewResolutionSetting(ValidateResolutionSetting): context_width, context_height = ( cls.get_folder_resolution(instance) ) - instance.data["review_width"] = context_width - instance.data["review_height"] = context_height + creator_attrs = instance.data["creator_attributes"] + creator_attrs["review_width"] = context_width + creator_attrs["review_height"] = context_height + creator_attrs_data = { + "creator_attributes": creator_attrs + } + # update the width and height of review + # data in creator_attributes + imprint(instance.data["instance_node"], creator_attrs_data) From 70f64accbcaa6d395d166c3938d9d23d6e0f1357 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 20:10:56 +0800 Subject: [PATCH 202/203] use "validate resolution setting" as label for validate resolution setting in the review family --- .../hosts/max/plugins/publish/validate_resolution_setting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py index 9f2d94f6e6..5f6cd0a21d 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_resolution_setting.py @@ -68,7 +68,6 @@ class ValidateResolutionSetting(pyblish.api.InstancePlugin, class ValidateReviewResolutionSetting(ValidateResolutionSetting): families = ["review"] - label = "Validate Review Animation Resolution Setting" optional = True actions = [RepairAction] From b09e181e27644a51b1b7d58e3aca73fc6918c13a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 17 Apr 2024 14:52:44 +0200 Subject: [PATCH 203/203] Fix usage of `BaseServerAddon` when only imported for type checking --- server_addon/deadline/server/settings/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 83c7567c0d..21a314cd2f 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -22,7 +22,7 @@ class ServerListSubmodel(BaseSettingsModel): async def defined_deadline_ws_name_enum_resolver( - addon: BaseServerAddon, + addon: "BaseServerAddon", settings_variant: str = "production", project_name: str | None = None, ) -> list[str]: