From c2f2fdb38b7d6f08c7edf06e5ff1efef81c091e6 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 11:20:42 +0200 Subject: [PATCH 001/363] add workfile colorspace to Houdini settings --- .../defaults/project_settings/houdini.json | 6 ++++ .../schema_project_houdini.json | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 87983620ec..93fa7e2c6f 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -21,6 +21,12 @@ "file_rules": { "activate_host_rules": false, "rules": {} + }, + "workfile":{ + "enabled": false, + "default_display": "ACES", + "default_view": "sRGB", + "review_color_space": "Output - sRGB" } }, "shelves": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index d4d0565ec9..4e7f1aa4c9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -19,6 +19,40 @@ { "type": "template", "name": "template_host_color_management_ocio" + }, + { + "key": "workfile", + "type": "dict", + "label": "Workfile", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Render space in Houdini is always set to 'scene_linear' Role." + }, + { + "type": "text", + "key": "default_display", + "label": "Display" + }, + { + "type": "text", + "key": "default_view", + "label": "View" + }, + { + "type": "text", + "key": "review_color_space", + "label": "Review colorspace" + } + + ] } ] }, From bf32236e9477205c3cb65ffaa60e5f0d5d21c357 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 13:24:40 +0200 Subject: [PATCH 002/363] add Houdini workfile colorspace to Ayon settings --- .../houdini/server/settings/imageio.py | 34 ++++++++++++++++++- server_addon/houdini/server/settings/main.py | 6 +++- server_addon/houdini/server/version.py | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 88aa40ecd6..6a61171b66 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -34,9 +34,18 @@ class ImageIOFileRulesModel(BaseSettingsModel): return value +class WorkfileImageIOModel(BaseSettingsModel): + """Render space in Houdini is always set to 'scene_linear' Role.""" + + enabled: bool = Field(False, title="Enabled") + default_display: str = Field(title="Display") + default_view: str = Field(title="View") + review_color_space: str = Field(title="Review colorspace") + + class HoudiniImageIOModel(BaseSettingsModel): activate_host_color_management: bool = Field( - True, title="Enable Color Management" + False, title="Enable Color Management" ) ocio_config: ImageIOConfigModel = Field( default_factory=ImageIOConfigModel, @@ -46,3 +55,26 @@ class HoudiniImageIOModel(BaseSettingsModel): default_factory=ImageIOFileRulesModel, title="File Rules" ) + workfile: WorkfileImageIOModel = Field( + default_factory=WorkfileImageIOModel, + title="Workfile" + ) + + +DEFAULT_IMAGEIO_SETTINGS = { + "activate_host_color_management": False, + "ocio_config": { + "override_global_config": False, + "filepath": [] + }, + "file_rules": { + "activate_host_rules": False, + "rules": [] + }, + "workfile": { + "enabled": False, + "default_display": "ACES", + "default_view": "sRGB", + "review_color_space": "Output - sRGB" + } +} diff --git a/server_addon/houdini/server/settings/main.py b/server_addon/houdini/server/settings/main.py index 9cfec54f22..250e654afd 100644 --- a/server_addon/houdini/server/settings/main.py +++ b/server_addon/houdini/server/settings/main.py @@ -4,7 +4,10 @@ from .general import ( GeneralSettingsModel, DEFAULT_GENERAL_SETTINGS ) -from .imageio import HoudiniImageIOModel +from .imageio import ( + HoudiniImageIOModel, + DEFAULT_IMAGEIO_SETTINGS +) from .shelves import ShelvesModel from .create import ( CreatePluginsModel, @@ -41,6 +44,7 @@ class HoudiniSettings(BaseSettingsModel): DEFAULT_VALUES = { "general": DEFAULT_GENERAL_SETTINGS, + "imageio": DEFAULT_IMAGEIO_SETTINGS, "shelves": [], "create": DEFAULT_HOUDINI_CREATE_SETTINGS, "publish": DEFAULT_HOUDINI_PUBLISH_SETTINGS diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 01ef12070d..6cd38b7465 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.6" +__version__ = "0.2.7" From fca6abde553beb286268299803c17f4d22e63d3f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 13:26:11 +0200 Subject: [PATCH 003/363] add Houdini SetDefaultDislayView prelaunch hook --- .../hooks/set_default_display_and_view.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 openpype/hosts/houdini/hooks/set_default_display_and_view.py diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py new file mode 100644 index 0000000000..33278d7fee --- /dev/null +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -0,0 +1,52 @@ +from openpype.lib.applications import PreLaunchHook, LaunchTypes +from openpype.settings import get_project_settings + + +class SetDefaultDislayView(PreLaunchHook): + """Set default view and default display for houdini hosts that use OpenColorIO. + + Houdini's defaultDisplay and defaultView are set by + setting 'OCIO_ACTIVE_DISPLAYS' and 'OCIO_ACTIVE_VIEWS' + environment variables respectively. + + More info: https://www.sidefx.com/docs/houdini/io/ocio.html#set-up + """ + + app_groups = {"houdini"} + launch_types = {LaunchTypes.local} + + def execute(self): + + OCIO = self.launch_context.env.get("OCIO") + + # This is a cheap way to skip this hook if either + # global color management or houdini color management was disabled. + if not OCIO: + return + + project_settings = get_project_settings( + project_name=self.data["project_name"] + ) + + houdini_color_Settings = project_settings["houdini"]["imageio"]["workfile"] + + if not houdini_color_Settings["enabled"] : + self.log.info( + "Houdini's workefile color settings are disabled." + ) + return + + default_display = houdini_color_Settings["default_display"] + default_view = houdini_color_Settings["default_view"] + + self.log.info( + "Setting OCIO_ACTIVE_DISPLAYS environment to : {}" + .format(default_display) + ) + self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display + + self.log.info( + "Setting OCIO_ACTIVE_VIEWS environment to config path: {}" + .format(default_view) + ) + self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view From 10cf562f359a39948b6db6245fd56533190d824d Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 13:44:38 +0200 Subject: [PATCH 004/363] resolve hound --- .../hosts/houdini/hooks/set_default_display_and_view.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 33278d7fee..2c98247b8c 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -3,7 +3,7 @@ from openpype.settings import get_project_settings class SetDefaultDislayView(PreLaunchHook): - """Set default view and default display for houdini hosts that use OpenColorIO. + """Set default view and default display for houdini host that use OpenColorIO. Houdini's defaultDisplay and defaultView are set by setting 'OCIO_ACTIVE_DISPLAYS' and 'OCIO_ACTIVE_VIEWS' @@ -28,9 +28,10 @@ class SetDefaultDislayView(PreLaunchHook): project_name=self.data["project_name"] ) - houdini_color_Settings = project_settings["houdini"]["imageio"]["workfile"] + houdini_color_Settings = \ + project_settings["houdini"]["imageio"]["workfile"] - if not houdini_color_Settings["enabled"] : + if not houdini_color_Settings["enabled"]: self.log.info( "Houdini's workefile color settings are disabled." ) From 537d1acac854c46dbe20eac95977886f81619253 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 15:38:11 +0200 Subject: [PATCH 005/363] BigRoy's comments - Better logging & remove unnecessary logic --- .../houdini/hooks/set_default_display_and_view.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 2c98247b8c..cb5dba51cf 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,5 +1,4 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes -from openpype.settings import get_project_settings class SetDefaultDislayView(PreLaunchHook): @@ -19,21 +18,18 @@ class SetDefaultDislayView(PreLaunchHook): OCIO = self.launch_context.env.get("OCIO") - # This is a cheap way to skip this hook if either - # global color management or houdini color management was disabled. + # This is a cheap way to skip this hook if either global color + # management or houdini color management was disabled because the + # OCIO var would be set by the global OCIOEnvHook if not OCIO: return - project_settings = get_project_settings( - project_name=self.data["project_name"] - ) - houdini_color_Settings = \ - project_settings["houdini"]["imageio"]["workfile"] + self.data["project_settings"]["houdini"]["imageio"]["workfile"] if not houdini_color_Settings["enabled"]: self.log.info( - "Houdini's workefile color settings are disabled." + "Houdini workfile color management is disabled." ) return From 3ffbf736517db28aef379ec1281c71753325e7f7 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 2 Nov 2023 15:45:22 +0200 Subject: [PATCH 006/363] resolve hound --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index cb5dba51cf..8c91ef7d06 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -25,7 +25,7 @@ class SetDefaultDislayView(PreLaunchHook): return houdini_color_Settings = \ - self.data["project_settings"]["houdini"]["imageio"]["workfile"] + self.data["project_settings"]["houdini"]["imageio"]["workfile"] if not houdini_color_Settings["enabled"]: self.log.info( From 1a1e48e4a5b44d1e0498952f78ad06a77f68e6f1 Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Thu, 2 Nov 2023 16:23:27 +0200 Subject: [PATCH 007/363] BigRoy's comment - update variable name Co-authored-by: Roy Nieterau --- .../hosts/houdini/hooks/set_default_display_and_view.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 8c91ef7d06..85e605b37d 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -24,17 +24,17 @@ class SetDefaultDislayView(PreLaunchHook): if not OCIO: return - houdini_color_Settings = \ + houdini_color_settings = \ self.data["project_settings"]["houdini"]["imageio"]["workfile"] - if not houdini_color_Settings["enabled"]: + if not houdini_color_settings["enabled"]: self.log.info( "Houdini workfile color management is disabled." ) return - default_display = houdini_color_Settings["default_display"] - default_view = houdini_color_Settings["default_view"] + default_display = houdini_color_settings["default_display"] + default_view = houdini_color_settings["default_view"] self.log.info( "Setting OCIO_ACTIVE_DISPLAYS environment to : {}" From 7d80da84e21e615e32119c42e87b6e741356b7fa Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Thu, 2 Nov 2023 17:18:18 +0200 Subject: [PATCH 008/363] BigRoy's Comment - fix typo Co-authored-by: Roy Nieterau --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 85e605b37d..edff73117f 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,7 +1,7 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes -class SetDefaultDislayView(PreLaunchHook): +class SetDefaultDisplayView(PreLaunchHook): """Set default view and default display for houdini host that use OpenColorIO. Houdini's defaultDisplay and defaultView are set by From cd4d8bd2b1c9eddf4cbe7204999ead31d2308a4a Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Fri, 3 Nov 2023 09:17:00 +0200 Subject: [PATCH 009/363] BigRoy's comment - better doc string Co-authored-by: Roy Nieterau --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index edff73117f..ff55e6275d 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -2,7 +2,7 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes class SetDefaultDisplayView(PreLaunchHook): - """Set default view and default display for houdini host that use OpenColorIO. + """Set default view and default display for houdini via OpenColorIO. Houdini's defaultDisplay and defaultView are set by setting 'OCIO_ACTIVE_DISPLAYS' and 'OCIO_ACTIVE_VIEWS' From 0a8e293e6e6157dd21efd439af874e095c488c5d Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 3 Nov 2023 09:25:07 +0200 Subject: [PATCH 010/363] bump patch version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 6cd38b7465..c49a95c357 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.7" +__version__ = "0.2.8" From a32f0a62934a031ce25f9be24c83c2b7462699ed Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 09:20:41 +0200 Subject: [PATCH 011/363] use 'review color space' setting in review creator --- .../houdini/plugins/create/create_review.py | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 60c34a358b..a7a56cce03 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -2,6 +2,7 @@ """Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef, BoolDef, NumberDef +from openpype.settings import get_current_project_settings import os import hou @@ -87,7 +88,7 @@ class CreateReview(plugin.HoudiniCreator): # Set OCIO Colorspace to the default output colorspace # if there's OCIO if os.getenv("OCIO"): - self.set_colorcorrect_to_default_view_space(instance_node) + self.set_review_color_space(instance_node) to_lock = ["id", "family"] @@ -131,22 +132,40 @@ class CreateReview(plugin.HoudiniCreator): decimals=3) ] - def set_colorcorrect_to_default_view_space(self, - instance_node): - """Set ociocolorspace to the default output space.""" - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + def set_review_color_space(self, instance_node): + """Set ociocolorspace parameter. - # set Color Correction parameter to OpenColorIO + This function will use the value exposed in settings + if workfile settings were enabled. + + Otherwise, it will use the default colorspace corresponding + to the display & view of the current Houdini session. + """ + + # Set Color Correction parameter to OpenColorIO instance_node.setParms({"colorcorrect": 2}) - # Get default view space for ociocolorspace parm. - default_view_space = get_default_display_view_colorspace() + # Get view space for ociocolorspace parm. + view_space = self.get_review_colorspace_from_Settings() + + if not view_space: + from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + view_space = get_default_display_view_colorspace() + instance_node.setParms( - {"ociocolorspace": default_view_space} + {"ociocolorspace": view_space} ) self.log.debug( "'OCIO Colorspace' parm on '{}' has been set to " - "the default view color space '{}'" - .format(instance_node, default_view_space) + "the view color space '{}'" + .format(instance_node, view_space) ) + + def get_review_colorspace_from_Settings(self): + project_settings = get_current_project_settings() + color_settings = project_settings["houdini"]["imageio"]["workfile"] + if color_settings["enabled"]: + return color_settings.get("review_color_space") + + return "" From 0eacab216675438d65c697428916d055d57631de Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 11:51:01 +0200 Subject: [PATCH 012/363] update houdini workfile color settings --- .../settings/defaults/project_settings/houdini.json | 3 ++- .../projects_schema/schema_project_houdini.json | 10 +++++++++- server_addon/houdini/server/settings/imageio.py | 10 +++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 93fa7e2c6f..826a4e12f1 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -26,7 +26,8 @@ "enabled": false, "default_display": "ACES", "default_view": "sRGB", - "review_color_space": "Output - sRGB" + "review_color_space": "Output - sRGB", + "override_review_color": false } }, "shelves": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 4e7f1aa4c9..70b6d63ee8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -50,8 +50,16 @@ "type": "text", "key": "review_color_space", "label": "Review colorspace" + }, + { + "type": "label", + "label": "Allow artists to override review colorspace, therefore review color space validator\nwon't error if artists used another review colorspace." + }, + { + "type": "boolean", + "key": "override_review_color", + "label": "Override review colorspace" } - ] } ] diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 6a61171b66..6404942d5b 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -41,6 +41,13 @@ class WorkfileImageIOModel(BaseSettingsModel): default_display: str = Field(title="Display") default_view: str = Field(title="View") review_color_space: str = Field(title="Review colorspace") + override_review_color: bool = Field( + False, + title="Override review colorspace", + description=("Allow artists to override review colorspace, " + "therefore review color space validator won't error " + "if artists used another review colorspace.") + ) class HoudiniImageIOModel(BaseSettingsModel): @@ -76,5 +83,6 @@ DEFAULT_IMAGEIO_SETTINGS = { "default_display": "ACES", "default_view": "sRGB", "review_color_space": "Output - sRGB" - } + }, + "override_review_color": False } From c4cd10c63740243e97bf270d1b9de7c26f82ae98 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 11:51:51 +0200 Subject: [PATCH 013/363] use houdini color settings while review creation and validation --- openpype/hosts/houdini/api/lib.py | 62 +++++++++++++++++++ .../houdini/plugins/create/create_review.py | 43 +------------ .../publish/validate_review_colorspace.py | 54 +++++++++------- 3 files changed, 94 insertions(+), 65 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index ac375c56d6..f4db492978 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -844,6 +844,68 @@ def get_current_context_template_data_with_asset_data(): return template_data +def get_houdini_color_settings(): + """Get Houdini working file color settings. + + Returns: + Dict: The dictionary contains the Houdini working file color settings + if the settings are enabled, otherwise it is an empty dictionary. + """ + + project_settings = get_current_project_settings() + color_settings = project_settings["houdini"]["imageio"]["workfile"] + if color_settings["enabled"]: + color_settings.pop("enabled") + # Remove leading, and trailing whitespaces + view_Space = color_settings["review_color_space"] + color_settings["review_color_space"] = view_Space.strip() + return color_settings + + return {} + +def set_review_color_space(opengl_node, log=None): + """Set ociocolorspace parameter for the given OpenGL node. + + This function will use the value exposed in settings + if workfile settings were enabled. + + Otherwise, it will use the default colorspace corresponding + to the display & view of the current Houdini session. + + Args: + OpenGl node (hou.Node): ROP node to set its ociocolorspace parameter. + log (logging.Logger): Logger to log to. + """ + + if log is None: + log = self.log + + # Set Color Correction parameter to OpenColorIO + if opengl_node.evalParm("colorcorrect") != 2: + opengl_node.setParms({"colorcorrect": 2}) + log.debug( + "'Color Correction' parm on '{}' has been set to" + " 'OpenColorIO'".format(opengl_node.path()) + ) + + # Get view space for ociocolorspace parm. + view_space = get_houdini_color_settings().get("review_color_space") + + if not view_space: + from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + view_space = get_default_display_view_colorspace() + + opengl_node.setParms( + {"ociocolorspace": view_space} + ) + + self.log.debug( + "'OCIO Colorspace' parm on '{}' has been set to " + "the view color space '{}'" + .format(opengl_node, view_space) + ) + + def get_context_var_changes(): """get context var changes.""" diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index a7a56cce03..775babbab6 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -2,8 +2,7 @@ """Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef, BoolDef, NumberDef -from openpype.settings import get_current_project_settings - +from openpype.hosts.houdini.api.lib import set_review_color_space import os import hou @@ -88,7 +87,7 @@ class CreateReview(plugin.HoudiniCreator): # Set OCIO Colorspace to the default output colorspace # if there's OCIO if os.getenv("OCIO"): - self.set_review_color_space(instance_node) + set_review_color_space(instance_node, log=self.log) to_lock = ["id", "family"] @@ -131,41 +130,3 @@ class CreateReview(plugin.HoudiniCreator): minimum=0.0001, decimals=3) ] - - def set_review_color_space(self, instance_node): - """Set ociocolorspace parameter. - - This function will use the value exposed in settings - if workfile settings were enabled. - - Otherwise, it will use the default colorspace corresponding - to the display & view of the current Houdini session. - """ - - # Set Color Correction parameter to OpenColorIO - instance_node.setParms({"colorcorrect": 2}) - - # Get view space for ociocolorspace parm. - view_space = self.get_review_colorspace_from_Settings() - - if not view_space: - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - view_space = get_default_display_view_colorspace() - - instance_node.setParms( - {"ociocolorspace": view_space} - ) - - self.log.debug( - "'OCIO Colorspace' parm on '{}' has been set to " - "the view color space '{}'" - .format(instance_node, view_space) - ) - - def get_review_colorspace_from_Settings(self): - project_settings = get_current_project_settings() - color_settings = project_settings["houdini"]["imageio"]["workfile"] - if color_settings["enabled"]: - return color_settings.get("review_color_space") - - return "" diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index 03ecd1b052..2bc62516d5 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -6,13 +6,17 @@ from openpype.pipeline import ( ) from openpype.pipeline.publish import RepairAction from openpype.hosts.houdini.api.action import SelectROPAction +from openpype.hosts.houdini.api.lib import ( + get_houdini_color_settings, + set_review_color_space +) import os import hou -class SetDefaultViewSpaceAction(RepairAction): - label = "Set default view colorspace" +class SetReviewColorSpaceAction(RepairAction): + label = "Set Review Color Space" icon = "mdi.monitor" @@ -27,7 +31,7 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, families = ["review"] hosts = ["houdini"] label = "Validate Review Colorspace" - actions = [SetDefaultViewSpaceAction, SelectROPAction] + actions = [SetReviewColorSpaceAction, SelectROPAction] optional = True @@ -61,30 +65,32 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, .format(rop_node.path()) ) + color_settings = get_houdini_color_settings() + if color_settings.get("override_review_color"): + return + + if rop_node.evalParm("ociocolorspace") != \ + color_settings["review_color_space"]: + + raise PublishValidationError( + "Invalid value: Colorspace name doesn't match studio settings.\n" + "Check 'OCIO Colorspace' parameter on '{}' ROP" + .format(rop_node.path()) + ) + @classmethod def repair(cls, instance): """Set Default View Space Action. - It is a helper action more than a repair action, - used to set colorspace on opengl node to the default view. + It sets ociocolorspace parameter. + + if workfile settings are enabled, it will use the value + exposed in the settings. + + if workfile settings are disabled, it will use the default + colorspace corresponding to the display & view of + the current Houdini session. """ - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - rop_node = hou.node(instance.data["instance_node"]) - - if rop_node.evalParm("colorcorrect") != 2: - rop_node.setParms({"colorcorrect": 2}) - cls.log.debug( - "'Color Correction' parm on '{}' has been set to" - " 'OpenColorIO'".format(rop_node.path()) - ) - - # Get default view colorspace name - default_view_space = get_default_display_view_colorspace() - - rop_node.setParms({"ociocolorspace": default_view_space}) - cls.log.info( - "'OCIO Colorspace' parm on '{}' has been set to " - "the default view color space '{}'" - .format(rop_node, default_view_space) - ) + opengl_node = hou.node(instance.data["instance_node"]) + set_review_color_space(opengl_node, log=cls.log) From 2caac26ac7c3c0dc8d0262488840162d5bd5e09e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 11:55:46 +0200 Subject: [PATCH 014/363] Resolve Hound --- openpype/hosts/houdini/api/lib.py | 1 + .../houdini/plugins/publish/validate_review_colorspace.py | 4 ++-- server_addon/houdini/server/settings/imageio.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index ae65cadf1b..bfbfcb586e 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -855,6 +855,7 @@ def get_houdini_color_settings(): return {} + def set_review_color_space(opengl_node, log=None): """Set ociocolorspace parameter for the given OpenGL node. diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index 2bc62516d5..783e67014c 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -73,8 +73,8 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, color_settings["review_color_space"]: raise PublishValidationError( - "Invalid value: Colorspace name doesn't match studio settings.\n" - "Check 'OCIO Colorspace' parameter on '{}' ROP" + "Invalid value: Colorspace name doesn't match studio " + "settings.\nCheck 'OCIO Colorspace' parameter on '{}' ROP" .format(rop_node.path()) ) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 6404942d5b..ab98152232 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -45,8 +45,8 @@ class WorkfileImageIOModel(BaseSettingsModel): False, title="Override review colorspace", description=("Allow artists to override review colorspace, " - "therefore review color space validator won't error " - "if artists used another review colorspace.") + "therefore review color space validator won't error " + "if artists used another review colorspace.") ) From 8de9dccbfb4734452286fc6dc55d0d9cbcfef4ed Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 12:07:10 +0200 Subject: [PATCH 015/363] update doc string --- openpype/hosts/houdini/api/lib.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index bfbfcb586e..b40f8b2fcf 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -859,11 +859,12 @@ def get_houdini_color_settings(): def set_review_color_space(opengl_node, log=None): """Set ociocolorspace parameter for the given OpenGL node. - This function will use the value exposed in settings - if workfile settings were enabled. + if workfile settings are enabled, it will use the value + exposed in the settings. - Otherwise, it will use the default colorspace corresponding - to the display & view of the current Houdini session. + if workfile settings are disabled, it will use the default + colorspace corresponding to the display & view of + the current Houdini session. Args: OpenGl node (hou.Node): ROP node to set its ociocolorspace parameter. From 96849785936881a79ea1a325a643a957a6612f4d Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 6 Nov 2023 12:27:17 +0200 Subject: [PATCH 016/363] bump Houdini addon patch version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index c49a95c357..75cf7831c4 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.8" +__version__ = "0.2.9" From dadddfb00e9e8c9ee52ffc19960c2db4333a65ab Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 7 Nov 2023 08:18:10 +0200 Subject: [PATCH 017/363] Minkiu comment - remove unnecessary variable --- openpype/hosts/houdini/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index b40f8b2fcf..27e70ce152 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -849,8 +849,8 @@ def get_houdini_color_settings(): if color_settings["enabled"]: color_settings.pop("enabled") # Remove leading, and trailing whitespaces - view_Space = color_settings["review_color_space"] - color_settings["review_color_space"] = view_Space.strip() + color_settings["review_color_space"] = \ + color_settings["review_color_space"].strip() return color_settings return {} From a1d1c49e3866c4cc5f9fad235f2822e308423daa Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 7 Nov 2023 08:36:01 +0200 Subject: [PATCH 018/363] fabia's comment - Better conditional --- openpype/hosts/houdini/api/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 27e70ce152..bdcc368d7f 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -875,8 +875,9 @@ def set_review_color_space(opengl_node, log=None): log = self.log # Set Color Correction parameter to OpenColorIO - if opengl_node.evalParm("colorcorrect") != 2: - opengl_node.setParms({"colorcorrect": 2}) + colorcorrect_parm = opengl_node.parm("colorcorrect") + if colorcorrect_parm.eval() != 2: + colorcorrect_parm.set(2) log.debug( "'Color Correction' parm on '{}' has been set to" " 'OpenColorIO'".format(opengl_node.path()) From 83089da81a93c62c65cf8a768f9891e63637898a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 7 Nov 2023 10:43:53 +0200 Subject: [PATCH 019/363] update set_review_color_space() and get_houdini_color_settings() logic --- openpype/hosts/houdini/api/lib.py | 23 +++++++-------- .../publish/validate_review_colorspace.py | 29 ++++++++++--------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index bdcc368d7f..77ae929857 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -846,25 +846,22 @@ def get_houdini_color_settings(): project_settings = get_current_project_settings() color_settings = project_settings["houdini"]["imageio"]["workfile"] - if color_settings["enabled"]: - color_settings.pop("enabled") - # Remove leading, and trailing whitespaces - color_settings["review_color_space"] = \ - color_settings["review_color_space"].strip() - return color_settings - return {} + # Remove leading, and trailing whitespaces + color_settings["review_color_space"] = \ + color_settings["review_color_space"].strip() + return color_settings def set_review_color_space(opengl_node, log=None): """Set ociocolorspace parameter for the given OpenGL node. - if workfile settings are enabled, it will use the value + If workfile settings are enabled, it will use the value exposed in the settings. - if workfile settings are disabled, it will use the default - colorspace corresponding to the display & view of - the current Houdini session. + If the value exposed in the settings is empty, + it will use the default colorspace corresponding to + the display & view of the current Houdini session. Args: OpenGl node (hou.Node): ROP node to set its ociocolorspace parameter. @@ -884,8 +881,10 @@ def set_review_color_space(opengl_node, log=None): ) # Get view space for ociocolorspace parm. - view_space = get_houdini_color_settings().get("review_color_space") + color_settings = get_houdini_color_settings() + view_space = color_settings["review_color_space"] if color_settings["enabled"] else "" # noqa + # fall to default review color space if the setting is empty. if not view_space: from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa view_space = get_default_display_view_colorspace() diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index 783e67014c..e4356a741c 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -66,17 +66,18 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, ) color_settings = get_houdini_color_settings() - if color_settings.get("override_review_color"): - return + # skip if houdini color settings are disabled + if color_settings["enabled"]: + view_space = color_settings["review_color_space"] + # skip if review color space setting is empty. + if view_space and \ + rop_node.evalParm("ociocolorspace") != view_space: - if rop_node.evalParm("ociocolorspace") != \ - color_settings["review_color_space"]: - - raise PublishValidationError( - "Invalid value: Colorspace name doesn't match studio " - "settings.\nCheck 'OCIO Colorspace' parameter on '{}' ROP" - .format(rop_node.path()) - ) + raise PublishValidationError( + "Invalid value: Colorspace name doesn't match studio " + "settings.\nCheck 'OCIO Colorspace' parameter on '{}' ROP" + .format(rop_node.path()) + ) @classmethod def repair(cls, instance): @@ -84,12 +85,12 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, It sets ociocolorspace parameter. - if workfile settings are enabled, it will use the value + If workfile settings are enabled, it will use the value exposed in the settings. - if workfile settings are disabled, it will use the default - colorspace corresponding to the display & view of - the current Houdini session. + If the value exposed in the settings is empty, + it will use the default colorspace corresponding to + the display & view of the current Houdini session. """ opengl_node = hou.node(instance.data["instance_node"]) From f349df03407e67ac91e19292b250c00f58c9ddb7 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 7 Nov 2023 10:52:50 +0200 Subject: [PATCH 020/363] undo adding unnecessary setting --- .../defaults/project_settings/houdini.json | 3 +-- .../projects_schema/schema_project_houdini.json | 7 +------ server_addon/houdini/server/settings/imageio.py | 16 ++++++---------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 017682782d..7473e83275 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -26,8 +26,7 @@ "enabled": false, "default_display": "ACES", "default_view": "sRGB", - "review_color_space": "Output - sRGB", - "override_review_color": false + "review_color_space": "" } }, "shelves": [], diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 70b6d63ee8..af87f4ff35 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -53,12 +53,7 @@ }, { "type": "label", - "label": "Allow artists to override review colorspace, therefore review color space validator\nwon't error if artists used another review colorspace." - }, - { - "type": "boolean", - "key": "override_review_color", - "label": "Override review colorspace" + "label": "Leave Review colorspace empty to use the default colorspace." } ] } diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index ab98152232..9e9c2a6092 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -40,13 +40,10 @@ class WorkfileImageIOModel(BaseSettingsModel): enabled: bool = Field(False, title="Enabled") default_display: str = Field(title="Display") default_view: str = Field(title="View") - review_color_space: str = Field(title="Review colorspace") - override_review_color: bool = Field( - False, - title="Override review colorspace", - description=("Allow artists to override review colorspace, " - "therefore review color space validator won't error " - "if artists used another review colorspace.") + review_color_space: str = Field( + title="Review colorspace", + description=("Leave Review colorspace empty to use the" + " default colorspace.") ) @@ -82,7 +79,6 @@ DEFAULT_IMAGEIO_SETTINGS = { "enabled": False, "default_display": "ACES", "default_view": "sRGB", - "review_color_space": "Output - sRGB" - }, - "override_review_color": False + "review_color_space": "" + } } From fd648509626edafdc4f545e5b4ac7c9b50482d73 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 8 Nov 2023 17:43:38 +0200 Subject: [PATCH 021/363] add colorspace data to houdini review extractor --- openpype/hosts/houdini/plugins/publish/extract_opengl.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index 38808089ac..25203f444e 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -8,7 +8,8 @@ from openpype.hosts.houdini.api.lib import render_rop import hou -class ExtractOpenGL(publish.Extractor): +class ExtractOpenGL(publish.Extractor, + publish.ColormanagedPyblishPluginMixin): order = pyblish.api.ExtractorOrder - 0.01 label = "Extract OpenGL" @@ -46,6 +47,12 @@ class ExtractOpenGL(publish.Extractor): "camera_name": instance.data.get("review_camera") } + colorspace = ropnode.evalParm("ociocolorspace") + # inject colorspace data + self.set_representation_colorspace( + representation, instance.context, + colorspace=colorspace + ) if "representations" not in instance.data: instance.data["representations"] = [] instance.data["representations"].append(representation) From 205c0642d52ee9cf4f035b3418be75eaabc4848e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 8 Nov 2023 18:00:02 +0200 Subject: [PATCH 022/363] BigRoy's comment - use a conditional --- .../houdini/plugins/publish/extract_opengl.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_opengl.py b/openpype/hosts/houdini/plugins/publish/extract_opengl.py index 25203f444e..e6579e03a3 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_opengl.py +++ b/openpype/hosts/houdini/plugins/publish/extract_opengl.py @@ -47,12 +47,14 @@ class ExtractOpenGL(publish.Extractor, "camera_name": instance.data.get("review_camera") } - colorspace = ropnode.evalParm("ociocolorspace") - # inject colorspace data - self.set_representation_colorspace( - representation, instance.context, - colorspace=colorspace - ) + if ropnode.evalParm("colorcorrect") == 2: # OpenColorIO enabled + colorspace = ropnode.evalParm("ociocolorspace") + # inject colorspace data + self.set_representation_colorspace( + representation, instance.context, + colorspace=colorspace + ) + if "representations" not in instance.data: instance.data["representations"] = [] instance.data["representations"].append(representation) From 7a62d5d9b866b162a2f9d6f18ea947d171147ca1 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 9 Nov 2023 17:18:27 +0200 Subject: [PATCH 023/363] add colorspace data to houdini image sequence extractor --- .../houdini/plugins/publish/extract_composite.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/extract_composite.py b/openpype/hosts/houdini/plugins/publish/extract_composite.py index 11cf83a46d..5047b719c9 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_composite.py +++ b/openpype/hosts/houdini/plugins/publish/extract_composite.py @@ -7,7 +7,8 @@ from openpype.hosts.houdini.api.lib import render_rop, splitext import hou -class ExtractComposite(publish.Extractor): +class ExtractComposite(publish.Extractor, + publish.ColormanagedPyblishPluginMixin): order = pyblish.api.ExtractorOrder label = "Extract Composite (Image Sequence)" @@ -45,8 +46,11 @@ class ExtractComposite(publish.Extractor): "frameEnd": instance.data["frameEndHandle"], } - from pprint import pformat - - self.log.info(pformat(representation)) + # inject colorspace data + # It's always scene_linear (Houdini's default) + self.set_representation_colorspace( + representation, instance.context, + colorspace="scene_linear" + ) instance.data["representations"].append(representation) From 251291aa38e0cccc560ae9d460f4b0f4cc7453f4 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Sun, 12 Nov 2023 11:37:38 +0200 Subject: [PATCH 024/363] append existent view and display env vars --- .../houdini/hooks/set_default_display_and_view.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index ff55e6275d..54a1e39a87 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,5 +1,5 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes - +import os class SetDefaultDisplayView(PreLaunchHook): """Set default view and default display for houdini via OpenColorIO. @@ -33,8 +33,18 @@ class SetDefaultDisplayView(PreLaunchHook): ) return + # This is a way to get values specified by admins if they already + # added 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' manually + # using Ayon global env vars or Ayon app env vars or Ayon houdini tool + OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get("OCIO_ACTIVE_DISPLAYS", "") + OCIO_ACTIVE_VIEWS = self.launch_context.env.get("OCIO_ACTIVE_VIEWS", "") + + # default_display and default_view default_display = houdini_color_settings["default_display"] + default_display = ":".join([default_display, OCIO_ACTIVE_DISPLAYS]) + default_view = houdini_color_settings["default_view"] + default_view = ":".join([default_view, OCIO_ACTIVE_VIEWS]) self.log.info( "Setting OCIO_ACTIVE_DISPLAYS environment to : {}" From 38bf9c17c4c2c992d92d3a4adf511e9bee5ec0f2 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Sun, 12 Nov 2023 11:40:05 +0200 Subject: [PATCH 025/363] resolve hound --- .../houdini/hooks/set_default_display_and_view.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 54a1e39a87..14f4fc5829 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,5 +1,5 @@ from openpype.lib.applications import PreLaunchHook, LaunchTypes -import os + class SetDefaultDisplayView(PreLaunchHook): """Set default view and default display for houdini via OpenColorIO. @@ -35,9 +35,14 @@ class SetDefaultDisplayView(PreLaunchHook): # This is a way to get values specified by admins if they already # added 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' manually - # using Ayon global env vars or Ayon app env vars or Ayon houdini tool - OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get("OCIO_ACTIVE_DISPLAYS", "") - OCIO_ACTIVE_VIEWS = self.launch_context.env.get("OCIO_ACTIVE_VIEWS", "") + # using Ayon global env vars or Ayon app env vars + # or Ayon houdini tool + OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get( + "OCIO_ACTIVE_DISPLAYS", "" + ) + OCIO_ACTIVE_VIEWS = self.launch_context.env.get( + "OCIO_ACTIVE_VIEWS", "" + ) # default_display and default_view default_display = houdini_color_settings["default_display"] From 87dc8ca57984a3e1fc5ea2505ee227677d9fd4d6 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 11:52:13 +0200 Subject: [PATCH 026/363] fix a typo --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 14f4fc5829..82d402a86b 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -52,13 +52,13 @@ class SetDefaultDisplayView(PreLaunchHook): default_view = ":".join([default_view, OCIO_ACTIVE_VIEWS]) self.log.info( - "Setting OCIO_ACTIVE_DISPLAYS environment to : {}" + "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" .format(default_display) ) self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display self.log.info( - "Setting OCIO_ACTIVE_VIEWS environment to config path: {}" + "Setting OCIO_ACTIVE_VIEWS environment to: {}" .format(default_view) ) self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view From a7e2267eef6abeab2edbd876619deee377ff7e30 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 14:28:51 +0200 Subject: [PATCH 027/363] BigRoy's comment - Cleaner env var values --- .../hooks/set_default_display_and_view.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 82d402a86b..243f32fcf6 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -46,19 +46,23 @@ class SetDefaultDisplayView(PreLaunchHook): # default_display and default_view default_display = houdini_color_settings["default_display"] - default_display = ":".join([default_display, OCIO_ACTIVE_DISPLAYS]) - - default_view = houdini_color_settings["default_view"] - default_view = ":".join([default_view, OCIO_ACTIVE_VIEWS]) - - self.log.info( + if default_display: + default_display = ":".join( + key for key in [default_display, OCIO_ACTIVE_DISPLAYS] if key + ) + self.log.info( "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" .format(default_display) - ) - self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display + ) + self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display - self.log.info( - "Setting OCIO_ACTIVE_VIEWS environment to: {}" - .format(default_view) - ) - self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view + default_view = houdini_color_settings["default_view"] + if default_view: + default_view = ":".join( + key for key in [default_view, OCIO_ACTIVE_VIEWS] if key + ) + self.log.info( + "Setting OCIO_ACTIVE_VIEWS environment to: {}" + .format(default_view) + ) + self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view From a82367603f95ee554aefd673f9fae30018ee434d Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 14:37:00 +0200 Subject: [PATCH 028/363] update houdini workfile colorsettings help --- .../schemas/projects_schema/schema_project_houdini.json | 4 ---- server_addon/houdini/server/settings/imageio.py | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index af87f4ff35..45ed6024db 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -50,10 +50,6 @@ "type": "text", "key": "review_color_space", "label": "Review colorspace" - }, - { - "type": "label", - "label": "Leave Review colorspace empty to use the default colorspace." } ] } diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 9e9c2a6092..cc12a15c0d 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -40,11 +40,7 @@ class WorkfileImageIOModel(BaseSettingsModel): enabled: bool = Field(False, title="Enabled") default_display: str = Field(title="Display") default_view: str = Field(title="View") - review_color_space: str = Field( - title="Review colorspace", - description=("Leave Review colorspace empty to use the" - " default colorspace.") - ) + review_color_space: str = Field(title="Review colorspace") class HoudiniImageIOModel(BaseSettingsModel): From cb9e22ea6a33b319b8cd9a363fab3055cf2e3512 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 14:38:29 +0200 Subject: [PATCH 029/363] resolve hound --- openpype/hosts/houdini/hooks/set_default_display_and_view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 243f32fcf6..0cfc23c930 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -51,8 +51,8 @@ class SetDefaultDisplayView(PreLaunchHook): key for key in [default_display, OCIO_ACTIVE_DISPLAYS] if key ) self.log.info( - "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" - .format(default_display) + "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" + .format(default_display) ) self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display From ebc3d95773212d00076374f0808ab14173a741ce Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Mon, 13 Nov 2023 15:16:01 +0200 Subject: [PATCH 030/363] BigRoy's suggestion - Better help text Co-authored-by: Roy Nieterau --- .../schemas/projects_schema/schema_project_houdini.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 45ed6024db..275fc54053 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -34,7 +34,7 @@ }, { "type": "label", - "label": "Render space in Houdini is always set to 'scene_linear' Role." + "label": "Empty values will be skipped, allowing any existing env vars to pass through as defined.\nNote: The render space in Houdini is always set to the 'scene_linear' role." }, { "type": "text", From be6d015b45fa67710dd5d7acb41d252f2ed2d97f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 15:32:52 +0200 Subject: [PATCH 031/363] BigRoy's suggestion - Better help text, Ayon settings --- server_addon/houdini/server/settings/imageio.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index cc12a15c0d..4fa5cac82d 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -35,7 +35,13 @@ class ImageIOFileRulesModel(BaseSettingsModel): class WorkfileImageIOModel(BaseSettingsModel): - """Render space in Houdini is always set to 'scene_linear' Role.""" + """Workfile settings help. + + Empty values will be skipped, allowing any existing env vars to + pass through as defined. + + Note: The render space in Houdini is + always set to the 'scene_linear' role.""" enabled: bool = Field(False, title="Enabled") default_display: str = Field(title="Display") From d3362eb192a5f4b8c0eaf5fe54eca660a5a8d79a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Nov 2023 15:33:49 +0200 Subject: [PATCH 032/363] bump houdini addon version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 75cf7831c4..6232f7ab18 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.9" +__version__ = "0.2.10" From 0c3ff0f4918884eb90b87f17f43c549fcdb56363 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 17 Nov 2023 21:38:10 +0200 Subject: [PATCH 033/363] Fabia's Comments --- openpype/hosts/houdini/api/lib.py | 12 +++++------ .../hooks/set_default_display_and_view.py | 21 +++++++++---------- .../plugins/publish/extract_composite.py | 15 +++++++------ .../publish/validate_review_colorspace.py | 10 ++++----- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 77ae929857..d506df429b 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -880,23 +880,23 @@ def set_review_color_space(opengl_node, log=None): " 'OpenColorIO'".format(opengl_node.path()) ) - # Get view space for ociocolorspace parm. + # Get review color space color_settings = get_houdini_color_settings() - view_space = color_settings["review_color_space"] if color_settings["enabled"] else "" # noqa + review_color_space = color_settings["review_color_space"] if color_settings["enabled"] else "" # noqa # fall to default review color space if the setting is empty. - if not view_space: + if not review_color_space: from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - view_space = get_default_display_view_colorspace() + review_color_space = get_default_display_view_colorspace() opengl_node.setParms( - {"ociocolorspace": view_space} + {"ociocolorspace": review_color_space} ) self.log.debug( "'OCIO Colorspace' parm on '{}' has been set to " "the view color space '{}'" - .format(opengl_node, view_space) + .format(opengl_node, review_color_space) ) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 0cfc23c930..bb5f8bf71a 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -33,20 +33,15 @@ class SetDefaultDisplayView(PreLaunchHook): ) return - # This is a way to get values specified by admins if they already - # added 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' manually - # using Ayon global env vars or Ayon app env vars - # or Ayon houdini tool - OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get( - "OCIO_ACTIVE_DISPLAYS", "" - ) - OCIO_ACTIVE_VIEWS = self.launch_context.env.get( - "OCIO_ACTIVE_VIEWS", "" - ) + # 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' are checked + # as Admins can add them in Ayon env vars or Ayon tools. - # default_display and default_view default_display = houdini_color_settings["default_display"] if default_display: + # get 'OCIO_ACTIVE_DISPLAYS' value if exists. + OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get( + "OCIO_ACTIVE_DISPLAYS", "" + ) default_display = ":".join( key for key in [default_display, OCIO_ACTIVE_DISPLAYS] if key ) @@ -58,6 +53,10 @@ class SetDefaultDisplayView(PreLaunchHook): default_view = houdini_color_settings["default_view"] if default_view: + # get 'OCIO_ACTIVE_VIEWS' value if exists. + OCIO_ACTIVE_VIEWS = self.launch_context.env.get( + "OCIO_ACTIVE_VIEWS", "" + ) default_view = ":".join( key for key in [default_view, OCIO_ACTIVE_VIEWS] if key ) diff --git a/openpype/hosts/houdini/plugins/publish/extract_composite.py b/openpype/hosts/houdini/plugins/publish/extract_composite.py index 5047b719c9..a3194026bf 100644 --- a/openpype/hosts/houdini/plugins/publish/extract_composite.py +++ b/openpype/hosts/houdini/plugins/publish/extract_composite.py @@ -46,11 +46,14 @@ class ExtractComposite(publish.Extractor, "frameEnd": instance.data["frameEndHandle"], } - # inject colorspace data - # It's always scene_linear (Houdini's default) - self.set_representation_colorspace( - representation, instance.context, - colorspace="scene_linear" - ) + if ext == "exr": + # Inject colorspace with 'scene_linear' as that's the + # default Houdini working colorspace and all extracted + # OpenEXR images should be in that colorspace. + # https://www.sidefx.com/docs/houdini/render/linear.html#image-formats + self.set_representation_colorspace( + representation, instance.context, + colorspace="scene_linear" + ) instance.data["representations"].append(representation) diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index e4356a741c..db6533d4ec 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -16,7 +16,7 @@ import hou class SetReviewColorSpaceAction(RepairAction): - label = "Set Review Color Space" + label = "Set Default Review Color Space" icon = "mdi.monitor" @@ -68,10 +68,10 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, color_settings = get_houdini_color_settings() # skip if houdini color settings are disabled if color_settings["enabled"]: - view_space = color_settings["review_color_space"] + review_color_space = color_settings["review_color_space"] # skip if review color space setting is empty. - if view_space and \ - rop_node.evalParm("ociocolorspace") != view_space: + if review_color_space and \ + rop_node.evalParm("ociocolorspace") != review_color_space: raise PublishValidationError( "Invalid value: Colorspace name doesn't match studio " @@ -81,7 +81,7 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): - """Set Default View Space Action. + """Set Default Review Space Action. It sets ociocolorspace parameter. From e45df04e5c63a998167863832d73838d879bb98e Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 23 Nov 2023 09:24:15 +0200 Subject: [PATCH 034/363] Jeza Comment - Better Labels --- .../schemas/projects_schema/schema_project_houdini.json | 4 ++-- server_addon/houdini/server/settings/imageio.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 275fc54053..9aef7c3525 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -39,12 +39,12 @@ { "type": "text", "key": "default_display", - "label": "Display" + "label": "Default active displays" }, { "type": "text", "key": "default_view", - "label": "View" + "label": "Default active views" }, { "type": "text", diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 4fa5cac82d..694d653720 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -44,8 +44,8 @@ class WorkfileImageIOModel(BaseSettingsModel): always set to the 'scene_linear' role.""" enabled: bool = Field(False, title="Enabled") - default_display: str = Field(title="Display") - default_view: str = Field(title="View") + default_display: str = Field(title="Default active displays") + default_view: str = Field(title="Default active views") review_color_space: str = Field(title="Review colorspace") From ab04429f5d6424ceacfc23705ec5008b157fbe53 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 24 Nov 2023 16:23:09 +0200 Subject: [PATCH 035/363] BigRoy's comment - add settings tip --- .../houdini/server/settings/imageio.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py index 694d653720..18ebc2c0a1 100644 --- a/server_addon/houdini/server/settings/imageio.py +++ b/server_addon/houdini/server/settings/imageio.py @@ -44,9 +44,22 @@ class WorkfileImageIOModel(BaseSettingsModel): always set to the 'scene_linear' role.""" enabled: bool = Field(False, title="Enabled") - default_display: str = Field(title="Default active displays") - default_view: str = Field(title="Default active views") - review_color_space: str = Field(title="Review colorspace") + default_display: str = Field( + title="Default active displays", + description="It behaves like the 'OCIO_ACTIVE_DISPLAYS' env var," + " Colon-separated list of displays, e.g ACES:P3" + ) + default_view: str = Field( + title="Default active views", + description="It behaves like the 'OCIO_ACTIVE_VIEWS' env var," + " Colon-separated list of views, e.g sRGB:DCDM" + ) + review_color_space: str = Field( + title="Review colorspace", + description="It exposes OCIO Colorspace parameter in opengl nodes." + "if left empty, Ayon will figure out the default " + "colorspace using your default display and default view." + ) class HoudiniImageIOModel(BaseSettingsModel): From c40b7139d07861ee869f67d209810d867a37d0e6 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 14 Dec 2023 14:49:05 +0200 Subject: [PATCH 036/363] bump houdini addon version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 6232f7ab18..5635676f6b 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.10" +__version__ = "0.2.11" From 361d21eff6afb403781e92fa32fc6588af0a9257 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Sun, 31 Dec 2023 14:20:37 +0200 Subject: [PATCH 037/363] =?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 038/363] =?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 b8f89e141c71327d032e1463ab8d3f523c5ceeb2 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 14:51:35 +0200 Subject: [PATCH 039/363] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Minkiu=20Commen?= =?UTF-8?q?t=20-=20refactor=20repeated=20code=20into=20a=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/set_default_display_and_view.py | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index bb5f8bf71a..3462ab1647 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -39,29 +39,20 @@ class SetDefaultDisplayView(PreLaunchHook): default_display = houdini_color_settings["default_display"] if default_display: # get 'OCIO_ACTIVE_DISPLAYS' value if exists. - OCIO_ACTIVE_DISPLAYS = self.launch_context.env.get( - "OCIO_ACTIVE_DISPLAYS", "" - ) - default_display = ":".join( - key for key in [default_display, OCIO_ACTIVE_DISPLAYS] if key - ) - self.log.info( - "Setting OCIO_ACTIVE_DISPLAYS environment to: {}" - .format(default_display) - ) - self.launch_context.env["OCIO_ACTIVE_DISPLAYS"] = default_display + self._set_context_env("OCIO_ACTIVE_DISPLAYS", default_display) default_view = houdini_color_settings["default_view"] if default_view: # get 'OCIO_ACTIVE_VIEWS' value if exists. - OCIO_ACTIVE_VIEWS = self.launch_context.env.get( - "OCIO_ACTIVE_VIEWS", "" + self._set_context_env("OCIO_ACTIVE_VIEWS", default_view) + + def _set_context_env(self, env_var, default_value): + env_value = self.launch_context.env.get(env_var, "") + new_value = ":".join( + key for key in [default_value, env_value] if key ) - default_view = ":".join( - key for key in [default_view, OCIO_ACTIVE_VIEWS] if key + self.log.info( + "Setting {} environment to: {}" + .format(env_var, new_value) ) - self.log.info( - "Setting OCIO_ACTIVE_VIEWS environment to: {}" - .format(default_view) - ) - self.launch_context.env["OCIO_ACTIVE_VIEWS"] = default_view + self.launch_context.env[env_var] = new_value From 71fe47dc411fa3a0a50fc951b807f868986d7e5f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 14:54:14 +0200 Subject: [PATCH 040/363] =?UTF-8?q?=F0=9F=A6=B4=20Resolve=20Hound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../houdini/hooks/set_default_display_and_view.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/hooks/set_default_display_and_view.py b/openpype/hosts/houdini/hooks/set_default_display_and_view.py index 3462ab1647..4f19dcfcd6 100644 --- a/openpype/hosts/houdini/hooks/set_default_display_and_view.py +++ b/openpype/hosts/houdini/hooks/set_default_display_and_view.py @@ -49,10 +49,10 @@ class SetDefaultDisplayView(PreLaunchHook): def _set_context_env(self, env_var, default_value): env_value = self.launch_context.env.get(env_var, "") new_value = ":".join( - key for key in [default_value, env_value] if key - ) + key for key in [default_value, env_value] if key + ) self.log.info( - "Setting {} environment to: {}" - .format(env_var, new_value) - ) + "Setting {} environment to: {}" + .format(env_var, new_value) + ) self.launch_context.env[env_var] = new_value From ab61e755e99773d3f4997c4fcbf27250e47d8262 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Wed, 3 Jan 2024 17:22:52 +0200 Subject: [PATCH 041/363] =?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 042/363] =?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 043/363] =?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 044/363] =?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 045/363] 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 ec6a8b2a96eae0813e9a853495d4dfc813562254 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 16:21:21 +0200 Subject: [PATCH 046/363] =?UTF-8?q?=E2=9E=96=20Remove=20unnecessary=20func?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/hosts/houdini/api/lib.py | 36 ++++--------------- .../houdini/plugins/create/create_review.py | 16 ++++++++- .../publish/validate_review_colorspace.py | 24 ++++++++----- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 4f39677afc..89e5309f29 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -866,35 +866,17 @@ def get_current_context_template_data_with_asset_data(): return template_data -def get_houdini_color_settings(): - """Get Houdini working file color settings. - - Returns: - Dict: The dictionary contains the Houdini working file color settings - if the settings are enabled, otherwise it is an empty dictionary. - """ - - project_settings = get_current_project_settings() - color_settings = project_settings["houdini"]["imageio"]["workfile"] - - # Remove leading, and trailing whitespaces - color_settings["review_color_space"] = \ - color_settings["review_color_space"].strip() - return color_settings - - -def set_review_color_space(opengl_node, log=None): +def set_review_color_space(opengl_node, review_color_space="", log=None): """Set ociocolorspace parameter for the given OpenGL node. - If workfile settings are enabled, it will use the value - exposed in the settings. - - If the value exposed in the settings is empty, - it will use the default colorspace corresponding to - the display & view of the current Houdini session. + Set `ociocolorspace` parameter of the given OpenGl node + to to the given review_color_space value. + If review_color_space is empty, a default colorspace corresponding to + the display & view of the current Houdini session will be used. Args: - OpenGl node (hou.Node): ROP node to set its ociocolorspace parameter. + opengl_node (hou.Node): ROP node to set its ociocolorspace parameter. + review_color_space (str): Colorspace value for ociocolorspace parameter. log (logging.Logger): Logger to log to. """ @@ -910,10 +892,6 @@ def set_review_color_space(opengl_node, log=None): " 'OpenColorIO'".format(opengl_node.path()) ) - # Get review color space - color_settings = get_houdini_color_settings() - review_color_space = color_settings["review_color_space"] if color_settings["enabled"] else "" # noqa - # fall to default review color space if the setting is empty. if not review_color_space: from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 775babbab6..22a5dbd817 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -2,6 +2,7 @@ """Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef, BoolDef, NumberDef +from openpype.settings import get_current_project_settings from openpype.hosts.houdini.api.lib import set_review_color_space import os import hou @@ -14,6 +15,11 @@ class CreateReview(plugin.HoudiniCreator): label = "Review" family = "review" icon = "video-camera" + workfile_color_settings = {} + + def apply_settings(self, project_settings): + super(CreateReview, self).apply_settings(project_settings) + self.workfile_color_settings = project_settings["houdini"]["imageio"]["workfile"] def create(self, subset_name, instance_data, pre_create_data): @@ -87,7 +93,15 @@ class CreateReview(plugin.HoudiniCreator): # Set OCIO Colorspace to the default output colorspace # if there's OCIO if os.getenv("OCIO"): - set_review_color_space(instance_node, log=self.log) + workfile_color_settings = self.project_settings["houdini"]["imageio"]["workfile"] + review_color_space = workfile_color_settings["enabled"] and \ + workfile_color_settings["review_color_space"] + + set_review_color_space( + instance_node, + review_color_space, + log=self.log) + to_lock = ["id", "family"] diff --git a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py index db6533d4ec..528f4ae242 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/openpype/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -6,10 +6,7 @@ from openpype.pipeline import ( ) from openpype.pipeline.publish import RepairAction from openpype.hosts.houdini.api.action import SelectROPAction -from openpype.hosts.houdini.api.lib import ( - get_houdini_color_settings, - set_review_color_space -) +from openpype.hosts.houdini.api.lib import set_review_color_space import os import hou @@ -65,10 +62,11 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, .format(rop_node.path()) ) - color_settings = get_houdini_color_settings() - # skip if houdini color settings are disabled - if color_settings["enabled"]: - review_color_space = color_settings["review_color_space"] + workfile_color_settings = instance.context.data["project_settings"]["houdini"]["imageio"]["workfile"] # noqa + # skip if houdini workfile color settings are disabled + if workfile_color_settings["enabled"]: + review_color_space = workfile_color_settings["review_color_space"] + # skip if review color space setting is empty. if review_color_space and \ rop_node.evalParm("ociocolorspace") != review_color_space: @@ -94,4 +92,12 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, """ opengl_node = hou.node(instance.data["instance_node"]) - set_review_color_space(opengl_node, log=cls.log) + + workfile_color_settings = instance.context.data["project_settings"]["houdini"]["imageio"]["workfile"] # noqa + review_color_space = workfile_color_settings["enabled"] and \ + workfile_color_settings["review_color_space"] + + set_review_color_space( + opengl_node, + review_color_space, + log=cls.log) From 10cdd11270aa837d45e404d60ffc4ae8da3e5fe6 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 16:24:34 +0200 Subject: [PATCH 047/363] =?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 | 4 ++-- openpype/hosts/houdini/plugins/create/create_review.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 89e5309f29..51d490adc9 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -875,8 +875,8 @@ def set_review_color_space(opengl_node, review_color_space="", log=None): the display & view of the current Houdini session will be used. Args: - opengl_node (hou.Node): ROP node to set its ociocolorspace parameter. - review_color_space (str): Colorspace value for ociocolorspace parameter. + opengl_node (hou.Node): ROP node to set its ociocolorspace parm. + review_color_space (str): Colorspace value for ociocolorspace parm. log (logging.Logger): Logger to log to. """ diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 22a5dbd817..8e08c76c9a 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -93,7 +93,7 @@ class CreateReview(plugin.HoudiniCreator): # Set OCIO Colorspace to the default output colorspace # if there's OCIO if os.getenv("OCIO"): - workfile_color_settings = self.project_settings["houdini"]["imageio"]["workfile"] + workfile_color_settings = self.project_settings["houdini"]["imageio"]["workfile"] # noqa review_color_space = workfile_color_settings["enabled"] and \ workfile_color_settings["review_color_space"] From eb123e1fc028a5facca643df685bd6da12f8e81a Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 16:25:16 +0200 Subject: [PATCH 048/363] =?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/plugins/create/create_review.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index 8e08c76c9a..f758b8529f 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -2,7 +2,6 @@ """Creator plugin for creating openGL reviews.""" from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef, BoolDef, NumberDef -from openpype.settings import get_current_project_settings from openpype.hosts.houdini.api.lib import set_review_color_space import os import hou From 05948a968a70db8f81774af62a6ba3f306309a70 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 16:26:30 +0200 Subject: [PATCH 049/363] =?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/plugins/create/create_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_review.py b/openpype/hosts/houdini/plugins/create/create_review.py index f758b8529f..6c82afc4e5 100644 --- a/openpype/hosts/houdini/plugins/create/create_review.py +++ b/openpype/hosts/houdini/plugins/create/create_review.py @@ -18,7 +18,7 @@ class CreateReview(plugin.HoudiniCreator): def apply_settings(self, project_settings): super(CreateReview, self).apply_settings(project_settings) - self.workfile_color_settings = project_settings["houdini"]["imageio"]["workfile"] + self.workfile_color_settings = project_settings["houdini"]["imageio"]["workfile"] # noqa def create(self, subset_name, instance_data, pre_create_data): From 435cd9cce1e6a95fd5074aef1282866b8c126ef8 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Fri, 5 Jan 2024 17:04:36 +0200 Subject: [PATCH 050/363] =?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 051/363] =?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 75b6dfe5efc606ed123531faf08d0300c6f838c2 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Jan 2024 12:08:44 +0200 Subject: [PATCH 052/363] bump Houdini addon version --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index 5635676f6b..b5c9b6cb71 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.11" +__version__ = "0.2.12" From 62020ca29d2ec75b7e545fbf3d1a0b835dd86e98 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 4 Mar 2024 15:46:28 +0200 Subject: [PATCH 053/363] 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 054/363] 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 055/363] 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 056/363] 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 057/363] 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 058/363] 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 059/363] 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 5780a9c1cd9a767490140ea99fc286153b38b426 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 12:26:13 +0100 Subject: [PATCH 060/363] expect folder instead of asset argument in cli commands --- client/ayon_core/cli.py | 16 ++++++++-------- client/ayon_core/cli_commands.py | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index 2759b4fccf..26486afaa0 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -81,13 +81,13 @@ main_cli.set_alias("addon", "module") @main_cli.command() @click.argument("output_json_path") @click.option("--project", help="Project name", default=None) -@click.option("--asset", help="Asset name", default=None) +@click.option("--folder", help="Folder path", default=None) @click.option("--task", help="Task name", default=None) @click.option("--app", help="Application name", default=None) @click.option( "--envgroup", help="Environment group (e.g. \"farm\")", default=None ) -def extractenvironments(output_json_path, project, asset, task, app, envgroup): +def extractenvironments(output_json_path, project, folder, task, app, envgroup): """Extract environment variables for entered context to a json file. Entered output filepath will be created if does not exists. @@ -95,10 +95,10 @@ def extractenvironments(output_json_path, project, asset, task, app, envgroup): All context options must be passed otherwise only AYON's global environments will be extracted. - Context options are "project", "asset", "task", "app" + Context options are "project", "folder", "task", "app" """ Commands.extractenvironments( - output_json_path, project, asset, task, app, envgroup + output_json_path, project, folder, task, app, envgroup ) @@ -127,7 +127,7 @@ def publish_report_viewer(): @main_cli.command() @click.argument("output_path") @click.option("--project", help="Define project context") -@click.option("--asset", help="Define asset in project (project must be set)") +@click.option("--folder", help="Define folder in project (project must be set)") @click.option( "--strict", is_flag=True, @@ -136,18 +136,18 @@ def publish_report_viewer(): def contextselection( output_path, project, - asset, + folder, strict ): """Show Qt dialog to select context. - Context is project name, asset name and task name. The result is stored + Context is project name, folder path and task name. The result is stored into json file which path is passed in first argument. """ Commands.contextselection( output_path, project, - asset, + folder, strict ) diff --git a/client/ayon_core/cli_commands.py b/client/ayon_core/cli_commands.py index 4335a3f2d9..2e561cb487 100644 --- a/client/ayon_core/cli_commands.py +++ b/client/ayon_core/cli_commands.py @@ -150,7 +150,7 @@ class Commands: log.info("Publish finished.") @staticmethod - def extractenvironments(output_json_path, project, asset, task, app, + def extractenvironments(output_json_path, project, folder, task, app, env_group): """Produces json file with environment based on project and app. @@ -162,10 +162,10 @@ class Commands: LaunchTypes, ) - if all((project, asset, task, app)): + if all((project, folder, task, app)): env = get_app_environments_for_context( project, - asset, + folder, task, app, env_group=env_group, From 42cd3dfee263241e7e50df25bf7ca83ee6d8c6ea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 12:27:05 +0100 Subject: [PATCH 061/363] modify royal render to use folder kwargs --- client/ayon_core/modules/royalrender/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/modules/royalrender/lib.py b/client/ayon_core/modules/royalrender/lib.py index d552e7fb19..f96832ea79 100644 --- a/client/ayon_core/modules/royalrender/lib.py +++ b/client/ayon_core/modules/royalrender/lib.py @@ -319,7 +319,7 @@ class BaseCreateRoyalRenderJob(pyblish.api.InstancePlugin, add_kwargs = { "project": anatomy_data["project"]["name"], - "asset": instance.context.data["folderPath"], + "folder": instance.context.data["folderPath"], "task": anatomy_data["task"]["name"], "app": instance.context.data.get("appName"), "envgroup": "farm" From 23aecad653911fdfde5af45fc6648777fe262378 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 19 Mar 2024 12:30:22 +0100 Subject: [PATCH 062/363] modified deadline job pre load to support both folder and asset arguments --- .../custom/plugins/GlobalJobPreLoad.py | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/client/ayon_core/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 1565b2c496..4d12cd4753 100644 --- a/client/ayon_core/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/client/ayon_core/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -476,7 +476,7 @@ def inject_ayon_environment(deadlinePlugin): # Support backwards compatible keys for key, env_keys in ( ("project", ["AYON_PROJECT_NAME", "AVALON_PROJECT"]), - ("asset", ["AYON_FOLDER_PATH", "AVALON_ASSET"]), + ("folder", ["AYON_FOLDER_PATH", "AVALON_ASSET"]), ("task", ["AYON_TASK_NAME", "AVALON_TASK"]), ("app", ["AYON_APP_NAME", "AVALON_APP_NAME"]), ): @@ -490,15 +490,19 @@ def inject_ayon_environment(deadlinePlugin): if job.GetJobEnvironmentKeyValue("IS_TEST"): args.append("--automatic-tests") - if all(add_kwargs.values()): - for key, value in add_kwargs.items(): - args.extend(["--{}".format(key), value]) - else: + if not all(add_kwargs.values()): raise RuntimeError(( "Missing required env vars: AYON_PROJECT_NAME," " AYON_FOLDER_PATH, AYON_TASK_NAME, AYON_APP_NAME" )) + legacy_args = list(args) + for key, value in add_kwargs.items(): + args.extend(["--{}".format(key), value]) + if key == "folder": + key = "asset" + legacy_args.extend(["--{}".format(key), value]) + environment = { "AYON_SERVER_URL": ayon_server_url, "AYON_API_KEY": ayon_api_key, @@ -517,9 +521,18 @@ def inject_ayon_environment(deadlinePlugin): ) if process_exitcode != 0: - raise RuntimeError( - "Failed to run Ayon process to extract environments." + print( + "Failed to run AYON process to extract environments. Trying" + " to use legacy arguments." ) + legacy_args_str = subprocess.list2cmdline(legacy_args) + process_exitcode = deadlinePlugin.RunProcess( + exe, legacy_args_str, os.path.dirname(exe), -1 + ) + if process_exitcode != 0: + raise RuntimeError( + "Failed to run AYON process to extract environments." + ) print(">>> Loading file ...") with open(export_url) as fp: From 30e84371e0be60bfd31d912ae342b70f7f5383b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:16:32 +0100 Subject: [PATCH 063/363] Blender: Export Alembic with subdiv schema (creases) --- .../blender/plugins/publish/extract_abc.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py b/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py index 094f88fd8c..6251c1c0c5 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py +++ b/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py @@ -2,6 +2,7 @@ import os import bpy +from ayon_core.lib import BoolDef from ayon_core.pipeline import publish from ayon_core.hosts.blender.api import plugin @@ -17,6 +18,8 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): if not self.is_active(instance.data): return + attr_values = self.get_attr_values_from_data(instance.data) + # Define extract output file path stagingdir = self.staging_dir(instance) folder_name = instance.data["folderEntity"]["name"] @@ -46,7 +49,8 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): bpy.ops.wm.alembic_export( filepath=filepath, selected=True, - flatten=False + flatten=False, + subdiv_schema=attr_values.get("subdiv_schema", False) ) plugin.deselect_all() @@ -65,6 +69,21 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): self.log.debug("Extracted instance '%s' to: %s", instance.name, representation) + @classmethod + def get_attribute_defs(cls): + return [ + BoolDef( + "subdiv_schema", + label="Alembic Mesh Subdiv Schema", + tooltip="Export Meshes using Alembic's subdivision schema.\n" + "Enabling this includes creases with the export but " + "excludes the mesh's normals.\n" + "Enabling this usually result in smaller file size " + "due to lack of normals.", + default=True + ) + ] + class ExtractModelABC(ExtractABC): """Extract model as ABC.""" From b2af91ba37adcb8cff5fd669d8f2e65b5eb91aa5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:17:42 +0100 Subject: [PATCH 064/363] Default to False for backwards compatibility --- client/ayon_core/hosts/blender/plugins/publish/extract_abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py b/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py index 6251c1c0c5..6590be515c 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py +++ b/client/ayon_core/hosts/blender/plugins/publish/extract_abc.py @@ -80,7 +80,7 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin): "excludes the mesh's normals.\n" "Enabling this usually result in smaller file size " "due to lack of normals.", - default=True + default=False ) ] From 85dc52a2c1b9c63d29c9dd1755c2fe316cf5e2cd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Mar 2024 17:30:34 +0100 Subject: [PATCH 065/363] 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 066/363] 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 067/363] 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 068/363] 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 069/363] 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 070/363] 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 071/363] 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 1c75132943c03bf03f04e42c8e069812ad17df41 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Mar 2024 15:41:01 +0100 Subject: [PATCH 072/363] collect tracks only if sequence by name exists --- client/ayon_core/hosts/hiero/api/lib.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/hiero/api/lib.py b/client/ayon_core/hosts/hiero/api/lib.py index 8e08e8cbf3..9979b72f65 100644 --- a/client/ayon_core/hosts/hiero/api/lib.py +++ b/client/ayon_core/hosts/hiero/api/lib.py @@ -248,8 +248,12 @@ def get_track_items( # collect all available active sequence track items if not return_list: sequence = get_current_sequence(name=sequence_name) - # get all available tracks from sequence - tracks = list(sequence.audioTracks()) + list(sequence.videoTracks()) + tracks = [] + if sequence is not None: + # get all available tracks from sequence + tracks.extend(sequence.audioTracks()) + tracks.extend(sequence.videoTracks()) + # loop all tracks for track in tracks: if check_locked and track.isLocked(): From b17df02e418b2061ec6631d028e3039ce55105f5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Mar 2024 23:04:58 +0100 Subject: [PATCH 073/363] 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 074/363] 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 075/363] 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 018e98df0ba1c1d3e68207d982e6d6c8e3d52704 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 10:17:42 +0100 Subject: [PATCH 076/363] Fix #253 - Do not create `unpack`, `normal` and `null` on load --- .../houdini/plugins/load/load_alembic.py | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) 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 a77d06d409..3dba625da9 100644 --- a/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py +++ b/client/ayon_core/hosts/houdini/plugins/load/load_alembic.py @@ -45,33 +45,11 @@ class AbcLoader(load.LoaderPlugin): alembic = container.createNode("alembic", node_name=node_name) alembic.setParms({"fileName": file_path}) - # Add unpack node - unpack_name = "unpack_{}".format(name) - unpack = container.createNode("unpack", node_name=unpack_name) - unpack.setInput(0, alembic) - unpack.setParms({"transfer_attributes": "path"}) + # Position nodes nicely + container.moveToGoodPosition() + container.layoutChildren() - # Add normal to points - # Order of menu ['point', 'vertex', 'prim', 'detail'] - normal_name = "normal_{}".format(name) - normal_node = container.createNode("normal", node_name=normal_name) - normal_node.setParms({"type": 0}) - - normal_node.setInput(0, unpack) - - null = container.createNode("null", node_name="OUT".format(name)) - null.setInput(0, normal_node) - - # Ensure display flag is on the Alembic input node and not on the OUT - # node to optimize "debug" displaying in the viewport. - alembic.setDisplayFlag(True) - - # Set new position for unpack node else it gets cluttered - nodes = [container, alembic, unpack, normal_node, null] - for nr, node in enumerate(nodes): - node.setPosition([0, (0 - nr)]) - - self[:] = nodes + nodes = [container, alembic] return pipeline.containerise( node_name, From 07cc3bba0b61fbb4a7d8260dddd43690de2d2f0b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 11:09:28 +0100 Subject: [PATCH 077/363] 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 f36d9804c5aea7bf2dd9ad64d956ef109cd2102c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 14:55:52 +0100 Subject: [PATCH 078/363] Maya: Create yeticache instance directly in loading a yeti rig --- .../hosts/maya/plugins/load/load_yeti_rig.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) 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..6b0288afea 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 @@ -1,8 +1,13 @@ +from typing import List + import maya.cmds as cmds from ayon_core.hosts.maya.api import plugin from ayon_core.hosts.maya.api import lib +from openpype.pipeline import registered_host +from openpype.pipeline.create import CreateContext + class YetiRigLoader(plugin.ReferenceLoader): """This loader will load Yeti rig.""" @@ -49,4 +54,40 @@ class YetiRigLoader(plugin.ReferenceLoader): ) self[:] = nodes + # Automatically create in instance to allow publishing the loaded + # yeti rig into a yeti cache + self._create_yeti_cache_instance(nodes, variant=namespace) + return nodes + + def _create_yeti_cache_instance(self, nodes: List[str], variant: str): + """Create a yeticache product type instance to publish the output. + + This is similar to how loading animation rig will automatically create + an animation instance for publishing any loaded character rigs, but + then for yeti rigs. + + Args: + nodes (List[str]): Nodes generated on load. + variant (str): Variant for the yeti cache instance to create. + + """ + + # Find the roots amongst the loaded nodes + yeti_nodes = cmds.ls(nodes, type="pgYetiMaya", long=True) + assert yeti_nodes, "No pgYetiMaya nodes in rig, this is a bug." + + self.log.info("Creating variant: {}".format(variant)) + + creator_identifier = "io.openpype.creators.maya.yeticache" + + host = registered_host() + create_context = CreateContext(host) + + with lib.maintained_selection(): + cmds.select(yeti_nodes, noExpand=True) + create_context.create( + creator_identifier=creator_identifier, + variant=variant, + pre_create_data={"use_selection": True} + ) From 1c97c78a173aa3540587c2a4f2bf34d93d8e5ae6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Mar 2024 15:15:03 +0100 Subject: [PATCH 079/363] 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 080/363] 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 081/363] :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 082/363] 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 083/363] 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 084/363] 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 085/363] 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 086/363] 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 087/363] 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 3bae4dba34c1dd141139b914b61d948c4afedec8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 10:13:45 +0100 Subject: [PATCH 088/363] Maya: Fix redshift proxy export whilst in renderlayer + allow exporting from renderlayer --- .../hosts/maya/api/render_setup_tools.py | 4 +-- .../plugins/publish/extract_redshift_proxy.py | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/hosts/maya/api/render_setup_tools.py b/client/ayon_core/hosts/maya/api/render_setup_tools.py index a5e04de184..9b00b53eee 100644 --- a/client/ayon_core/hosts/maya/api/render_setup_tools.py +++ b/client/ayon_core/hosts/maya/api/render_setup_tools.py @@ -19,7 +19,7 @@ from .lib import pairwise @contextlib.contextmanager -def _allow_export_from_render_setup_layer(): +def allow_export_from_render_setup_layer(): """Context manager to override Maya settings to allow RS layer export""" try: @@ -102,7 +102,7 @@ def export_in_rs_layer(path, nodes, export=None): cmds.disconnectAttr(src, dest) # Export Selected - with _allow_export_from_render_setup_layer(): + with allow_export_from_render_setup_layer(): cmds.select(nodes, noExpand=True) if export: export() diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py b/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py index 9286869c60..e6a568ef8d 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -5,7 +5,13 @@ import os from maya import cmds from ayon_core.pipeline import publish -from ayon_core.hosts.maya.api.lib import maintained_selection +from ayon_core.hosts.maya.api.lib import ( + maintained_selection, + renderlayer +) +from ayon_core.hosts.maya.api.render_setup_tools import ( + allow_export_from_render_setup_layer +) class ExtractRedshiftProxy(publish.Extractor): @@ -60,14 +66,22 @@ class ExtractRedshiftProxy(publish.Extractor): # Write out rs file self.log.debug("Writing: '%s'" % file_path) + + # Allow overriding what renderlayer to export from. By default force + # it to the default render layer. (Note that the renderlayer isn't + # currently exposed as an attribute to artists) + layer = instance.data.get("renderLayer", "defaultRenderLayer") + with maintained_selection(): - cmds.select(instance.data["setMembers"], noExpand=True) - cmds.file(file_path, - pr=False, - force=True, - type="Redshift Proxy", - exportSelected=True, - options=rs_options) + with renderlayer(layer): + with allow_export_from_render_setup_layer(): + cmds.select(instance.data["setMembers"], noExpand=True) + cmds.file(file_path, + preserveReferences=False, + force=True, + type="Redshift Proxy", + exportSelected=True, + options=rs_options) if "representations" not in instance.data: instance.data["representations"] = [] From 94488e18d24504f3e771f081dd8d8ff52e7bb5f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 10:14:14 +0100 Subject: [PATCH 089/363] Make sure Redshift is loaded before trying to export --- .../hosts/maya/plugins/publish/extract_redshift_proxy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py b/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py index e6a568ef8d..66dd805437 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -24,6 +24,9 @@ class ExtractRedshiftProxy(publish.Extractor): def process(self, instance): """Extractor entry point.""" + # Make sure Redshift is loaded + cmds.loadPlugin("redshift4maya", quiet=True) + staging_dir = self.staging_dir(instance) file_name = "{}.rs".format(instance.name) file_path = os.path.join(staging_dir, file_name) From 33fd95dd009d65097cc68f7f4f10251eff4ea874 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 29 Mar 2024 10:57:27 +0100 Subject: [PATCH 090/363] 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 091/363] 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 092/363] 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 093/363] 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 094/363] 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 095/363] 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 096/363] 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 097/363] 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 098/363] 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 099/363] 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 100/363] 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 101/363] 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 102/363] 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 103/363] 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 104/363] 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 105/363] 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 106/363] 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 107/363] 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 108/363] 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 109/363] 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 110/363] 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 111/363] 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 112/363] 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 bf26eba7797d5f53a5eda6b2836c43276443d7dc Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 2 Apr 2024 12:45:57 +0200 Subject: [PATCH 113/363] Bump Houdini addon version - Color management for Houdini workfiles --- server_addon/houdini/server/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py index b5c9b6cb71..11ef092868 100644 --- a/server_addon/houdini/server/version.py +++ b/server_addon/houdini/server/version.py @@ -1 +1 @@ -__version__ = "0.2.12" +__version__ = "0.2.13" 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 114/363] :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 86624067c06d4cc28401693e07be15b3514e4209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 2 Apr 2024 15:20:07 +0200 Subject: [PATCH 115/363] :bug: add headless flag to prevent UI interactivity --- .../ayon_core/plugins/publish/extract_burnin.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_burnin.py b/client/ayon_core/plugins/publish/extract_burnin.py index 727d7f1bc2..93774842ca 100644 --- a/client/ayon_core/plugins/publish/extract_burnin.py +++ b/client/ayon_core/plugins/publish/extract_burnin.py @@ -27,7 +27,7 @@ class ExtractBurnin(publish.Extractor): Extractor to create video with pre-defined burnins from existing extracted video representation. - It will work only on represenations having `burnin = True` or + It will work only on representations having `burnin = True` or `tags` including `burnin` """ @@ -125,7 +125,7 @@ class ExtractBurnin(publish.Extractor): burnin_defs = copy.deepcopy(src_burnin_defs) - # Filter output definition by `burnin` represetation key + # Filter output definition by `burnin` representation key repre_linked_burnins = [ burnin_def for burnin_def in burnin_defs @@ -378,6 +378,7 @@ class ExtractBurnin(publish.Extractor): # Prepare subprocess arguments args = list(executable_args) args.append(temporary_json_filepath) + args.append("--headless") self.log.debug("Executing: {}".format(" ".join(args))) # Run burnin script @@ -547,7 +548,7 @@ class ExtractBurnin(publish.Extractor): return burnin_data, temp_data def repres_is_valid(self, repre): - """Validation if representaion should be processed. + """Validation if representation should be processed. Args: repre (dict): Representation which should be checked. @@ -579,7 +580,7 @@ class ExtractBurnin(publish.Extractor): tags (list): Tags of processed representation. Returns: - list: Containg all burnin definitions matching entered tags. + list: Contain all burnin definitions matching entered tags. """ filtered_burnins = [] @@ -604,7 +605,7 @@ class ExtractBurnin(publish.Extractor): Store data to `temp_data` for keys "full_input_path" which is full path to source files optionally with sequence formatting, - "full_output_path" full path to otput with optionally with sequence + "full_output_path" full path to output with optionally with sequence formatting, "full_input_paths" list of all source files which will be deleted when burnin script ends, "repre_files" list of output filenames. @@ -754,7 +755,7 @@ class ExtractBurnin(publish.Extractor): profile (dict): Profile from presets matching current context. Returns: - list: Containg all valid output definitions. + list: Contain all valid output definitions. """ filtered_burnin_defs = [] @@ -775,7 +776,7 @@ class ExtractBurnin(publish.Extractor): ): self.log.debug(( "Skipped burnin definition \"{}\". Family" - " fiters ({}) does not match current instance families: {}" + " filters ({}) does not match current instance families: {}" ).format( filename_suffix, str(families_filters), str(families) )) From 059191c8d47ac321f8804b55cabad9425968ddce Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 2 Apr 2024 22:03:04 +0800 Subject: [PATCH 116/363] 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 117/363] 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 118/363] 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 119/363] 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 120/363] 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 121/363] 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 f3e79c05c9347343f5e371ea36d9a4d88f2be7ca Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 12:31:53 +0200 Subject: [PATCH 122/363] renamed 'get_current_project_folder' to 'get_current_folder_entity' --- client/ayon_core/hosts/fusion/api/lib.py | 12 ++++---- .../ayon_core/hosts/harmony/api/pipeline.py | 4 +-- client/ayon_core/hosts/houdini/api/lib.py | 6 ++-- .../inventory/set_camera_resolution.py | 4 +-- client/ayon_core/hosts/max/api/lib.py | 6 ++-- .../hosts/max/api/lib_rendersettings.py | 4 +-- client/ayon_core/hosts/maya/api/lib.py | 6 ++-- .../hosts/maya/api/lib_rendersettings.py | 4 +-- .../plugins/publish/validate_maya_units.py | 4 +-- .../unreal/plugins/load/load_animation.py | 6 ++-- .../hosts/unreal/plugins/load/load_layout.py | 4 +-- client/ayon_core/pipeline/context_tools.py | 29 +++++++------------ 12 files changed, 40 insertions(+), 49 deletions(-) diff --git a/client/ayon_core/hosts/fusion/api/lib.py b/client/ayon_core/hosts/fusion/api/lib.py index ba650cc73f..03a1eeeb65 100644 --- a/client/ayon_core/hosts/fusion/api/lib.py +++ b/client/ayon_core/hosts/fusion/api/lib.py @@ -7,7 +7,7 @@ from ayon_core.lib import Logger from ayon_core.pipeline import registered_host from ayon_core.pipeline.create import CreateContext -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity self = sys.modules[__name__] self._project = None @@ -57,7 +57,7 @@ def update_frame_range(start, end, comp=None, set_render_range=True, def set_current_context_framerange(folder_entity=None): """Set Comp's frame range based on current folder.""" if folder_entity is None: - folder_entity = get_current_project_folder( + folder_entity = get_current_folder_entity( fields={"attrib.frameStart", "attrib.frameEnd", "attrib.handleStart", @@ -76,7 +76,7 @@ def set_current_context_framerange(folder_entity=None): def set_current_context_fps(folder_entity=None): """Set Comp's frame rate (FPS) to based on current asset""" if folder_entity is None: - folder_entity = get_current_project_folder(fields={"attrib.fps"}) + folder_entity = get_current_folder_entity(fields={"attrib.fps"}) fps = float(folder_entity["attrib"].get("fps", 24.0)) comp = get_current_comp() @@ -88,7 +88,7 @@ def set_current_context_fps(folder_entity=None): def set_current_context_resolution(folder_entity=None): """Set Comp's resolution width x height default based on current folder""" if folder_entity is None: - folder_entity = get_current_project_folder( + folder_entity = get_current_folder_entity( fields={"attrib.resolutionWidth", "attrib.resolutionHeight"}) folder_attributes = folder_entity["attrib"] @@ -124,7 +124,7 @@ def validate_comp_prefs(comp=None, force_repair=False): "attrib.resolutionHeight", "attrib.pixelAspect", } - folder_entity = get_current_project_folder(fields=fields) + folder_entity = get_current_folder_entity(fields=fields) folder_path = folder_entity["path"] folder_attributes = folder_entity["attrib"] @@ -389,7 +389,7 @@ def prompt_reset_context(): return None options = dialog.get_values() - folder_entity = get_current_project_folder() + folder_entity = get_current_folder_entity() if options["frame_range"]: set_current_context_framerange(folder_entity) diff --git a/client/ayon_core/hosts/harmony/api/pipeline.py b/client/ayon_core/hosts/harmony/api/pipeline.py index d842ccd414..1e3ea0ba21 100644 --- a/client/ayon_core/hosts/harmony/api/pipeline.py +++ b/client/ayon_core/hosts/harmony/api/pipeline.py @@ -13,7 +13,7 @@ from ayon_core.pipeline import ( AVALON_CONTAINER_ID, ) from ayon_core.pipeline.load import get_outdated_containers -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.hosts.harmony import HARMONY_ADDON_ROOT import ayon_core.hosts.harmony.api as harmony @@ -50,7 +50,7 @@ def get_current_context_settings(): """ - folder_entity = get_current_project_folder() + folder_entity = get_current_folder_entity() folder_attributes = folder_entity["attrib"] fps = folder_attributes.get("fps") diff --git a/client/ayon_core/hosts/houdini/api/lib.py b/client/ayon_core/hosts/houdini/api/lib.py index 2755b646fa..972b10ad1b 100644 --- a/client/ayon_core/hosts/houdini/api/lib.py +++ b/client/ayon_core/hosts/houdini/api/lib.py @@ -22,7 +22,7 @@ from ayon_core.pipeline import ( ) from ayon_core.pipeline.create import CreateContext from ayon_core.pipeline.template_data import get_template_data -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.tools.utils import PopupUpdateKeys, SimplePopup from ayon_core.tools.utils.host_tools import get_tool_by_name @@ -39,7 +39,7 @@ def get_folder_fps(folder_entity=None): """Return current folder fps.""" if folder_entity is None: - folder_entity = get_current_project_folder(fields=["attrib.fps"]) + folder_entity = get_current_folder_entity(fields=["attrib.fps"]) return folder_entity["attrib"]["fps"] @@ -741,7 +741,7 @@ def set_camera_resolution(camera, folder_entity=None): """Apply resolution to camera from folder entity of the publish""" if not folder_entity: - folder_entity = get_current_project_folder() + folder_entity = get_current_folder_entity() resolution = get_resolution_from_folder(folder_entity) diff --git a/client/ayon_core/hosts/houdini/plugins/inventory/set_camera_resolution.py b/client/ayon_core/hosts/houdini/plugins/inventory/set_camera_resolution.py index b813f82e2e..4cebd537bb 100644 --- a/client/ayon_core/hosts/houdini/plugins/inventory/set_camera_resolution.py +++ b/client/ayon_core/hosts/houdini/plugins/inventory/set_camera_resolution.py @@ -3,7 +3,7 @@ from ayon_core.hosts.houdini.api.lib import ( get_camera_from_container, set_camera_resolution ) -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity class SetCameraResolution(InventoryAction): @@ -19,7 +19,7 @@ class SetCameraResolution(InventoryAction): ) def process(self, containers): - folder_entity = get_current_project_folder() + folder_entity = get_current_folder_entity() for container in containers: node = container["node"] camera = get_camera_from_container(node) diff --git a/client/ayon_core/hosts/max/api/lib.py b/client/ayon_core/hosts/max/api/lib.py index 5f13856c9b..48bb15f538 100644 --- a/client/ayon_core/hosts/max/api/lib.py +++ b/client/ayon_core/hosts/max/api/lib.py @@ -11,7 +11,7 @@ import ayon_api from ayon_core.pipeline import get_current_project_name, colorspace from ayon_core.settings import get_project_settings from ayon_core.pipeline.context_tools import ( - get_current_project_folder, + get_current_folder_entity, ) from ayon_core.style import load_stylesheet from pymxs import runtime as rt @@ -222,7 +222,7 @@ def reset_scene_resolution(): contains any information regarding scene resolution. """ - folder_entity = get_current_project_folder( + folder_entity = get_current_folder_entity( fields={"attrib.resolutionWidth", "attrib.resolutionHeight"} ) folder_attributes = folder_entity["attrib"] @@ -243,7 +243,7 @@ def get_frame_range(folder_entiy=None) -> Union[Dict[str, Any], None]: """ # Set frame start/end if folder_entiy is None: - folder_entiy = get_current_project_folder() + folder_entiy = get_current_folder_entity() folder_attributes = folder_entiy["attrib"] frame_start = folder_attributes.get("frameStart") diff --git a/client/ayon_core/hosts/max/api/lib_rendersettings.py b/client/ayon_core/hosts/max/api/lib_rendersettings.py index 8a9881f032..35b6d064c1 100644 --- a/client/ayon_core/hosts/max/api/lib_rendersettings.py +++ b/client/ayon_core/hosts/max/api/lib_rendersettings.py @@ -3,7 +3,7 @@ from pymxs import runtime as rt from ayon_core.lib import Logger from ayon_core.settings import get_project_settings from ayon_core.pipeline import get_current_project_name -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.hosts.max.api.lib import ( set_render_frame_range, @@ -57,7 +57,7 @@ class RenderSettings(object): if not os.path.exists(output_dir): os.makedirs(output_dir) # hard-coded, should be customized in the setting - folder_attributes = get_current_project_folder()["attrib"] + folder_attributes = get_current_folder_entity()["attrib"] # get project resolution width = folder_attributes.get("resolutionWidth") diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 7569e88e4c..8fae6c04dc 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_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.pipeline.create import CreateContext from ayon_core.lib.profiles_filtering import filter_profiles @@ -2631,7 +2631,7 @@ def reset_scene_resolution(): None """ - folder_attributes = get_current_project_folder()["attrib"] + folder_attributes = get_current_folder_entity()["attrib"] # Set resolution width = folder_attributes.get("resolutionWidth", 1920) @@ -3240,7 +3240,7 @@ 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_project_folder() + folder_entity = get_current_folder_entity() folder_attributes = folder_entity["attrib"] new_folder_path = folder_entity["path"] for s in scene_sets: diff --git a/client/ayon_core/hosts/maya/api/lib_rendersettings.py b/client/ayon_core/hosts/maya/api/lib_rendersettings.py index 905e8c69af..f9e243146a 100644 --- a/client/ayon_core/hosts/maya/api/lib_rendersettings.py +++ b/client/ayon_core/hosts/maya/api/lib_rendersettings.py @@ -7,7 +7,7 @@ from ayon_core.lib import Logger from ayon_core.settings import get_project_settings from ayon_core.pipeline import CreatorError, get_current_project_name -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.hosts.maya.api.lib import reset_frame_range @@ -77,7 +77,7 @@ class RenderSettings(object): renderer = cmds.getAttr( 'defaultRenderGlobals.currentRenderer').lower() - folder_entity = get_current_project_folder() + folder_entity = get_current_folder_entity() folder_attributes = folder_entity["attrib"] # project_settings/maya/create/CreateRender/aov_separator try: diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py b/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py index f1c171bddc..998c0b3c78 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py @@ -3,7 +3,7 @@ import maya.cmds as cmds import pyblish.api import ayon_core.hosts.maya.api.lib as mayalib -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.pipeline.publish import ( RepairContextAction, ValidateSceneOrder, @@ -132,5 +132,5 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin, cls.log.info("Setting time unit to match project") # TODO replace query with using 'context.data["folderEntity"]' - folder_entity = get_current_project_folder() + folder_entity = get_current_folder_entity() mayalib.set_scene_fps(folder_entity["attrib"]["fps"]) 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 0f51ac39e0..59b9f66b78 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_animation.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_animation.py @@ -8,7 +8,7 @@ from unreal import EditorAssetLibrary from unreal import MovieSceneSkeletalAnimationTrack from unreal import MovieSceneSkeletalAnimationSection -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.pipeline import ( get_representation_path, AYON_CONTAINER_ID @@ -53,7 +53,7 @@ class AnimationFBXLoader(plugin.Loader): if not actor: return None - folder_entity = get_current_project_folder(fields=["attrib.fps"]) + folder_entity = get_current_folder_entity(fields=["attrib.fps"]) task.set_editor_property('filename', path) task.set_editor_property('destination_path', asset_dir) @@ -256,7 +256,7 @@ class AnimationFBXLoader(plugin.Loader): repre_entity = context["representation"] folder_name = container["asset_name"] source_path = get_representation_path(repre_entity) - folder_entity = get_current_project_folder(fields=["attrib.fps"]) + folder_entity = get_current_folder_entity(fields=["attrib.fps"]) destination_path = container["namespace"] task = unreal.AssetImportTask() 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 6c01925453..b0f09ee8b0 100644 --- a/client/ayon_core/hosts/unreal/plugins/load/load_layout.py +++ b/client/ayon_core/hosts/unreal/plugins/load/load_layout.py @@ -25,7 +25,7 @@ from ayon_core.pipeline import ( AYON_CONTAINER_ID, get_current_project_name, ) -from ayon_core.pipeline.context_tools import get_current_project_folder +from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.settings import get_current_project_settings from ayon_core.hosts.unreal.api import plugin from ayon_core.hosts.unreal.api.pipeline import ( @@ -169,7 +169,7 @@ class LayoutLoader(plugin.Loader): anim_path = f"{asset_dir}/animations/{anim_file_name}" - folder_entity = get_current_project_folder() + folder_entity = get_current_folder_entity() # Import animation task = unreal.AssetImportTask() task.options = unreal.FbxImportUI() diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index ca409fadf2..df4fbb1f77 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -384,37 +384,28 @@ def get_current_project_entity(fields=None): return ayon_api.get_project(project_name, fields=fields) -def get_current_project_folder(folder_path=None, folder_id=None, fields=None): +def get_current_folder_entity(fields=None): """Helper function to get folder entity based on current context. This function should be called only in process where host is installed. - Folder is found out based on passed folder path or id (not both). Folder - path is not used for filtering if folder id is passed. When both - folder path and id are missing then current folder path is used. + Folder is found out based on current project name and folder path. Args: - folder_path (Union[str, None]): Folder path used for filter. - folder_id (Union[str, None]): Folder id. If entered then - is used as only filter. fields (Optional[Iterable[str]]): Limit returned data of folder entity to specific keys. Returns: - Union[dict[str, Any], None]: Fodler entity or None. + Union[dict[str, Any], None]: Folder entity or None. + """ + context = get_current_context() + project_name = context["project_name"] + folder_path = context["folder_path"] - project_name = get_current_project_name() - if folder_id: - return ayon_api.get_folder_by_id( - project_name, folder_id, fields=fields - ) - - if not folder_path: - folder_path = get_current_folder_path() - # Skip if is not set even on context - if not folder_path: - return None + # Skip if is not set even on context + if not project_name or not folder_path: + return None return ayon_api.get_folder_by_path( project_name, folder_path, fields=fields ) From 22e4a54ad289eb3ed8229ea08a6299798c0cea66 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 12:33:22 +0200 Subject: [PATCH 123/363] added 'get_current_task_entity' function --- client/ayon_core/pipeline/context_tools.py | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index df4fbb1f77..7602ee43b0 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -411,6 +411,40 @@ def get_current_folder_entity(fields=None): ) +def get_current_task_entity(fields=None): + """Helper function to get task entity based on current context. + + This function should be called only in process where host is installed. + + Task is found out based on current project name, folder path + and task name. + + Args: + fields (Optional[Iterable[str]]): Limit returned data of task entity + to specific keys. + + Returns: + Union[dict[str, Any], None]: Task entity or None. + + """ + context = get_current_context() + project_name = context["project_name"] + folder_path = context["folder_path"] + task_name = context["task_name"] + + # Skip if is not set even on context + if not project_name or not folder_path or not task_name: + return None + folder_entity = ayon_api.get_folder_by_path( + project_name, folder_path, fields={"id"} + ) + if not folder_entity: + return None + return ayon_api.get_task_by_name( + project_name, folder_entity["id"], task_name, fields=fields + ) + + def is_representation_from_latest(representation): """Return whether the representation is from latest version From 9e3bba4feab7b4a9f78c338249cf080c0fb8bebc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 12:40:27 +0200 Subject: [PATCH 124/363] use project name from context for settings collection --- client/ayon_core/plugins/publish/collect_settings.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_settings.py b/client/ayon_core/plugins/publish/collect_settings.py index 66b89a114c..5b0e17d8c5 100644 --- a/client/ayon_core/plugins/publish/collect_settings.py +++ b/client/ayon_core/plugins/publish/collect_settings.py @@ -1,5 +1,5 @@ from pyblish import api -from ayon_core.settings import get_current_project_settings +from ayon_core.settings import get_project_settings class CollectSettings(api.ContextPlugin): @@ -9,4 +9,7 @@ class CollectSettings(api.ContextPlugin): label = "Collect Settings" def process(self, context): - context.data["project_settings"] = get_current_project_settings() + project_name = context.data["projectName"] + project_settings = get_project_settings(project_name) + context.data["project_settings"] = project_settings + context.data["projectSettings"] = project_settings From 3ddcae8f4ecb89b15d25f4a3fe1cb08fe0875d8d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 12:42:40 +0200 Subject: [PATCH 125/363] added debug log --- client/ayon_core/plugins/publish/collect_settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/plugins/publish/collect_settings.py b/client/ayon_core/plugins/publish/collect_settings.py index 5b0e17d8c5..6628bb1bb0 100644 --- a/client/ayon_core/plugins/publish/collect_settings.py +++ b/client/ayon_core/plugins/publish/collect_settings.py @@ -10,6 +10,9 @@ class CollectSettings(api.ContextPlugin): def process(self, context): project_name = context.data["projectName"] + self.log.debug( + "Collecting settings for project: {}".format(project_name) + ) project_settings = get_project_settings(project_name) context.data["project_settings"] = project_settings context.data["projectSettings"] = project_settings From dd77f243ddd2eaf3ed26ca8ec66d50af6e914451 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 11:42:10 +0200 Subject: [PATCH 126/363] 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 010e1256273df8639da19f527a199c051057e088 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:43:56 +0200 Subject: [PATCH 127/363] remove 'projectSettings' key --- client/ayon_core/plugins/publish/collect_settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_settings.py b/client/ayon_core/plugins/publish/collect_settings.py index 6628bb1bb0..db58e7eaa9 100644 --- a/client/ayon_core/plugins/publish/collect_settings.py +++ b/client/ayon_core/plugins/publish/collect_settings.py @@ -15,4 +15,3 @@ class CollectSettings(api.ContextPlugin): ) project_settings = get_project_settings(project_name) context.data["project_settings"] = project_settings - context.data["projectSettings"] = project_settings From 31e3b1d7be7cfaae3023224ae57b9f80b013b383 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:51:25 +0200 Subject: [PATCH 128/363] Better docstrings Co-authored-by: Roy Nieterau --- client/ayon_core/pipeline/context_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index 7602ee43b0..e9151bcd1f 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -389,7 +389,7 @@ def get_current_folder_entity(fields=None): This function should be called only in process where host is installed. - Folder is found out based on current project name and folder path. + Folder is based on current context project name and folder path. Args: fields (Optional[Iterable[str]]): Limit returned data of folder entity @@ -416,7 +416,7 @@ def get_current_task_entity(fields=None): This function should be called only in process where host is installed. - Task is found out based on current project name, folder path + Task is based on current context project name, folder path and task name. Args: From 9da82b229c00ffd90864942d949afb0688e3faa8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 14:01:11 +0200 Subject: [PATCH 129/363] use folder entity from context data --- .../hosts/maya/plugins/publish/validate_maya_units.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py b/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py index 998c0b3c78..47314b64ac 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_maya_units.py @@ -3,7 +3,6 @@ import maya.cmds as cmds import pyblish.api import ayon_core.hosts.maya.api.lib as mayalib -from ayon_core.pipeline.context_tools import get_current_folder_entity from ayon_core.pipeline.publish import ( RepairContextAction, ValidateSceneOrder, @@ -131,6 +130,5 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin, cls.log.debug(current_linear) cls.log.info("Setting time unit to match project") - # TODO replace query with using 'context.data["folderEntity"]' - folder_entity = get_current_folder_entity() + folder_entity = context.data["folderEntity"] mayalib.set_scene_fps(folder_entity["attrib"]["fps"]) From 72aeb63ef974cfc80bd1ff0d22b1f51448336939 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 14:12:13 +0200 Subject: [PATCH 130/363] 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 131/363] 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 132/363] 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 133/363] 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 134/363] 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 135/363] 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 136/363] 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 5247a30720f914a4a95067cc8221d852fb9e4c87 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 3 Apr 2024 16:22:02 +0200 Subject: [PATCH 137/363] disable application addon if does not have settings available --- .../ayon_core/addons/applications/ayon_applications/addon.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/ayon_core/addons/applications/ayon_applications/addon.py b/client/ayon_core/addons/applications/ayon_applications/addon.py index 1db221fdf6..0f1b68af0e 100644 --- a/client/ayon_core/addons/applications/ayon_applications/addon.py +++ b/client/ayon_core/addons/applications/ayon_applications/addon.py @@ -11,6 +11,10 @@ from .manager import ApplicationManager class ApplicationsAddon(AYONAddon, IPluginPaths): name = "applications" + def initialize(self, settings): + # TODO remove when addon is removed from ayon-core + self.enabled = self.name in settings + def get_app_environments_for_context( self, project_name, From 2803b8521f701291547325053df8de9ff987f8e0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Apr 2024 16:29:52 +0200 Subject: [PATCH 138/363] 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 139/363] 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 140/363] 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 141/363] 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 142/363] 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 143/363] 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 144/363] 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 145/363] 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 146/363] 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 147/363] 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 148/363] 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 149/363] 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 150/363] 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 151/363] 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 152/363] 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 153/363] 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 154/363] 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 155/363] 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 156/363] 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 157/363] 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 158/363] 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 10c0004c80ca5437f37a7729f3acfc31b2344f94 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 15:41:58 +0800 Subject: [PATCH 159/363] delete old versions loader action does not work --- client/ayon_core/pipeline/load/plugins.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 064af4ddc1..29edc73b57 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -220,19 +220,6 @@ class LoaderPlugin(list): """ return cls.options or [] - @property - def fname(self): - """Backwards compatibility with deprecation warning""" - - self.log.warning(( - "DEPRECATION WARNING: Source - Loader plugin {}." - " The 'fname' property on the Loader plugin will be removed in" - " future versions of OpenPype. Planned version to drop the support" - " is 3.16.6 or 3.17.0." - ).format(self.__class__.__name__)) - if hasattr(self, "_fname"): - return self._fname - class ProductLoaderPlugin(LoaderPlugin): """Load product into host application From 6c8b4e33bce15361690d446f0b204040abc42c04 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 16:23:16 +0800 Subject: [PATCH 160/363] should be removing delete_old_versions.py from the load plugins --- client/ayon_core/pipeline/load/plugins.py | 13 + .../plugins/load/delete_old_versions.py | 501 ------------------ 2 files changed, 13 insertions(+), 501 deletions(-) delete mode 100644 client/ayon_core/plugins/load/delete_old_versions.py diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 29edc73b57..064af4ddc1 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -220,6 +220,19 @@ class LoaderPlugin(list): """ return cls.options or [] + @property + def fname(self): + """Backwards compatibility with deprecation warning""" + + self.log.warning(( + "DEPRECATION WARNING: Source - Loader plugin {}." + " The 'fname' property on the Loader plugin will be removed in" + " future versions of OpenPype. Planned version to drop the support" + " is 3.16.6 or 3.17.0." + ).format(self.__class__.__name__)) + if hasattr(self, "_fname"): + return self._fname + class ProductLoaderPlugin(LoaderPlugin): """Load product into host application diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py deleted file mode 100644 index 04873d8b5c..0000000000 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ /dev/null @@ -1,501 +0,0 @@ -# TODO This plugin is not converted for AYON -# -# import collections -# import os -# import uuid -# -# import clique -# import ayon_api -# from pymongo import UpdateOne -# import qargparse -# from qtpy import QtWidgets, QtCore -# -# from ayon_core import style -# from ayon_core.addon import AddonsManager -# from ayon_core.lib import format_file_size -# from ayon_core.pipeline import load, Anatomy -# from ayon_core.pipeline.load import ( -# get_representation_path_with_anatomy, -# InvalidRepresentationContext, -# ) -# -# -# class DeleteOldVersions(load.ProductLoaderPlugin): -# """Deletes specific number of old version""" -# -# is_multiple_contexts_compatible = True -# sequence_splitter = "__sequence_splitter__" -# -# representations = ["*"] -# product_types = {"*"} -# tool_names = ["library_loader"] -# -# label = "Delete Old Versions" -# order = 35 -# icon = "trash" -# color = "#d8d8d8" -# -# options = [ -# qargparse.Integer( -# "versions_to_keep", default=2, min=0, help="Versions to keep:" -# ), -# qargparse.Boolean( -# "remove_publish_folder", help="Remove publish folder:" -# ) -# ] -# -# def delete_whole_dir_paths(self, dir_paths, delete=True): -# size = 0 -# -# for dir_path in dir_paths: -# # Delete all files and fodlers in dir path -# for root, dirs, files in os.walk(dir_path, topdown=False): -# for name in files: -# file_path = os.path.join(root, name) -# size += os.path.getsize(file_path) -# if delete: -# os.remove(file_path) -# self.log.debug("Removed file: {}".format(file_path)) -# -# for name in dirs: -# if delete: -# os.rmdir(os.path.join(root, name)) -# -# if not delete: -# continue -# -# # Delete even the folder and it's parents folders if they are empty -# while True: -# if not os.path.exists(dir_path): -# dir_path = os.path.dirname(dir_path) -# continue -# -# if len(os.listdir(dir_path)) != 0: -# break -# -# os.rmdir(os.path.join(dir_path)) -# -# return size -# -# def path_from_representation(self, representation, anatomy): -# try: -# context = representation["context"] -# except KeyError: -# return (None, None) -# -# try: -# path = get_representation_path_with_anatomy( -# representation, anatomy -# ) -# except InvalidRepresentationContext: -# return (None, None) -# -# sequence_path = None -# if "frame" in context: -# context["frame"] = self.sequence_splitter -# sequence_path = get_representation_path_with_anatomy( -# representation, anatomy -# ) -# -# if sequence_path: -# sequence_path = sequence_path.normalized() -# -# return (path.normalized(), sequence_path) -# -# def delete_only_repre_files(self, dir_paths, file_paths, delete=True): -# size = 0 -# -# for dir_id, dir_path in dir_paths.items(): -# dir_files = os.listdir(dir_path) -# collections, remainders = clique.assemble(dir_files) -# for file_path, seq_path in file_paths[dir_id]: -# file_path_base = os.path.split(file_path)[1] -# # Just remove file if `frame` key was not in context or -# # filled path is in remainders (single file sequence) -# if not seq_path or file_path_base in remainders: -# if not os.path.exists(file_path): -# self.log.debug( -# "File was not found: {}".format(file_path) -# ) -# continue -# -# size += os.path.getsize(file_path) -# -# if delete: -# os.remove(file_path) -# self.log.debug("Removed file: {}".format(file_path)) -# -# if file_path_base in remainders: -# remainders.remove(file_path_base) -# continue -# -# seq_path_base = os.path.split(seq_path)[1] -# head, tail = seq_path_base.split(self.sequence_splitter) -# -# final_col = None -# for collection in collections: -# if head != collection.head or tail != collection.tail: -# continue -# final_col = collection -# break -# -# if final_col is not None: -# # Fill full path to head -# final_col.head = os.path.join(dir_path, final_col.head) -# for _file_path in final_col: -# if os.path.exists(_file_path): -# -# size += os.path.getsize(_file_path) -# -# if delete: -# os.remove(_file_path) -# self.log.debug( -# "Removed file: {}".format(_file_path) -# ) -# -# _seq_path = final_col.format("{head}{padding}{tail}") -# self.log.debug("Removed files: {}".format(_seq_path)) -# collections.remove(final_col) -# -# elif os.path.exists(file_path): -# size += os.path.getsize(file_path) -# -# if delete: -# os.remove(file_path) -# self.log.debug("Removed file: {}".format(file_path)) -# else: -# self.log.debug( -# "File was not found: {}".format(file_path) -# ) -# -# # Delete as much as possible parent folders -# if not delete: -# return size -# -# for dir_path in dir_paths.values(): -# while True: -# if not os.path.exists(dir_path): -# dir_path = os.path.dirname(dir_path) -# continue -# -# if len(os.listdir(dir_path)) != 0: -# break -# -# self.log.debug("Removed folder: {}".format(dir_path)) -# os.rmdir(dir_path) -# -# return size -# -# def message(self, text): -# msgBox = QtWidgets.QMessageBox() -# msgBox.setText(text) -# msgBox.setStyleSheet(style.load_stylesheet()) -# msgBox.setWindowFlags( -# msgBox.windowFlags() | QtCore.Qt.FramelessWindowHint -# ) -# msgBox.exec_() -# -# def get_data(self, context, versions_count): -# product_entity = context["product"] -# folder_entity = context["folder"] -# project_name = context["project"]["name"] -# anatomy = Anatomy(project_name) -# -# versions = list(ayon_api.get_versions( -# project_name, product_ids=[product_entity["id"]] -# )) -# -# versions_by_parent = collections.defaultdict(list) -# for ent in versions: -# versions_by_parent[ent["productId"]].append(ent) -# -# def sort_func(ent): -# return int(ent["version"]) -# -# all_last_versions = [] -# for _parent_id, _versions in versions_by_parent.items(): -# for idx, version in enumerate( -# sorted(_versions, key=sort_func, reverse=True) -# ): -# if idx >= versions_count: -# break -# all_last_versions.append(version) -# -# self.log.debug("Collected versions ({})".format(len(versions))) -# -# # Filter latest versions -# for version in all_last_versions: -# versions.remove(version) -# -# # Update versions_by_parent without filtered versions -# versions_by_parent = collections.defaultdict(list) -# for ent in versions: -# versions_by_parent[ent["productId"]].append(ent) -# -# # Filter already deleted versions -# versions_to_pop = [] -# for version in versions: -# version_tags = version["data"].get("tags") -# if version_tags and "deleted" in version_tags: -# versions_to_pop.append(version) -# -# for version in versions_to_pop: -# msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( -# folder_entity["path"], -# product_entity["name"], -# version["version"] -# ) -# self.log.debug(( -# "Skipping version. Already tagged as `deleted`. < {} >" -# ).format(msg)) -# versions.remove(version) -# -# version_ids = [ent["id"] for ent in versions] -# -# self.log.debug( -# "Filtered versions to delete ({})".format(len(version_ids)) -# ) -# -# if not version_ids: -# msg = "Skipping processing. Nothing to delete on {}/{}".format( -# folder_entity["path"], product_entity["name"] -# ) -# self.log.info(msg) -# print(msg) -# return -# -# repres = list(ayon_api.get_representations( -# project_name, version_ids=version_ids -# )) -# -# self.log.debug( -# "Collected representations to remove ({})".format(len(repres)) -# ) -# -# dir_paths = {} -# file_paths_by_dir = collections.defaultdict(list) -# for repre in repres: -# file_path, seq_path = self.path_from_representation( -# repre, anatomy -# ) -# if file_path is None: -# self.log.debug(( -# "Could not format path for represenation \"{}\"" -# ).format(str(repre))) -# continue -# -# dir_path = os.path.dirname(file_path) -# dir_id = None -# for _dir_id, _dir_path in dir_paths.items(): -# if _dir_path == dir_path: -# dir_id = _dir_id -# break -# -# if dir_id is None: -# dir_id = uuid.uuid4() -# dir_paths[dir_id] = dir_path -# -# file_paths_by_dir[dir_id].append([file_path, seq_path]) -# -# dir_ids_to_pop = [] -# for dir_id, dir_path in dir_paths.items(): -# if os.path.exists(dir_path): -# continue -# -# dir_ids_to_pop.append(dir_id) -# -# # Pop dirs from both dictionaries -# for dir_id in dir_ids_to_pop: -# dir_paths.pop(dir_id) -# paths = file_paths_by_dir.pop(dir_id) -# # TODO report of missing directories? -# paths_msg = ", ".join([ -# "'{}'".format(path[0].replace("\\", "/")) for path in paths -# ]) -# self.log.debug(( -# "Folder does not exist. Deleting it's files skipped: {}" -# ).format(paths_msg)) -# -# return { -# "dir_paths": dir_paths, -# "file_paths_by_dir": file_paths_by_dir, -# "versions": versions, -# "folder": folder_entity, -# "product": product_entity, -# "archive_product": versions_count == 0 -# } -# -# def main(self, project_name, data, remove_publish_folder): -# # Size of files. -# size = 0 -# if not data: -# return size -# -# if remove_publish_folder: -# size = self.delete_whole_dir_paths(data["dir_paths"].values()) -# else: -# size = self.delete_only_repre_files( -# data["dir_paths"], data["file_paths_by_dir"] -# ) -# -# mongo_changes_bulk = [] -# for version in data["versions"]: -# orig_version_tags = version["data"].get("tags") or [] -# version_tags = [tag for tag in orig_version_tags] -# if "deleted" not in version_tags: -# version_tags.append("deleted") -# -# if version_tags == orig_version_tags: -# continue -# -# update_query = {"id": version["id"]} -# update_data = {"$set": {"data.tags": version_tags}} -# mongo_changes_bulk.append(UpdateOne(update_query, update_data)) -# -# if data["archive_product"]: -# mongo_changes_bulk.append(UpdateOne( -# { -# "id": data["product"]["id"], -# "type": "subset" -# }, -# {"$set": {"type": "archived_subset"}} -# )) -# -# if mongo_changes_bulk: -# dbcon = AvalonMongoDB() -# dbcon.Session["AYON_PROJECT_NAME"] = project_name -# dbcon.install() -# dbcon.bulk_write(mongo_changes_bulk) -# dbcon.uninstall() -# -# self._ftrack_delete_versions(data) -# -# return size -# -# def _ftrack_delete_versions(self, data): -# """Delete version on ftrack. -# -# Handling of ftrack logic in this plugin is not ideal. But in OP3 it is -# almost impossible to solve the issue other way. -# -# Note: -# Asset versions on ftrack are not deleted but marked as -# "not published" which cause that they're invisible. -# -# Args: -# data (dict): Data sent to product loader with full context. -# """ -# -# # First check for ftrack id on folder entity -# # - skip if ther is none -# ftrack_id = data["folder"]["attrib"].get("ftrackId") -# if not ftrack_id: -# self.log.info(( -# "Folder does not have filled ftrack id. Skipped delete" -# " of ftrack version." -# )) -# return -# -# # Check if ftrack module is enabled -# addons_manager = AddonsManager() -# ftrack_addon = addons_manager.get("ftrack") -# if not ftrack_addon or not ftrack_addon.enabled: -# return -# -# import ftrack_api -# -# session = ftrack_api.Session() -# product_name = data["product"]["name"] -# versions = { -# '"{}"'.format(version_doc["name"]) -# for version_doc in data["versions"] -# } -# asset_versions = session.query( -# ( -# "select id, is_published from AssetVersion where" -# " asset.parent.id is \"{}\"" -# " and asset.name is \"{}\"" -# " and version in ({})" -# ).format( -# ftrack_id, -# product_name, -# ",".join(versions) -# ) -# ).all() -# -# # Set attribute `is_published` to `False` on ftrack AssetVersions -# for asset_version in asset_versions: -# asset_version["is_published"] = False -# -# try: -# session.commit() -# -# except Exception: -# msg = ( -# "Could not set `is_published` attribute to `False`" -# " for selected AssetVersions." -# ) -# self.log.error(msg) -# self.message(msg) -# -# def load(self, contexts, name=None, namespace=None, options=None): -# try: -# size = 0 -# for count, context in enumerate(contexts): -# versions_to_keep = 2 -# remove_publish_folder = False -# if options: -# versions_to_keep = options.get( -# "versions_to_keep", versions_to_keep -# ) -# remove_publish_folder = options.get( -# "remove_publish_folder", remove_publish_folder -# ) -# -# data = self.get_data(context, versions_to_keep) -# if not data: -# continue -# -# project_name = context["project"]["name"] -# size += self.main(project_name, data, remove_publish_folder) -# print("Progressing {}/{}".format(count + 1, len(contexts))) -# -# msg = "Total size of files: {}".format(format_file_size(size)) -# self.log.info(msg) -# self.message(msg) -# -# except Exception: -# self.log.error("Failed to delete versions.", exc_info=True) -# -# -# class CalculateOldVersions(DeleteOldVersions): -# """Calculate file size of old versions""" -# label = "Calculate Old Versions" -# order = 30 -# tool_names = ["library_loader"] -# -# options = [ -# qargparse.Integer( -# "versions_to_keep", default=2, min=0, help="Versions to keep:" -# ), -# qargparse.Boolean( -# "remove_publish_folder", help="Remove publish folder:" -# ) -# ] -# -# def main(self, project_name, data, remove_publish_folder): -# size = 0 -# -# if not data: -# return size -# -# if remove_publish_folder: -# size = self.delete_whole_dir_paths( -# data["dir_paths"].values(), delete=False -# ) -# else: -# size = self.delete_only_repre_files( -# data["dir_paths"], data["file_paths_by_dir"], delete=False -# ) -# -# return size From ccc3c1710acda86eeaf4840a88384f127c55f549 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Apr 2024 11:01:48 +0200 Subject: [PATCH 161/363] 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 162/363] 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 be694cba5619f8cb7ac66cb378435614f52a98d8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 4 Apr 2024 17:43:24 +0800 Subject: [PATCH 163/363] remove the old versions loader action should be working --- .../plugins/load/delete_old_versions.py | 478 ++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 client/ayon_core/plugins/load/delete_old_versions.py diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py new file mode 100644 index 0000000000..4f591a503a --- /dev/null +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -0,0 +1,478 @@ +import collections +import os +import uuid + +import clique +import ayon_api +import qargparse +from qtpy import QtWidgets, QtCore + +from ayon_core import style +from ayon_core.addon import AddonsManager +from ayon_core.lib import format_file_size +from ayon_core.pipeline import load, Anatomy +from ayon_core.pipeline.load import ( + get_representation_path_with_anatomy, + InvalidRepresentationContext, +) + + +class DeleteOldVersions(load.ProductLoaderPlugin): + """Deletes specific number of old version""" + + is_multiple_contexts_compatible = True + sequence_splitter = "__sequence_splitter__" + + representations = ["*"] + product_types = {"*"} + tool_names = ["library_loader"] + + label = "Delete Old Versions" + order = 35 + icon = "trash" + color = "#d8d8d8" + + options = [ + qargparse.Integer( + "versions_to_keep", default=2, min=0, help="Versions to keep:" + ), + qargparse.Boolean( + "remove_publish_folder", help="Remove publish folder:" + ) + ] + + def delete_whole_dir_paths(self, dir_paths, delete=True): + size = 0 + + for dir_path in dir_paths: + # Delete all files and fodlers in dir path + for root, dirs, files in os.walk(dir_path, topdown=False): + for name in files: + file_path = os.path.join(root, name) + size += os.path.getsize(file_path) + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) + + for name in dirs: + if delete: + os.rmdir(os.path.join(root, name)) + + if not delete: + continue + + # Delete even the folder and it's parents folders if they are empty + while True: + if not os.path.exists(dir_path): + dir_path = os.path.dirname(dir_path) + continue + + if len(os.listdir(dir_path)) != 0: + break + + os.rmdir(os.path.join(dir_path)) + + return size + + def path_from_representation(self, representation, anatomy): + try: + context = representation["context"] + except KeyError: + return (None, None) + + try: + path = get_representation_path_with_anatomy( + representation, anatomy + ) + except InvalidRepresentationContext: + return (None, None) + + sequence_path = None + if "frame" in context: + context["frame"] = self.sequence_splitter + sequence_path = get_representation_path_with_anatomy( + representation, anatomy + ) + + if sequence_path: + sequence_path = sequence_path.normalized() + + return (path.normalized(), sequence_path) + + def delete_only_repre_files(self, dir_paths, file_paths, delete=True): + size = 0 + + for dir_id, dir_path in dir_paths.items(): + dir_files = os.listdir(dir_path) + collections, remainders = clique.assemble(dir_files) + for file_path, seq_path in file_paths[dir_id]: + file_path_base = os.path.split(file_path)[1] + # Just remove file if `frame` key was not in context or + # filled path is in remainders (single file sequence) + if not seq_path or file_path_base in remainders: + if not os.path.exists(file_path): + self.log.debug( + "File was not found: {}".format(file_path) + ) + continue + + size += os.path.getsize(file_path) + + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) + + if file_path_base in remainders: + remainders.remove(file_path_base) + continue + + seq_path_base = os.path.split(seq_path)[1] + head, tail = seq_path_base.split(self.sequence_splitter) + + final_col = None + for collection in collections: + if head != collection.head or tail != collection.tail: + continue + final_col = collection + break + + if final_col is not None: + # Fill full path to head + final_col.head = os.path.join(dir_path, final_col.head) + for _file_path in final_col: + if os.path.exists(_file_path): + + size += os.path.getsize(_file_path) + + if delete: + os.remove(_file_path) + self.log.debug( + "Removed file: {}".format(_file_path) + ) + + _seq_path = final_col.format("{head}{padding}{tail}") + self.log.debug("Removed files: {}".format(_seq_path)) + collections.remove(final_col) + + elif os.path.exists(file_path): + size += os.path.getsize(file_path) + + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) + else: + self.log.debug( + "File was not found: {}".format(file_path) + ) + + # Delete as much as possible parent folders + if not delete: + return size + + for dir_path in dir_paths.values(): + while True: + if not os.path.exists(dir_path): + dir_path = os.path.dirname(dir_path) + continue + + if len(os.listdir(dir_path)) != 0: + break + + self.log.debug("Removed folder: {}".format(dir_path)) + os.rmdir(dir_path) + + return size + + def message(self, text): + msgBox = QtWidgets.QMessageBox() + msgBox.setText(text) + msgBox.setStyleSheet(style.load_stylesheet()) + msgBox.setWindowFlags( + msgBox.windowFlags() | QtCore.Qt.FramelessWindowHint + ) + msgBox.exec_() + + def get_data(self, context, versions_count): + product_entity = context["product"] + folder_entity = context["folder"] + project_name = context["project"]["name"] + anatomy = Anatomy(project_name) + + versions = list(ayon_api.get_versions( + project_name, product_ids=[product_entity["id"]] + )) + self.log.debug( + "Version Number ({})".format(len(versions)) + ) + versions_by_parent = collections.defaultdict(list) + for ent in versions: + versions_by_parent[ent["productId"]].append(ent) + + def sort_func(ent): + return int(ent["version"]) + + all_last_versions = [] + for _parent_id, _versions in versions_by_parent.items(): + for idx, version in enumerate( + sorted(_versions, key=sort_func, reverse=True) + ): + if idx >= versions_count: + break + all_last_versions.append(version) + + self.log.debug("Collected versions ({})".format(len(versions))) + + # Filter latest versions + for version in all_last_versions: + versions.remove(version) + + # Update versions_by_parent without filtered versions + versions_by_parent = collections.defaultdict(list) + for ent in versions: + versions_by_parent[ent["productId"]].append(ent) + + # Filter already deleted versions + versions_to_pop = [] + for version in versions: + version_tags = version["data"].get("tags") + if version_tags and "deleted" in version_tags: + versions_to_pop.append(version) + + for version in versions_to_pop: + msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( + folder_entity["path"], + product_entity["name"], + version["version"] + ) + self.log.debug(( + "Skipping version. Already tagged as `deleted`. < {} >" + ).format(msg)) + versions.remove(version) + + version_ids = [ent["id"] for ent in versions] + + self.log.debug( + "Filtered versions to delete ({})".format(len(version_ids)) + ) + + if not version_ids: + msg = "Skipping processing. Nothing to delete on {}/{}".format( + folder_entity["path"], product_entity["name"] + ) + self.log.info(msg) + print(msg) + return + + repres = list(ayon_api.get_representations( + project_name, version_ids=version_ids + )) + + self.log.debug( + "Collected representations to remove ({})".format(len(repres)) + ) + + dir_paths = {} + file_paths_by_dir = collections.defaultdict(list) + for repre in repres: + file_path, seq_path = self.path_from_representation( + repre, anatomy + ) + if file_path is None: + self.log.debug(( + "Could not format path for represenation \"{}\"" + ).format(str(repre))) + continue + + dir_path = os.path.dirname(file_path) + dir_id = None + for _dir_id, _dir_path in dir_paths.items(): + if _dir_path == dir_path: + dir_id = _dir_id + break + + if dir_id is None: + dir_id = uuid.uuid4() + dir_paths[dir_id] = dir_path + + file_paths_by_dir[dir_id].append([file_path, seq_path]) + + dir_ids_to_pop = [] + for dir_id, dir_path in dir_paths.items(): + if os.path.exists(dir_path): + continue + + dir_ids_to_pop.append(dir_id) + + # Pop dirs from both dictionaries + for dir_id in dir_ids_to_pop: + dir_paths.pop(dir_id) + paths = file_paths_by_dir.pop(dir_id) + # TODO report of missing directories? + paths_msg = ", ".join([ + "'{}'".format(path[0].replace("\\", "/")) for path in paths + ]) + self.log.debug(( + "Folder does not exist. Deleting it's files skipped: {}" + ).format(paths_msg)) + + return { + "dir_paths": dir_paths, + "file_paths_by_dir": file_paths_by_dir, + "versions": versions, + "folder": folder_entity, + "product": product_entity, + "archive_product": versions_count == 0 + } + + def main(self, data, remove_publish_folder): + # Size of files. + size = 0 + if not data: + return size + + if remove_publish_folder: + size = self.delete_whole_dir_paths(data["dir_paths"].values()) + else: + size = self.delete_only_repre_files( + data["dir_paths"], data["file_paths_by_dir"] + ) + + for version in data["versions"]: + orig_version_tags = version["data"].get("tags") or [] + version_tags = [tag for tag in orig_version_tags] + if "deleted" not in version_tags: + version_tags.append("deleted") + + if version_tags == orig_version_tags: + continue + + self._ftrack_delete_versions(data) + + return size + + def _ftrack_delete_versions(self, data): + """Delete version on ftrack. + + Handling of ftrack logic in this plugin is not ideal. But in OP3 it is + almost impossible to solve the issue other way. + + Note: + Asset versions on ftrack are not deleted but marked as + "not published" which cause that they're invisible. + + Args: + data (dict): Data sent to product loader with full context. + """ + + # First check for ftrack id on folder entity + # - skip if ther is none + ftrack_id = data["folder"]["attrib"].get("ftrackId") + if not ftrack_id: + self.log.info(( + "Folder does not have filled ftrack id. Skipped delete" + " of ftrack version." + )) + return + + # Check if ftrack module is enabled + addons_manager = AddonsManager() + ftrack_addon = addons_manager.get("ftrack") + if not ftrack_addon or not ftrack_addon.enabled: + return + + import ftrack_api + + session = ftrack_api.Session() + product_name = data["product"]["name"] + versions = { + '"{}"'.format(version_doc["name"]) + for version_doc in data["versions"] + } + asset_versions = session.query( + ( + "select id, is_published from AssetVersion where" + " asset.parent.id is \"{}\"" + " and asset.name is \"{}\"" + " and version in ({})" + ).format( + ftrack_id, + product_name, + ",".join(versions) + ) + ).all() + + # Set attribute `is_published` to `False` on ftrack AssetVersions + for asset_version in asset_versions: + asset_version["is_published"] = False + + try: + session.commit() + + except Exception: + msg = ( + "Could not set `is_published` attribute to `False`" + " for selected AssetVersions." + ) + self.log.error(msg) + self.message(msg) + + def load(self, contexts, name=None, namespace=None, options=None): + try: + size = 0 + for count, context in enumerate(contexts): + versions_to_keep = 2 + remove_publish_folder = False + if options: + versions_to_keep = options.get( + "versions_to_keep", versions_to_keep + ) + remove_publish_folder = options.get( + "remove_publish_folder", remove_publish_folder + ) + + data = self.get_data(context, versions_to_keep) + if not data: + continue + + size += self.main(data, remove_publish_folder) + print("Progressing {}/{}".format(count + 1, len(contexts))) + + msg = "Total size of files: {}".format(format_file_size(size)) + self.log.info(msg) + self.message(msg) + + except Exception: + self.log.error("Failed to delete versions.", exc_info=True) + + +class CalculateOldVersions(DeleteOldVersions): + """Calculate file size of old versions""" + label = "Calculate Old Versions" + order = 30 + tool_names = ["library_loader"] + + options = [ + qargparse.Integer( + "versions_to_keep", default=2, min=0, help="Versions to keep:" + ), + qargparse.Boolean( + "remove_publish_folder", help="Remove publish folder:" + ) + ] + + def main(self, data, remove_publish_folder): + size = 0 + + if not data: + return size + + if remove_publish_folder: + size = self.delete_whole_dir_paths( + data["dir_paths"].values(), delete=False + ) + else: + size = self.delete_only_repre_files( + data["dir_paths"], data["file_paths_by_dir"], delete=False + ) + + return size From e1b22cbe8d6cf56ee8949fd54abf1b78b6bd0a22 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 4 Apr 2024 11:44:17 +0200 Subject: [PATCH 164/363] 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 165/363] 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 166/363] 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 167/363] 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 168/363] 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 169/363] 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 170/363] 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 171/363] 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 172/363] 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 173/363] 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 174/363] 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 175/363] 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 176/363] 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 177/363] 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 178/363] 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 179/363] 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 180/363] 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 181/363] 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 182/363] 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 183/363] 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 184/363] 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 185/363] 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 186/363] 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 187/363] 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 914367dba226b7424ef9f149e1faf68e44b82a25 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:54:01 +0800 Subject: [PATCH 188/363] Update client/ayon_core/plugins/load/delete_old_versions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 4f591a503a..79958af447 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -196,7 +196,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): product_entity = context["product"] folder_entity = context["folder"] project_name = context["project"]["name"] - anatomy = Anatomy(project_name) + anatomy = Anatomy(project_name, project_entity=context["project"]) versions = list(ayon_api.get_versions( project_name, product_ids=[product_entity["id"]] From 03e7a85e204248012271a995ec33d3d11b528a62 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:54:12 +0800 Subject: [PATCH 189/363] Update client/ayon_core/plugins/load/delete_old_versions.py Co-authored-by: Roy Nieterau --- client/ayon_core/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 79958af447..28e76b73d6 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -312,7 +312,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): "'{}'".format(path[0].replace("\\", "/")) for path in paths ]) self.log.debug(( - "Folder does not exist. Deleting it's files skipped: {}" + "Folder does not exist. Deleting its files skipped: {}" ).format(paths_msg)) return { From f6480281b40f8132446e09dea337e1786dee9efe Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 Apr 2024 17:40:06 +0800 Subject: [PATCH 190/363] remove ftrack-related functions --- .../plugins/load/delete_old_versions.py | 69 ------------------- 1 file changed, 69 deletions(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 28e76b73d6..4e2747e1dc 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -8,7 +8,6 @@ import qargparse from qtpy import QtWidgets, QtCore from ayon_core import style -from ayon_core.addon import AddonsManager from ayon_core.lib import format_file_size from ayon_core.pipeline import load, Anatomy from ayon_core.pipeline.load import ( @@ -346,76 +345,8 @@ class DeleteOldVersions(load.ProductLoaderPlugin): if version_tags == orig_version_tags: continue - self._ftrack_delete_versions(data) - return size - def _ftrack_delete_versions(self, data): - """Delete version on ftrack. - - Handling of ftrack logic in this plugin is not ideal. But in OP3 it is - almost impossible to solve the issue other way. - - Note: - Asset versions on ftrack are not deleted but marked as - "not published" which cause that they're invisible. - - Args: - data (dict): Data sent to product loader with full context. - """ - - # First check for ftrack id on folder entity - # - skip if ther is none - ftrack_id = data["folder"]["attrib"].get("ftrackId") - if not ftrack_id: - self.log.info(( - "Folder does not have filled ftrack id. Skipped delete" - " of ftrack version." - )) - return - - # Check if ftrack module is enabled - addons_manager = AddonsManager() - ftrack_addon = addons_manager.get("ftrack") - if not ftrack_addon or not ftrack_addon.enabled: - return - - import ftrack_api - - session = ftrack_api.Session() - product_name = data["product"]["name"] - versions = { - '"{}"'.format(version_doc["name"]) - for version_doc in data["versions"] - } - asset_versions = session.query( - ( - "select id, is_published from AssetVersion where" - " asset.parent.id is \"{}\"" - " and asset.name is \"{}\"" - " and version in ({})" - ).format( - ftrack_id, - product_name, - ",".join(versions) - ) - ).all() - - # Set attribute `is_published` to `False` on ftrack AssetVersions - for asset_version in asset_versions: - asset_version["is_published"] = False - - try: - session.commit() - - except Exception: - msg = ( - "Could not set `is_published` attribute to `False`" - " for selected AssetVersions." - ) - self.log.error(msg) - self.message(msg) - def load(self, contexts, name=None, namespace=None, options=None): try: size = 0 From 738f0924b5a92d3346f18ddae2e7959df8172423 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 9 Apr 2024 12:27:56 +0200 Subject: [PATCH 191/363] 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 043b528155450ca81dba41ca8d7a49667f6f282f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 Apr 2024 18:43:59 +0800 Subject: [PATCH 192/363] uses OperationsSessions from ayon_api to delete version version_tags --- .../plugins/load/delete_old_versions.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 4e2747e1dc..ee116a71cf 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -4,6 +4,7 @@ import uuid import clique import ayon_api +from ayon_api.operations import OperationsSession import qargparse from qtpy import QtWidgets, QtCore @@ -231,20 +232,16 @@ class DeleteOldVersions(load.ProductLoaderPlugin): versions_by_parent[ent["productId"]].append(ent) # Filter already deleted versions - versions_to_pop = [] for version in versions: - version_tags = version["data"].get("tags") - if version_tags and "deleted" in version_tags: - versions_to_pop.append(version) - - for version in versions_to_pop: + if version["active"] or "deleted" in version["tags"]: + continue msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( folder_entity["path"], product_entity["name"], version["version"] ) self.log.debug(( - "Skipping version. Already tagged as `deleted`. < {} >" + "Skipping version. Already tagged as inactive. < {} >" ).format(msg)) versions.remove(version) @@ -323,7 +320,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): "archive_product": versions_count == 0 } - def main(self, data, remove_publish_folder): + def main(self, project_name, data, remove_publish_folder): # Size of files. size = 0 if not data: @@ -336,14 +333,25 @@ class DeleteOldVersions(load.ProductLoaderPlugin): data["dir_paths"], data["file_paths_by_dir"] ) + op_session = OperationsSession() for version in data["versions"]: - orig_version_tags = version["data"].get("tags") or [] - version_tags = [tag for tag in orig_version_tags] + orig_version_tags = version["tags"] + version_tags = list(orig_version_tags) + changes = {} if "deleted" not in version_tags: version_tags.append("deleted") + changes["tags"] = version_tags - if version_tags == orig_version_tags: + if version["active"]: + changes["active"] = False + + if not changes: continue + op_session.update_entity( + project_name, "version", version["id"], changes + ) + + op_session.commit() return size @@ -364,8 +372,8 @@ class DeleteOldVersions(load.ProductLoaderPlugin): data = self.get_data(context, versions_to_keep) if not data: continue - - size += self.main(data, remove_publish_folder) + project_name = context["project"]["name"] + size += self.main(project_name, data, remove_publish_folder) print("Progressing {}/{}".format(count + 1, len(contexts))) msg = "Total size of files: {}".format(format_file_size(size)) From c8f4f3681ac0290b13debe1283bdf1e47426d3c1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 Apr 2024 18:45:23 +0800 Subject: [PATCH 193/363] make sure to skip hero version --- client/ayon_core/plugins/load/delete_old_versions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index ee116a71cf..bfbccce33d 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -199,7 +199,10 @@ class DeleteOldVersions(load.ProductLoaderPlugin): anatomy = Anatomy(project_name, project_entity=context["project"]) versions = list(ayon_api.get_versions( - project_name, product_ids=[product_entity["id"]] + project_name, + product_ids=[product_entity["id"]], + active=None, + hero=False )) self.log.debug( "Version Number ({})".format(len(versions)) From cf5d7d9a7d12c65f723cfbd044d9d68c01023a72 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:52:34 +0800 Subject: [PATCH 194/363] Update client/ayon_core/plugins/load/delete_old_versions.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/load/delete_old_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index bfbccce33d..78603104d8 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -236,7 +236,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): # Filter already deleted versions for version in versions: - if version["active"] or "deleted" in version["tags"]: + if "deleted" in version["tags"]: continue msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( folder_entity["path"], From c193e2e6144c259ee7dbcefaebfe9800c2a0bd3b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Apr 2024 13:19:12 +0200 Subject: [PATCH 195/363] 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 196/363] 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 197/363] 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 198/363] 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 199/363] 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 200/363] 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 201/363] 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 202/363] 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 203/363] 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 204/363] 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 205/363] 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 206/363] 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 207/363] 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 208/363] 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 209/363] 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 210/363] 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 211/363] 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 212/363] 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 213/363] 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 214/363] 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 215/363] 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 216/363] 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 217/363] 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 218/363] 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 219/363] 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 220/363] 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 221/363] 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 222/363] 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 223/363] 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 224/363] 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 225/363] 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 226/363] 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 227/363] 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 228/363] 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 229/363] 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 230/363] 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 231/363] 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 232/363] 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 233/363] 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 364dee88f1a99e3410b6cc0d31566ceb25485b67 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Wed, 10 Apr 2024 22:03:37 +0800 Subject: [PATCH 234/363] add the version tags if there is not one --- client/ayon_core/plugins/load/delete_old_versions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 78603104d8..fd331ec14b 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -198,11 +198,14 @@ class DeleteOldVersions(load.ProductLoaderPlugin): project_name = context["project"]["name"] anatomy = Anatomy(project_name, project_entity=context["project"]) + version_fields = ayon_api.get_default_fields_for_type("version") + version_fields.add("tags") versions = list(ayon_api.get_versions( project_name, product_ids=[product_entity["id"]], active=None, - hero=False + hero=False, + fields=version_fields )) self.log.debug( "Version Number ({})".format(len(versions)) 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 235/363] 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 236/363] 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 237/363] 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 c5ccf8a3904dd9a2dbe5c14538044e39fe89dece Mon Sep 17 00:00:00 2001 From: moonyuet Date: Wed, 10 Apr 2024 23:24:28 +0800 Subject: [PATCH 238/363] add texture resolution setting when loading mesh to set up project in substance painter --- .../plugins/load/load_mesh.py | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) 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..07b53fb85c 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -2,6 +2,7 @@ from ayon_core.pipeline import ( load, get_representation_path, ) +from ayon_core.lib import BoolDef, EnumDef from ayon_core.pipeline.load import LoadError from ayon_core.hosts.substancepainter.api.pipeline import ( imprint_container, @@ -11,7 +12,6 @@ from ayon_core.hosts.substancepainter.api.pipeline import ( from ayon_core.hosts.substancepainter.api.lib import prompt_new_file_with_mesh import substance_painter.project -import qargparse class SubstanceLoadProjectMesh(load.LoaderPlugin): @@ -25,26 +25,35 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): icon = "code-fork" color = "orange" - options = [ - qargparse.Boolean( - "preserve_strokes", - default=True, - help="Preserve strokes positions on mesh.\n" - "(only relevant when loading into existing project)" - ), - qargparse.Boolean( - "import_cameras", - default=True, - help="Import cameras from the mesh file." - ) - ] + @classmethod + def get_options(cls, contexts): + return [ + BoolDef("preserve_strokes", + default=True, + label="Preserve Strokes", + tooltip=("Preserve strokes positions on mesh.\n" + "(only relevant when loading into " + "existing project)")), + BoolDef("import_cameras", + default=True, + label="Import Cameras", + tooltip="Import cameras from the mesh file." + ), + EnumDef("texture_resolution", + items=[128, 256, 512, 1024, 2048, 4096], + default=1024, + label="Texture Resolution", + tooltip="Set texture resolution for the project") + ] - def load(self, context, name, namespace, data): + def load(self, context, name, namespace, options=None): # Get user inputs - import_cameras = data.get("import_cameras", True) - preserve_strokes = data.get("preserve_strokes", True) + import_cameras = options.get("import_cameras", True) + preserve_strokes = options.get("preserve_strokes", True) + texture_resolution = options.get("texture_resolution", 1024) sp_settings = substance_painter.project.Settings( + default_texture_resolution=texture_resolution, import_cameras=import_cameras ) if not substance_painter.project.is_open(): 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 239/363] 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 240/363] 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 241/363] 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 242/363] 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 243/363] 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 244/363] 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 245/363] 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 6758a2a7c57ceb4f9d8e707506321830a478b7ae Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Apr 2024 18:17:36 +0800 Subject: [PATCH 246/363] add the boolean options to allow user to set the project setting by their own --- client/ayon_core/hosts/substancepainter/api/lib.py | 6 +++--- .../hosts/substancepainter/plugins/load/load_mesh.py | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index 1cb480b552..95d45da436 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -549,7 +549,7 @@ def _get_new_project_action(): return new_action -def prompt_new_file_with_mesh(mesh_filepath): +def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): """Prompts the user for a new file using Substance Painter's own dialog. This will set the mesh path to load to the given mesh and disables the @@ -586,7 +586,6 @@ def prompt_new_file_with_mesh(mesh_filepath): # TODO: find a way to improve the process event to # load more complicated mesh app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 3000) - file_dialog.done(file_dialog.Accepted) app.processEvents(QtCore.QEventLoop.AllEvents) @@ -624,7 +623,8 @@ def prompt_new_file_with_mesh(mesh_filepath): f"{mesh_filepath}\n\n" "Creating new project directly with the mesh path instead.") else: - dialog.done(dialog.Accepted) + if not allow_user_setting: + dialog.done(dialog.Accepted) new_action = _get_new_project_action() if not new_action: 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 07b53fb85c..8809f3b5bf 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -28,6 +28,11 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): @classmethod def get_options(cls, contexts): return [ + BoolDef("allow_user_setting", + default=True, + label="Allow User Setting", + tooltip=("Allow user to set up the project" + " by their own\n")), BoolDef("preserve_strokes", default=True, label="Preserve Strokes", @@ -49,6 +54,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs + allow_user_setting = options.get("allow_user_setting", True) import_cameras = options.get("import_cameras", True) preserve_strokes = options.get("preserve_strokes", True) texture_resolution = options.get("texture_resolution", 1024) @@ -61,7 +67,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): path = self.filepath_from_context(context) # TODO: improve the prompt dialog function to not # only works for simple polygon scene - result = prompt_new_file_with_mesh(mesh_filepath=path) + result = prompt_new_file_with_mesh( + mesh_filepath=path, allow_user_setting=allow_user_setting) if not result: self.log.info("User cancelled new project prompt." "Creating new project directly from" From be4d65c3d234826ab56d5b5276f35fde55e87d90 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 Apr 2024 18:26:31 +0800 Subject: [PATCH 247/363] cosmetic fix --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8809f3b5bf..b355400233 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -48,7 +48,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): items=[128, 256, 512, 1024, 2048, 4096], default=1024, label="Texture Resolution", - tooltip="Set texture resolution for the project") + tooltip="Set texture resolution when creating new project") ] def load(self, context, name, namespace, options=None): From f9a0b7a1ec62f42d9a96ed2524671e792195849f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Apr 2024 12:58:47 +0200 Subject: [PATCH 248/363] 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 249/363] 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 250/363] 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 251/363] 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 252/363] 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 253/363] 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 254/363] 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 255/363] 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 256/363] 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 257/363] 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 258/363] 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 259/363] 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 260/363] 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 261/363] 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 262/363] 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 263/363] 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 264/363] 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 265/363] 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 266/363] 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 267/363] 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 268/363] 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 269/363] 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 270/363] 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 1229311d679283e2d8b93e0881b3d85126955da8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 00:20:05 +0800 Subject: [PATCH 271/363] commit to test on the triggering different file format and file size --- client/ayon_core/hosts/substancepainter/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index 95d45da436..b1aed5d4f3 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -577,7 +577,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): assert isinstance(file_dialog, QtWidgets.QFileDialog) # Quickly hide the dialog - file_dialog.hide() + # file_dialog.hide() app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 1000) file_dialog.setDirectory(os.path.dirname(mesh_filepath)) @@ -586,7 +586,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): # TODO: find a way to improve the process event to # load more complicated mesh app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 3000) - file_dialog.done(file_dialog.Accepted) + # file_dialog.done(file_dialog.Accepted) app.processEvents(QtCore.QEventLoop.AllEvents) def _setup_prompt(): From 5adfd62e9a025bbced343c121ef9982160a95887 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 11 Apr 2024 21:20:53 +0100 Subject: [PATCH 272/363] 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 ede44ea0fba00303dd56986e5b6ab7920182bc08 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 17:00:02 +0800 Subject: [PATCH 273/363] specific the time for the process event --- client/ayon_core/hosts/substancepainter/api/lib.py | 11 ++++------- .../hosts/substancepainter/plugins/load/load_mesh.py | 3 +-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index b1aed5d4f3..64c39943ce 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -549,7 +549,7 @@ def _get_new_project_action(): return new_action -def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): +def prompt_new_file_with_mesh(mesh_filepath): """Prompts the user for a new file using Substance Painter's own dialog. This will set the mesh path to load to the given mesh and disables the @@ -577,7 +577,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): assert isinstance(file_dialog, QtWidgets.QFileDialog) # Quickly hide the dialog - # file_dialog.hide() + file_dialog.hide() app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 1000) file_dialog.setDirectory(os.path.dirname(mesh_filepath)) @@ -586,7 +586,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): # TODO: find a way to improve the process event to # load more complicated mesh app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 3000) - # file_dialog.done(file_dialog.Accepted) + file_dialog.done(file_dialog.Accepted) app.processEvents(QtCore.QEventLoop.AllEvents) def _setup_prompt(): @@ -605,7 +605,7 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): mesh_select.setVisible(False) # Ensure UI is visually up-to-date - app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents) + app.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents, 8000) # Trigger the 'select file' dialog to set the path and have the # new file dialog to use the path. @@ -622,9 +622,6 @@ def prompt_new_file_with_mesh(mesh_filepath, allow_user_setting=True): "Failed to set mesh path with the prompt dialog:" f"{mesh_filepath}\n\n" "Creating new project directly with the mesh path instead.") - else: - if not allow_user_setting: - dialog.done(dialog.Accepted) new_action = _get_new_project_action() if not new_action: 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 6a521493db..89dbcdbddd 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -67,8 +67,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): path = self.filepath_from_context(context) # TODO: improve the prompt dialog function to not # only works for simple polygon scene - result = prompt_new_file_with_mesh( - mesh_filepath=path, allow_user_setting=allow_user_setting) + result = prompt_new_file_with_mesh(mesh_filepath=path) if not result: self.log.info("User cancelled new project prompt." "Creating new project directly from" From dada34d6f4bb6b263298dfd5a4c91fd73f47b31c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 17:00:35 +0800 Subject: [PATCH 274/363] remove allow user settings --- .../hosts/substancepainter/plugins/load/load_mesh.py | 6 ------ 1 file changed, 6 deletions(-) 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 89dbcdbddd..ad81309957 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -28,11 +28,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): @classmethod def get_options(cls, contexts): return [ - BoolDef("allow_user_setting", - default=True, - label="Allow User Setting", - tooltip=("Allow user to set up the project" - " by their own\n")), BoolDef("preserve_strokes", default=True, label="Preserve Strokes", @@ -54,7 +49,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs - allow_user_setting = options.get("allow_user_setting", True) import_cameras = options.get("import_cameras", True) preserve_strokes = options.get("preserve_strokes", True) texture_resolution = options.get("texture_resolution", 1024) From 74bc0102c829bfad88bbad6d40f8dee6da972447 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 12 Apr 2024 18:23:33 +0800 Subject: [PATCH 275/363] add supports for udim settings in mesh loader --- .../substancepainter/plugins/load/load_mesh.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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 ad81309957..562ccc1f80 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -27,6 +27,11 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): @classmethod def get_options(cls, contexts): + project_workflow_option = { + substance_painter.project.ProjectWorkflow.Default: "default", + substance_painter.project.ProjectWorkflow.UVTile: "uvTile", + substance_painter.project.ProjectWorkflow.TextureSetPerUVTile: "textureSetPerUVTile" # noqa + } return [ BoolDef("preserve_strokes", default=True, @@ -43,7 +48,12 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): items=[128, 256, 512, 1024, 2048, 4096], default=1024, label="Texture Resolution", - tooltip="Set texture resolution when creating new project") + tooltip="Set texture resolution when creating new project"), + EnumDef("project_uv_workflow", + items=project_workflow_option, + default="default", + label="UV Workflow", + tooltip="Set UV workflow when creating new project") ] def load(self, context, name, namespace, options=None): @@ -52,9 +62,11 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): import_cameras = options.get("import_cameras", True) preserve_strokes = options.get("preserve_strokes", True) texture_resolution = options.get("texture_resolution", 1024) + uv_workflow = options.get("project_uv_workflow", "default") sp_settings = substance_painter.project.Settings( default_texture_resolution=texture_resolution, - import_cameras=import_cameras + import_cameras=import_cameras, + project_workflow=uv_workflow ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project From 0a2a1105df3f1ced13e460bc9c4a46e6e6d7a5aa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Apr 2024 14:45:57 +0200 Subject: [PATCH 276/363] 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 277/363] 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 ef71dad1d35f025627645e2c63033fd51c0be861 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 15 Apr 2024 17:37:43 +0800 Subject: [PATCH 278/363] rename project_uv_workflow_items --- .../hosts/substancepainter/plugins/load/load_mesh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 562ccc1f80..0816a67b6a 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -27,7 +27,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): @classmethod def get_options(cls, contexts): - project_workflow_option = { + project_uv_workflow_items = { substance_painter.project.ProjectWorkflow.Default: "default", substance_painter.project.ProjectWorkflow.UVTile: "uvTile", substance_painter.project.ProjectWorkflow.TextureSetPerUVTile: "textureSetPerUVTile" # noqa @@ -50,7 +50,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): label="Texture Resolution", tooltip="Set texture resolution when creating new project"), EnumDef("project_uv_workflow", - items=project_workflow_option, + items=project_uv_workflow_items, default="default", label="UV Workflow", tooltip="Set UV workflow when creating new project") From f2a9eedbda9fca088629a0fa95be4771b6365680 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 15 Apr 2024 21:27:40 +0800 Subject: [PATCH 279/363] fix the default settings not being able to be cased as python instance --- .../plugins/load/load_mesh.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) 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 0816a67b6a..03f47eb451 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -14,6 +14,15 @@ from ayon_core.hosts.substancepainter.api.lib import prompt_new_file_with_mesh import substance_painter.project +def get_uv_workflow(uv_option="default"): + if uv_option == "default": + return substance_painter.project.ProjectWorkflow.Default + elif uv_option == "uvTile": + return substance_painter.project.ProjectWorkflow.UVTile + else: + return substance_painter.project.ProjectWorkflow.TextureSetPerUVTile + + class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -50,7 +59,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): label="Texture Resolution", tooltip="Set texture resolution when creating new project"), EnumDef("project_uv_workflow", - items=project_uv_workflow_items, + items=["default", "uvTile", "textureSetPerUVTile"], default="default", label="UV Workflow", tooltip="Set UV workflow when creating new project") @@ -62,7 +71,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): import_cameras = options.get("import_cameras", True) preserve_strokes = options.get("preserve_strokes", True) texture_resolution = options.get("texture_resolution", 1024) - uv_workflow = options.get("project_uv_workflow", "default") + uv_option = options.get("project_uv_workflow", "default") + uv_workflow = get_uv_workflow(uv_option=uv_option) sp_settings = substance_painter.project.Settings( default_texture_resolution=texture_resolution, import_cameras=import_cameras, @@ -75,12 +85,16 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # only works for simple polygon scene result = prompt_new_file_with_mesh(mesh_filepath=path) if not result: - self.log.info("User cancelled new project prompt." - "Creating new project directly from" - " Substance Painter API Instead.") - settings = substance_painter.project.create( - mesh_file_path=path, settings=sp_settings - ) + if not substance_painter.project.is_open(): + self.log.info("User cancelled new project prompt." + "Creating new project directly from" + " Substance Painter API Instead.") + settings = substance_painter.project.create( + mesh_file_path=path, settings=sp_settings + ) + else: + self.log.info("The project is already created after " + "the new project prompt action") else: # Reload the mesh From 028375620fe7a7c44955e4ec6c025f4cf9ed13a0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:02:56 +0200 Subject: [PATCH 280/363] filter representations by id earlier --- .../workfile/workfile_template_builder.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 5e63ba444a..20783dd1ca 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1587,14 +1587,14 @@ class PlaceholderLoadMixin(object): placeholder_representations = self._get_representations(placeholder) - filtered_representations = [] - for representation in self._reduce_last_version_repre_entities( - placeholder_representations - ): - repre_id = representation["id"] - if repre_id not in ignore_repre_ids: - filtered_representations.append(representation) - + filtered_representations = [ + repre_entity + for repre_entity in self._get_representations(placeholder) + if repre_entity["id"] not in ignore_repre_ids + ] + filtered_representations = self._reduce_last_version_repre_entities( + filtered_representations + ) if not filtered_representations: self.log.info(( "There's no representation for this placeholder: {}" From f3ab54493d3f2c276680fe4aca6c931800093d03 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:03:31 +0200 Subject: [PATCH 281/363] get and use repre contexts earlier --- .../workfile/workfile_template_builder.py | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 20783dd1ca..ae54b732f3 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1529,35 +1529,24 @@ class PlaceholderLoadMixin(object): pass - def _reduce_last_version_repre_entities(self, representations): + def _reduce_last_version_repre_entities(self, repre_contexts): """Reduce representations to last verison.""" mapping = {} - # TODO use representation context with entities - # - using 'folder', 'subset' and 'version' from context on - # representation is danger - for repre_entity in representations: - repre_context = repre_entity["context"] + for repre_context in repre_contexts: + folder_id = repre_context["folder"]["id"] + product_id = repre_context["product"]["id"] + version = repre_context["version"]["version"] - folder_name = repre_context["asset"] - product_name = repre_context["subset"] - version = repre_context.get("version", -1) + parents_path = "/".join([folder_id, product_id]) + version_mapping = mapping.setdefault(parents_path, {}) - if folder_name not in mapping: - mapping[folder_name] = {} - - product_mapping = mapping[folder_name] - if product_name not in product_mapping: - product_mapping[product_name] = collections.defaultdict(list) - - version_mapping = product_mapping[product_name] - version_mapping[version].append(repre_entity) + version_mapping[version].append(repre_context) output = [] - for product_mapping in mapping.values(): - for version_mapping in product_mapping.values(): - last_version = tuple(sorted(version_mapping.keys()))[-1] - output.extend(version_mapping[last_version]) + for version_mapping in mapping.values(): + last_version = tuple(sorted(version_mapping.keys()))[-1] + output.extend(version_mapping[last_version]) return output def populate_load_placeholder(self, placeholder, ignore_repre_ids=None): @@ -1585,32 +1574,31 @@ class PlaceholderLoadMixin(object): loader_name = placeholder.data["loader"] loader_args = self.parse_loader_args(placeholder.data["loader_args"]) - placeholder_representations = self._get_representations(placeholder) - - filtered_representations = [ + placeholder_representations = [ repre_entity for repre_entity in self._get_representations(placeholder) if repre_entity["id"] not in ignore_repre_ids ] - filtered_representations = self._reduce_last_version_repre_entities( - filtered_representations + + repre_load_contexts = get_representation_contexts( + self.project_name, placeholder_representations ) - if not filtered_representations: + filtered_repre_contexts = self._reduce_last_version_repre_entities( + repre_load_contexts + ) + if not filtered_repre_contexts: self.log.info(( "There's no representation for this placeholder: {}" ).format(placeholder.scene_identifier)) return - repre_load_contexts = get_representation_contexts( - self.project_name, filtered_representations - ) loaders_by_name = self.builder.get_loaders_by_name() self._before_placeholder_load( placeholder ) failed = False - for repre_load_context in repre_load_contexts.values(): + for repre_load_context in filtered_repre_contexts: folder_path = repre_load_context["folder"]["path"] product_name = repre_load_context["product"]["name"] representation = repre_load_context["representation"] From b1e73835bbe9868b0dbf30ed5ce8fcb17c4646c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:25:26 +0200 Subject: [PATCH 282/363] skip folder id in mapping --- .../pipeline/workfile/workfile_template_builder.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index ae54b732f3..3e4d47f195 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1532,19 +1532,17 @@ class PlaceholderLoadMixin(object): def _reduce_last_version_repre_entities(self, repre_contexts): """Reduce representations to last verison.""" - mapping = {} + version_mapping_by_product_id = {} for repre_context in repre_contexts: - folder_id = repre_context["folder"]["id"] product_id = repre_context["product"]["id"] version = repre_context["version"]["version"] - - parents_path = "/".join([folder_id, product_id]) - version_mapping = mapping.setdefault(parents_path, {}) - + version_mapping = version_mapping_by_product_id.setdefault( + product_id, {} + ) version_mapping[version].append(repre_context) output = [] - for version_mapping in mapping.values(): + for version_mapping in version_mapping_by_product_id.values(): last_version = tuple(sorted(version_mapping.keys()))[-1] output.extend(version_mapping[last_version]) return output From 78c73259ef4d724b72a7c442c3dcda7c3752a6d4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:30:10 +0200 Subject: [PATCH 283/363] fix typo Co-authored-by: Roy Nieterau --- client/ayon_core/pipeline/workfile/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 3e4d47f195..c3b1267466 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1530,7 +1530,7 @@ class PlaceholderLoadMixin(object): pass def _reduce_last_version_repre_entities(self, repre_contexts): - """Reduce representations to last verison.""" + """Reduce representations to last version.""" version_mapping_by_product_id = {} for repre_context in repre_contexts: From 64a26e21874681102e73cd434de95699f6ac6b09 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 17:40:18 +0800 Subject: [PATCH 284/363] update server-addon settings for template --- .../substancepainter/server/settings/main.py | 79 ++++++++++++++++++- .../substancepainter/server/version.py | 2 +- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/server_addon/substancepainter/server/settings/main.py b/server_addon/substancepainter/server/settings/main.py index f80fa9fe1e..20cf6d77b2 100644 --- a/server_addon/substancepainter/server/settings/main.py +++ b/server_addon/substancepainter/server/settings/main.py @@ -2,6 +2,78 @@ from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import ImageIOSettings, DEFAULT_IMAGEIO_SETTINGS +def normal_map_format_enum(): + return [ + {"label": "DirectX", "value": "DirectX"}, + {"label": "OpenGL", "value": "OpenGL"}, + ] + + +def tangent_space_enum(): + return [ + {"label": "PerFragment", "value": "PerFragment"}, + {"label": "PerVertex", "value": "PerVertex"}, + ] + + +def uv_workflow_enum(): + return [ + {"label": "Default", "value": "default"}, + {"label": "UV Tile", "value": "uvTile"}, + {"label": "Texture Set Per UV Tile", + "value": "textureSetPerUVTile"} + ] + + +def document_resolution_enum(): + return [ + {"label": "128", "value": 128}, + {"label": "256", "value": 256}, + {"label": "512", "value": 512}, + {"label": "1024", "value": 1024}, + {"label": "2048", "value": 2048}, + {"label": "4096", "value": 4096} + ] + + +class ProjectTemplatesModel(BaseSettingsModel): + _layout = "expanded" + name: str = SettingsField(title="Template Name") + document_resolution: int = SettingsField( + 1024, enum_resolver=document_resolution_enum, + title="Document Resolution", + description=("Set texture resolution when " + "creating new project.") + ) + normal_map_format: str = SettingsField( + "DirectX", enum_resolver=normal_map_format_enum, + title="Normal Map Format", + description=("Set normal map format when " + "creating new project.") + ) + tangent_space: str = SettingsField( + "PerFragment", enum_resolver=tangent_space_enum, + title="Tangent Space", + description=("An option to compute tangent space " + "when creating new project.") + ) + uv_workflow: str = SettingsField( + "default", enum_resolver=uv_workflow_enum, + title="UV Tile Settings", + description=("Set UV workflow when " + "creating new project.") + ) + import_cameras: bool = SettingsField( + True, title="Import Cameras", + description="Import cameras from the mesh file.") + preserve_strokes: bool = SettingsField( + True, title="Preserve Strokes", + description=("Preserve strokes positions on mesh.\n" + "(only relevant when loading into " + "existing project)") + ) + + class ShelvesSettingsModel(BaseSettingsModel): _layout = "compact" name: str = SettingsField(title="Name") @@ -17,9 +89,14 @@ class SubstancePainterSettings(BaseSettingsModel): default_factory=list, title="Shelves" ) + project_templates: list[ProjectTemplatesModel] = SettingsField( + default_factory=ProjectTemplatesModel, + title="Project Templates" + ) DEFAULT_SPAINTER_SETTINGS = { "imageio": DEFAULT_IMAGEIO_SETTINGS, - "shelves": [] + "shelves": [], + "project_templates": [], } diff --git a/server_addon/substancepainter/server/version.py b/server_addon/substancepainter/server/version.py index 3dc1f76bc6..485f44ac21 100644 --- a/server_addon/substancepainter/server/version.py +++ b/server_addon/substancepainter/server/version.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.1" From a3f9651de1a8825c0827d731b1467c9f01fb3e2a Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Tue, 16 Apr 2024 11:56:19 +0200 Subject: [PATCH 285/363] use ext.lower() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- .../hosts/houdini/plugins/publish/extract_composite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/extract_composite.py b/client/ayon_core/hosts/houdini/plugins/publish/extract_composite.py index fe88adc120..0fab69ef4a 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/extract_composite.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/extract_composite.py @@ -46,7 +46,7 @@ class ExtractComposite(publish.Extractor, "frameEnd": instance.data["frameEndHandle"], } - if ext == "exr": + if ext.lower() == "exr": # Inject colorspace with 'scene_linear' as that's the # default Houdini working colorspace and all extracted # OpenEXR images should be in that colorspace. From 00926cf9e95d19eef828344d8eff3e5c59ab532c Mon Sep 17 00:00:00 2001 From: Mustafa Taher Date: Tue, 16 Apr 2024 11:58:50 +0200 Subject: [PATCH 286/363] update import Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../hosts/houdini/hooks/set_default_display_and_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py b/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py index c9f79c74b9..2e97c06bff 100644 --- a/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py +++ b/client/ayon_core/hosts/houdini/hooks/set_default_display_and_view.py @@ -1,4 +1,4 @@ -from ayon_core.lib.applications import PreLaunchHook, LaunchTypes +from ayon_applications import PreLaunchHook, LaunchTypes class SetDefaultDisplayView(PreLaunchHook): From b53281f4905115d18419279ef6a6846c3df95b6f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 12:45:34 +0200 Subject: [PATCH 287/363] update import --- client/ayon_core/hosts/houdini/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/houdini/api/lib.py b/client/ayon_core/hosts/houdini/api/lib.py index 3da81b2b51..9cac2ab210 100644 --- a/client/ayon_core/hosts/houdini/api/lib.py +++ b/client/ayon_core/hosts/houdini/api/lib.py @@ -839,7 +839,7 @@ def set_review_color_space(opengl_node, review_color_space="", log=None): # fall to default review color space if the setting is empty. if not review_color_space: - from openpype.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa review_color_space = get_default_display_view_colorspace() opengl_node.setParms( 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 288/363] 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 e8e1a1a2dffd197e61101ccc910c9d3f69c01a1f Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Tue, 16 Apr 2024 14:09:21 +0200 Subject: [PATCH 289/363] add missing logic of set_review_color_space --- client/ayon_core/hosts/houdini/api/lib.py | 7 +- .../houdini/plugins/create/create_review.py | 43 ++++----- .../publish/validate_review_colorspace.py | 95 +++++++++++++------ 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/client/ayon_core/hosts/houdini/api/lib.py b/client/ayon_core/hosts/houdini/api/lib.py index 9cac2ab210..7ca8f7f8f0 100644 --- a/client/ayon_core/hosts/houdini/api/lib.py +++ b/client/ayon_core/hosts/houdini/api/lib.py @@ -837,16 +837,11 @@ def set_review_color_space(opengl_node, review_color_space="", log=None): " 'OpenColorIO'".format(opengl_node.path()) ) - # fall to default review color space if the setting is empty. - if not review_color_space: - from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - review_color_space = get_default_display_view_colorspace() - opengl_node.setParms( {"ociocolorspace": review_color_space} ) - self.log.debug( + log.debug( "'OCIO Colorspace' parm on '{}' has been set to " "the view color space '{}'" .format(opengl_node, review_color_space) diff --git a/client/ayon_core/hosts/houdini/plugins/create/create_review.py b/client/ayon_core/hosts/houdini/plugins/create/create_review.py index 18f7ce498d..94dcf23181 100644 --- a/client/ayon_core/hosts/houdini/plugins/create/create_review.py +++ b/client/ayon_core/hosts/houdini/plugins/create/create_review.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating openGL reviews.""" -from ayon_core.hosts.houdini.api import plugin +from ayon_core.hosts.houdini.api import lib, plugin from ayon_core.lib import EnumDef, BoolDef, NumberDef import os @@ -14,6 +14,13 @@ class CreateReview(plugin.HoudiniCreator): label = "Review" product_type = "review" icon = "video-camera" + review_color_space = "" + + def apply_settings(self, project_settings): + super(CreateReview, self).apply_settings(project_settings) + color_settings = project_settings["houdini"]["imageio"]["workfile"] + if color_settings["enabled"]: + self.review_color_space = color_settings.get("review_color_space") def create(self, product_name, instance_data, pre_create_data): @@ -85,10 +92,20 @@ class CreateReview(plugin.HoudiniCreator): instance_node.setParms(parms) - # Set OCIO Colorspace to the default output colorspace + # Set OCIO Colorspace to the default colorspace # if there's OCIO if os.getenv("OCIO"): - self.set_colorcorrect_to_default_view_space(instance_node) + # Fall to the default value if cls.review_color_space is empty. + if not self.review_color_space: + # cls.review_color_space is an empty string + # when the imageio/workfile setting is disabled or + # when the Review colorspace setting is empty. + from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + self.review_color_space = get_default_display_view_colorspace() + + lib.set_review_color_space(instance_node, + self.review_color_space, + self.log) to_lock = ["id", "productType"] @@ -131,23 +148,3 @@ class CreateReview(plugin.HoudiniCreator): minimum=0.0001, decimals=3) ] - - def set_colorcorrect_to_default_view_space(self, - instance_node): - """Set ociocolorspace to the default output space.""" - from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - - # set Color Correction parameter to OpenColorIO - instance_node.setParms({"colorcorrect": 2}) - - # Get default view space for ociocolorspace parm. - default_view_space = get_default_display_view_colorspace() - instance_node.setParms( - {"ociocolorspace": default_view_space} - ) - - self.log.debug( - "'OCIO Colorspace' parm on '{}' has been set to " - "the default view color space '{}'" - .format(instance_node, default_view_space) - ) diff --git a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py index 031138e21d..d3afa83b67 100644 --- a/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py +++ b/client/ayon_core/hosts/houdini/plugins/publish/validate_review_colorspace.py @@ -4,15 +4,19 @@ from ayon_core.pipeline import ( PublishValidationError, OptionalPyblishPluginMixin ) -from ayon_core.pipeline.publish import RepairAction +from ayon_core.pipeline.publish import ( + RepairAction, + get_plugin_settings, + apply_plugin_settings_automatically +) from ayon_core.hosts.houdini.api.action import SelectROPAction import os import hou -class SetDefaultViewSpaceAction(RepairAction): - label = "Set default view colorspace" +class ResetViewSpaceAction(RepairAction): + label = "Reset OCIO colorspace parm" icon = "mdi.monitor" @@ -27,9 +31,25 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, families = ["review"] hosts = ["houdini"] label = "Validate Review Colorspace" - actions = [SetDefaultViewSpaceAction, SelectROPAction] + actions = [ResetViewSpaceAction, SelectROPAction] optional = True + review_color_space = "" + + @classmethod + def apply_settings(cls, project_settings): + # Preserve automatic settings applying logic + settings = get_plugin_settings(plugin=cls, + project_settings=project_settings, + log=cls.log, + category="houdini") + apply_plugin_settings_automatically(cls, settings, logger=cls.log) + + # Add review color settings + color_settings = project_settings["houdini"]["imageio"]["workfile"] + if color_settings["enabled"]: + cls.review_color_space = color_settings.get("review_color_space") + def process(self, instance): @@ -52,39 +72,54 @@ class ValidateReviewColorspace(pyblish.api.InstancePlugin, " 'OpenColorIO'".format(rop_node.path()) ) - if rop_node.evalParm("ociocolorspace") not in \ - hou.Color.ocio_spaces(): - + current_color_space = rop_node.evalParm("ociocolorspace") + if current_color_space not in hou.Color.ocio_spaces(): raise PublishValidationError( "Invalid value: Colorspace name doesn't exist.\n" "Check 'OCIO Colorspace' parameter on '{}' ROP" .format(rop_node.path()) ) - @classmethod - def repair(cls, instance): - """Set Default View Space Action. + # if houdini/imageio/workfile is enabled and + # Review colorspace setting is empty then this check should + # actually check if the current_color_space setting equals + # the default colorspace value. + # However, it will make the black cmd screen show up more often + # which is very annoying. + if self.review_color_space and \ + self.review_color_space != current_color_space: - It is a helper action more than a repair action, - used to set colorspace on opengl node to the default view. - """ - from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa - - rop_node = hou.node(instance.data["instance_node"]) - - if rop_node.evalParm("colorcorrect") != 2: - rop_node.setParms({"colorcorrect": 2}) - cls.log.debug( - "'Color Correction' parm on '{}' has been set to" - " 'OpenColorIO'".format(rop_node.path()) + raise PublishValidationError( + "Invalid value: Colorspace name doesn't match" + "the Colorspace specified in settings." ) - # Get default view colorspace name - default_view_space = get_default_display_view_colorspace() + @classmethod + def repair(cls, instance): + """Reset view colorspace. - rop_node.setParms({"ociocolorspace": default_view_space}) - cls.log.info( - "'OCIO Colorspace' parm on '{}' has been set to " - "the default view color space '{}'" - .format(rop_node, default_view_space) - ) + It is used to set colorspace on opengl node. + + It uses the colorspace value specified in the Houdini addon settings. + If the value in the Houdini addon settings is empty, + it will fall to the default colorspace. + + Note: + This repair action assumes that OCIO is enabled. + As if OCIO is disabled the whole validation is skipped + and this repair action won't show up. + """ + from ayon_core.hosts.houdini.api.lib import set_review_color_space + + # Fall to the default value if cls.review_color_space is empty. + if not cls.review_color_space: + # cls.review_color_space is an empty string + # when the imageio/workfile setting is disabled or + # when the Review colorspace setting is empty. + from ayon_core.hosts.houdini.api.colorspace import get_default_display_view_colorspace # noqa + cls.review_color_space = get_default_display_view_colorspace() + + rop_node = hou.node(instance.data["instance_node"]) + set_review_color_space(rop_node, + cls.review_color_space, + cls.log) From c1cfd266f61add3a45cd0c0f4b5bb772c6044f82 Mon Sep 17 00:00:00 2001 From: Libor Batek Date: Tue, 16 Apr 2024 16:51:54 +0200 Subject: [PATCH 290/363] 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 e5eec7f558e20e138fa532c3a881c27fa89d39d7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 22:53:09 +0800 Subject: [PATCH 291/363] update the settings and code tweaks for creating project --- .../hosts/substancepainter/api/lib.py | 36 +++++++ .../plugins/load/load_mesh.py | 79 ++++---------- .../server/settings/load_plugins.py | 102 ++++++++++++++++++ .../substancepainter/server/settings/main.py | 81 +------------- 4 files changed, 160 insertions(+), 138 deletions(-) create mode 100644 server_addon/substancepainter/server/settings/load_plugins.py diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index 64c39943ce..e344076222 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -640,3 +640,39 @@ def prompt_new_file_with_mesh(mesh_filepath): return return project_mesh + + +def convert_substance_object_to_python(subst_proj_option="default"): + if subst_proj_option == "default": + return substance_painter.project.ProjectWorkflow.Default + elif subst_proj_option == "uvTile": + return substance_painter.project.ProjectWorkflow.UVTile + elif subst_proj_option == "textureSetPerUVTile": + return substance_painter.project.ProjectWorkflow.TextureSetPerUVTile + elif subst_proj_option == "PerFragment": + return substance_painter.project.TangentSpace.PerFragment + elif subst_proj_option == "PerVertex": + return substance_painter.project.TangentSpace.PerVertex + elif subst_proj_option == "DirectX": + return substance_painter.project.NormalMapFormat.DirectX + elif subst_proj_option == "OpenGL": + return substance_painter.project.NormalMapFormat.OpenGL + else: + raise ValueError( + f"Unsupported Substance Objects: {subst_proj_option}") + + +def parse_substance_attributes_setting(template_name, project_templates): + attributes_data = {} + for template in project_templates: + if template["name"] == template_name: + attributes_data.update(template) + attributes_data["normal_map_format"] = convert_substance_object_to_python( + subst_proj_option=attributes_data["normal_map_format"]) + attributes_data["project_workflow"] = convert_substance_object_to_python( + subst_proj_option=attributes_data["project_workflow"]) + attributes_data["tangent_space_mode"] = convert_substance_object_to_python( + subst_proj_option=attributes_data["tangent_space_mode"]) + attributes_data.pop("name") + attributes_data.pop("preserve_strokes") + return attributes_data 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 03f47eb451..563d6eb6e1 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -9,20 +9,14 @@ from ayon_core.hosts.substancepainter.api.pipeline import ( set_container_metadata, remove_container_metadata ) -from ayon_core.hosts.substancepainter.api.lib import prompt_new_file_with_mesh +from ayon_core.hosts.substancepainter.api.lib import ( + prompt_new_file_with_mesh, + parse_substance_attributes_setting +) import substance_painter.project -def get_uv_workflow(uv_option="default"): - if uv_option == "default": - return substance_painter.project.ProjectWorkflow.Default - elif uv_option == "uvTile": - return substance_painter.project.ProjectWorkflow.UVTile - else: - return substance_painter.project.ProjectWorkflow.TextureSetPerUVTile - - class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -33,74 +27,37 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): order = -10 icon = "code-fork" color = "orange" + project_templates = [] @classmethod def get_options(cls, contexts): - project_uv_workflow_items = { - substance_painter.project.ProjectWorkflow.Default: "default", - substance_painter.project.ProjectWorkflow.UVTile: "uvTile", - substance_painter.project.ProjectWorkflow.TextureSetPerUVTile: "textureSetPerUVTile" # noqa - } + template_enum = [template["name"] for template in cls.project_templates] return [ - BoolDef("preserve_strokes", - default=True, - label="Preserve Strokes", - tooltip=("Preserve strokes positions on mesh.\n" - "(only relevant when loading into " - "existing project)")), - BoolDef("import_cameras", - default=True, - label="Import Cameras", - tooltip="Import cameras from the mesh file." - ), - EnumDef("texture_resolution", - items=[128, 256, 512, 1024, 2048, 4096], - default=1024, - label="Texture Resolution", - tooltip="Set texture resolution when creating new project"), - EnumDef("project_uv_workflow", - items=["default", "uvTile", "textureSetPerUVTile"], + EnumDef("project_template", + items=template_enum, default="default", - label="UV Workflow", - tooltip="Set UV workflow when creating new project") + label="Project Template") ] def load(self, context, name, namespace, options=None): # Get user inputs - import_cameras = options.get("import_cameras", True) - preserve_strokes = options.get("preserve_strokes", True) - texture_resolution = options.get("texture_resolution", 1024) - uv_option = options.get("project_uv_workflow", "default") - uv_workflow = get_uv_workflow(uv_option=uv_option) - sp_settings = substance_painter.project.Settings( - default_texture_resolution=texture_resolution, - import_cameras=import_cameras, - project_workflow=uv_workflow - ) + template_name = options.get("project_template", "default") + template_settings = parse_substance_attributes_setting(template_name, self.project_templates) + sp_settings = substance_painter.project.Settings(**template_settings) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) - # TODO: improve the prompt dialog function to not - # only works for simple polygon scene - result = prompt_new_file_with_mesh(mesh_filepath=path) - if not result: - if not substance_painter.project.is_open(): - self.log.info("User cancelled new project prompt." - "Creating new project directly from" - " Substance Painter API Instead.") - settings = substance_painter.project.create( - mesh_file_path=path, settings=sp_settings - ) - else: - self.log.info("The project is already created after " - "the new project prompt action") + settings = substance_painter.project.create( + mesh_file_path=path, settings=sp_settings + ) else: # Reload the mesh + # TODO: fix the hardcoded when the preset setting in SP addon. settings = substance_painter.project.MeshReloadingSettings( - import_cameras=import_cameras, - preserve_strokes=preserve_strokes + import_cameras=True, + preserve_strokes=True ) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py new file mode 100644 index 0000000000..4d3e64f0b6 --- /dev/null +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -0,0 +1,102 @@ +from ayon_server.settings import BaseSettingsModel, SettingsField + + +def normal_map_format_enum(): + return [ + {"label": "DirectX", "value": "DirectX"}, + {"label": "OpenGL", "value": "OpenGL"}, + ] + + +def tangent_space_enum(): + return [ + {"label": "PerFragment", "value": "PerFragment"}, + {"label": "PerVertex", "value": "PerVertex"}, + ] + + +def uv_workflow_enum(): + return [ + {"label": "Default", "value": "default"}, + {"label": "UV Tile", "value": "uvTile"}, + {"label": "Texture Set Per UV Tile", + "value": "textureSetPerUVTile"} + ] + + +def document_resolution_enum(): + return [ + {"label": "128", "value": 128}, + {"label": "256", "value": 256}, + {"label": "512", "value": 512}, + {"label": "1024", "value": 1024}, + {"label": "2048", "value": 2048}, + {"label": "4096", "value": 4096} + ] + + +class ProjectTemplatesModel(BaseSettingsModel): + _layout = "expanded" + name: str = SettingsField("default", title="Template Name") + default_texture_resolution: int = SettingsField( + 1024, enum_resolver=document_resolution_enum, + title="Document Resolution", + description=("Set texture resolution when " + "creating new project.") + ) + import_cameras: bool = SettingsField( + True, title="Import Cameras", + description="Import cameras from the mesh file.") + normal_map_format: str = SettingsField( + "DirectX", enum_resolver=normal_map_format_enum, + title="Normal Map Format", + description=("Set normal map format when " + "creating new project.") + ) + project_workflow: str = SettingsField( + "default", enum_resolver=uv_workflow_enum, + title="UV Tile Settings", + description=("Set UV workflow when " + "creating new project.") + ) + tangent_space_mode: str = SettingsField( + "PerFragment", enum_resolver=tangent_space_enum, + title="Tangent Space", + description=("An option to compute tangent space " + "when creating new project.") + ) + preserve_strokes: bool = SettingsField( + True, title="Preserve Strokes", + description=("Preserve strokes positions on mesh.\n" + "(only relevant when loading into " + "existing project)") + ) + + +class ProjectTemplateSettingModel(BaseSettingsModel): + project_templates: list[ProjectTemplatesModel] = SettingsField( + default_factory=ProjectTemplatesModel, + title="Project Templates" +) + + +class LoadersModel(BaseSettingsModel): + SubstanceLoadProjectMesh: ProjectTemplateSettingModel = SettingsField( + default_factory=ProjectTemplateSettingModel, + title="Load Mesh" + ) + + +DEFAULT_LOADER_SETTINGS = { + "SubstanceLoadProjectMesh":{ + "project_templates": [{ + "name": "default", + "default_texture_resolution": 1024, + "import_cameras": True, + "normal_map_format": "DirectX", + "project_workflow": "default", + "tangent_space_mode": "PerFragment", + "preserve_strokes": True + }] + } +} diff --git a/server_addon/substancepainter/server/settings/main.py b/server_addon/substancepainter/server/settings/main.py index 20cf6d77b2..93523fd650 100644 --- a/server_addon/substancepainter/server/settings/main.py +++ b/server_addon/substancepainter/server/settings/main.py @@ -1,77 +1,6 @@ from ayon_server.settings import BaseSettingsModel, SettingsField from .imageio import ImageIOSettings, DEFAULT_IMAGEIO_SETTINGS - - -def normal_map_format_enum(): - return [ - {"label": "DirectX", "value": "DirectX"}, - {"label": "OpenGL", "value": "OpenGL"}, - ] - - -def tangent_space_enum(): - return [ - {"label": "PerFragment", "value": "PerFragment"}, - {"label": "PerVertex", "value": "PerVertex"}, - ] - - -def uv_workflow_enum(): - return [ - {"label": "Default", "value": "default"}, - {"label": "UV Tile", "value": "uvTile"}, - {"label": "Texture Set Per UV Tile", - "value": "textureSetPerUVTile"} - ] - - -def document_resolution_enum(): - return [ - {"label": "128", "value": 128}, - {"label": "256", "value": 256}, - {"label": "512", "value": 512}, - {"label": "1024", "value": 1024}, - {"label": "2048", "value": 2048}, - {"label": "4096", "value": 4096} - ] - - -class ProjectTemplatesModel(BaseSettingsModel): - _layout = "expanded" - name: str = SettingsField(title="Template Name") - document_resolution: int = SettingsField( - 1024, enum_resolver=document_resolution_enum, - title="Document Resolution", - description=("Set texture resolution when " - "creating new project.") - ) - normal_map_format: str = SettingsField( - "DirectX", enum_resolver=normal_map_format_enum, - title="Normal Map Format", - description=("Set normal map format when " - "creating new project.") - ) - tangent_space: str = SettingsField( - "PerFragment", enum_resolver=tangent_space_enum, - title="Tangent Space", - description=("An option to compute tangent space " - "when creating new project.") - ) - uv_workflow: str = SettingsField( - "default", enum_resolver=uv_workflow_enum, - title="UV Tile Settings", - description=("Set UV workflow when " - "creating new project.") - ) - import_cameras: bool = SettingsField( - True, title="Import Cameras", - description="Import cameras from the mesh file.") - preserve_strokes: bool = SettingsField( - True, title="Preserve Strokes", - description=("Preserve strokes positions on mesh.\n" - "(only relevant when loading into " - "existing project)") - ) +from .load_plugins import LoadersModel, DEFAULT_LOADER_SETTINGS class ShelvesSettingsModel(BaseSettingsModel): @@ -89,14 +18,12 @@ class SubstancePainterSettings(BaseSettingsModel): default_factory=list, title="Shelves" ) - project_templates: list[ProjectTemplatesModel] = SettingsField( - default_factory=ProjectTemplatesModel, - title="Project Templates" - ) + load: LoadersModel = SettingsField( + default_factory=DEFAULT_LOADER_SETTINGS, title="Loaders") DEFAULT_SPAINTER_SETTINGS = { "imageio": DEFAULT_IMAGEIO_SETTINGS, "shelves": [], - "project_templates": [], + "load": DEFAULT_LOADER_SETTINGS, } From 9aea94bf1fcfa90f8bc539e138ee8e75b42f8380 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 22:55:29 +0800 Subject: [PATCH 292/363] add todo to remember to fix the hard code --- .../hosts/substancepainter/plugins/load/load_mesh.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 563d6eb6e1..631af88eb5 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -2,17 +2,15 @@ from ayon_core.pipeline import ( load, get_representation_path, ) -from ayon_core.lib import BoolDef, EnumDef +from ayon_core.lib import EnumDef from ayon_core.pipeline.load import LoadError from ayon_core.hosts.substancepainter.api.pipeline import ( imprint_container, set_container_metadata, remove_container_metadata ) -from ayon_core.hosts.substancepainter.api.lib import ( - prompt_new_file_with_mesh, - parse_substance_attributes_setting -) +from ayon_core.hosts.substancepainter.api.lib import parse_substance_attributes_setting + import substance_painter.project @@ -83,8 +81,9 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # We want store some options for updating to keep consistent behavior # from the user's original choice. We don't store 'preserve_strokes' # as we always preserve strokes on updates. + # TODO: update the code container["options"] = { - "import_cameras": import_cameras, + "import_cameras": True, } set_container_metadata(project_mesh_object_name, container) From 878fc9cf2c3fb7a0c5639590993176add3563c41 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 16 Apr 2024 23:28:05 +0800 Subject: [PATCH 293/363] update the attributes options for reloading mesh --- .../hosts/substancepainter/api/lib.py | 47 +++++++++++++++++++ .../plugins/load/load_mesh.py | 17 ++++--- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index e344076222..f95e47f99d 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -643,6 +643,21 @@ def prompt_new_file_with_mesh(mesh_filepath): def convert_substance_object_to_python(subst_proj_option="default"): + """Function to convert substance C++ objects to python instance. + It is made to avoid any possible ValueError when C++ objects casting + as python instance. + + Args: + subst_proj_option (str, optional): Substance project option. + Defaults to "default". + + Raises: + ValueError: Raise Error when unsupported Substance + Project was detected + + Returns: + python instance: converted python instance of the C++ objects. + """ if subst_proj_option == "default": return substance_painter.project.ProjectWorkflow.Default elif subst_proj_option == "uvTile": @@ -663,6 +678,16 @@ def convert_substance_object_to_python(subst_proj_option="default"): def parse_substance_attributes_setting(template_name, project_templates): + """Function to parse the dictionary from the AYON setting to be used + as the attributes for Substance Project Creation + + Args: + template_name (str): name of the template from the setting + project_templates (dict): project template data from the setting + + Returns: + dict: data to be used as attributes for Substance Project Creation + """ attributes_data = {} for template in project_templates: if template["name"] == template_name: @@ -676,3 +701,25 @@ def parse_substance_attributes_setting(template_name, project_templates): attributes_data.pop("name") attributes_data.pop("preserve_strokes") return attributes_data + + +def parse_subst_attrs_reloading_mesh(template_name, project_templates): + """Function to parse the substances attributes ('import_cameras' + and 'preserve_strokes') for reloading mesh + with the existing projects. + + Args: + template_name (str): name of the template from the setting + project_templates (dict): project template data from the setting + + Returns: + dict: data to be used as attributes for reloading mesh with the + existing project + """ + attributes_data = {} + for template in project_templates: + if template["name"] == template_name: + for key, value in template.items(): + if isinstance(value, bool): + attributes_data.update({key: value}) + return attributes_data 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 631af88eb5..49f11251c9 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -9,7 +9,10 @@ from ayon_core.hosts.substancepainter.api.pipeline import ( set_container_metadata, remove_container_metadata ) -from ayon_core.hosts.substancepainter.api.lib import parse_substance_attributes_setting +from ayon_core.hosts.substancepainter.api.lib import ( + parse_substance_attributes_setting, + parse_subst_attrs_reloading_mesh +) import substance_painter.project @@ -41,7 +44,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs template_name = options.get("project_template", "default") - template_settings = parse_substance_attributes_setting(template_name, self.project_templates) + template_settings = parse_substance_attributes_setting( + template_name, self.project_templates) sp_settings = substance_painter.project.Settings(**template_settings) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project @@ -52,11 +56,10 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh + mesh_settings = parse_subst_attrs_reloading_mesh( + template_name, self.project_templates) # TODO: fix the hardcoded when the preset setting in SP addon. - settings = substance_painter.project.MeshReloadingSettings( - import_cameras=True, - preserve_strokes=True - ) + settings = substance_painter.project.MeshReloadingSettings(**mesh_settings) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa @@ -83,7 +86,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # as we always preserve strokes on updates. # TODO: update the code container["options"] = { - "import_cameras": True, + "import_cameras": template_settings["import_cameras"], } set_container_metadata(project_mesh_object_name, container) From 991eee9657e7a2e3a56f016170a9d095ce8c1637 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Apr 2024 18:25:49 +0200 Subject: [PATCH 294/363] 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 95a69a1d8d994c5345c78d1354e32a333983eef2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 13:31:13 +0800 Subject: [PATCH 295/363] make sure deleting old version should remove the 'right' folder --- client/ayon_core/plugins/load/delete_old_versions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index f432829860..62302e7123 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -238,9 +238,12 @@ class DeleteOldVersions(load.ProductLoaderPlugin): versions_by_parent[ent["productId"]].append(ent) # Filter already deleted versions + versions_to_pop = [] for version in versions: if "deleted" in version["tags"]: - continue + versions_to_pop.append(version) + + for version in versions_to_pop: msg = "Folder: \"{}\" | Product: \"{}\" | Version: \"{}\"".format( folder_entity["path"], product_entity["name"], From 787ebed3466254af39db4734c50592399b9eb33b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 18:13:31 +0800 Subject: [PATCH 296/363] 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 297/363] 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 298/363] 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]: From d4fdf8530605ca4f03957415f48d99eef292d11b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 17 Apr 2024 22:45:47 +0800 Subject: [PATCH 299/363] Add Qt dialog to support users to choose their templates for project creation --- .../hosts/substancepainter/api/lib.py | 83 ----------- .../plugins/load/load_mesh.py | 136 +++++++++++++++--- .../server/settings/load_plugins.py | 36 +++-- 3 files changed, 145 insertions(+), 110 deletions(-) diff --git a/client/ayon_core/hosts/substancepainter/api/lib.py b/client/ayon_core/hosts/substancepainter/api/lib.py index f95e47f99d..64c39943ce 100644 --- a/client/ayon_core/hosts/substancepainter/api/lib.py +++ b/client/ayon_core/hosts/substancepainter/api/lib.py @@ -640,86 +640,3 @@ def prompt_new_file_with_mesh(mesh_filepath): return return project_mesh - - -def convert_substance_object_to_python(subst_proj_option="default"): - """Function to convert substance C++ objects to python instance. - It is made to avoid any possible ValueError when C++ objects casting - as python instance. - - Args: - subst_proj_option (str, optional): Substance project option. - Defaults to "default". - - Raises: - ValueError: Raise Error when unsupported Substance - Project was detected - - Returns: - python instance: converted python instance of the C++ objects. - """ - if subst_proj_option == "default": - return substance_painter.project.ProjectWorkflow.Default - elif subst_proj_option == "uvTile": - return substance_painter.project.ProjectWorkflow.UVTile - elif subst_proj_option == "textureSetPerUVTile": - return substance_painter.project.ProjectWorkflow.TextureSetPerUVTile - elif subst_proj_option == "PerFragment": - return substance_painter.project.TangentSpace.PerFragment - elif subst_proj_option == "PerVertex": - return substance_painter.project.TangentSpace.PerVertex - elif subst_proj_option == "DirectX": - return substance_painter.project.NormalMapFormat.DirectX - elif subst_proj_option == "OpenGL": - return substance_painter.project.NormalMapFormat.OpenGL - else: - raise ValueError( - f"Unsupported Substance Objects: {subst_proj_option}") - - -def parse_substance_attributes_setting(template_name, project_templates): - """Function to parse the dictionary from the AYON setting to be used - as the attributes for Substance Project Creation - - Args: - template_name (str): name of the template from the setting - project_templates (dict): project template data from the setting - - Returns: - dict: data to be used as attributes for Substance Project Creation - """ - attributes_data = {} - for template in project_templates: - if template["name"] == template_name: - attributes_data.update(template) - attributes_data["normal_map_format"] = convert_substance_object_to_python( - subst_proj_option=attributes_data["normal_map_format"]) - attributes_data["project_workflow"] = convert_substance_object_to_python( - subst_proj_option=attributes_data["project_workflow"]) - attributes_data["tangent_space_mode"] = convert_substance_object_to_python( - subst_proj_option=attributes_data["tangent_space_mode"]) - attributes_data.pop("name") - attributes_data.pop("preserve_strokes") - return attributes_data - - -def parse_subst_attrs_reloading_mesh(template_name, project_templates): - """Function to parse the substances attributes ('import_cameras' - and 'preserve_strokes') for reloading mesh - with the existing projects. - - Args: - template_name (str): name of the template from the setting - project_templates (dict): project template data from the setting - - Returns: - dict: data to be used as attributes for reloading mesh with the - existing project - """ - attributes_data = {} - for template in project_templates: - if template["name"] == template_name: - for key, value in template.items(): - if isinstance(value, bool): - attributes_data.update({key: value}) - return attributes_data 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 49f11251c9..b377cf9a1d 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -1,23 +1,130 @@ +from qtpy import QtWidgets, QtCore from ayon_core.pipeline import ( load, get_representation_path, ) -from ayon_core.lib import EnumDef from ayon_core.pipeline.load import LoadError from ayon_core.hosts.substancepainter.api.pipeline import ( imprint_container, set_container_metadata, remove_container_metadata ) -from ayon_core.hosts.substancepainter.api.lib import ( - parse_substance_attributes_setting, - parse_subst_attrs_reloading_mesh -) - import substance_painter.project +def _convert(subst_attr): + """Function to convert substance C++ objects to python instance. + It is made to avoid any possible ValueError when C++ objects casting + as python instance. + + Args: + subst_attr (str): Substance attributes + + Raises: + ValueError: Raise Error when unsupported Substance + Project was detected + + Returns: + python instance: converted python instance of the C++ objects. + """ + if subst_attr in {"Default", "UVTile", "TextureSetPerUVTile"}: + return getattr(substance_painter.project.ProjectWorkflow, subst_attr) + elif subst_attr in {"PerFragment", "PerVertex"}: + return getattr(substance_painter.project.TangentSpace, subst_attr) + elif subst_attr in {"DirectX", "OpenGL"}: + return getattr(substance_painter.project.NormalMapFormat, subst_attr) + else: + raise ValueError( + f"Unsupported Substance Objects: {subst_attr}") + + +def parse_substance_attributes_setting(template_name, project_templates): + """Function to parse the dictionary from the AYON setting to be used + as the attributes for Substance Project Creation + + Args: + template_name (str): name of the template from the setting + project_templates (dict): project template data from the setting + + Returns: + dict: data to be used as attributes for Substance Project Creation + """ + attributes_data = {} + for template in project_templates: + if template["name"] == template_name: + attributes_data.update(template) + attributes_data["normal_map_format"] = _convert( + attributes_data["normal_map_format"]) + attributes_data["project_workflow"] = _convert( + attributes_data["project_workflow"]) + attributes_data["tangent_space_mode"] = _convert( + attributes_data["tangent_space_mode"]) + attributes_data.pop("name") + attributes_data.pop("preserve_strokes") + return attributes_data + + +def parse_subst_attrs_reloading_mesh(template_name, project_templates): + """Function to parse the substances attributes ('import_cameras' + and 'preserve_strokes') for reloading mesh + with the existing projects. + + Args: + template_name (str): name of the template from the setting + project_templates (dict): project template data from the setting + + Returns: + dict: data to be used as attributes for reloading mesh with the + existing project + """ + attributes_data = {} + for template in project_templates: + if template["name"] == template_name: + for key, value in template.items(): + if isinstance(value, bool): + attributes_data.update({key: value}) + return attributes_data + + +class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): + """The pop-up dialog allows users to choose material + duplicate options for importing Max objects when updating + or switching assets. + """ + def __init__(self, project_templates): + super(SubstanceProjectConfigurationWindow, self).__init__() + self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) + + self.template_name = None + self.project_templates = project_templates + + self.widgets = { + "label": QtWidgets.QLabel("Project Configuration"), + "template_options": QtWidgets.QComboBox(), + "buttons": QtWidgets.QWidget(), + "okButton": QtWidgets.QPushButton("Ok"), + } + for template in project_templates: + self.widgets["template_options"].addItem(template) + # Build buttons. + layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) + layout.addWidget(self.widgets["template_options"]) + layout.addWidget(self.widgets["okButton"]) + # Build layout. + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.widgets["label"]) + layout.addWidget(self.widgets["buttons"]) + + self.widgets["okButton"].pressed.connect(self.on_ok_pressed) + + def on_ok_pressed(self): + self.template_name = ( + self.widgets["template_options"].currentText() + ) + self.close() + + class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -30,20 +137,13 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): color = "orange" project_templates = [] - @classmethod - def get_options(cls, contexts): - template_enum = [template["name"] for template in cls.project_templates] - return [ - EnumDef("project_template", - items=template_enum, - default="default", - label="Project Template") - ] - def load(self, context, name, namespace, options=None): - # Get user inputs - template_name = options.get("project_template", "default") + template_enum = [template["name"] for template in self.project_templates] + window = SubstanceProjectConfigurationWindow(template_enum) + window.exec_() + template_name = window.template_name + template_settings = parse_substance_attributes_setting( template_name, self.project_templates) sp_settings = substance_painter.project.Settings(**template_settings) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index 4d3e64f0b6..294ecfcef6 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -10,17 +10,17 @@ def normal_map_format_enum(): def tangent_space_enum(): return [ - {"label": "PerFragment", "value": "PerFragment"}, - {"label": "PerVertex", "value": "PerVertex"}, + {"label": "Per Fragment", "value": "PerFragment"}, + {"label": "Per Vertex", "value": "PerVertex"}, ] def uv_workflow_enum(): return [ - {"label": "Default", "value": "default"}, - {"label": "UV Tile", "value": "uvTile"}, + {"label": "Default", "value": "Default"}, + {"label": "UV Tile", "value": "UVTile"}, {"label": "Texture Set Per UV Tile", - "value": "textureSetPerUVTile"} + "value": "TextureSetPerUVTile"} ] @@ -54,7 +54,7 @@ class ProjectTemplatesModel(BaseSettingsModel): "creating new project.") ) project_workflow: str = SettingsField( - "default", enum_resolver=uv_workflow_enum, + "Default", enum_resolver=uv_workflow_enum, title="UV Tile Settings", description=("Set UV workflow when " "creating new project.") @@ -90,11 +90,29 @@ class LoadersModel(BaseSettingsModel): DEFAULT_LOADER_SETTINGS = { "SubstanceLoadProjectMesh":{ "project_templates": [{ - "name": "default", - "default_texture_resolution": 1024, + "name": "2K(Default)", + "default_texture_resolution": 2048, "import_cameras": True, "normal_map_format": "DirectX", - "project_workflow": "default", + "project_workflow": "Default", + "tangent_space_mode": "PerFragment", + "preserve_strokes": True + }, + { + "name": "2K(UV tile)", + "default_texture_resolution": 2048, + "import_cameras": True, + "normal_map_format": "DirectX", + "project_workflow": "UVTile", + "tangent_space_mode": "PerFragment", + "preserve_strokes": True + }, + { + "name": "4K(Custom)", + "default_texture_resolution": 4096, + "import_cameras": True, + "normal_map_format": "OpenGL", + "project_workflow": "UVTile", "tangent_space_mode": "PerFragment", "preserve_strokes": True }] From 81bdaf9915b51e1b6f41cb95cbab703def72c19e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 00:06:33 +0800 Subject: [PATCH 300/363] optimize the codes and the settings for loading mesh more smoothly --- .../plugins/load/load_mesh.py | 95 ++++++------------- .../server/settings/load_plugins.py | 14 +-- 2 files changed, 35 insertions(+), 74 deletions(-) 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 b377cf9a1d..8f23600216 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -13,10 +13,10 @@ from ayon_core.hosts.substancepainter.api.pipeline import ( import substance_painter.project -def _convert(subst_attr): +def _convert(substance_attr): """Function to convert substance C++ objects to python instance. - It is made to avoid any possible ValueError when C++ objects casting - as python instance. + It is made to avoid any possible ValueError when C++ objects is + converted to the Substance Painter Python API equivalent objects. Args: subst_attr (str): Substance attributes @@ -28,63 +28,21 @@ def _convert(subst_attr): Returns: python instance: converted python instance of the C++ objects. """ - if subst_attr in {"Default", "UVTile", "TextureSetPerUVTile"}: - return getattr(substance_painter.project.ProjectWorkflow, subst_attr) - elif subst_attr in {"PerFragment", "PerVertex"}: - return getattr(substance_painter.project.TangentSpace, subst_attr) - elif subst_attr in {"DirectX", "OpenGL"}: - return getattr(substance_painter.project.NormalMapFormat, subst_attr) - else: - raise ValueError( - f"Unsupported Substance Objects: {subst_attr}") + root = substance_painter.project + for attr in substance_attr.split("."): + root = getattr(root, attr, None) + if root is None: + raise ValueError( + f"Substance Painter project attribute does not exist: {substance_attr}") + + return root -def parse_substance_attributes_setting(template_name, project_templates): - """Function to parse the dictionary from the AYON setting to be used - as the attributes for Substance Project Creation - - Args: - template_name (str): name of the template from the setting - project_templates (dict): project template data from the setting - - Returns: - dict: data to be used as attributes for Substance Project Creation - """ - attributes_data = {} - for template in project_templates: - if template["name"] == template_name: - attributes_data.update(template) - attributes_data["normal_map_format"] = _convert( - attributes_data["normal_map_format"]) - attributes_data["project_workflow"] = _convert( - attributes_data["project_workflow"]) - attributes_data["tangent_space_mode"] = _convert( - attributes_data["tangent_space_mode"]) - attributes_data.pop("name") - attributes_data.pop("preserve_strokes") - return attributes_data - - -def parse_subst_attrs_reloading_mesh(template_name, project_templates): - """Function to parse the substances attributes ('import_cameras' - and 'preserve_strokes') for reloading mesh - with the existing projects. - - Args: - template_name (str): name of the template from the setting - project_templates (dict): project template data from the setting - - Returns: - dict: data to be used as attributes for reloading mesh with the - existing project - """ - attributes_data = {} - for template in project_templates: - if template["name"] == template_name: - for key, value in template.items(): - if isinstance(value, bool): - attributes_data.update({key: value}) - return attributes_data +def get_template_by_name(name: str, templates: list[dict]) -> dict: + return next( + template for template in templates + if template["name"] == name + ) class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): @@ -139,14 +97,18 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs + print(self.project_templates) template_enum = [template["name"] for template in self.project_templates] window = SubstanceProjectConfigurationWindow(template_enum) window.exec_() template_name = window.template_name - - template_settings = parse_substance_attributes_setting( - template_name, self.project_templates) - sp_settings = substance_painter.project.Settings(**template_settings) + template = get_template_by_name(template_name, self.project_templates) + sp_settings = substance_painter.project.Settings( + normal_map_format=_convert(template["normal_map_format"]), + project_workflow=_convert(template["project_workflow"]), + tangent_space_mode=_convert(template["tangent_space_mode"]), + default_texture_resolution=template["default_texture_resolution"] + ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) @@ -156,10 +118,9 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh - mesh_settings = parse_subst_attrs_reloading_mesh( - template_name, self.project_templates) - # TODO: fix the hardcoded when the preset setting in SP addon. - settings = substance_painter.project.MeshReloadingSettings(**mesh_settings) + settings = substance_painter.project.MeshReloadingSettings( + import_cameras=template["import_cameras"], + preserve_strokes=template["preserve_strokes"]) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa @@ -186,7 +147,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # as we always preserve strokes on updates. # TODO: update the code container["options"] = { - "import_cameras": template_settings["import_cameras"], + "import_cameras": template["import_cameras"], } set_container_metadata(project_mesh_object_name, container) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index 294ecfcef6..b404ad4316 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -3,24 +3,24 @@ from ayon_server.settings import BaseSettingsModel, SettingsField def normal_map_format_enum(): return [ - {"label": "DirectX", "value": "DirectX"}, - {"label": "OpenGL", "value": "OpenGL"}, + {"label": "DirectX", "value": "NormalMapFormat.DirectX"}, + {"label": "OpenGL", "value": "NormalMapFormat.OpenGL"}, ] def tangent_space_enum(): return [ - {"label": "Per Fragment", "value": "PerFragment"}, - {"label": "Per Vertex", "value": "PerVertex"}, + {"label": "Per Fragment", "value": "TangentSpace.PerFragment"}, + {"label": "Per Vertex", "value": "TangentSpace.PerVertex"}, ] def uv_workflow_enum(): return [ - {"label": "Default", "value": "Default"}, - {"label": "UV Tile", "value": "UVTile"}, + {"label": "Default", "value": "ProjectWorkflow.Default"}, + {"label": "UV Tile", "value": "ProjectWorkflow.UVTile"}, {"label": "Texture Set Per UV Tile", - "value": "TextureSetPerUVTile"} + "value": "ProjectWorkflow.TextureSetPerUVTile"} ] From 6873f02e6b08fec57cec6cd65a3ac840759e3ed2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 00:06:51 +0200 Subject: [PATCH 301/363] Fix typos --- .../workfile/workfile_template_builder.py | 22 +++++++++---------- .../tools/workfile_template_build/lib.py | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 5e63ba444a..012251cd91 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -329,7 +329,7 @@ class AbstractTemplateBuilder(object): is good practice to check if the same value is not already stored under different key or if the key is not already used for something else. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_name' @@ -375,7 +375,7 @@ class AbstractTemplateBuilder(object): is good practice to check if the same value is not already stored under different key or if the key is not already used for something else. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_path' @@ -395,7 +395,7 @@ class AbstractTemplateBuilder(object): is good practice to check if the same value is not already stored under different key or if the key is not already used for something else. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_path' @@ -466,7 +466,7 @@ class AbstractTemplateBuilder(object): return list(sorted( placeholders, - key=lambda i: i.order + key=lambda placeholder: placeholder.order )) def build_template( @@ -685,7 +685,7 @@ class AbstractTemplateBuilder(object): for placeholder in placeholders } all_processed = len(placeholders) == 0 - # Counter is checked at the ned of a loop so the loop happens at least + # Counter is checked at the end of a loop so the loop happens at least # once. iter_counter = 0 while not all_processed: @@ -1045,7 +1045,7 @@ class PlaceholderPlugin(object): Using shared data from builder but stored under plugin identifier. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_path' @@ -1085,7 +1085,7 @@ class PlaceholderPlugin(object): Using shared data from builder but stored under plugin identifier. - Key should be self explanatory to content. + Key should be self-explanatory to content. - wrong: 'folder' - good: 'folder_path' @@ -1107,10 +1107,10 @@ class PlaceholderItem(object): """Item representing single item in scene that is a placeholder to process. Items are always created and updated by their plugins. Each plugin can use - modified class of 'PlacehoderItem' but only to add more options instead of + modified class of 'PlaceholderItem' but only to add more options instead of new other. - Scene identifier is used to avoid processing of the palceholder item + Scene identifier is used to avoid processing of the placeholder item multiple times so must be unique across whole workfile builder. Args: @@ -1162,7 +1162,7 @@ class PlaceholderItem(object): """Placeholder data which can modify how placeholder is processed. Possible general keys - - order: Can define the order in which is palceholder processed. + - order: Can define the order in which is placeholder processed. Lower == earlier. Other keys are defined by placeholder and should validate them on item @@ -1264,7 +1264,7 @@ class PlaceholderLoadMixin(object): """Unified attribute definitions for load placeholder. Common function for placeholder plugins used for loading of - repsentations. Use it in 'get_placeholder_options'. + representations. Use it in 'get_placeholder_options'. Args: plugin (PlaceholderPlugin): Plugin used for loading of diff --git a/client/ayon_core/tools/workfile_template_build/lib.py b/client/ayon_core/tools/workfile_template_build/lib.py index de3a0d0084..ffd6fefc38 100644 --- a/client/ayon_core/tools/workfile_template_build/lib.py +++ b/client/ayon_core/tools/workfile_template_build/lib.py @@ -8,12 +8,12 @@ from ayon_core.tools.utils.dialogs import show_message_dialog def open_template_ui(builder, main_window): """Open template from `builder` - Asks user about overwriting current scene and feedsback exceptions. + Asks user about overwriting current scene and feedback exceptions. """ result = QtWidgets.QMessageBox.question( main_window, "Opening template", - "Caution! You will loose unsaved changes.\nDo you want to continue?", + "Caution! You will lose unsaved changes.\nDo you want to continue?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No ) if result == QtWidgets.QMessageBox.Yes: From a59c54d1aa8807659cac331af9a0ee9a2019a6c8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 00:09:29 +0200 Subject: [PATCH 302/363] Remove non-existing arguments from docstring --- .../ayon_core/pipeline/workfile/workfile_template_builder.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 012251cd91..1c094e1d52 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1267,8 +1267,6 @@ class PlaceholderLoadMixin(object): representations. Use it in 'get_placeholder_options'. Args: - plugin (PlaceholderPlugin): Plugin used for loading of - representations. options (Dict[str, Any]): Already available options which are used as defaults for attributes. @@ -1695,8 +1693,6 @@ class PlaceholderCreateMixin(object): publishable instances. Use it with 'get_placeholder_options'. Args: - plugin (PlaceholderPlugin): Plugin used for creating of - publish instances. options (Dict[str, Any]): Already available options which are used as defaults for attributes. From 2b6889a6ea55dd8a5916f5584b3e39ced8c4d913 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 00:12:42 +0200 Subject: [PATCH 303/363] Also delete placeholder if Keep Placeholder is not enabled and no representations were loaded --- client/ayon_core/pipeline/workfile/workfile_template_builder.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 5e63ba444a..56a7a5f33a 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1599,6 +1599,8 @@ class PlaceholderLoadMixin(object): self.log.info(( "There's no representation for this placeholder: {}" ).format(placeholder.scene_identifier)) + if not placeholder.data.get("keep_placeholder", True): + self.delete_placeholder(placeholder) return repre_load_contexts = get_representation_contexts( From 425f7cbd89c3ac695b39373b9283df3b83de410b Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Thu, 18 Apr 2024 13:13:55 +0800 Subject: [PATCH 304/363] Update server_addon/substancepainter/server/settings/load_plugins.py Co-authored-by: Roy Nieterau --- .../server/settings/load_plugins.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index b404ad4316..ed9b6f0d64 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -93,27 +93,27 @@ DEFAULT_LOADER_SETTINGS = { "name": "2K(Default)", "default_texture_resolution": 2048, "import_cameras": True, - "normal_map_format": "DirectX", - "project_workflow": "Default", - "tangent_space_mode": "PerFragment", + "normal_map_format": "NormalMapFormat.DirectX", + "project_workflow": "ProjectWorkflow.Default", + "tangent_space_mode": "TangentSpace.PerFragment", "preserve_strokes": True }, { "name": "2K(UV tile)", "default_texture_resolution": 2048, "import_cameras": True, - "normal_map_format": "DirectX", - "project_workflow": "UVTile", - "tangent_space_mode": "PerFragment", + "normal_map_format": "NormalMapFormat.DirectX", + "project_workflow": "ProjectWorkflow.UVTile", + "tangent_space_mode": "TangentSpace.PerFragment", "preserve_strokes": True }, { "name": "4K(Custom)", "default_texture_resolution": 4096, "import_cameras": True, - "normal_map_format": "OpenGL", - "project_workflow": "UVTile", - "tangent_space_mode": "PerFragment", + "normal_map_format": "NormalMapFormat.OpenGL", + "project_workflow": "ProjectWorkflow.UVTile", + "tangent_space_mode": "TangentSpace.PerFragment", "preserve_strokes": True }] } From 53627d34d3762ca2ec2396f315ff7515b93a1993 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Thu, 18 Apr 2024 13:14:15 +0800 Subject: [PATCH 305/363] Update client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py Co-authored-by: Roy Nieterau --- .../plugins/load/load_mesh.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) 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 8f23600216..7af0d71a1d 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -14,19 +14,22 @@ import substance_painter.project def _convert(substance_attr): - """Function to convert substance C++ objects to python instance. - It is made to avoid any possible ValueError when C++ objects is - converted to the Substance Painter Python API equivalent objects. + """Return Substance Painter Python API Project attribute from string. + + This converts a string like "ProjectWorkflow.Default" to for example + the Substance Painter Python API equivalent object, like: + `substance_painter.project.ProjectWorkflow.Default` Args: - subst_attr (str): Substance attributes - - Raises: - ValueError: Raise Error when unsupported Substance - Project was detected + substance_attr (str): The `substance_painter.project` attribute, + for example "ProjectWorkflow.Default" Returns: - python instance: converted python instance of the C++ objects. + Any: Substance Python API object of the project attribute. + + Raises: + ValueError: If attribute does not exist on the + `substance_painter.project` python api. """ root = substance_painter.project for attr in substance_attr.split("."): From ca442c52cd18b0594a80af53c129ca5c567e440b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 14:00:14 +0800 Subject: [PATCH 306/363] remove print function --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7af0d71a1d..16a525b279 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -99,8 +99,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): project_templates = [] def load(self, context, name, namespace, options=None): + # Get user inputs - print(self.project_templates) template_enum = [template["name"] for template in self.project_templates] window = SubstanceProjectConfigurationWindow(template_enum) window.exec_() From 5bb61efab28c09fdaa51a2965e019e58e07c20e6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 14:50:56 +0800 Subject: [PATCH 307/363] cosmetic fix --- .../plugins/load/load_mesh.py | 6 +- .../server/settings/load_plugins.py | 62 ++++++++++--------- 2 files changed, 36 insertions(+), 32 deletions(-) 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 16a525b279..b3f0109942 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -36,7 +36,8 @@ def _convert(substance_attr): root = getattr(root, attr, None) if root is None: raise ValueError( - f"Substance Painter project attribute does not exist: {substance_attr}") + "Substance Painter project attribute" + f" does not exist: {substance_attr}") return root @@ -101,7 +102,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs - template_enum = [template["name"] for template in self.project_templates] + template_enum = [template["name"] for template + in self.project_templates] window = SubstanceProjectConfigurationWindow(template_enum) window.exec_() template_name = window.template_name diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index ed9b6f0d64..e6b2fd86c3 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -74,10 +74,10 @@ class ProjectTemplatesModel(BaseSettingsModel): class ProjectTemplateSettingModel(BaseSettingsModel): - project_templates: list[ProjectTemplatesModel] = SettingsField( + project_templates: list[ProjectTemplatesModel] = SettingsField( default_factory=ProjectTemplatesModel, title="Project Templates" -) + ) class LoadersModel(BaseSettingsModel): @@ -88,33 +88,35 @@ class LoadersModel(BaseSettingsModel): DEFAULT_LOADER_SETTINGS = { - "SubstanceLoadProjectMesh":{ - "project_templates": [{ - "name": "2K(Default)", - "default_texture_resolution": 2048, - "import_cameras": True, - "normal_map_format": "NormalMapFormat.DirectX", - "project_workflow": "ProjectWorkflow.Default", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True - }, - { - "name": "2K(UV tile)", - "default_texture_resolution": 2048, - "import_cameras": True, - "normal_map_format": "NormalMapFormat.DirectX", - "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True - }, - { - "name": "4K(Custom)", - "default_texture_resolution": 4096, - "import_cameras": True, - "normal_map_format": "NormalMapFormat.OpenGL", - "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True - }] + "SubstanceLoadProjectMesh": { + "project_templates": [ + { + "name": "2K(Default)", + "default_texture_resolution": 2048, + "import_cameras": True, + "normal_map_format": "NormalMapFormat.DirectX", + "project_workflow": "ProjectWorkflow.Default", + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True + }, + { + "name": "2K(UV tile)", + "default_texture_resolution": 2048, + "import_cameras": True, + "normal_map_format": "NormalMapFormat.DirectX", + "project_workflow": "ProjectWorkflow.UVTile", + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True + }, + { + "name": "4K(Custom)", + "default_texture_resolution": 4096, + "import_cameras": True, + "normal_map_format": "NormalMapFormat.OpenGL", + "project_workflow": "ProjectWorkflow.UVTile", + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True + } + ] } } From bf0d3477d9b7ac5f7b24392404efe5dd0222721c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:24:14 +0200 Subject: [PATCH 308/363] fix expading of values Co-authored-by: Roy Nieterau --- client/ayon_core/pipeline/workfile/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index c3b1267466..ed7a2b8475 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1582,7 +1582,7 @@ class PlaceholderLoadMixin(object): self.project_name, placeholder_representations ) filtered_repre_contexts = self._reduce_last_version_repre_entities( - repre_load_contexts + repre_load_contexts.values() ) if not filtered_repre_contexts: self.log.info(( From 646c0df474e100b307d33c1c896cd46b6c610580 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:25:22 +0200 Subject: [PATCH 309/363] fix version entity manipulation --- .../ayon_core/pipeline/workfile/workfile_template_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index ed7a2b8475..7b8d29ee37 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1539,11 +1539,11 @@ class PlaceholderLoadMixin(object): version_mapping = version_mapping_by_product_id.setdefault( product_id, {} ) - version_mapping[version].append(repre_context) + version_mapping.setdefault(version, []).append(repre_context) output = [] for version_mapping in version_mapping_by_product_id.values(): - last_version = tuple(sorted(version_mapping.keys()))[-1] + last_version = max(version_mapping.keys()) output.extend(version_mapping[last_version]) return output From 3d496be7c65a28fc1567cc4e62e6caadc6d45a4e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:26:20 +0200 Subject: [PATCH 310/363] fix product type key access --- .../ayon_core/pipeline/workfile/workfile_template_builder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 7b8d29ee37..013a8fb37f 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1468,7 +1468,9 @@ class PlaceholderLoadMixin(object): product_name_regex = None if product_name_regex_value: product_name_regex = re.compile(product_name_regex_value) - product_type = placeholder.data["family"] + product_type = placeholder.data.get("product_type") + if product_type is None: + product_type = placeholder.data.get("family") builder_type = placeholder.data["builder_type"] folder_ids = [] From d5a3296d4c09086d5805c3d62e617f48c1fd2b1a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Apr 2024 10:48:12 +0200 Subject: [PATCH 311/363] Crash if product type is not available. --- client/ayon_core/pipeline/workfile/workfile_template_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 013a8fb37f..525a7396e7 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -1470,7 +1470,7 @@ class PlaceholderLoadMixin(object): product_name_regex = re.compile(product_name_regex_value) product_type = placeholder.data.get("product_type") if product_type is None: - product_type = placeholder.data.get("family") + product_type = placeholder.data["family"] builder_type = placeholder.data["builder_type"] folder_ids = [] From f7c8a23d98729dd462bf60026682bfa3f2e09ae6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 21:23:23 +0800 Subject: [PATCH 312/363] add import cameras and perserve strokes into the project configuration dialog --- .../plugins/load/load_mesh.py | 29 +++++++++++++++---- .../server/settings/load_plugins.py | 21 ++------------ 2 files changed, 26 insertions(+), 24 deletions(-) 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 b3f0109942..2560bd96ae 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -58,29 +58,44 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): super(SubstanceProjectConfigurationWindow, self).__init__() self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) + self.import_cameras = False + self.preserve_strokes = False self.template_name = None self.project_templates = project_templates self.widgets = { "label": QtWidgets.QLabel("Project Configuration"), "template_options": QtWidgets.QComboBox(), - "buttons": QtWidgets.QWidget(), + "import_cameras": QtWidgets.QCheckBox("Improve Cameras"), + "preserve_strokes": QtWidgets.QCheckBox("Preserve Strokes"), + "clickbox": QtWidgets.QWidget(), + "combobox": QtWidgets.QWidget(), "okButton": QtWidgets.QPushButton("Ok"), } for template in project_templates: self.widgets["template_options"].addItem(template) + + # Build clickboxes + layout = QtWidgets.QHBoxLayout(self.widgets["clickbox"]) + layout.addWidget(self.widgets["import_cameras"]) + layout.addWidget(self.widgets["preserve_strokes"]) # Build buttons. - layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) + layout = QtWidgets.QHBoxLayout(self.widgets["combobox"]) layout.addWidget(self.widgets["template_options"]) layout.addWidget(self.widgets["okButton"]) # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) - layout.addWidget(self.widgets["buttons"]) + layout.addWidget(self.widgets["clickbox"]) + layout.addWidget(self.widgets["combobox"]) self.widgets["okButton"].pressed.connect(self.on_ok_pressed) def on_ok_pressed(self): + if self.widgets["import_cameras"].isChecked(): + self.import_cameras = True + if self.widgets["preserve_strokes"].isChecked(): + self.preserve_strokes = True self.template_name = ( self.widgets["template_options"].currentText() ) @@ -107,6 +122,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): window = SubstanceProjectConfigurationWindow(template_enum) window.exec_() template_name = window.template_name + import_cameras = window.import_cameras + preserve_strokes = window.preserve_strokes template = get_template_by_name(template_name, self.project_templates) sp_settings = substance_painter.project.Settings( normal_map_format=_convert(template["normal_map_format"]), @@ -124,8 +141,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): else: # Reload the mesh settings = substance_painter.project.MeshReloadingSettings( - import_cameras=template["import_cameras"], - preserve_strokes=template["preserve_strokes"]) + import_cameras=import_cameras, + preserve_strokes=preserve_strokes) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa @@ -152,7 +169,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # as we always preserve strokes on updates. # TODO: update the code container["options"] = { - "import_cameras": template["import_cameras"], + "import_cameras": import_cameras, } set_container_metadata(project_mesh_object_name, container) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index e6b2fd86c3..e5519c9773 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -44,9 +44,6 @@ class ProjectTemplatesModel(BaseSettingsModel): description=("Set texture resolution when " "creating new project.") ) - import_cameras: bool = SettingsField( - True, title="Import Cameras", - description="Import cameras from the mesh file.") normal_map_format: str = SettingsField( "DirectX", enum_resolver=normal_map_format_enum, title="Normal Map Format", @@ -65,12 +62,6 @@ class ProjectTemplatesModel(BaseSettingsModel): description=("An option to compute tangent space " "when creating new project.") ) - preserve_strokes: bool = SettingsField( - True, title="Preserve Strokes", - description=("Preserve strokes positions on mesh.\n" - "(only relevant when loading into " - "existing project)") - ) class ProjectTemplateSettingModel(BaseSettingsModel): @@ -93,29 +84,23 @@ DEFAULT_LOADER_SETTINGS = { { "name": "2K(Default)", "default_texture_resolution": 2048, - "import_cameras": True, "normal_map_format": "NormalMapFormat.DirectX", "project_workflow": "ProjectWorkflow.Default", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True + "tangent_space_mode": "TangentSpace.PerFragment" }, { "name": "2K(UV tile)", "default_texture_resolution": 2048, - "import_cameras": True, "normal_map_format": "NormalMapFormat.DirectX", "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True + "tangent_space_mode": "TangentSpace.PerFragment" }, { "name": "4K(Custom)", "default_texture_resolution": 4096, - "import_cameras": True, "normal_map_format": "NormalMapFormat.OpenGL", "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment", - "preserve_strokes": True + "tangent_space_mode": "TangentSpace.PerFragment" } ] } From 5875016e5f3b9094de3c24a80fd7c8f650e059f6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 18 Apr 2024 22:06:38 +0800 Subject: [PATCH 313/363] import cameras instead of improve camertas --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2560bd96ae..8536914095 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -66,7 +66,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets = { "label": QtWidgets.QLabel("Project Configuration"), "template_options": QtWidgets.QComboBox(), - "import_cameras": QtWidgets.QCheckBox("Improve Cameras"), + "import_cameras": QtWidgets.QCheckBox("Import Cameras"), "preserve_strokes": QtWidgets.QCheckBox("Preserve Strokes"), "clickbox": QtWidgets.QWidget(), "combobox": QtWidgets.QWidget(), From 276b293577ad9c8396584d4dab63f55b8d21f05d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 17:09:02 +0200 Subject: [PATCH 314/363] Fix `apply_settings` signature --- .../hosts/maya/plugins/publish/collect_file_dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 93b46c511b..60853bd1ee 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 @@ -12,7 +12,7 @@ class CollectFileDependencies(pyblish.api.ContextPlugin): families = ["renderlayer"] @classmethod - def apply_settings(cls, project_settings, system_settings): + def apply_settings(cls, project_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) From fe77e0a578aa6286298b4966c25d1a8b7729b33a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Apr 2024 17:22:44 +0200 Subject: [PATCH 315/363] Fix refactor of imports to `ayon_core` --- client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d1fea33597..7f076a8273 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 @@ -5,8 +5,8 @@ import maya.cmds as cmds from ayon_core.hosts.maya.api import plugin from ayon_core.hosts.maya.api import lib -from openpype.pipeline import registered_host -from openpype.pipeline.create import CreateContext +from ayon_core.pipeline import registered_host +from ayon_core.pipeline.create import CreateContext class YetiRigLoader(plugin.ReferenceLoader): From d5b0f2274837f9cec8c9d74433e3cc57bf513d1f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 16:07:54 +0800 Subject: [PATCH 316/363] redesign the dialog with ok & cancel button and links the boolean options to the AYON settings --- .../plugins/load/load_mesh.py | 65 ++++++++++++++++--- .../server/settings/load_plugins.py | 21 +++++- 2 files changed, 73 insertions(+), 13 deletions(-) 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 8536914095..42a3e5b5b2 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -61,35 +61,70 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.import_cameras = False self.preserve_strokes = False self.template_name = None + self.template_names = [template["name"] for template + in project_templates] self.project_templates = project_templates self.widgets = { - "label": QtWidgets.QLabel("Project Configuration"), + "label": QtWidgets.QLabel( + "Select your template for project configuration"), "template_options": QtWidgets.QComboBox(), "import_cameras": QtWidgets.QCheckBox("Import Cameras"), "preserve_strokes": QtWidgets.QCheckBox("Preserve Strokes"), "clickbox": QtWidgets.QWidget(), "combobox": QtWidgets.QWidget(), + "buttons": QtWidgets.QWidget(), "okButton": QtWidgets.QPushButton("Ok"), + "cancelButton": QtWidgets.QPushButton("Cancel") } - for template in project_templates: + for template in self.template_names: self.widgets["template_options"].addItem(template) + template_name = self.widgets["template_options"].currentText() + + self.import_cameras = next(template["import_cameras"] for + template in self.project_templates + if template["name"] == template_name) + self.preserve_strokes = next(template["preserve_strokes"] for + template in self.project_templates + if template["name"] == template_name) + self.widgets["import_cameras"].setChecked(self.import_cameras) + self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + # Build clickboxes layout = QtWidgets.QHBoxLayout(self.widgets["clickbox"]) layout.addWidget(self.widgets["import_cameras"]) layout.addWidget(self.widgets["preserve_strokes"]) - # Build buttons. + # Build combobox layout = QtWidgets.QHBoxLayout(self.widgets["combobox"]) layout.addWidget(self.widgets["template_options"]) + + # Build buttons + layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) layout.addWidget(self.widgets["okButton"]) + layout.addWidget(self.widgets["cancelButton"]) + # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) layout.addWidget(self.widgets["clickbox"]) layout.addWidget(self.widgets["combobox"]) + layout.addWidget(self.widgets["buttons"]) + self.widgets["template_options"].currentTextChanged.connect( + self.on_options_changed) self.widgets["okButton"].pressed.connect(self.on_ok_pressed) + self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) + + def on_options_changed(self, value): + self.import_cameras = next(template["import_cameras"] for + template in self.project_templates + if template["name"] == value) + self.preserve_strokes = next(template["preserve_strokes"] for + template in self.project_templates + if template["name"] == value) + self.widgets["import_cameras"].setChecked(self.import_cameras) + self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) def on_ok_pressed(self): if self.widgets["import_cameras"].isChecked(): @@ -101,6 +136,16 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): ) self.close() + def on_cancel_pressed(self): + self.template_name = None + self.close() + + @classmethod + def prompt(cls, templates): + dialog = cls(templates) + dialog.exec_() + return dialog + class SubstanceLoadProjectMesh(load.LoaderPlugin): """Load mesh for project""" @@ -117,13 +162,12 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): def load(self, context, name, namespace, options=None): # Get user inputs - template_enum = [template["name"] for template - in self.project_templates] - window = SubstanceProjectConfigurationWindow(template_enum) - window.exec_() - template_name = window.template_name - import_cameras = window.import_cameras - preserve_strokes = window.preserve_strokes + result = SubstanceProjectConfigurationWindow.prompt( + self.project_templates) + template_name = result.template_name + if template_name is None: + return + import_cameras = result.import_cameras template = get_template_by_name(template_name, self.project_templates) sp_settings = substance_painter.project.Settings( normal_map_format=_convert(template["normal_map_format"]), @@ -140,6 +184,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh + preserve_strokes = result.preserve_strokes settings = substance_painter.project.MeshReloadingSettings( import_cameras=import_cameras, preserve_strokes=preserve_strokes) diff --git a/server_addon/substancepainter/server/settings/load_plugins.py b/server_addon/substancepainter/server/settings/load_plugins.py index e5519c9773..e6b2fd86c3 100644 --- a/server_addon/substancepainter/server/settings/load_plugins.py +++ b/server_addon/substancepainter/server/settings/load_plugins.py @@ -44,6 +44,9 @@ class ProjectTemplatesModel(BaseSettingsModel): description=("Set texture resolution when " "creating new project.") ) + import_cameras: bool = SettingsField( + True, title="Import Cameras", + description="Import cameras from the mesh file.") normal_map_format: str = SettingsField( "DirectX", enum_resolver=normal_map_format_enum, title="Normal Map Format", @@ -62,6 +65,12 @@ class ProjectTemplatesModel(BaseSettingsModel): description=("An option to compute tangent space " "when creating new project.") ) + preserve_strokes: bool = SettingsField( + True, title="Preserve Strokes", + description=("Preserve strokes positions on mesh.\n" + "(only relevant when loading into " + "existing project)") + ) class ProjectTemplateSettingModel(BaseSettingsModel): @@ -84,23 +93,29 @@ DEFAULT_LOADER_SETTINGS = { { "name": "2K(Default)", "default_texture_resolution": 2048, + "import_cameras": True, "normal_map_format": "NormalMapFormat.DirectX", "project_workflow": "ProjectWorkflow.Default", - "tangent_space_mode": "TangentSpace.PerFragment" + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True }, { "name": "2K(UV tile)", "default_texture_resolution": 2048, + "import_cameras": True, "normal_map_format": "NormalMapFormat.DirectX", "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment" + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True }, { "name": "4K(Custom)", "default_texture_resolution": 4096, + "import_cameras": True, "normal_map_format": "NormalMapFormat.OpenGL", "project_workflow": "ProjectWorkflow.UVTile", - "tangent_space_mode": "TangentSpace.PerFragment" + "tangent_space_mode": "TangentSpace.PerFragment", + "preserve_strokes": True } ] } From e362b11184923a0404fb315771be4a7a768d3b7a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 16:15:22 +0800 Subject: [PATCH 317/363] refactor the repetitive code into a function --- .../plugins/load/load_mesh.py | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) 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 42a3e5b5b2..f6abfabaf9 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -81,16 +81,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["template_options"].addItem(template) template_name = self.widgets["template_options"].currentText() - - self.import_cameras = next(template["import_cameras"] for - template in self.project_templates - if template["name"] == template_name) - self.preserve_strokes = next(template["preserve_strokes"] for - template in self.project_templates - if template["name"] == template_name) - self.widgets["import_cameras"].setChecked(self.import_cameras) - self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) - + self.get_boolean_setting(template_name) # Build clickboxes layout = QtWidgets.QHBoxLayout(self.widgets["clickbox"]) layout.addWidget(self.widgets["import_cameras"]) @@ -117,14 +108,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) def on_options_changed(self, value): - self.import_cameras = next(template["import_cameras"] for - template in self.project_templates - if template["name"] == value) - self.preserve_strokes = next(template["preserve_strokes"] for - template in self.project_templates - if template["name"] == value) - self.widgets["import_cameras"].setChecked(self.import_cameras) - self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + self.get_boolean_setting(value) def on_ok_pressed(self): if self.widgets["import_cameras"].isChecked(): @@ -140,6 +124,16 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.template_name = None self.close() + def get_boolean_setting(self, template_name): + self.import_cameras = next(template["import_cameras"] for + template in self.project_templates + if template["name"] == template_name) + self.preserve_strokes = next(template["preserve_strokes"] for + template in self.project_templates + if template["name"] == template_name) + self.widgets["import_cameras"].setChecked(self.import_cameras) + self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + @classmethod def prompt(cls, templates): dialog = cls(templates) From e4b5da7850f9036c74308b7cae227ef8798a06eb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 16:26:41 +0800 Subject: [PATCH 318/363] move combobox before the checkboxes in the popup dialog --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f6abfabaf9..2ba5b10034 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -98,8 +98,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) - layout.addWidget(self.widgets["clickbox"]) layout.addWidget(self.widgets["combobox"]) + layout.addWidget(self.widgets["clickbox"]) layout.addWidget(self.widgets["buttons"]) self.widgets["template_options"].currentTextChanged.connect( From c11c8a3cad019428e8f2201025b18608da177988 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 10:51:37 +0200 Subject: [PATCH 319/363] Implement `create_cache_instance_on_load` setting for Yeti Rig loader --- .../hosts/maya/plugins/load/load_yeti_rig.py | 10 +++++++--- server_addon/maya/server/settings/loaders.py | 20 ++++++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) 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 7f076a8273..7444566ee1 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 @@ -20,6 +20,9 @@ class YetiRigLoader(plugin.ReferenceLoader): icon = "code-fork" color = "orange" + # From settings + create_cache_instance_on_load = True + def process_reference( self, context, name=None, namespace=None, options=None ): @@ -54,9 +57,10 @@ class YetiRigLoader(plugin.ReferenceLoader): ) self[:] = nodes - # Automatically create in instance to allow publishing the loaded - # yeti rig into a yeti cache - self._create_yeti_cache_instance(nodes, variant=namespace) + if self.create_cache_instance_on_load: + # Automatically create in instance to allow publishing the loaded + # yeti rig into a yeti cache + self._create_yeti_cache_instance(nodes, variant=namespace) return nodes diff --git a/server_addon/maya/server/settings/loaders.py b/server_addon/maya/server/settings/loaders.py index f59711b1e6..2f104d2858 100644 --- a/server_addon/maya/server/settings/loaders.py +++ b/server_addon/maya/server/settings/loaders.py @@ -103,6 +103,17 @@ class ImportLoaderModel(BaseSettingsModel): group_name: str = SettingsField(title="Group name") +class YetiRigLoaderModel(LoaderEnabledModel): + create_cache_instance_on_load: bool = SettingsField( + title="Create Yeti Cache instance on load", + description=( + "When enabled, upon loading a Yeti Rig product a new Yeti cache " + "instance is automatically created as preparation to publishing " + "the output directly." + ) + ) + + class LoadersModel(BaseSettingsModel): colors: ColorsSetting = SettingsField( default_factory=ColorsSetting, @@ -195,8 +206,8 @@ class LoadersModel(BaseSettingsModel): default_factory=LoaderEnabledModel, title="Yeti Cache Loader" ) - YetiRigLoader: LoaderEnabledModel = SettingsField( - default_factory=LoaderEnabledModel, + YetiRigLoader: YetiRigLoaderModel = SettingsField( + default_factory=YetiRigLoaderModel, title="Yeti Rig Loader" ) @@ -266,5 +277,8 @@ DEFAULT_LOADERS_SETTING = { "VRaySceneLoader": {"enabled": True}, "XgenLoader": {"enabled": True}, "YetiCacheLoader": {"enabled": True}, - "YetiRigLoader": {"enabled": True}, + "YetiRigLoader": { + "enabled": True, + "create_cache_instance_on_load": True + }, } From 4d7a781517ade7df3fe052c03beab9aa0f3d8400 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Apr 2024 10:51:57 +0200 Subject: [PATCH 320/363] 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 75b463f198..c1b7ff9d79 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.15" +__version__ = "0.1.16" From 289d21250d3ef63a2dc2e39466b07af1f10ccef3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 17:24:42 +0800 Subject: [PATCH 321/363] updating the dialog functions --- .../plugins/load/load_mesh.py | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) 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 2ba5b10034..601e723f1f 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -73,9 +73,9 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): "preserve_strokes": QtWidgets.QCheckBox("Preserve Strokes"), "clickbox": QtWidgets.QWidget(), "combobox": QtWidgets.QWidget(), - "buttons": QtWidgets.QWidget(), - "okButton": QtWidgets.QPushButton("Ok"), - "cancelButton": QtWidgets.QPushButton("Cancel") + "buttons": QtWidgets.QDialogButtonBox( + QtWidgets.QDialogButtonBox.Ok + | QtWidgets.QDialogButtonBox.Cancel) } for template in self.template_names: self.widgets["template_options"].addItem(template) @@ -92,9 +92,6 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): # Build buttons layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) - layout.addWidget(self.widgets["okButton"]) - layout.addWidget(self.widgets["cancelButton"]) - # Build layout. layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.widgets["label"]) @@ -104,8 +101,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["template_options"].currentTextChanged.connect( self.on_options_changed) - self.widgets["okButton"].pressed.connect(self.on_ok_pressed) - self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed) + self.widgets["buttons"].accepted.connect(self.on_ok_pressed) + self.widgets["buttons"].rejected.connect(self.on_cancel_pressed) def on_options_changed(self, value): self.get_boolean_setting(value) @@ -134,10 +131,24 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["import_cameras"].setChecked(self.import_cameras) self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + + def get_result(self): + import copy + templates = self.project_templates + name = self.template_name + if not name: + return None + template = get_template_by_name(name, templates) + template = copy.deepcopy(template) # do not edit the original + template["import_cameras"] = self.widgets["import_cameras"].isChecked() + template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() + return template + @classmethod def prompt(cls, templates): dialog = cls(templates) dialog.exec_() + return dialog @@ -157,17 +168,16 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs result = SubstanceProjectConfigurationWindow.prompt( - self.project_templates) - template_name = result.template_name - if template_name is None: + self.project_templates).get_result() + if result is None: return - import_cameras = result.import_cameras - template = get_template_by_name(template_name, self.project_templates) + import_cameras = result["import_cameras"] sp_settings = substance_painter.project.Settings( - normal_map_format=_convert(template["normal_map_format"]), - project_workflow=_convert(template["project_workflow"]), - tangent_space_mode=_convert(template["tangent_space_mode"]), - default_texture_resolution=template["default_texture_resolution"] + normal_map_format=_convert(result["normal_map_format"]), + import_cameras=result["import_cameras"], + project_workflow=_convert(result["project_workflow"]), + tangent_space_mode=_convert(result["tangent_space_mode"]), + default_texture_resolution=result["default_texture_resolution"] ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project @@ -178,7 +188,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh - preserve_strokes = result.preserve_strokes + preserve_strokes = result["preserve_cameras"] settings = substance_painter.project.MeshReloadingSettings( import_cameras=import_cameras, preserve_strokes=preserve_strokes) From 70b11f9a50dcea4571d6155fc98483d77a289222 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 17:26:04 +0800 Subject: [PATCH 322/363] cosmetic fix --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 1 - 1 file changed, 1 deletion(-) 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 601e723f1f..b74c6ca08b 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -131,7 +131,6 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["import_cameras"].setChecked(self.import_cameras) self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) - def get_result(self): import copy templates = self.project_templates From cdfe59fd18270951d1ba6ac9a4ca5afe40ea5573 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 17:27:34 +0800 Subject: [PATCH 323/363] add comment to explain the action --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 ++ 1 file changed, 2 insertions(+) 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 b74c6ca08b..4148c8ab8a 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -136,6 +136,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): templates = self.project_templates name = self.template_name if not name: + # if user close the dialog, + # template name would be None return None template = get_template_by_name(name, templates) template = copy.deepcopy(template) # do not edit the original From 41e4af06da3d7e9cb77d273ce112c168046dd870 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 18:04:57 +0800 Subject: [PATCH 324/363] updating the code with big roy's feedback --- .../plugins/load/load_mesh.py | 65 +++++++++---------- 1 file changed, 30 insertions(+), 35 deletions(-) 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 4148c8ab8a..de99dcbc95 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -1,3 +1,4 @@ +import copy from qtpy import QtWidgets, QtCore from ayon_core.pipeline import ( load, @@ -77,11 +78,11 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) } - for template in self.template_names: - self.widgets["template_options"].addItem(template) + + self.widgets["template_options"].addItem(self.template_names) template_name = self.widgets["template_options"].currentText() - self.get_boolean_setting(template_name) + self._update_to_match_template(template_name) # Build clickboxes layout = QtWidgets.QHBoxLayout(self.widgets["clickbox"]) layout.addWidget(self.widgets["import_cameras"]) @@ -100,14 +101,14 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): layout.addWidget(self.widgets["buttons"]) self.widgets["template_options"].currentTextChanged.connect( - self.on_options_changed) - self.widgets["buttons"].accepted.connect(self.on_ok_pressed) - self.widgets["buttons"].rejected.connect(self.on_cancel_pressed) + self._update_to_match_template) + self.widgets["buttons"].accepted.connect(self.on_accept) + self.widgets["buttons"].rejected.connect(self.on_reject) def on_options_changed(self, value): self.get_boolean_setting(value) - def on_ok_pressed(self): + def on_accept(self): if self.widgets["import_cameras"].isChecked(): self.import_cameras = True if self.widgets["preserve_strokes"].isChecked(): @@ -117,32 +118,28 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): ) self.close() - def on_cancel_pressed(self): - self.template_name = None + def on_reject(self): self.close() - def get_boolean_setting(self, template_name): - self.import_cameras = next(template["import_cameras"] for - template in self.project_templates - if template["name"] == template_name) - self.preserve_strokes = next(template["preserve_strokes"] for - template in self.project_templates - if template["name"] == template_name) - self.widgets["import_cameras"].setChecked(self.import_cameras) - self.widgets["preserve_strokes"].setChecked(self.preserve_strokes) + def _update_to_match_template(self, template_name): + template = get_template_by_name(template_name, self.project_templates) + self.widgets["import_cameras"].setChecked(template["import_cameras"]) + self.widgets["preserve_strokes"].setChecked( + template["preserve_strokes"]) - def get_result(self): - import copy + def get_project_configuration(self): templates = self.project_templates - name = self.template_name - if not name: - # if user close the dialog, - # template name would be None + if not self.template_name: return None - template = get_template_by_name(name, templates) + template = get_template_by_name(self.template_name, templates) template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() + for key in template.keys(): + if key in ["normal_map_format", + "project_workflow", + "tangent_space_mode"]: + template[key] = _convert(template[key]) return template @classmethod @@ -150,7 +147,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): dialog = cls(templates) dialog.exec_() - return dialog + return dialog.get_project_configuration() class SubstanceLoadProjectMesh(load.LoaderPlugin): @@ -169,15 +166,14 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs result = SubstanceProjectConfigurationWindow.prompt( - self.project_templates).get_result() + self.project_templates) if result is None: return - import_cameras = result["import_cameras"] sp_settings = substance_painter.project.Settings( - normal_map_format=_convert(result["normal_map_format"]), + normal_map_format=result["normal_map_format"], import_cameras=result["import_cameras"], - project_workflow=_convert(result["project_workflow"]), - tangent_space_mode=_convert(result["tangent_space_mode"]), + project_workflow=result["project_workflow"], + tangent_space_mode=result["tangent_space_mode"], default_texture_resolution=result["default_texture_resolution"] ) if not substance_painter.project.is_open(): @@ -189,10 +185,9 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): ) else: # Reload the mesh - preserve_strokes = result["preserve_cameras"] settings = substance_painter.project.MeshReloadingSettings( - import_cameras=import_cameras, - preserve_strokes=preserve_strokes) + import_cameras=result["import_cameras"], + preserve_strokes=result["preserve_strokes"]) def on_mesh_reload(status: substance_painter.project.ReloadMeshStatus): # noqa if status == substance_painter.project.ReloadMeshStatus.SUCCESS: # noqa @@ -219,7 +214,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # as we always preserve strokes on updates. # TODO: update the code container["options"] = { - "import_cameras": import_cameras, + "import_cameras": result["import_cameras"], } set_container_metadata(project_mesh_object_name, container) From bb225a3f66841d2487be8168a7b412ea335ef7f5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 18:33:03 +0800 Subject: [PATCH 325/363] clean up the dialog code --- .../plugins/load/load_mesh.py | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) 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 de99dcbc95..623f5a175f 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -59,9 +59,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): super(SubstanceProjectConfigurationWindow, self).__init__() self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint) - self.import_cameras = False - self.preserve_strokes = False - self.template_name = None + self.configuration = None self.template_names = [template["name"] for template in project_templates] self.project_templates = project_templates @@ -79,7 +77,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): | QtWidgets.QDialogButtonBox.Cancel) } - self.widgets["template_options"].addItem(self.template_names) + self.widgets["template_options"].addItems(self.template_names) template_name = self.widgets["template_options"].currentText() self._update_to_match_template(template_name) @@ -90,7 +88,6 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): # Build combobox layout = QtWidgets.QHBoxLayout(self.widgets["combobox"]) layout.addWidget(self.widgets["template_options"]) - # Build buttons layout = QtWidgets.QHBoxLayout(self.widgets["buttons"]) # Build layout. @@ -105,17 +102,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): self.widgets["buttons"].accepted.connect(self.on_accept) self.widgets["buttons"].rejected.connect(self.on_reject) - def on_options_changed(self, value): - self.get_boolean_setting(value) - def on_accept(self): - if self.widgets["import_cameras"].isChecked(): - self.import_cameras = True - if self.widgets["preserve_strokes"].isChecked(): - self.preserve_strokes = True - self.template_name = ( - self.widgets["template_options"].currentText() - ) + self.configuration = self.get_project_configuration() self.close() def on_reject(self): @@ -129,9 +117,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): def get_project_configuration(self): templates = self.project_templates - if not self.template_name: - return None - template = get_template_by_name(self.template_name, templates) + template_name = self.widgets["template_options"].currentText() + template = get_template_by_name(template_name, templates) template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() @@ -147,7 +134,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): dialog = cls(templates) dialog.exec_() - return dialog.get_project_configuration() + return dialog.configuration class SubstanceLoadProjectMesh(load.LoaderPlugin): @@ -167,15 +154,14 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs result = SubstanceProjectConfigurationWindow.prompt( self.project_templates) - if result is None: - return sp_settings = substance_painter.project.Settings( - normal_map_format=result["normal_map_format"], import_cameras=result["import_cameras"], + normal_map_format=result["normal_map_format"], project_workflow=result["project_workflow"], tangent_space_mode=result["tangent_space_mode"], default_texture_resolution=result["default_texture_resolution"] ) + print(sp_settings) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) From a46489edc38f9cd2ff9adf3ab8101a6cf76f1e79 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 19:15:46 +0800 Subject: [PATCH 326/363] add deleteLater() after dialog.exec_ --- .../substancepainter/plugins/load/load_mesh.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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 623f5a175f..b103ef8e8f 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -16,7 +16,7 @@ import substance_painter.project def _convert(substance_attr): """Return Substance Painter Python API Project attribute from string. - + This converts a string like "ProjectWorkflow.Default" to for example the Substance Painter Python API equivalent object, like: `substance_painter.project.ProjectWorkflow.Default` @@ -122,16 +122,16 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() - for key in template.keys(): - if key in ["normal_map_format", - "project_workflow", - "tangent_space_mode"]: - template[key] = _convert(template[key]) + for key in ["normal_map_format", + "project_workflow", + "tangent_space_mode"]: + template[key] = _convert(template[key]) return template @classmethod def prompt(cls, templates): dialog = cls(templates) + dialog.deleteLater() dialog.exec_() return dialog.configuration @@ -147,6 +147,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): order = -10 icon = "code-fork" color = "orange" + + # Defined via settings project_templates = [] def load(self, context, name, namespace, options=None): @@ -161,7 +163,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): tangent_space_mode=result["tangent_space_mode"], default_texture_resolution=result["default_texture_resolution"] ) - print(sp_settings) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) From 7b68bb84983888e492e03c7fcd8ee2a881b8acd4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 19:17:46 +0800 Subject: [PATCH 327/363] ensure to have no error after pressing cancel button --- .../hosts/substancepainter/plugins/load/load_mesh.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 b103ef8e8f..8e61a8c4e5 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -131,8 +131,8 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): @classmethod def prompt(cls, templates): dialog = cls(templates) - dialog.deleteLater() dialog.exec_() + dialog.deleteLater() return dialog.configuration @@ -156,6 +156,8 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # Get user inputs result = SubstanceProjectConfigurationWindow.prompt( self.project_templates) + if not result: + return sp_settings = substance_painter.project.Settings( import_cameras=result["import_cameras"], normal_map_format=result["normal_map_format"], From dd11cf95caa2eb0faec7d3f2348ed2a00adf216c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 19:18:23 +0800 Subject: [PATCH 328/363] add comment on the condition on checking result variable --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 1 + 1 file changed, 1 insertion(+) 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 8e61a8c4e5..a6d8aef3c0 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -157,6 +157,7 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): result = SubstanceProjectConfigurationWindow.prompt( self.project_templates) if not result: + # cancelling loader action return sp_settings = substance_painter.project.Settings( import_cameras=result["import_cameras"], From 369321b18c30f93e46b044bd57cf7623cba4f44a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 19:43:40 +0800 Subject: [PATCH 329/363] cosmetic fix --- .../hosts/substancepainter/plugins/load/load_mesh.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 a6d8aef3c0..0764789b66 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -121,10 +121,12 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): template = get_template_by_name(template_name, templates) template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() - template["preserve_strokes"] = self.widgets["preserve_strokes"].isChecked() + template["preserve_strokes"] = ( + self.widgets["preserve_strokes"].isChecked() + ) for key in ["normal_map_format", - "project_workflow", - "tangent_space_mode"]: + "project_workflow", + "tangent_space_mode"]: template[key] = _convert(template[key]) return template From 9173e77e3066232e97a3335f2577de7180148f69 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:23:08 +0800 Subject: [PATCH 330/363] Update client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py Co-authored-by: Roy Nieterau --- .../hosts/substancepainter/plugins/load/load_mesh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 0764789b66..1a5ca1aec3 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -134,9 +134,9 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): def prompt(cls, templates): dialog = cls(templates) dialog.exec_() + configuration = dialog.configuration dialog.deleteLater() - - return dialog.configuration + return configuration class SubstanceLoadProjectMesh(load.LoaderPlugin): From 7ebdeeae26230e164f6c7451f8e56657fac1c05d Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:23:19 +0800 Subject: [PATCH 331/363] cosmetic Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1a5ca1aec3..e5cfa469ed 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -119,7 +119,7 @@ class SubstanceProjectConfigurationWindow(QtWidgets.QDialog): templates = self.project_templates template_name = self.widgets["template_options"].currentText() template = get_template_by_name(template_name, templates) - template = copy.deepcopy(template) # do not edit the original + template = copy.deepcopy(template) # do not edit the original template["import_cameras"] = self.widgets["import_cameras"].isChecked() template["preserve_strokes"] = ( self.widgets["preserve_strokes"].isChecked() From 0ec9d1e99365e009d82c2ecc28fb78210a3300a3 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Fri, 19 Apr 2024 20:23:36 +0800 Subject: [PATCH 332/363] Remove unnecessary comment Co-authored-by: Roy Nieterau --- .../ayon_core/hosts/substancepainter/plugins/load/load_mesh.py | 1 - 1 file changed, 1 deletion(-) 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 e5cfa469ed..6a67f5c686 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -204,7 +204,6 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): # We want store some options for updating to keep consistent behavior # from the user's original choice. We don't store 'preserve_strokes' # as we always preserve strokes on updates. - # TODO: update the code container["options"] = { "import_cameras": result["import_cameras"], } From efd0e0774deaa7a76c45cc9be039f71bea95dbfb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 19 Apr 2024 21:43:47 +0800 Subject: [PATCH 333/363] move sp_setting into if condition --- .../substancepainter/plugins/load/load_mesh.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 6a67f5c686..d5aac1191c 100644 --- a/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py +++ b/client/ayon_core/hosts/substancepainter/plugins/load/load_mesh.py @@ -161,17 +161,16 @@ class SubstanceLoadProjectMesh(load.LoaderPlugin): if not result: # cancelling loader action return - sp_settings = substance_painter.project.Settings( - import_cameras=result["import_cameras"], - normal_map_format=result["normal_map_format"], - project_workflow=result["project_workflow"], - tangent_space_mode=result["tangent_space_mode"], - default_texture_resolution=result["default_texture_resolution"] - ) if not substance_painter.project.is_open(): # Allow to 'initialize' a new project path = self.filepath_from_context(context) - + sp_settings = substance_painter.project.Settings( + import_cameras=result["import_cameras"], + normal_map_format=result["normal_map_format"], + project_workflow=result["project_workflow"], + tangent_space_mode=result["tangent_space_mode"], + default_texture_resolution=result["default_texture_resolution"] + ) settings = substance_painter.project.create( mesh_file_path=path, settings=sp_settings ) From 814220e64a665ac38f051cfe5f09e5c9cf3e9295 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:24:23 +0200 Subject: [PATCH 334/363] unreal uses package.py --- server_addon/unreal/package.py | 3 +++ server_addon/unreal/server/__init__.py | 6 ------ server_addon/unreal/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/unreal/package.py delete mode 100644 server_addon/unreal/server/version.py diff --git a/server_addon/unreal/package.py b/server_addon/unreal/package.py new file mode 100644 index 0000000000..cab89ca873 --- /dev/null +++ b/server_addon/unreal/package.py @@ -0,0 +1,3 @@ +name = "unreal" +title = "Unreal" +version = "0.1.0" diff --git a/server_addon/unreal/server/__init__.py b/server_addon/unreal/server/__init__.py index a5f3e9597d..751560b623 100644 --- a/server_addon/unreal/server/__init__.py +++ b/server_addon/unreal/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import UnrealSettings, DEFAULT_VALUES class UnrealAddon(BaseServerAddon): - name = "unreal" - title = "Unreal" - version = __version__ settings_model: Type[UnrealSettings] = UnrealSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/unreal/server/version.py b/server_addon/unreal/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/unreal/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 36c4bc8667632772b4802dd8c6962bacfd5310c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:24:34 +0200 Subject: [PATCH 335/363] tvpaint uses package.py --- server_addon/tvpaint/package.py | 3 +++ server_addon/tvpaint/server/__init__.py | 4 ---- server_addon/tvpaint/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/tvpaint/package.py delete mode 100644 server_addon/tvpaint/server/version.py diff --git a/server_addon/tvpaint/package.py b/server_addon/tvpaint/package.py new file mode 100644 index 0000000000..2be3164f4a --- /dev/null +++ b/server_addon/tvpaint/package.py @@ -0,0 +1,3 @@ +name = "tvpaint" +title = "TVPaint" +version = "0.1.2" diff --git a/server_addon/tvpaint/server/__init__.py b/server_addon/tvpaint/server/__init__.py index 033d7d3792..658dcf0bb6 100644 --- a/server_addon/tvpaint/server/__init__.py +++ b/server_addon/tvpaint/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import TvpaintSettings, DEFAULT_VALUES class TvpaintAddon(BaseServerAddon): - name = "tvpaint" - title = "TVPaint" - version = __version__ settings_model: Type[TvpaintSettings] = TvpaintSettings async def get_default_settings(self): diff --git a/server_addon/tvpaint/server/version.py b/server_addon/tvpaint/server/version.py deleted file mode 100644 index b3f4756216..0000000000 --- a/server_addon/tvpaint/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.2" From 7184ee5c5ca8c2d7dc1b2784b094afe6e1d7632b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:24:51 +0200 Subject: [PATCH 336/363] traypublisher uses package.py --- server_addon/traypublisher/package.py | 3 +++ server_addon/traypublisher/server/__init__.py | 5 ----- server_addon/traypublisher/server/version.py | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 server_addon/traypublisher/package.py delete mode 100644 server_addon/traypublisher/server/version.py diff --git a/server_addon/traypublisher/package.py b/server_addon/traypublisher/package.py new file mode 100644 index 0000000000..4ca8ae9fd3 --- /dev/null +++ b/server_addon/traypublisher/package.py @@ -0,0 +1,3 @@ +name = "traypublisher" +title = "TrayPublisher" +version = "0.1.4" diff --git a/server_addon/traypublisher/server/__init__.py b/server_addon/traypublisher/server/__init__.py index e6f079609f..830f325ac0 100644 --- a/server_addon/traypublisher/server/__init__.py +++ b/server_addon/traypublisher/server/__init__.py @@ -1,14 +1,9 @@ from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import TraypublisherSettings, DEFAULT_TRAYPUBLISHER_SETTING class Traypublisher(BaseServerAddon): - name = "traypublisher" - title = "TrayPublisher" - version = __version__ - settings_model = TraypublisherSettings async def get_default_settings(self): diff --git a/server_addon/traypublisher/server/version.py b/server_addon/traypublisher/server/version.py deleted file mode 100644 index de699158fd..0000000000 --- a/server_addon/traypublisher/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.4" From 76482b3bfbde769232f89383b9d071e11dfe6a89 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:02 +0200 Subject: [PATCH 337/363] timers manager uses package.py --- server_addon/timers_manager/package.py | 3 +++ server_addon/timers_manager/server/__init__.py | 4 ---- server_addon/timers_manager/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/timers_manager/package.py delete mode 100644 server_addon/timers_manager/server/version.py diff --git a/server_addon/timers_manager/package.py b/server_addon/timers_manager/package.py new file mode 100644 index 0000000000..bd6b81b4b7 --- /dev/null +++ b/server_addon/timers_manager/package.py @@ -0,0 +1,3 @@ +name = "timers_manager" +title = "Timers Manager" +version = "0.1.1" diff --git a/server_addon/timers_manager/server/__init__.py b/server_addon/timers_manager/server/__init__.py index 29f9d47370..32e83d295c 100644 --- a/server_addon/timers_manager/server/__init__.py +++ b/server_addon/timers_manager/server/__init__.py @@ -2,12 +2,8 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import TimersManagerSettings class TimersManagerAddon(BaseServerAddon): - name = "timers_manager" - version = __version__ - title = "Timers Manager" settings_model: Type[TimersManagerSettings] = TimersManagerSettings diff --git a/server_addon/timers_manager/server/version.py b/server_addon/timers_manager/server/version.py deleted file mode 100644 index 485f44ac21..0000000000 --- a/server_addon/timers_manager/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.1" From 41b95ff1869f2abdc1b5f702eda898e2e566c6c1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:12 +0200 Subject: [PATCH 338/363] substancepainter uses package.py --- server_addon/substancepainter/package.py | 3 +++ server_addon/substancepainter/server/__init__.py | 4 ---- server_addon/substancepainter/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/substancepainter/package.py delete mode 100644 server_addon/substancepainter/server/version.py diff --git a/server_addon/substancepainter/package.py b/server_addon/substancepainter/package.py new file mode 100644 index 0000000000..a064d80fd7 --- /dev/null +++ b/server_addon/substancepainter/package.py @@ -0,0 +1,3 @@ +name = "substancepainter" +title = "Substance Painter" +version = "0.1.0" diff --git a/server_addon/substancepainter/server/__init__.py b/server_addon/substancepainter/server/__init__.py index 2bf808d508..f6cd51e610 100644 --- a/server_addon/substancepainter/server/__init__.py +++ b/server_addon/substancepainter/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import SubstancePainterSettings, DEFAULT_SPAINTER_SETTINGS class SubstancePainterAddon(BaseServerAddon): - name = "substancepainter" - title = "Substance Painter" - version = __version__ settings_model: Type[SubstancePainterSettings] = SubstancePainterSettings async def get_default_settings(self): diff --git a/server_addon/substancepainter/server/version.py b/server_addon/substancepainter/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/substancepainter/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 10823b40a9f7ae35f27e60ead1301b7612edfb88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:33 +0200 Subject: [PATCH 339/363] resolve uses package.py --- server_addon/resolve/package.py | 3 +++ server_addon/resolve/server/__init__.py | 6 ------ server_addon/resolve/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/resolve/package.py delete mode 100644 server_addon/resolve/server/version.py diff --git a/server_addon/resolve/package.py b/server_addon/resolve/package.py new file mode 100644 index 0000000000..cf92413bce --- /dev/null +++ b/server_addon/resolve/package.py @@ -0,0 +1,3 @@ +name = "resolve" +title = "DaVinci Resolve" +version = "0.1.0" diff --git a/server_addon/resolve/server/__init__.py b/server_addon/resolve/server/__init__.py index a84180d0f5..35d2db19e4 100644 --- a/server_addon/resolve/server/__init__.py +++ b/server_addon/resolve/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import ResolveSettings, DEFAULT_VALUES class ResolveAddon(BaseServerAddon): - name = "resolve" - title = "DaVinci Resolve" - version = __version__ settings_model: Type[ResolveSettings] = ResolveSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/resolve/server/version.py b/server_addon/resolve/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/resolve/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 8749bdea3c7d34006b536c1c944ef05b5ab23271 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:42 +0200 Subject: [PATCH 340/363] photoshop uses package.py --- server_addon/photoshop/package.py | 3 +++ server_addon/photoshop/server/__init__.py | 5 ----- server_addon/photoshop/server/version.py | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 server_addon/photoshop/package.py delete mode 100644 server_addon/photoshop/server/version.py diff --git a/server_addon/photoshop/package.py b/server_addon/photoshop/package.py new file mode 100644 index 0000000000..25615529d1 --- /dev/null +++ b/server_addon/photoshop/package.py @@ -0,0 +1,3 @@ +name = "photoshop" +title = "Photoshop" +version = "0.1.2" diff --git a/server_addon/photoshop/server/__init__.py b/server_addon/photoshop/server/__init__.py index 3a45f7a809..86d1025a2d 100644 --- a/server_addon/photoshop/server/__init__.py +++ b/server_addon/photoshop/server/__init__.py @@ -1,14 +1,9 @@ from ayon_server.addons import BaseServerAddon from .settings import PhotoshopSettings, DEFAULT_PHOTOSHOP_SETTING -from .version import __version__ class Photoshop(BaseServerAddon): - name = "photoshop" - title = "Photoshop" - version = __version__ - settings_model = PhotoshopSettings async def get_default_settings(self): diff --git a/server_addon/photoshop/server/version.py b/server_addon/photoshop/server/version.py deleted file mode 100644 index df0c92f1e2..0000000000 --- a/server_addon/photoshop/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.2" From 8c6c5deca201e3875c809eb05f6225b65136a61c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:25:51 +0200 Subject: [PATCH 341/363] nuke uses package.py --- server_addon/nuke/package.py | 3 +++ server_addon/nuke/server/__init__.py | 4 ---- server_addon/nuke/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/nuke/package.py delete mode 100644 server_addon/nuke/server/version.py diff --git a/server_addon/nuke/package.py b/server_addon/nuke/package.py new file mode 100644 index 0000000000..9630c370bc --- /dev/null +++ b/server_addon/nuke/package.py @@ -0,0 +1,3 @@ +name = "nuke" +title = "Nuke" +version = "0.1.10" diff --git a/server_addon/nuke/server/__init__.py b/server_addon/nuke/server/__init__.py index 032ceea5fb..aeb5e36675 100644 --- a/server_addon/nuke/server/__init__.py +++ b/server_addon/nuke/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import NukeSettings, DEFAULT_VALUES class NukeAddon(BaseServerAddon): - name = "nuke" - title = "Nuke" - version = __version__ settings_model: Type[NukeSettings] = NukeSettings async def get_default_settings(self): diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py deleted file mode 100644 index 569b1212f7..0000000000 --- a/server_addon/nuke/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.10" From d5100a1cc56ac6667d885b1c5f2586c4d17bc6e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:01 +0200 Subject: [PATCH 342/363] maya uses package.py --- server_addon/maya/package.py | 3 +++ server_addon/maya/server/__init__.py | 4 ---- server_addon/maya/server/version.py | 3 --- 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/maya/package.py delete mode 100644 server_addon/maya/server/version.py diff --git a/server_addon/maya/package.py b/server_addon/maya/package.py new file mode 100644 index 0000000000..00f28d901e --- /dev/null +++ b/server_addon/maya/package.py @@ -0,0 +1,3 @@ +name = "maya" +title = "Maya" +version = "0.1.16" diff --git a/server_addon/maya/server/__init__.py b/server_addon/maya/server/__init__.py index 8784427dcf..6dda2cdd77 100644 --- a/server_addon/maya/server/__init__.py +++ b/server_addon/maya/server/__init__.py @@ -2,13 +2,9 @@ from ayon_server.addons import BaseServerAddon from .settings.main import MayaSettings, DEFAULT_MAYA_SETTING -from .version import __version__ class MayaAddon(BaseServerAddon): - name = "maya" - title = "Maya" - version = __version__ settings_model = MayaSettings async def get_default_settings(self): diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py deleted file mode 100644 index c1b7ff9d79..0000000000 --- a/server_addon/maya/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.16" From 73782cf148d03e85fb5ee9a870296065e4cd0cdc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:10 +0200 Subject: [PATCH 343/363] 3dsmax uses package.py --- server_addon/max/package.py | 3 +++ server_addon/max/server/__init__.py | 4 ---- server_addon/max/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/max/package.py delete mode 100644 server_addon/max/server/version.py diff --git a/server_addon/max/package.py b/server_addon/max/package.py new file mode 100644 index 0000000000..fb1f1b3050 --- /dev/null +++ b/server_addon/max/package.py @@ -0,0 +1,3 @@ +name = "max" +title = "Max" +version = "0.1.7" diff --git a/server_addon/max/server/__init__.py b/server_addon/max/server/__init__.py index 31c694a084..d03b29d249 100644 --- a/server_addon/max/server/__init__.py +++ b/server_addon/max/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import MaxSettings, DEFAULT_VALUES class MaxAddon(BaseServerAddon): - name = "max" - title = "Max" - version = __version__ settings_model: Type[MaxSettings] = MaxSettings async def get_default_settings(self): diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py deleted file mode 100644 index f1380eede2..0000000000 --- a/server_addon/max/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.7" From 0e7c112d8d9628fd66ca91a93be2771fc23f3b84 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:24 +0200 Subject: [PATCH 344/363] houdini uses package.py --- server_addon/houdini/package.py | 3 +++ server_addon/houdini/server/__init__.py | 4 ---- server_addon/houdini/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/houdini/package.py delete mode 100644 server_addon/houdini/server/version.py diff --git a/server_addon/houdini/package.py b/server_addon/houdini/package.py new file mode 100644 index 0000000000..4b72af2a89 --- /dev/null +++ b/server_addon/houdini/package.py @@ -0,0 +1,3 @@ +name = "houdini" +title = "Houdini" +version = "0.2.12" diff --git a/server_addon/houdini/server/__init__.py b/server_addon/houdini/server/__init__.py index 870ec2d0b7..8c1ffcb0b3 100644 --- a/server_addon/houdini/server/__init__.py +++ b/server_addon/houdini/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import HoudiniSettings, DEFAULT_VALUES class Houdini(BaseServerAddon): - name = "houdini" - title = "Houdini" - version = __version__ settings_model: Type[HoudiniSettings] = HoudiniSettings async def get_default_settings(self): diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py deleted file mode 100644 index b5c9b6cb71..0000000000 --- a/server_addon/houdini/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.2.12" From 85fe7f0de73708b5e113d6b37e4a560526b43800 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:31 +0200 Subject: [PATCH 345/363] hiero uses package.py --- server_addon/hiero/package.py | 3 +++ server_addon/hiero/server/__init__.py | 6 ------ server_addon/hiero/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/hiero/package.py delete mode 100644 server_addon/hiero/server/version.py diff --git a/server_addon/hiero/package.py b/server_addon/hiero/package.py new file mode 100644 index 0000000000..cabe68eb68 --- /dev/null +++ b/server_addon/hiero/package.py @@ -0,0 +1,3 @@ +name = "hiero" +title = "Hiero" +version = "0.1.2" diff --git a/server_addon/hiero/server/__init__.py b/server_addon/hiero/server/__init__.py index d0f9bcefc3..3db78eafd7 100644 --- a/server_addon/hiero/server/__init__.py +++ b/server_addon/hiero/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import HieroSettings, DEFAULT_VALUES class HieroAddon(BaseServerAddon): - name = "hiero" - title = "Hiero" - version = __version__ settings_model: Type[HieroSettings] = HieroSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/hiero/server/version.py b/server_addon/hiero/server/version.py deleted file mode 100644 index b3f4756216..0000000000 --- a/server_addon/hiero/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.2" From 2b1e67c88f1a01aa39b3b1ef20f7109991b45b43 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:38 +0200 Subject: [PATCH 346/363] harmony uses package.py --- server_addon/harmony/package.py | 3 +++ server_addon/harmony/server/__init__.py | 5 ----- server_addon/harmony/server/version.py | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 server_addon/harmony/package.py delete mode 100644 server_addon/harmony/server/version.py diff --git a/server_addon/harmony/package.py b/server_addon/harmony/package.py new file mode 100644 index 0000000000..83e88e7d57 --- /dev/null +++ b/server_addon/harmony/package.py @@ -0,0 +1,3 @@ +name = "harmony" +title = "Harmony" +version = "0.1.2" diff --git a/server_addon/harmony/server/__init__.py b/server_addon/harmony/server/__init__.py index 4ecda1989e..154618241e 100644 --- a/server_addon/harmony/server/__init__.py +++ b/server_addon/harmony/server/__init__.py @@ -1,14 +1,9 @@ from ayon_server.addons import BaseServerAddon from .settings import HarmonySettings, DEFAULT_HARMONY_SETTING -from .version import __version__ class Harmony(BaseServerAddon): - name = "harmony" - title = "Harmony" - version = __version__ - settings_model = HarmonySettings async def get_default_settings(self): diff --git a/server_addon/harmony/server/version.py b/server_addon/harmony/server/version.py deleted file mode 100644 index df0c92f1e2..0000000000 --- a/server_addon/harmony/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.2" From a1c3e57709862c015f568f37a92cadc026c083ef Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:46 +0200 Subject: [PATCH 347/363] fusion uses package.py --- server_addon/fusion/package.py | 3 +++ server_addon/fusion/server/__init__.py | 6 ------ server_addon/fusion/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/fusion/package.py delete mode 100644 server_addon/fusion/server/version.py diff --git a/server_addon/fusion/package.py b/server_addon/fusion/package.py new file mode 100644 index 0000000000..9e7a46df2c --- /dev/null +++ b/server_addon/fusion/package.py @@ -0,0 +1,3 @@ +name = "fusion" +title = "Fusion" +version = "0.1.5" diff --git a/server_addon/fusion/server/__init__.py b/server_addon/fusion/server/__init__.py index 4d43f28812..0456cfd5ee 100644 --- a/server_addon/fusion/server/__init__.py +++ b/server_addon/fusion/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import FusionSettings, DEFAULT_VALUES class FusionAddon(BaseServerAddon): - name = "fusion" - title = "Fusion" - version = __version__ settings_model: Type[FusionSettings] = FusionSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py deleted file mode 100644 index 1276d0254f..0000000000 --- a/server_addon/fusion/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.5" From 78e1c08fac15da24f41d8109db52ebf6a13e5f10 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:26:52 +0200 Subject: [PATCH 348/363] flame uses package.py --- server_addon/flame/package.py | 3 +++ server_addon/flame/server/__init__.py | 6 ------ server_addon/flame/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/flame/package.py delete mode 100644 server_addon/flame/server/version.py diff --git a/server_addon/flame/package.py b/server_addon/flame/package.py new file mode 100644 index 0000000000..8c077ed91d --- /dev/null +++ b/server_addon/flame/package.py @@ -0,0 +1,3 @@ +name = "flame" +title = "Flame" +version = "0.1.0" diff --git a/server_addon/flame/server/__init__.py b/server_addon/flame/server/__init__.py index 7d5eb3960f..4aa46617ee 100644 --- a/server_addon/flame/server/__init__.py +++ b/server_addon/flame/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import FlameSettings, DEFAULT_VALUES class FlameAddon(BaseServerAddon): - name = "flame" - title = "Flame" - version = __version__ settings_model: Type[FlameSettings] = FlameSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/flame/server/version.py b/server_addon/flame/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/flame/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From 286156d1ff0ec5f47600b4040442576130d09aef Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:01 +0200 Subject: [PATCH 349/363] deadline uses package.py --- server_addon/deadline/package.py | 3 +++ server_addon/deadline/server/__init__.py | 4 ---- server_addon/deadline/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/deadline/package.py delete mode 100644 server_addon/deadline/server/version.py diff --git a/server_addon/deadline/package.py b/server_addon/deadline/package.py new file mode 100644 index 0000000000..944797fea6 --- /dev/null +++ b/server_addon/deadline/package.py @@ -0,0 +1,3 @@ +name = "deadline" +title = "Deadline" +version = "0.1.10" diff --git a/server_addon/deadline/server/__init__.py b/server_addon/deadline/server/__init__.py index 36d04189a9..e7dcb7d347 100644 --- a/server_addon/deadline/server/__init__.py +++ b/server_addon/deadline/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import DeadlineSettings, DEFAULT_VALUES class Deadline(BaseServerAddon): - name = "deadline" - title = "Deadline" - version = __version__ settings_model: Type[DeadlineSettings] = DeadlineSettings async def get_default_settings(self): diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py deleted file mode 100644 index 569b1212f7..0000000000 --- a/server_addon/deadline/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.10" From 35c4176c3d9b23a430e0182e04bc5be1c0feadd8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:13 +0200 Subject: [PATCH 350/363] clockify uses package.py --- server_addon/clockify/package.py | 3 +++ server_addon/clockify/server/__init__.py | 6 ------ server_addon/clockify/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/clockify/package.py delete mode 100644 server_addon/clockify/server/version.py diff --git a/server_addon/clockify/package.py b/server_addon/clockify/package.py new file mode 100644 index 0000000000..bcf9425b3f --- /dev/null +++ b/server_addon/clockify/package.py @@ -0,0 +1,3 @@ +name = "clockify" +title = "Clockify" +version = "0.1.1" diff --git a/server_addon/clockify/server/__init__.py b/server_addon/clockify/server/__init__.py index 0fa453fdf4..11bbfed261 100644 --- a/server_addon/clockify/server/__init__.py +++ b/server_addon/clockify/server/__init__.py @@ -2,14 +2,8 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import ClockifySettings class ClockifyAddon(BaseServerAddon): - name = "clockify" - title = "Clockify" - version = __version__ settings_model: Type[ClockifySettings] = ClockifySettings - frontend_scopes = {} - services = {} diff --git a/server_addon/clockify/server/version.py b/server_addon/clockify/server/version.py deleted file mode 100644 index 485f44ac21..0000000000 --- a/server_addon/clockify/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.1" From 2b957195198ebf2ccef6747b43e0af2fbcbffd0d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:22 +0200 Subject: [PATCH 351/363] celaction uses package.py --- server_addon/celaction/package.py | 3 +++ server_addon/celaction/server/__init__.py | 6 ------ server_addon/celaction/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/celaction/package.py delete mode 100644 server_addon/celaction/server/version.py diff --git a/server_addon/celaction/package.py b/server_addon/celaction/package.py new file mode 100644 index 0000000000..2b11a8630f --- /dev/null +++ b/server_addon/celaction/package.py @@ -0,0 +1,3 @@ +name = "celaction" +title = "CelAction" +version = "0.1.0" diff --git a/server_addon/celaction/server/__init__.py b/server_addon/celaction/server/__init__.py index 90d3dbaa01..e3769a4b7f 100644 --- a/server_addon/celaction/server/__init__.py +++ b/server_addon/celaction/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import CelActionSettings, DEFAULT_VALUES class CelActionAddon(BaseServerAddon): - name = "celaction" - title = "CelAction" - version = __version__ settings_model: Type[CelActionSettings] = CelActionSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/celaction/server/version.py b/server_addon/celaction/server/version.py deleted file mode 100644 index 3dc1f76bc6..0000000000 --- a/server_addon/celaction/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.0" From ce42a71555a1d3f3a399a668f9b4bb7eeb489699 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:32 +0200 Subject: [PATCH 352/363] blender uses package.py --- server_addon/blender/package.py | 3 +++ server_addon/blender/server/__init__.py | 6 ------ server_addon/blender/server/version.py | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) create mode 100644 server_addon/blender/package.py delete mode 100644 server_addon/blender/server/version.py diff --git a/server_addon/blender/package.py b/server_addon/blender/package.py new file mode 100644 index 0000000000..667076e533 --- /dev/null +++ b/server_addon/blender/package.py @@ -0,0 +1,3 @@ +name = "blender" +title = "Blender" +version = "0.1.8" diff --git a/server_addon/blender/server/__init__.py b/server_addon/blender/server/__init__.py index a7d6cb4400..b274e3bc29 100644 --- a/server_addon/blender/server/__init__.py +++ b/server_addon/blender/server/__init__.py @@ -2,17 +2,11 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import BlenderSettings, DEFAULT_VALUES class BlenderAddon(BaseServerAddon): - name = "blender" - title = "Blender" - version = __version__ settings_model: Type[BlenderSettings] = BlenderSettings - frontend_scopes = {} - services = {} async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/blender/server/version.py b/server_addon/blender/server/version.py deleted file mode 100644 index 9cb17e7976..0000000000 --- a/server_addon/blender/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.8" From acaafa9b66b16fe243ee8e49f8afe1eacc9d8221 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:42 +0200 Subject: [PATCH 353/363] aftereffects uses package.py --- server_addon/aftereffects/package.py | 3 +++ server_addon/aftereffects/server/__init__.py | 5 ----- server_addon/aftereffects/server/version.py | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 server_addon/aftereffects/package.py delete mode 100644 server_addon/aftereffects/server/version.py diff --git a/server_addon/aftereffects/package.py b/server_addon/aftereffects/package.py new file mode 100644 index 0000000000..a680b37602 --- /dev/null +++ b/server_addon/aftereffects/package.py @@ -0,0 +1,3 @@ +name = "aftereffects" +title = "AfterEffects" +version = "0.1.3" diff --git a/server_addon/aftereffects/server/__init__.py b/server_addon/aftereffects/server/__init__.py index e14e76e9db..76e6d5b2eb 100644 --- a/server_addon/aftereffects/server/__init__.py +++ b/server_addon/aftereffects/server/__init__.py @@ -1,14 +1,9 @@ from ayon_server.addons import BaseServerAddon from .settings import AfterEffectsSettings, DEFAULT_AFTEREFFECTS_SETTING -from .version import __version__ class AfterEffects(BaseServerAddon): - name = "aftereffects" - title = "AfterEffects" - version = __version__ - settings_model = AfterEffectsSettings async def get_default_settings(self): diff --git a/server_addon/aftereffects/server/version.py b/server_addon/aftereffects/server/version.py deleted file mode 100644 index e57ad00718..0000000000 --- a/server_addon/aftereffects/server/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -"""Package declaring addon version.""" -__version__ = "0.1.3" From bc9a0dc59801b1a86b48d2b00fc7419e9ed548f6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:27:51 +0200 Subject: [PATCH 354/363] royal render uses package.py --- server_addon/royal_render/package.py | 3 +++ server_addon/royal_render/server/__init__.py | 4 ---- server_addon/royal_render/server/version.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 server_addon/royal_render/package.py delete mode 100644 server_addon/royal_render/server/version.py diff --git a/server_addon/royal_render/package.py b/server_addon/royal_render/package.py new file mode 100644 index 0000000000..1fdea4abbb --- /dev/null +++ b/server_addon/royal_render/package.py @@ -0,0 +1,3 @@ +name = "royalrender" +title = "Royal Render" +version = "0.1.1" diff --git a/server_addon/royal_render/server/__init__.py b/server_addon/royal_render/server/__init__.py index c5f0aafa00..5b10678136 100644 --- a/server_addon/royal_render/server/__init__.py +++ b/server_addon/royal_render/server/__init__.py @@ -2,14 +2,10 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .version import __version__ from .settings import RoyalRenderSettings, DEFAULT_VALUES class RoyalRenderAddon(BaseServerAddon): - name = "royalrender" - version = __version__ - title = "Royal Render" settings_model: Type[RoyalRenderSettings] = RoyalRenderSettings async def get_default_settings(self): diff --git a/server_addon/royal_render/server/version.py b/server_addon/royal_render/server/version.py deleted file mode 100644 index 485f44ac21..0000000000 --- a/server_addon/royal_render/server/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.1.1" From e669563a2ef62d71ece87d8d2640c710406de047 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:28:11 +0200 Subject: [PATCH 355/363] renamed royal_render folder to royalrender --- server_addon/{royal_render => royalrender}/package.py | 0 server_addon/{royal_render => royalrender}/server/__init__.py | 0 server_addon/{royal_render => royalrender}/server/settings.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename server_addon/{royal_render => royalrender}/package.py (100%) rename server_addon/{royal_render => royalrender}/server/__init__.py (100%) rename server_addon/{royal_render => royalrender}/server/settings.py (100%) diff --git a/server_addon/royal_render/package.py b/server_addon/royalrender/package.py similarity index 100% rename from server_addon/royal_render/package.py rename to server_addon/royalrender/package.py diff --git a/server_addon/royal_render/server/__init__.py b/server_addon/royalrender/server/__init__.py similarity index 100% rename from server_addon/royal_render/server/__init__.py rename to server_addon/royalrender/server/__init__.py diff --git a/server_addon/royal_render/server/settings.py b/server_addon/royalrender/server/settings.py similarity index 100% rename from server_addon/royal_render/server/settings.py rename to server_addon/royalrender/server/settings.py From 233794df9752b09e5f1823d91078354db5391263 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:29:52 +0200 Subject: [PATCH 356/363] simplified package creation --- server_addon/create_ayon_addons.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index bfd601af07..79b9aa5450 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -245,12 +245,8 @@ def create_addon_package( keep_source: bool, ): 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) + package = import_filepath(src_package_py) + addon_version = package.version addon_output_dir = output_dir / addon_dir.name / addon_version if addon_output_dir.exists(): @@ -259,18 +255,7 @@ def create_addon_package( # Copy server content 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(dst_package_py, "w+") as pkg_py: - pkg_py.write(package_py_content) + shutil.copy(src_package_py, dst_package_py) server_dir = addon_dir / "server" shutil.copytree( From 6f1228ad42779bd654716a1cf4d6bac52a26dea0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 22 Apr 2024 15:01:08 +0200 Subject: [PATCH 357/363] Fix #397: Always refresh workfiles tool on show Note: this also refreshes when the window is minimized and then brought up again --- .../ayon_core/tools/workfiles/widgets/window.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/window.py b/client/ayon_core/tools/workfiles/widgets/window.py index 8a2617d270..1cfae7ec90 100644 --- a/client/ayon_core/tools/workfiles/widgets/window.py +++ b/client/ayon_core/tools/workfiles/widgets/window.py @@ -118,11 +118,11 @@ class WorkfilesToolWindow(QtWidgets.QWidget): overlay_invalid_host = InvalidHostOverlay(self) overlay_invalid_host.setVisible(False) - first_show_timer = QtCore.QTimer() - first_show_timer.setSingleShot(True) - first_show_timer.setInterval(50) + show_timer = QtCore.QTimer() + show_timer.setSingleShot(True) + show_timer.setInterval(50) - first_show_timer.timeout.connect(self._on_first_show) + show_timer.timeout.connect(self._on_show) controller.register_event_callback( "save_as.finished", @@ -159,7 +159,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._tasks_widget = tasks_widget self._side_panel = side_panel - self._first_show_timer = first_show_timer + self._show_timer = show_timer self._post_init() @@ -287,9 +287,9 @@ class WorkfilesToolWindow(QtWidgets.QWidget): def showEvent(self, event): super(WorkfilesToolWindow, self).showEvent(event) + self._show_timer.start() if self._first_show: self._first_show = False - self._first_show_timer.start() self.setStyleSheet(style.load_stylesheet()) def keyPressEvent(self, event): @@ -303,9 +303,8 @@ class WorkfilesToolWindow(QtWidgets.QWidget): pass - def _on_first_show(self): - if not self._controller_refreshed: - self.refresh() + def _on_show(self): + self.refresh() def _on_file_text_filter_change(self, text): self._files_widget.set_text_filter(text) From 17390f839a2ce4a72cb347966f7174aab2b10424 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:26:09 +0200 Subject: [PATCH 358/363] select latest workfile after model refresh --- .../widgets/files_widget_workarea.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py index 6a1572deb2..47b04d36fe 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py @@ -20,6 +20,8 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): controller (AbstractWorkfilesFrontend): The control object. """ + refreshed = QtCore.Signal() + def __init__(self, controller): super(WorkAreaFilesModel, self).__init__() @@ -163,6 +165,12 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel): self._fill_items() def _fill_items(self): + try: + self._fill_items_impl() + finally: + self.refreshed.emit() + + def _fill_items_impl(self): folder_id = self._selected_folder_id task_id = self._selected_task_id if not folder_id or not task_id: @@ -285,6 +293,7 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): selection_model.selectionChanged.connect(self._on_selection_change) view.double_clicked.connect(self._on_mouse_double_click) view.customContextMenuRequested.connect(self._on_context_menu) + model.refreshed.connect(self._on_model_refresh) controller.register_event_callback( "expected_selection_changed", @@ -298,6 +307,7 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): self._controller = controller self._published_mode = False + self._change_selection_on_refresh = True def set_published_mode(self, published_mode): """Set the published mode. @@ -379,7 +389,9 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): if not workfile_info["current"]: return + self._change_selection_on_refresh = False self._model.refresh() + self._change_selection_on_refresh = True workfile_name = workfile_info["name"] if ( @@ -394,3 +406,24 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): self._controller.expected_workfile_selected( event["folder"]["id"], event["task"]["name"], workfile_name ) + + def _on_model_refresh(self): + if ( + not self._change_selection_on_refresh + or self._proxy_model.rowCount() < 1 + ): + return + + first_index = self._proxy_model.index(0, 0) + last_index = self._proxy_model.index( + 0, self._proxy_model.columnCount() - 1 + ) + selection = QtCore.QItemSelection(first_index, last_index) + seleciton_model = self._view.selectionModel() + seleciton_model.select( + selection, + ( + QtCore.QItemSelectionModel.ClearAndSelect + | QtCore.QItemSelectionModel.Current + ) + ) From 7cd9ec8a8dedf9a508b109df783ebaccb6f16a05 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Apr 2024 00:30:46 +0200 Subject: [PATCH 359/363] Select index by latest date modified --- .../workfiles/widgets/files_widget_workarea.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py index 47b04d36fe..39abbfe739 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py @@ -414,16 +414,20 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): ): return - first_index = self._proxy_model.index(0, 0) - last_index = self._proxy_model.index( - 0, self._proxy_model.columnCount() - 1 + # Find the row with latest date modified + latest_index = max( + (self._proxy_model.index(i, 0) for + i in range(self._proxy_model.rowCount())), + key=lambda model_index: model_index.date(DATE_MODIFIED_ROLE) ) - selection = QtCore.QItemSelection(first_index, last_index) - seleciton_model = self._view.selectionModel() - seleciton_model.select( - selection, + + # Select row of latest modified + selection_model = self._view.selectionModel() + selection_model.select( + latest_index, ( QtCore.QItemSelectionModel.ClearAndSelect | QtCore.QItemSelectionModel.Current + | QtCore.QItemSelectionModel.Rows ) ) From f6411be0c26623502efe1e59ba66c2b894c27d71 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 24 Apr 2024 01:36:42 +0200 Subject: [PATCH 360/363] Update client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../tools/workfiles/widgets/files_widget_workarea.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py index 39abbfe739..fe6abee951 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget_workarea.py @@ -416,9 +416,11 @@ class WorkAreaFilesWidget(QtWidgets.QWidget): # Find the row with latest date modified latest_index = max( - (self._proxy_model.index(i, 0) for - i in range(self._proxy_model.rowCount())), - key=lambda model_index: model_index.date(DATE_MODIFIED_ROLE) + ( + self._proxy_model.index(idx, 0) + for idx in range(self._proxy_model.rowCount()) + ), + key=lambda model_index: model_index.data(DATE_MODIFIED_ROLE) ) # Select row of latest modified From 7ef499e5a56c670faf3f9d7857089f5a2d81c207 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:09:18 +0200 Subject: [PATCH 361/363] change substance painter version --- server_addon/substancepainter/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/substancepainter/package.py b/server_addon/substancepainter/package.py index a064d80fd7..d445b0059f 100644 --- a/server_addon/substancepainter/package.py +++ b/server_addon/substancepainter/package.py @@ -1,3 +1,3 @@ name = "substancepainter" title = "Substance Painter" -version = "0.1.0" +version = "0.1.1" From e38ef81bf21ca716f0b4c666145b7134fad9e830 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Thu, 25 Apr 2024 11:00:36 +0200 Subject: [PATCH 362/363] add WorkfileImageIO settings --- server_addon/houdini/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/houdini/package.py b/server_addon/houdini/package.py index 4b72af2a89..4e441c76ae 100644 --- a/server_addon/houdini/package.py +++ b/server_addon/houdini/package.py @@ -1,3 +1,3 @@ name = "houdini" title = "Houdini" -version = "0.2.12" +version = "0.2.13" From 129070aefec7a6b855f80b95933845298fb2465e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 25 Apr 2024 17:53:29 +0800 Subject: [PATCH 363/363] make sure dict for Alembic Extractors having the attributes for enabling it --- .../hosts/max/plugins/publish/extract_alembic.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/extract_alembic.py b/client/ayon_core/hosts/max/plugins/publish/extract_alembic.py index 67b5174200..67cec23ecc 100644 --- a/client/ayon_core/hosts/max/plugins/publish/extract_alembic.py +++ b/client/ayon_core/hosts/max/plugins/publish/extract_alembic.py @@ -53,6 +53,7 @@ class ExtractAlembic(publish.Extractor, hosts = ["max"] families = ["pointcache"] optional = True + active = True def process(self, instance): if not self.is_active(instance.data): @@ -102,24 +103,27 @@ class ExtractAlembic(publish.Extractor, @classmethod def get_attribute_defs(cls): - return [ + defs = super(ExtractAlembic, cls).get_attribute_defs() + defs.extend([ BoolDef("custom_attrs", label="Custom Attributes", default=False), - ] + ]) + return defs class ExtractCameraAlembic(ExtractAlembic): """Extract Camera with AlembicExport.""" - label = "Extract Alembic Camera" families = ["camera"] + optional = True -class ExtractModel(ExtractAlembic): +class ExtractModelAlembic(ExtractAlembic): """Extract Geometry in Alembic Format""" label = "Extract Geometry (Alembic)" families = ["model"] + optional = True def _set_abc_attributes(self, instance): attr_values = self.get_attr_values_from_data(instance.data)