From 6c04a6df1a120eead69fb90b192e26017e95f334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 21 Dec 2023 11:53:35 +0100 Subject: [PATCH 01/59] :bug: fix default render product name --- openpype/hosts/houdini/plugins/publish/collect_vray_rop.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index a1f4554726..f60d8c664c 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -67,7 +67,7 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): beauty_product = self.get_render_product_name(default_prefix) render_products.append(beauty_product) files_by_aov = { - "RGB Color": self.generate_expected_files(instance, + "": self.generate_expected_files(instance, beauty_product)} if instance.data.get("RenderElement", True): @@ -75,7 +75,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): if render_element: for aov, renderpass in render_element.items(): render_products.append(renderpass) - files_by_aov[aov] = self.generate_expected_files(instance, renderpass) # noqa + files_by_aov[aov] = self.generate_expected_files( + instance, renderpass) + for product in render_products: self.log.debug("Found render product: %s" % product) From 45bb933e801d703f527bc5813c649f29b3c5312e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 25 Jan 2024 17:09:44 +0800 Subject: [PATCH 02/59] implement validator for model name in 3dsmax --- openpype/hosts/max/api/action.py | 39 ++++++++ .../plugins/publish/validate_model_name.py | 93 +++++++++++++++++++ .../defaults/project_settings/max.json | 6 ++ .../schemas/schema_max_publish.json | 24 +++++ .../max/server/settings/publishers.py | 20 ++++ server_addon/max/server/version.py | 2 +- 6 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 openpype/hosts/max/api/action.py create mode 100644 openpype/hosts/max/plugins/publish/validate_model_name.py diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py new file mode 100644 index 0000000000..506847652b --- /dev/null +++ b/openpype/hosts/max/api/action.py @@ -0,0 +1,39 @@ +from pymxs import runtime as rt + +import pyblish.api + +from openpype.pipeline.publish import get_errored_instances_from_context + + +class SelectInvalidAction(pyblish.api.Action): + """Select invalid objects in Blender when a publish plug-in failed.""" + label = "Select Invalid" + on = "failed" + icon = "search" + + def process(self, context, plugin): + errored_instances = get_errored_instances_from_context(context, + plugin=plugin) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding invalid nodes...") + invalid = list() + for instance in errored_instances: + invalid_nodes = plugin.get_invalid(instance) + if invalid_nodes: + if isinstance(invalid_nodes, (list, tuple)): + invalid.extend(invalid_nodes) + else: + self.log.warning( + "Failed plug-in doesn't have any selectable objects." + ) + + if not invalid: + self.log.info("No invalid nodes found.") + return + invalid_names = [obj.name for obj in invalid] + self.log.info( + "Selecting invalid objects: %s", ", ".join(invalid_names) + ) + + rt.Select(invalid) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py new file mode 100644 index 0000000000..3da9c9e0eb --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +"""Validate model nodes names.""" +import re + +import pyblish.api +from pymxs import runtime as rt + +from openpype.hosts.max.api.action import SelectInvalidAction + +from openpype.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishValidationError, + ValidateContentsOrder) + + +class ValidateModelName(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validate name of model + + starts with (somename)_###_(materialID)_GEO + + """ + optional = True + order = ValidateContentsOrder + hosts = ["max"] + families = ["model"] + label = "Validate Model Name" + actions = [SelectInvalidAction] + regex = "" + + @classmethod + def get_invalid(cls, instance): + invalid = [] + #TODO: validation regex for validation + model_names = [model.name for model in instance.data.get("members")] + cls.log.debug(model_names) + if not model_names: + cls.log.error("No Model found in the OP Data.") + invalid.append(model_names) + for name in model_names: + invalid_model_name = cls.get_invalid_model_name(instance, name) + invalid.extend(invalid_model_name) + + return invalid + + @classmethod + def get_invalid_model_name(cls, instance, name): + invalid = [] + + regex = cls.regex + reg = re.compile(regex) + matched_name = reg.match(name) + project_name = instance.context.data["projectName"] + current_asset_name = instance.context.data["asset"] + if matched_name is None: + cls.log.error("invalid model name on: {}".format(name)) + cls.log.error("name doesn't match regex {}".format(regex)) + invalid.append((rt.getNodeByName(name), + "Model name doesn't match regex")) + else: + if "asset" in reg.groupindex: + if matched_name.group("asset") != current_asset_name: + cls.log.error( + "Invalid asset name of the model {}.".format(name) + ) + invalid.append((rt.getNodeByName(name), + "Model with invalid asset name")) + if "subset" in reg.groupindex: + if matched_name.group("subset") != instance.name: + cls.log.error( + "Invalid subset name of the model {}.".format(name) + ) + invalid.append((rt.getNodeByName(name), + "Model with invalid subset name")) + if "project" in reg.groupindex: + if matched_name.group("project") != project_name: + cls.log.error( + "Invalid project name of the model {}.".format(name) + ) + invalid.append((rt.getNodeByName(name), + "Model with invalid project name")) + return invalid + + def process(self, instance): + if not self.is_active(instance.data): + self.log.debug("Skipping Validate Frame Range...") + return + + invalid = self.get_invalid(instance) + + if invalid: + raise PublishValidationError( + "Model naming is invalid. See the log.") diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d1610610dc..f719e7a156 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -56,6 +56,12 @@ "enabled": false, "attributes": {} }, + "ValidateModelName": { + "enabled": true, + "optional": true, + "active": false, + "regex": "(?P.*)_(GEO)" + }, "ValidateLoadedPlugin": { "enabled": false, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index b4d85bda98..1e42b017ff 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -48,6 +48,30 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ValidateModelName", + "label": "Validate Model Name", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "text", + "key": "regex", + "label": "validation regex" + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index d40d85a99b..9b11946195 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -38,6 +38,20 @@ class FamilyMappingItemModel(BaseSettingsModel): ) +class ValidateModelName(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + regex: str = Field( + "(?P.*)_(GEO)", + title="Validation regex", + description=( + "Regex for validating model name. You can use named " + " capturing groups:(?P.*) for Asset name" + ) + ) + + class ValidateLoadedPluginModel(BaseSettingsModel): enabled: bool = Field(title="Enabled") optional: bool = Field(title="Optional") @@ -101,6 +115,12 @@ DEFAULT_PUBLISH_SETTINGS = { "enabled": False, "attributes": "{}" }, + "ValidateModelName": { + "enabled": True, + "optional": True, + "active": False, + "regex": "(?P.*)_(GEO)" + }, "ValidateLoadedPlugin": { "enabled": False, "optional": True, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From d387dca0272c9be2a80beb52ac8272cabb10f426 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 26 Jan 2024 10:44:13 +0000 Subject: [PATCH 03/59] Use duration from streams as its more precise --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 10eb261482..37f7ea7737 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,7 +445,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) - duration = float(video_data["format"]["duration"]) + duration = float(video_data["streams"][0]["duration"]) cmd_args = [ "-y", From e6818f94f5fead64b6ff8767462064fef8e10a41 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 Jan 2024 09:17:07 +0000 Subject: [PATCH 04/59] Illicit suggestion --- openpype/plugins/publish/extract_thumbnail.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 37f7ea7737..8a1e9fd12d 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,7 +445,11 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) - duration = float(video_data["streams"][0]["duration"]) + duration = max( + float(stream.get("duration", 0)) + for stream in video_data["streams"] + if stream.get("codec_type") == "video" + ) cmd_args = [ "-y", From 1d83c6aaa8adcb77ba9b5395cceae064e4aa8e0a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 Jan 2024 17:19:04 +0800 Subject: [PATCH 05/59] add validate model name settings for ayon --- server_addon/max/server/settings/publishers.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 672f422fce..fcd034bcf0 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -38,11 +38,11 @@ class FamilyMappingItemModel(BaseSettingsModel): ) -class ValidateModelName(BaseSettingsModel): - enabled: bool = Field(title="Enabled") - optional: bool = Field(title="Optional") - active: bool = Field(title="Active") - regex: str = Field( +class ValidateModelNameModel(BaseSettingsModel): + enabled: bool = SettingsField(title="Enabled") + optional: bool = SettingsField(title="Optional") + active: bool = SettingsField(title="Active") + regex: str = SettingsField( "(?P.*)_(GEO)", title="Validation regex", description=( @@ -82,6 +82,10 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" ) + ValidateModelName: ValidateModelNameModel = SettingsField( + default_factory=ValidateModelNameModel, + title="Validate Model Name" + ) ExtractModelObj: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract OBJ", From 43aadb215b631d5ed6f149d945ba020652425046 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 Jan 2024 17:20:08 +0800 Subject: [PATCH 06/59] resolve hound --- openpype/hosts/max/plugins/publish/validate_model_name.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py index 3da9c9e0eb..c85b4729c5 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_name.py +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -31,7 +31,6 @@ class ValidateModelName(pyblish.api.InstancePlugin, @classmethod def get_invalid(cls, instance): invalid = [] - #TODO: validation regex for validation model_names = [model.name for model in instance.data.get("members")] cls.log.debug(model_names) if not model_names: From b822dd0e8d762c6641f1637fad4f9220bdfc7480 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 Jan 2024 17:27:22 +0800 Subject: [PATCH 07/59] add docstring --- openpype/hosts/max/plugins/publish/validate_model_name.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py index c85b4729c5..9603c45150 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_name.py +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -15,9 +15,11 @@ from openpype.pipeline.publish import ( class ValidateModelName(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): - """Validate name of model + """Validate Model Name + Validation regex is (?P.*)_(GEO) by default. + e.g. (subset_name)_GEO should be your model name - starts with (somename)_###_(materialID)_GEO + starts with (somename)_GEO """ optional = True @@ -45,7 +47,6 @@ class ValidateModelName(pyblish.api.InstancePlugin, @classmethod def get_invalid_model_name(cls, instance, name): invalid = [] - regex = cls.regex reg = re.compile(regex) matched_name = reg.match(name) From 5e8af09c4b116476cc80d5ca7ba22f969ae534e7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 Jan 2024 17:53:35 +0800 Subject: [PATCH 08/59] update regex name --- openpype/hosts/max/plugins/publish/validate_model_name.py | 2 +- openpype/settings/defaults/project_settings/max.json | 2 +- server_addon/max/server/settings/publishers.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py index 9603c45150..a264221372 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_name.py +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -83,7 +83,7 @@ class ValidateModelName(pyblish.api.InstancePlugin, def process(self, instance): if not self.is_active(instance.data): - self.log.debug("Skipping Validate Frame Range...") + self.log.debug("Skipping Validate Model Name...") return invalid = self.get_invalid(instance) diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index f719e7a156..e8fb2d31c4 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -60,7 +60,7 @@ "enabled": true, "optional": true, "active": false, - "regex": "(?P.*)_(GEO)" + "regex": "(.*)_(?P.*)_(GEO)" }, "ValidateLoadedPlugin": { "enabled": false, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index fcd034bcf0..761f4c54f1 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -43,7 +43,7 @@ class ValidateModelNameModel(BaseSettingsModel): optional: bool = SettingsField(title="Optional") active: bool = SettingsField(title="Active") regex: str = SettingsField( - "(?P.*)_(GEO)", + "(.*)_(?P.*)_(GEO)", title="Validation regex", description=( "Regex for validating model name. You can use named " @@ -123,7 +123,7 @@ DEFAULT_PUBLISH_SETTINGS = { "enabled": True, "optional": True, "active": False, - "regex": "(?P.*)_(GEO)" + "regex": "*_(?P.*)_(GEO)" }, "ValidateLoadedPlugin": { "enabled": False, From 3f8830e27d9c7f4002985ebcf05f7fe395fc3e17 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 29 Jan 2024 10:13:18 +0000 Subject: [PATCH 09/59] Update openpype/plugins/publish/extract_thumbnail.py --- openpype/plugins/publish/extract_thumbnail.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 8a1e9fd12d..2e272b061b 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -445,6 +445,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) + # Use duration of the individual streams since it is returned with + # higher decimal precision than 'format.duration'. We need this + # more precise value for calculating the correct amount of frames + # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS duration = max( float(stream.get("duration", 0)) for stream in video_data["streams"] From 3807172faed3addc22790c6831065d7424e08035 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 29 Jan 2024 10:13:40 +0000 Subject: [PATCH 10/59] Update openpype/plugins/publish/extract_thumbnail.py --- openpype/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 2e272b061b..cbeada6bac 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -448,7 +448,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Use duration of the individual streams since it is returned with # higher decimal precision than 'format.duration'. We need this # more precise value for calculating the correct amount of frames - # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS + # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS duration = max( float(stream.get("duration", 0)) for stream in video_data["streams"] From 6b9da9b1e83a1be30197d00d4aae198050590d33 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 2 Feb 2024 16:14:08 +0800 Subject: [PATCH 11/59] update docstring --- .../max/plugins/publish/validate_model_name.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py index a264221372..b0e5677592 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_name.py +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -16,10 +16,17 @@ from openpype.pipeline.publish import ( class ValidateModelName(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Validate Model Name - Validation regex is (?P.*)_(GEO) by default. - e.g. (subset_name)_GEO should be your model name + Validation regex is (.*)_(?P.*)_(GEO) by default. + e.g. {SOME_RANDOM_NAME}_{YOUR_SUBSET_NAME}_GEO should be your + default model name - starts with (somename)_GEO + The regex of (?P.*) can be replaced by (?P.*) + and (?P.*). + e.g. + - (.*)_(?P.*)_(GEO) check if your model name is + {SOME_RANDOM_NAME}_{CURRENT_ASSET_NAME}_GEO + - (.*)_(?P.*)_(GEO) check if your model name is + {SOME_RANDOM_NAME}_{CURRENT_PROJECT_NAME}_GEO """ optional = True From 02dea53a17449572897fb1ee30255f8c4aa6ddfc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Feb 2024 18:54:26 +0800 Subject: [PATCH 12/59] optional validator to check invalid context data --- openpype/hosts/max/api/action.py | 39 ++++++ .../publish/validate_instance_in_context.py | 111 ++++++++++++++++++ .../defaults/project_settings/max.json | 5 + .../schemas/schema_max_publish.json | 25 ++++ .../max/server/settings/publishers.py | 8 +- server_addon/max/server/version.py | 2 +- 6 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/max/api/action.py create mode 100644 openpype/hosts/max/plugins/publish/validate_instance_in_context.py diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py new file mode 100644 index 0000000000..506847652b --- /dev/null +++ b/openpype/hosts/max/api/action.py @@ -0,0 +1,39 @@ +from pymxs import runtime as rt + +import pyblish.api + +from openpype.pipeline.publish import get_errored_instances_from_context + + +class SelectInvalidAction(pyblish.api.Action): + """Select invalid objects in Blender when a publish plug-in failed.""" + label = "Select Invalid" + on = "failed" + icon = "search" + + def process(self, context, plugin): + errored_instances = get_errored_instances_from_context(context, + plugin=plugin) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding invalid nodes...") + invalid = list() + for instance in errored_instances: + invalid_nodes = plugin.get_invalid(instance) + if invalid_nodes: + if isinstance(invalid_nodes, (list, tuple)): + invalid.extend(invalid_nodes) + else: + self.log.warning( + "Failed plug-in doesn't have any selectable objects." + ) + + if not invalid: + self.log.info("No invalid nodes found.") + return + invalid_names = [obj.name for obj in invalid] + self.log.info( + "Selecting invalid objects: %s", ", ".join(invalid_names) + ) + + rt.Select(invalid) diff --git a/openpype/hosts/max/plugins/publish/validate_instance_in_context.py b/openpype/hosts/max/plugins/publish/validate_instance_in_context.py new file mode 100644 index 0000000000..afe5ca5a7f --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_instance_in_context.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +"""Validate if instance asset is the same as context asset.""" +from __future__ import absolute_import + +import pyblish.api +from openpype import AYON_SERVER_ENABLED +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError, + OptionalPyblishPluginMixin +) +from openpype.hosts.max.api.action import SelectInvalidAction +from pymxs import runtime as rt + + +class ValidateInstanceInContext(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validator to check if instance asset match context asset. + + When working in per-shot style you always publish data in context of + current asset (shot). This validator checks if this is so. It is optional + so it can be disabled when needed. + + Action on this validator will select invalid instances. + """ + + order = ValidateContentsOrder + label = "Instance in same Context" + optional = True + hosts = ["max"] + actions = [SelectInvalidAction, RepairAction] + + def process(self, instance): + if not self.is_active(instance.data): + return + + instance_node = rt.getNodeByName(instance.data.get( + "instance_node", "")) + if not instance_node: + return + asset_name_attr = "folderPath" if AYON_SERVER_ENABLED else "asset" + asset = rt.getUserProp(instance_node, asset_name_attr) + context_asset = self.get_context_asset(instance) + task = rt.getUserProp(instance_node, "task") + context_task = self.get_context_task(instance) + if asset != context_asset: + raise PublishValidationError( + message=( + "Instance '{}' publishes to different asset than current " + "context: {}. Current context: {}".format( + instance.name, asset, context_asset + ) + ), + description=( + "## Publishing to a different asset\n" + "There are publish instances present which are publishing " + "into a different asset than your current context.\n\n" + "Usually this is not what you want but there can be cases " + "where you might want to publish into another asset or " + "shot. If that's the case you can disable the validation " + "on the instance to ignore it." + ) + ) + if task != context_task: + raise PublishValidationError( + message=( + "Instance '{}' publishes to different task than current " + "context: {}. Current context: {}".format( + instance.name, task, context_task + ) + ), + description=( + "## Publishing to a different asset\n" + "There are publish instances present which are publishing " + "into a different asset than your current context.\n\n" + "Usually this is not what you want but there can be cases " + "where you might want to publish into another asset or " + "shot. If that's the case you can disable the validation " + "on the instance to ignore it." + ) + ) + + @classmethod + def get_invalid(cls, instance): + asset_name_attr = "folderPath" if AYON_SERVER_ENABLED else "asset" + node = rt.getNodeByName(instance.data["instance_node"]) + asset = rt.getUserProp(node, asset_name_attr) + context_asset = cls.get_context_asset(instance) + if asset != context_asset: + return instance.data["instance_node"] + + @classmethod + def repair(cls, instance): + context_asset = cls.get_context_asset(instance) + context_task = cls.get_context_task(instance) + instance_node = rt.getNodeByName(instance.data.get( + "instance_node", "")) + if not instance_node: + return + asset_name_attr = "folderPath" if AYON_SERVER_ENABLED else "asset" + rt.SetUserProp(instance_node, asset_name_attr, context_asset) + rt.SetUserProp(instance_node, "task", context_task) + + @staticmethod + def get_context_asset(instance): + return instance.context.data["asset"] + + @staticmethod + def get_context_task(instance): + return instance.context.data["task"] diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d1610610dc..eb4667c36e 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -47,6 +47,11 @@ } }, "publish": { + "ValidateInstanceInContext": { + "enabled": true, + "optional": true, + "active": true + }, "ValidateFrameRange": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json index b4d85bda98..fc563ff372 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_max_publish.json @@ -4,6 +4,31 @@ "key": "publish", "label": "Publish plugins", "children": [ + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateInstanceInContext", + "label": "Validate Instance In Context", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index da782cb494..03cfefa303 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -54,10 +54,14 @@ class BasicValidateModel(BaseSettingsModel): class PublishersModel(BaseSettingsModel): + ValidateInstanceInContext: BasicValidateModel = SettingsField( + default_factory=BasicValidateModel, + title="Validate Instance In Context", + section="Validators" + ) ValidateFrameRange: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, - title="Validate Frame Range", - section="Validators" + title="Validate Frame Range" ) ValidateAttributes: ValidateAttributesModel = SettingsField( default_factory=ValidateAttributesModel, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" From 12f78b17668c8f5034c60f8ba6921a5aa507c015 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Feb 2024 20:12:22 +0800 Subject: [PATCH 13/59] update regex in ayon settings --- server_addon/max/server/settings/publishers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 761f4c54f1..05d084aac5 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -123,7 +123,7 @@ DEFAULT_PUBLISH_SETTINGS = { "enabled": True, "optional": True, "active": False, - "regex": "*_(?P.*)_(GEO)" + "regex": "(.*)_(?P.*)_(GEO)" }, "ValidateLoadedPlugin": { "enabled": False, From 09342f56338456d3a2e8245a7738d0d23c6da281 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Feb 2024 23:29:18 +0800 Subject: [PATCH 14/59] fix action.py --- openpype/hosts/max/api/action.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py index 506847652b..818d4db6e5 100644 --- a/openpype/hosts/max/api/action.py +++ b/openpype/hosts/max/api/action.py @@ -31,9 +31,10 @@ class SelectInvalidAction(pyblish.api.Action): if not invalid: self.log.info("No invalid nodes found.") return - invalid_names = [obj.name for obj in invalid] + self.log.debug(f"invalid{invalid}") + invalid_names = [obj for obj, _ in invalid] self.log.info( - "Selecting invalid objects: %s", ", ".join(invalid_names) + f"Selecting invalid objects: {invalid_names}" ) - rt.Select(invalid) + rt.Select(invalid_names) From 1a63065c13ef70762e994519aaec1d4294ecd81c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Feb 2024 23:34:30 +0800 Subject: [PATCH 15/59] fix action.py --- openpype/hosts/max/api/action.py | 7 +++---- .../hosts/max/plugins/publish/validate_model_name.py | 12 ++++-------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py index 818d4db6e5..506847652b 100644 --- a/openpype/hosts/max/api/action.py +++ b/openpype/hosts/max/api/action.py @@ -31,10 +31,9 @@ class SelectInvalidAction(pyblish.api.Action): if not invalid: self.log.info("No invalid nodes found.") return - self.log.debug(f"invalid{invalid}") - invalid_names = [obj for obj, _ in invalid] + invalid_names = [obj.name for obj in invalid] self.log.info( - f"Selecting invalid objects: {invalid_names}" + "Selecting invalid objects: %s", ", ".join(invalid_names) ) - rt.Select(invalid_names) + rt.Select(invalid) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py index b0e5677592..3cd7265077 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_name.py +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -62,30 +62,26 @@ class ValidateModelName(pyblish.api.InstancePlugin, if matched_name is None: cls.log.error("invalid model name on: {}".format(name)) cls.log.error("name doesn't match regex {}".format(regex)) - invalid.append((rt.getNodeByName(name), - "Model name doesn't match regex")) + invalid.append((name,"Model name doesn't match regex")) else: if "asset" in reg.groupindex: if matched_name.group("asset") != current_asset_name: cls.log.error( "Invalid asset name of the model {}.".format(name) ) - invalid.append((rt.getNodeByName(name), - "Model with invalid asset name")) + invalid.append((name, "Model with invalid asset name")) if "subset" in reg.groupindex: if matched_name.group("subset") != instance.name: cls.log.error( "Invalid subset name of the model {}.".format(name) ) - invalid.append((rt.getNodeByName(name), - "Model with invalid subset name")) + invalid.append((name, "Model with invalid subset name")) if "project" in reg.groupindex: if matched_name.group("project") != project_name: cls.log.error( "Invalid project name of the model {}.".format(name) ) - invalid.append((rt.getNodeByName(name), - "Model with invalid project name")) + invalid.append((name, "Model with invalid project name")) return invalid def process(self, instance): From bfd952007feb1f37eec2924d510637cfbee01c60 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Feb 2024 23:35:41 +0800 Subject: [PATCH 16/59] hound --- openpype/hosts/max/plugins/publish/validate_model_name.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py index 3cd7265077..a36947931c 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_name.py +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -3,7 +3,6 @@ import re import pyblish.api -from pymxs import runtime as rt from openpype.hosts.max.api.action import SelectInvalidAction @@ -62,7 +61,7 @@ class ValidateModelName(pyblish.api.InstancePlugin, if matched_name is None: cls.log.error("invalid model name on: {}".format(name)) cls.log.error("name doesn't match regex {}".format(regex)) - invalid.append((name,"Model name doesn't match regex")) + invalid.append((name, "Model name doesn't match regex")) else: if "asset" in reg.groupindex: if matched_name.group("asset") != current_asset_name: From 83c8239a4918804af3379b44bf45698a965a377d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 6 Feb 2024 14:32:04 +0800 Subject: [PATCH 17/59] add missing ayon settings in json for validate instance in context --- server_addon/max/server/settings/publishers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 03cfefa303..58c71d977d 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -96,6 +96,11 @@ class PublishersModel(BaseSettingsModel): DEFAULT_PUBLISH_SETTINGS = { + "ValidateInstanceInContext": { + "enabled": True, + "optional": True, + "active": True + }, "ValidateFrameRange": { "enabled": True, "optional": True, From 05b97437b8f28bb43dc6ed303cbb6049f42c1316 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Feb 2024 19:49:38 +0800 Subject: [PATCH 18/59] fix Libor's mentioned bug on action.py --- openpype/hosts/max/api/action.py | 5 ++++- .../hosts/max/plugins/publish/validate_model_name.py | 11 +++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py index 506847652b..c3c1957af1 100644 --- a/openpype/hosts/max/api/action.py +++ b/openpype/hosts/max/api/action.py @@ -31,7 +31,10 @@ class SelectInvalidAction(pyblish.api.Action): if not invalid: self.log.info("No invalid nodes found.") return - invalid_names = [obj.name for obj in invalid] + invalid_names = [obj.name for obj in invalid if isinstance(obj, str)] + if not invalid_names: + invalid_names = [obj.name for obj, _ in invalid] + invalid = [obj for obj, _ in invalid] self.log.info( "Selecting invalid objects: %s", ", ".join(invalid_names) ) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py index a36947931c..bf4f29fa97 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_name.py +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -3,6 +3,7 @@ import re import pyblish.api +from pymxs import runtime as rt from openpype.hosts.max.api.action import SelectInvalidAction @@ -61,26 +62,28 @@ class ValidateModelName(pyblish.api.InstancePlugin, if matched_name is None: cls.log.error("invalid model name on: {}".format(name)) cls.log.error("name doesn't match regex {}".format(regex)) - invalid.append((name, "Model name doesn't match regex")) + invalid.append((rt.getNodeByName(name), + "Model name doesn't match regex")) else: if "asset" in reg.groupindex: if matched_name.group("asset") != current_asset_name: cls.log.error( "Invalid asset name of the model {}.".format(name) ) - invalid.append((name, "Model with invalid asset name")) + invalid.append((rt.getNodeByName(name), + "Model with invalid asset name")) if "subset" in reg.groupindex: if matched_name.group("subset") != instance.name: cls.log.error( "Invalid subset name of the model {}.".format(name) ) - invalid.append((name, "Model with invalid subset name")) + invalid.append((rt.getNodeByName(name), "Model with invalid subset name")) if "project" in reg.groupindex: if matched_name.group("project") != project_name: cls.log.error( "Invalid project name of the model {}.".format(name) ) - invalid.append((name, "Model with invalid project name")) + invalid.append((rt.getNodeByName(name), "Model with invalid project name")) return invalid def process(self, instance): From c9db6449dac57cc0a60210724bac8061c9d88da1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Feb 2024 19:50:48 +0800 Subject: [PATCH 19/59] hound shut --- openpype/hosts/max/plugins/publish/validate_model_name.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_model_name.py b/openpype/hosts/max/plugins/publish/validate_model_name.py index bf4f29fa97..455c12a85b 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_name.py +++ b/openpype/hosts/max/plugins/publish/validate_model_name.py @@ -77,13 +77,15 @@ class ValidateModelName(pyblish.api.InstancePlugin, cls.log.error( "Invalid subset name of the model {}.".format(name) ) - invalid.append((rt.getNodeByName(name), "Model with invalid subset name")) + invalid.append((rt.getNodeByName(name), + "Model with invalid subset name")) if "project" in reg.groupindex: if matched_name.group("project") != project_name: cls.log.error( "Invalid project name of the model {}.".format(name) ) - invalid.append((rt.getNodeByName(name), "Model with invalid project name")) + invalid.append((rt.getNodeByName(name), + "Model with invalid project name")) return invalid def process(self, instance): From e27b6e902822c7295f948b5f0e687abf344413c3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Feb 2024 20:09:40 +0800 Subject: [PATCH 20/59] change action.py as regard to #6164 --- openpype/hosts/max/api/action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/action.py b/openpype/hosts/max/api/action.py index 506847652b..c3c1957af1 100644 --- a/openpype/hosts/max/api/action.py +++ b/openpype/hosts/max/api/action.py @@ -31,7 +31,10 @@ class SelectInvalidAction(pyblish.api.Action): if not invalid: self.log.info("No invalid nodes found.") return - invalid_names = [obj.name for obj in invalid] + invalid_names = [obj.name for obj in invalid if isinstance(obj, str)] + if not invalid_names: + invalid_names = [obj.name for obj, _ in invalid] + invalid = [obj for obj, _ in invalid] self.log.info( "Selecting invalid objects: %s", ", ".join(invalid_names) ) From 0aea2ae39f4f947716c7ef69f49bda5dd13ad5c1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Feb 2024 08:11:11 +0000 Subject: [PATCH 21/59] Add submodule --- client/ayon_core/hosts/unreal/integration | 1 + 1 file changed, 1 insertion(+) create mode 160000 client/ayon_core/hosts/unreal/integration diff --git a/client/ayon_core/hosts/unreal/integration b/client/ayon_core/hosts/unreal/integration new file mode 160000 index 0000000000..6d2793170e --- /dev/null +++ b/client/ayon_core/hosts/unreal/integration @@ -0,0 +1 @@ +Subproject commit 6d2793170ed57187842f683a943593973abcc337 From 4a85b2ec45a05f066758b6be4e17588cdfd51598 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Feb 2024 21:01:12 +0800 Subject: [PATCH 22/59] bug fix the action.py --- client/ayon_core/hosts/max/api/action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/api/action.py b/client/ayon_core/hosts/max/api/action.py index 58834d0172..bed72bc493 100644 --- a/client/ayon_core/hosts/max/api/action.py +++ b/client/ayon_core/hosts/max/api/action.py @@ -31,7 +31,7 @@ class SelectInvalidAction(pyblish.api.Action): if not invalid: self.log.info("No invalid nodes found.") return - invalid_names = [obj.name for obj in invalid if isinstance(obj, str)] + invalid_names = [obj.name for obj in invalid if not isinstance(obj, tuple)] if not invalid_names: invalid_names = [obj.name for obj, _ in invalid] invalid = [obj for obj, _ in invalid] From 8d608f72981d1e7b440b948153c2eb65cdbbdda4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Feb 2024 21:01:58 +0800 Subject: [PATCH 23/59] fix the bug in action.py --- client/ayon_core/hosts/max/api/action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/api/action.py b/client/ayon_core/hosts/max/api/action.py index 58834d0172..bed72bc493 100644 --- a/client/ayon_core/hosts/max/api/action.py +++ b/client/ayon_core/hosts/max/api/action.py @@ -31,7 +31,7 @@ class SelectInvalidAction(pyblish.api.Action): if not invalid: self.log.info("No invalid nodes found.") return - invalid_names = [obj.name for obj in invalid if isinstance(obj, str)] + invalid_names = [obj.name for obj in invalid if not isinstance(obj, tuple)] if not invalid_names: invalid_names = [obj.name for obj, _ in invalid] invalid = [obj for obj, _ in invalid] From 6f875f8799544d685e298cf8b19d025ecf179d20 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 22 Feb 2024 18:18:04 +0800 Subject: [PATCH 24/59] make sure all instance.context.data['asset'] converted to instance.context.data['folderPath'] --- .../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 872a687597..5d544513c0 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 @@ -100,7 +100,7 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, @staticmethod def get_context_asset(instance): - return instance.context.data["asset"] + return instance.context.data["folderPath"] @staticmethod def get_context_task(instance): From c25161d454ca05dc21abff0eacf885c2e6cddbd8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 22 Feb 2024 18:26:54 +0800 Subject: [PATCH 25/59] coverting asset to folderPath --- .../ayon_core/hosts/max/plugins/publish/validate_model_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py index 7859889561..87a9132989 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py @@ -58,7 +58,7 @@ class ValidateModelName(pyblish.api.InstancePlugin, reg = re.compile(regex) matched_name = reg.match(name) project_name = instance.context.data["projectName"] - current_asset_name = instance.context.data["asset"] + current_asset_name = instance.context.data["folderPath"] if matched_name is None: cls.log.error("invalid model name on: {}".format(name)) cls.log.error("name doesn't match regex {}".format(regex)) From 3edbf698a039905917b34b5e9f2c9509ce0eeb15 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 22 Feb 2024 22:17:21 +0100 Subject: [PATCH 26/59] Update issue template titles to remove prefixes. Improve clarity and consistency in issue reporting templates. --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/enhancement_request.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 655ffe289e..5a0ed1ae1d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug Report description: File a bug report -title: 'Bug: ' +title: '' labels: - 'type: bug' body: diff --git a/.github/ISSUE_TEMPLATE/enhancement_request.yml b/.github/ISSUE_TEMPLATE/enhancement_request.yml index 52b49e0481..da4d0d9319 100644 --- a/.github/ISSUE_TEMPLATE/enhancement_request.yml +++ b/.github/ISSUE_TEMPLATE/enhancement_request.yml @@ -1,6 +1,6 @@ name: Enhancement Request description: Create a report to help us enhance a particular feature -title: "Enhancement: " +title: "" labels: - "type: enhancement" body: From 344e244a993a3082b8836afdb87f5883e63ff0e8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 26 Feb 2024 18:04:40 +0800 Subject: [PATCH 27/59] validate no animation for model family in Max --- .../plugins/publish/validate_no_animation.py | 67 +++++++++++++++++++ .../max/server/settings/publishers.py | 9 +++ server_addon/max/server/version.py | 2 +- 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py b/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py new file mode 100644 index 0000000000..9f859a1b28 --- /dev/null +++ b/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +import pyblish.api +from pymxs import runtime as rt +from ayon_core.pipeline import ( + PublishValidationError, + OptionalPyblishPluginMixin +) +from ayon_core.hosts.max.api.action import SelectInvalidAction + + +class ValidateNoAnimation(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validates No Animation + + Ensure no keyframes on nodes in the Instance + """ + + order = pyblish.api.ValidatorOrder + families = ["model"] + hosts = ["max"] + optional = True + label = "Validate No Animation" + actions = [SelectInvalidAction] + + def process(self, instance): + if not self.is_active(instance.data): + return + invalid = self.get_invalid(instance) + if invalid: + bullet_point_invalid_statement = "\n".join( + "- {}: {}".format(obj, message) + for obj, message in invalid + ) + raise PublishValidationError( + "Keyframes found on:\n\n{0}".format( + bullet_point_invalid_statement) + , + title="Keyframes on model" + ) + + @staticmethod + def get_invalid(instance): + invalid = [] + selected_objects = instance.data["members"] + for sel in selected_objects: + sel_pos_ctl = rt.getPropertyController( + sel.controller, 'Position') + ctl_count = (sel_pos_ctl.keys).count + if len(ctl_count) > 0: + invalid.append( + (sel), f"Object Position(s) has {ctl_count} keyframe(s)") + sel_rot_ctl = rt.getPropertyController( + sel.controller, "Rotation" + ) + ctl_count = (sel_rot_ctl.keys).count + if len(ctl_count) > 0: + invalid.append( + (sel), f"Object Rotation(s) has {ctl_count} keyframe(s)") + sel_scale_ctl = rt.getPropertyController( + sel.controller, "Scale" + ) + ctl_count = (sel_scale_ctl.keys).count + if len(ctl_count) > 0: + invalid.append( + (sel), f"Object Rotation(s) has {ctl_count} keyframe(s)") + + return invalid diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 5e28c1b467..8f9f1009f4 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -82,6 +82,10 @@ class PublishersModel(BaseSettingsModel): "the system automatically skips checking it" ) ) + ValidateNoAnimation: BasicValidateModel = SettingsField( + default_factory=BasicValidateModel, + title="Validate No Animation" + ) ValidateLoadedPlugin: ValidateLoadedPluginModel = SettingsField( default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" @@ -134,6 +138,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "family_plugins_mapping": [] }, + "ValidateNoAnimation": { + "enabled": True, + "optional": True, + "active": False, + }, "ExtractModelObj": { "enabled": True, "optional": True, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index 1276d0254f..0a8da88258 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" From 999563ea521a877fd9074c52cec00d8ccb21bf77 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 26 Feb 2024 18:31:39 +0800 Subject: [PATCH 28/59] tweak comment and some syntax error --- .../max/plugins/publish/validate_no_animation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py b/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py index 9f859a1b28..4f57933085 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py @@ -46,22 +46,22 @@ class ValidateNoAnimation(pyblish.api.InstancePlugin, sel_pos_ctl = rt.getPropertyController( sel.controller, 'Position') ctl_count = (sel_pos_ctl.keys).count - if len(ctl_count) > 0: + if ctl_count > 0: invalid.append( - (sel), f"Object Position(s) has {ctl_count} keyframe(s)") + ( (sel), f"Object Position(s) has {ctl_count} keyframe(s)")) sel_rot_ctl = rt.getPropertyController( sel.controller, "Rotation" ) ctl_count = (sel_rot_ctl.keys).count - if len(ctl_count) > 0: + if ctl_count > 0: invalid.append( - (sel), f"Object Rotation(s) has {ctl_count} keyframe(s)") + ((sel), f"Object Rotation(s) has {ctl_count} keyframe(s)")) sel_scale_ctl = rt.getPropertyController( sel.controller, "Scale" ) ctl_count = (sel_scale_ctl.keys).count - if len(ctl_count) > 0: + if ctl_count > 0: invalid.append( - (sel), f"Object Rotation(s) has {ctl_count} keyframe(s)") + ((sel), f"Object Scale(s) has {ctl_count} keyframe(s)")) return invalid From c90ddbcb6b387b100f99acf6b759393c96f0e3bc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 26 Feb 2024 19:14:56 +0800 Subject: [PATCH 29/59] tweak on get_invalid function and clean up code for getting folderPath and task data --- .../publish/validate_instance_in_context.py | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 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 5d544513c0..e4d078b36c 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 @@ -39,9 +39,9 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, if not instance_node: return asset = rt.getUserProp(instance_node, "folderPath") - context_asset = self.get_context_asset(instance) + context_asset = instance.context.data["folderPath"] task = rt.getUserProp(instance_node, "task") - context_task = self.get_context_task(instance) + context_task = instance.context.data["task"] if asset != context_asset: raise PublishValidationError( message=( @@ -81,27 +81,26 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, @classmethod def get_invalid(cls, instance): + invalid = [] node = rt.getNodeByName(instance.data["instance_node"]) asset = rt.getUserProp(node, "folderPath") - context_asset = cls.get_context_asset(instance) + context_asset = instance.context.data["folderPath"] if asset != context_asset: - return instance.data["instance_node"] + invalid.append(node) + task = rt.getUserProp(node, "task") + context_task = instance.context.data["task"] + if task != context_task: + invalid.append(node) + + return invalid @classmethod def repair(cls, instance): - context_asset = cls.get_context_asset(instance) - context_task = cls.get_context_task(instance) + context_asset = instance.context.data["folderPath"] + context_task = instance.context.data["task"] instance_node = rt.getNodeByName(instance.data.get( "instance_node", "")) if not instance_node: return rt.SetUserProp(instance_node, "folderPath", context_asset) rt.SetUserProp(instance_node, "task", context_task) - - @staticmethod - def get_context_asset(instance): - return instance.context.data["folderPath"] - - @staticmethod - def get_context_task(instance): - return instance.context.data["task"] From ef654cd7d8d1cbe3aea4238328aced4082bd9d4a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 26 Feb 2024 20:05:09 +0800 Subject: [PATCH 30/59] use sel.isAnimated instead of doing check on controller --- .../plugins/publish/validate_no_animation.py | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py b/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py index 4f57933085..0b7a296cd9 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py @@ -27,41 +27,24 @@ class ValidateNoAnimation(pyblish.api.InstancePlugin, return invalid = self.get_invalid(instance) if invalid: - bullet_point_invalid_statement = "\n".join( - "- {}: {}".format(obj, message) - for obj, message in invalid - ) raise PublishValidationError( - "Keyframes found on:\n\n{0}".format( - bullet_point_invalid_statement) + "Keyframes found on:\n\n{0}".format(invalid) , title="Keyframes on model" ) @staticmethod def get_invalid(instance): - invalid = [] - selected_objects = instance.data["members"] - for sel in selected_objects: - sel_pos_ctl = rt.getPropertyController( - sel.controller, 'Position') - ctl_count = (sel_pos_ctl.keys).count - if ctl_count > 0: - invalid.append( - ( (sel), f"Object Position(s) has {ctl_count} keyframe(s)")) - sel_rot_ctl = rt.getPropertyController( - sel.controller, "Rotation" - ) - ctl_count = (sel_rot_ctl.keys).count - if ctl_count > 0: - invalid.append( - ((sel), f"Object Rotation(s) has {ctl_count} keyframe(s)")) - sel_scale_ctl = rt.getPropertyController( - sel.controller, "Scale" - ) - ctl_count = (sel_scale_ctl.keys).count - if ctl_count > 0: - invalid.append( - ((sel), f"Object Scale(s) has {ctl_count} keyframe(s)")) + """Get invalid object(s) which have keyframe(s) + + + Args: + instance (pyblish.api.instance): Instance + + Returns: + list: list of invalid objects + """ + invalid = [invalid for invalid in instance.data["members"] + if invalid.isAnimated] return invalid From 2c0cb76f35fb3530cc5762bbb61d1edbdba2b6e0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 27 Feb 2024 17:39:30 +0800 Subject: [PATCH 31/59] validate the mesh has uv --- .../plugins/publish/validate_mesh_has_uv.py | 45 +++++++++++++++++++ .../max/server/settings/publishers.py | 9 ++++ server_addon/max/server/version.py | 2 +- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py new file mode 100644 index 0000000000..3e966acc87 --- /dev/null +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -0,0 +1,45 @@ + +import pyblish.api +from ayon_core.hosts.max.api.action import SelectInvalidAction +from ayon_core.pipeline.publish import ( + ValidateMeshOrder, + OptionalPyblishPluginMixin, + PublishValidationError +) +from pymxs import runtime as rt + + +class ValidateMeshHasUVs(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + + """Validate the current mesh has UVs. + + It validates whether the current UV set has non-zero UVs and + at least more than the vertex count. It's not really bulletproof, + but a simple quick validation to check if there are likely + UVs for every face. + """ + + order = ValidateMeshOrder + hosts = ['max'] + families = ['model'] + label = 'Validate Mesh Has UVs' + actions = [SelectInvalidAction] + optional = True + + + @classmethod + def get_invalid(cls, instance): + invalid = [member for member in instance.data["members"] + if not member.mesh.numTVerts > 0] + return invalid + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise PublishValidationError( + title="Mesh has missing UVs", + message="Model meshes are required to have UVs.

" + "Meshes detected with invalid or missing UVs:
" + "{0}".format(invalid) + ) diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 5e28c1b467..6ea6a887d1 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -86,6 +86,10 @@ class PublishersModel(BaseSettingsModel): default_factory=ValidateLoadedPluginModel, title="Validate Loaded Plugin" ) + ValidateMeshHasUVs: BasicValidateModel = SettingsField( + default_factory=BasicValidateModel, + title="Validate Mesh Has UVs" + ) ExtractModelObj: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract OBJ", @@ -134,6 +138,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "family_plugins_mapping": [] }, + "ValidateMeshHasUVs": { + "enabled": True, + "optional": True, + "active": False + }, "ExtractModelObj": { "enabled": True, "optional": True, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index 1276d0254f..0a8da88258 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.5" +__version__ = "0.1.6" From d9a278e470af8f7542dd53e9f3c3976ccd6c3c00 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 27 Feb 2024 18:42:48 +0800 Subject: [PATCH 32/59] correct object name for the error message --- .../ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index 3e966acc87..a55b49b515 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -41,5 +41,5 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, title="Mesh has missing UVs", message="Model meshes are required to have UVs.

" "Meshes detected with invalid or missing UVs:
" - "{0}".format(invalid) + "{0}".format([err.name for err in invalid]) ) From f3ee01ba72cf25c9c1a9f8c540521025312ff619 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 27 Feb 2024 18:53:27 +0800 Subject: [PATCH 33/59] edit docstring --- .../hosts/max/plugins/publish/validate_mesh_has_uv.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index a55b49b515..eda638daae 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -14,10 +14,9 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, """Validate the current mesh has UVs. - It validates whether the current UV set has non-zero UVs and - at least more than the vertex count. It's not really bulletproof, - but a simple quick validation to check if there are likely - UVs for every face. + It validates whether the current mesh has texture vertex(s). + If the mesh does not have texture vertex(s), it does not + have UVs in Max. """ order = ValidateMeshOrder From cd694266b8375e8b5e96974b869fe5360fcdf89c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 27 Feb 2024 21:34:45 +0800 Subject: [PATCH 34/59] improve error message and docstring --- .../plugins/publish/validate_mesh_has_uv.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index eda638daae..4cef1a5658 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -14,9 +14,12 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, """Validate the current mesh has UVs. - It validates whether the current mesh has texture vertex(s). - If the mesh does not have texture vertex(s), it does not + This validator only checks if the mesh has UVs but not + whether the faces of the mesh have UVs. + It validates whether the current mesh has texture vertices. + If the mesh does not have texture vertices, it does not have UVs in Max. + """ order = ValidateMeshOrder @@ -36,9 +39,18 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise PublishValidationError( - title="Mesh has missing UVs", - message="Model meshes are required to have UVs.

" - "Meshes detected with invalid or missing UVs:
" - "{0}".format([err.name for err in invalid]) + bullet_point_invalid_statement = "\n".join( + "- {}".format(invalid.name) for invalid + in invalid ) + report = ( + "Model meshes are required to have UVs.\n\n" + "Meshes detected with invalid or missing UVs:\n" + f"{bullet_point_invalid_statement}\n\n" + ) + raise PublishValidationError( + report, + description=( + "Model meshes are required to have UVs.\n\n" + "Meshes detected with no texture vertice(s) or missing UVs"), + title="Mesh has missing UVs") From 16b28fe3bd3956974550a55f31cdf073dd3c9c15 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 17:54:19 +0800 Subject: [PATCH 35/59] tweak docstring --- .../hosts/max/plugins/publish/validate_mesh_has_uv.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index 4cef1a5658..846e537b69 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -15,7 +15,8 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, """Validate the current mesh has UVs. This validator only checks if the mesh has UVs but not - whether the faces of the mesh have UVs. + whether all the individual faces of the mesh have UVs. + It validates whether the current mesh has texture vertices. If the mesh does not have texture vertices, it does not have UVs in Max. @@ -52,5 +53,5 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, report, description=( "Model meshes are required to have UVs.\n\n" - "Meshes detected with no texture vertice(s) or missing UVs"), + "Meshes detected with no texture vertice or missing UVs"), title="Mesh has missing UVs") From fadf820fea6bcd4c40dd7a2ce43f43cf322a3b02 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 18:53:44 +0800 Subject: [PATCH 36/59] improve the code and the error message & docstring --- .../publish/help/validate_model_name.xml | 26 +++ .../plugins/publish/validate_model_name.py | 160 ++++++++++-------- 2 files changed, 116 insertions(+), 70 deletions(-) create mode 100644 client/ayon_core/hosts/max/plugins/publish/help/validate_model_name.xml diff --git a/client/ayon_core/hosts/max/plugins/publish/help/validate_model_name.xml b/client/ayon_core/hosts/max/plugins/publish/help/validate_model_name.xml new file mode 100644 index 0000000000..e41146910a --- /dev/null +++ b/client/ayon_core/hosts/max/plugins/publish/help/validate_model_name.xml @@ -0,0 +1,26 @@ + + + +Invalid Model Name +## Nodes found with Invalid Model Name + +Nodes were detected in your scene which have invalid model name which does not +match the regex you preset in AYON setting. +### How to repair? +Make sure the model name aligns with validation regex in your AYON setting. + + + +### Invalid nodes + +{nodes} + + +### How could this happen? + +This often happens if you have mesh with the model naming does not match +with regex in the setting. + + + + \ No newline at end of file diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py index 87a9132989..a0cad4e454 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py @@ -3,30 +3,34 @@ import re import pyblish.api -from pymxs import runtime as rt from ayon_core.hosts.max.api.action import SelectInvalidAction from ayon_core.pipeline.publish import ( OptionalPyblishPluginMixin, - PublishValidationError, - ValidateContentsOrder) - + PublishXmlValidationError, + ValidateContentsOrder +) class ValidateModelName(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): - """Validate Model Name - Validation regex is (.*)_(?P.*)_(GEO) by default. - e.g. {SOME_RANDOM_NAME}_{YOUR_SUBSET_NAME}_GEO should be your - default model name + """Validate Model Name. - The regex of (?P.*) can be replaced by (?P.*) - and (?P.*). - e.g. - - (.*)_(?P.*)_(GEO) check if your model name is - {SOME_RANDOM_NAME}_{CURRENT_ASSET_NAME}_GEO - - (.*)_(?P.*)_(GEO) check if your model name is - {SOME_RANDOM_NAME}_{CURRENT_PROJECT_NAME}_GEO + Validation regex is `(.*)_(?P.*)_(GEO)` by default. + The setting supports the following regex group name: + - project + - asset + - subset + + Examples: + `{SOME_RANDOM_NAME}_{YOUR_SUBSET_NAME}_GEO` should be your + default model name. + The regex of `(?P.*)` can be replaced by `(?P.*)` + and `(?P.*)`. + `(.*)_(?P.*)_(GEO)` check if your model name is + `{SOME_RANDOM_NAME}_{CURRENT_ASSET_NAME}_GEO` + `(.*)_(?P.*)_(GEO)` check if your model name is + `{SOME_RANDOM_NAME}_{CURRENT_PROJECT_NAME}_GEO` """ optional = True @@ -35,66 +39,82 @@ class ValidateModelName(pyblish.api.InstancePlugin, families = ["model"] label = "Validate Model Name" actions = [SelectInvalidAction] - regex = "" - - @classmethod - def get_invalid(cls, instance): - invalid = [] - model_names = [model.name for model in instance.data.get("members")] - cls.log.debug(model_names) - if not model_names: - cls.log.error("No Model found in the OP Data.") - invalid.append(model_names) - for name in model_names: - invalid_model_name = cls.get_invalid_model_name(instance, name) - invalid.extend(invalid_model_name) - - return invalid - - @classmethod - def get_invalid_model_name(cls, instance, name): - invalid = [] - regex = cls.regex - reg = re.compile(regex) - matched_name = reg.match(name) - project_name = instance.context.data["projectName"] - current_asset_name = instance.context.data["folderPath"] - if matched_name is None: - cls.log.error("invalid model name on: {}".format(name)) - cls.log.error("name doesn't match regex {}".format(regex)) - invalid.append((rt.getNodeByName(name), - "Model name doesn't match regex")) - else: - if "asset" in reg.groupindex: - if matched_name.group("asset") != current_asset_name: - cls.log.error( - "Invalid asset name of the model {}.".format(name) - ) - invalid.append((rt.getNodeByName(name), - "Model with invalid asset name")) - if "subset" in reg.groupindex: - if matched_name.group("subset") != instance.name: - cls.log.error( - "Invalid subset name of the model {}.".format(name) - ) - invalid.append((rt.getNodeByName(name), - "Model with invalid subset name")) - if "project" in reg.groupindex: - if matched_name.group("project") != project_name: - cls.log.error( - "Invalid project name of the model {}.".format(name) - ) - invalid.append((rt.getNodeByName(name), - "Model with invalid project name")) - return invalid + # defined by settings + regex = r"(.*)_(?P.*)_(GEO)" + # cache + regex_compiled = None def process(self, instance): if not self.is_active(instance.data): - self.log.debug("Skipping Validate Model Name...") return invalid = self.get_invalid(instance) + if invalid: + names = "\n".join( + "- {}".format(node.name) for node in invalid + ) + raise PublishXmlValidationError( + plugin=self, + message="Nodes found with invalid model names: {}".format(invalid), + formatting_data={"nodes": names} + ) + + @classmethod + def get_invalid(cls, instance): + if not cls.regex: + cls.log.warning("No regex pattern set. Nothing to validate.") + return + + members = instance.data.get("members") + if not members: + cls.log.error("No members found in the instance.") + return + + cls.regex_compiled = re.compile(cls.regex) + + invalid = [] + for obj in members: + if cls.invalid_name(instance, obj): + invalid.append(obj) + return invalid + + @classmethod + def invalid_name(cls, instance, obj): + """Function to check the object has invalid name + regarding to the validation regex in the AYON setttings + + Args: + instance (pyblish.api.instance): Instance + obj (str): object name + + Returns: + str: invalid object + """ + regex = cls.regex_compiled + name = obj.name + match = regex.match(name) + + if match is None: + cls.log.error("Invalid model name on: %s", name) + cls.log.error("Name doesn't match regex {}".format(regex.pattern)) + return obj + + # Validate regex groups + invalid = False + compare = { + "project": instance.context.data["projectName"], + "asset": instance.context.data["folderPath"], + "subset": instance.context.data["subset"], + } + for key, required_value in compare.items(): + if key in regex.groupindex: + if match.group(key) != required_value: + cls.log.error( + "Invalid %s name for the model %s, " + "required name is %s", + key, name, required_value + ) + invalid = True if invalid: - raise PublishValidationError( - "Model naming is invalid. See the log.") + return obj From 3597efbe9f14f06caafb1e0cec1feb48a4a9d56a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 19:48:28 +0800 Subject: [PATCH 37/59] remove unneccessary settings --- .../publish/validate_instance_in_context.py | 38 ++- client/ayon_core/settings/__init__.py | 18 -- .../defaults/project_settings/max.json | 105 --------- client/ayon_core/settings/lib.py | 223 ------------------ client/ayon_core/settings/local_settings.md | 79 ------- 5 files changed, 16 insertions(+), 447 deletions(-) delete mode 100644 client/ayon_core/settings/__init__.py delete mode 100644 client/ayon_core/settings/defaults/project_settings/max.json delete mode 100644 client/ayon_core/settings/lib.py delete mode 100644 client/ayon_core/settings/local_settings.md 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 e4d078b36c..788e9fb19c 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 @@ -42,36 +42,30 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, context_asset = instance.context.data["folderPath"] task = rt.getUserProp(instance_node, "task") context_task = instance.context.data["task"] + invalid = [] if asset != context_asset: - raise PublishValidationError( - message=( - "Instance '{}' publishes to different asset than current " - "context: {}. Current context: {}".format( - instance.name, asset, context_asset - ) - ), - description=( - "## Publishing to a different asset\n" - "There are publish instances present which are publishing " - "into a different asset than your current context.\n\n" - "Usually this is not what you want but there can be cases " - "where you might want to publish into another asset or " - "shot. If that's the case you can disable the validation " - "on the instance to ignore it." + invalid.append( + "Instance '{}' publishes to different asset than current " + "context: {}. Current context: {}".format( + instance.name, asset, context_asset ) ) if task != context_task: + invalid.append( + "Instance '{}' publishes to different task than current " + "context: {}. Current context: {}".format( + instance.name, task, context_task + ) + ) + + if invalid: + message = "\n".join(invalid) raise PublishValidationError( - message=( - "Instance '{}' publishes to different task than current " - "context: {}. Current context: {}".format( - instance.name, task, context_task - ) - ), + message=message, description=( "## Publishing to a different asset\n" "There are publish instances present which are publishing " - "into a different asset than your current context.\n\n" + "into a different asset or task than your current context.\n\n" "Usually this is not what you want but there can be cases " "where you might want to publish into another asset or " "shot. If that's the case you can disable the validation " diff --git a/client/ayon_core/settings/__init__.py b/client/ayon_core/settings/__init__.py deleted file mode 100644 index d32b5f3391..0000000000 --- a/client/ayon_core/settings/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from .lib import ( - get_ayon_settings, - get_studio_settings, - get_system_settings, - get_project_settings, - get_general_environments, - get_current_project_settings, -) - - -__all__ = ( - "get_ayon_settings", - "get_studio_settings", - "get_system_settings", - "get_general_environments", - "get_project_settings", - "get_current_project_settings", -) diff --git a/client/ayon_core/settings/defaults/project_settings/max.json b/client/ayon_core/settings/defaults/project_settings/max.json deleted file mode 100644 index 7927472489..0000000000 --- a/client/ayon_core/settings/defaults/project_settings/max.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "unit_scale_settings": { - "enabled": true, - "scene_unit_scale": "Meters" - }, - "imageio": { - "activate_host_color_management": true, - "ocio_config": { - "override_global_config": false, - "filepath": [] - }, - "file_rules": { - "activate_host_rules": false, - "rules": {} - } - }, - "RenderSettings": { - "default_render_image_folder": "renders/3dsmax", - "aov_separator": "underscore", - "image_format": "exr", - "multipass": true - }, - "CreateReview": { - "review_width": 1920, - "review_height": 1080, - "percentSize": 100.0, - "keep_images": false, - "image_format": "png", - "visual_style": "Realistic", - "viewport_preset": "Quality", - "anti_aliasing": "None", - "vp_texture": true - }, - "PointCloud": { - "attribute": { - "Age": "age", - "Radius": "radius", - "Position": "position", - "Rotation": "rotation", - "Scale": "scale", - "Velocity": "velocity", - "Color": "color", - "TextureCoordinate": "texcoord", - "MaterialID": "matid", - "custFloats": "custFloats", - "custVecs": "custVecs" - } - }, - "publish": { - "ValidateInstanceInContext": { - "enabled": true, - "optional": true, - "active": true - }, - "ValidateFrameRange": { - "enabled": true, - "optional": true, - "active": true - }, - "ValidateAttributes": { - "enabled": false, - "attributes": {} - }, - "ValidateCameraAttributes": { - "enabled": true, - "optional": true, - "active": false, - "fov": 45.0, - "nearrange": 0.0, - "farrange": 1000.0, - "nearclip": 1.0, - "farclip": 1000.0 - }, - "ValidateLoadedPlugin": { - "enabled": false, - "optional": true, - "family_plugins_mapping": [] - }, - "ExtractModelObj": { - "enabled": true, - "optional": true, - "active": false - }, - "ExtractModelFbx": { - "enabled": true, - "optional": true, - "active": false - }, - "ExtractModelUSD": { - "enabled": true, - "optional": true, - "active": false - }, - "ExtractModel": { - "enabled": true, - "optional": true, - "active": true - }, - "ExtractMaxSceneRaw": { - "enabled": true, - "optional": true, - "active": true - } - } -} diff --git a/client/ayon_core/settings/lib.py b/client/ayon_core/settings/lib.py deleted file mode 100644 index eadf3ba544..0000000000 --- a/client/ayon_core/settings/lib.py +++ /dev/null @@ -1,223 +0,0 @@ -import os -import json -import logging -import collections -import copy -import time - -from ayon_core.client import get_ayon_server_api_connection - -log = logging.getLogger(__name__) - - -class CacheItem: - lifetime = 10 - - def __init__(self, value, outdate_time=None): - self._value = value - if outdate_time is None: - outdate_time = time.time() + self.lifetime - self._outdate_time = outdate_time - - @classmethod - def create_outdated(cls): - return cls({}, 0) - - def get_value(self): - return copy.deepcopy(self._value) - - def update_value(self, value): - self._value = value - self._outdate_time = time.time() + self.lifetime - - @property - def is_outdated(self): - return time.time() > self._outdate_time - - -class _AyonSettingsCache: - use_bundles = None - variant = None - addon_versions = CacheItem.create_outdated() - studio_settings = CacheItem.create_outdated() - cache_by_project_name = collections.defaultdict( - CacheItem.create_outdated) - - @classmethod - def _use_bundles(cls): - if _AyonSettingsCache.use_bundles is None: - con = get_ayon_server_api_connection() - major, minor, _, _, _ = con.get_server_version_tuple() - use_bundles = True - if (major, minor) < (0, 3): - use_bundles = False - _AyonSettingsCache.use_bundles = use_bundles - return _AyonSettingsCache.use_bundles - - @classmethod - def _get_variant(cls): - if _AyonSettingsCache.variant is None: - from ayon_core.lib import is_staging_enabled, is_dev_mode_enabled - - variant = "production" - if is_dev_mode_enabled(): - variant = cls._get_bundle_name() - elif is_staging_enabled(): - variant = "staging" - - # Cache variant - _AyonSettingsCache.variant = variant - - # Set the variant to global ayon api connection - con = get_ayon_server_api_connection() - con.set_default_settings_variant(variant) - return _AyonSettingsCache.variant - - @classmethod - def _get_bundle_name(cls): - return os.environ["AYON_BUNDLE_NAME"] - - @classmethod - def get_value_by_project(cls, project_name): - cache_item = _AyonSettingsCache.cache_by_project_name[project_name] - if cache_item.is_outdated: - con = get_ayon_server_api_connection() - if cls._use_bundles(): - value = con.get_addons_settings( - bundle_name=cls._get_bundle_name(), - project_name=project_name, - variant=cls._get_variant() - ) - else: - value = con.get_addons_settings(project_name) - cache_item.update_value(value) - return cache_item.get_value() - - @classmethod - def _get_addon_versions_from_bundle(cls): - con = get_ayon_server_api_connection() - expected_bundle = cls._get_bundle_name() - bundles = con.get_bundles()["bundles"] - bundle = next( - ( - bundle - for bundle in bundles - if bundle["name"] == expected_bundle - ), - None - ) - if bundle is not None: - return bundle["addons"] - return {} - - @classmethod - def get_addon_versions(cls): - cache_item = _AyonSettingsCache.addon_versions - if cache_item.is_outdated: - if cls._use_bundles(): - addons = cls._get_addon_versions_from_bundle() - else: - con = get_ayon_server_api_connection() - settings_data = con.get_addons_settings( - only_values=False, - variant=cls._get_variant() - ) - addons = settings_data["versions"] - cache_item.update_value(addons) - - return cache_item.get_value() - - -def get_site_local_overrides(project_name, site_name, local_settings=None): - """Site overrides from local settings for passet project and site name. - - Deprecated: - This function is not implemented for AYON and will be removed. - - Args: - project_name (str): For which project are overrides. - site_name (str): For which site are overrides needed. - local_settings (dict): Preloaded local settings. They are loaded - automatically if not passed. - """ - - return {} - - -def get_ayon_settings(project_name=None): - """AYON studio settings. - - Raw AYON settings values. - - Args: - project_name (Optional[str]): Project name. - - Returns: - dict[str, Any]: AYON settings. - """ - - return _AyonSettingsCache.get_value_by_project(project_name) - - -def get_studio_settings(*args, **kwargs): - return _AyonSettingsCache.get_value_by_project(None) - - -# Backward compatibility -get_system_settings = get_studio_settings - - -def get_project_settings(project_name, *args, **kwargs): - return _AyonSettingsCache.get_value_by_project(project_name) - - -def get_general_environments(studio_settings=None): - """General studio environment variables. - - Args: - studio_settings (Optional[dict]): Pre-queried studio settings. - - Returns: - dict[str, Any]: General studio environment variables. - - """ - if studio_settings is None: - studio_settings = get_ayon_settings() - return json.loads(studio_settings["core"]["environments"]) - - -def get_project_environments(project_name, project_settings=None): - """Project environment variables. - - Args: - project_name (str): Project name. - project_settings (Optional[dict]): Pre-queried project settings. - - Returns: - dict[str, Any]: Project environment variables. - - """ - if project_settings is None: - project_settings = get_project_settings(project_name) - return json.loads( - project_settings["core"]["project_environments"] - ) - - -def get_current_project_settings(): - """Project settings for current context project. - - Project name should be stored in environment variable `AYON_PROJECT_NAME`. - This function should be used only in host context where environment - variable must be set and should not happen that any part of process will - change the value of the enviornment variable. - """ - project_name = os.environ.get("AYON_PROJECT_NAME") - if not project_name: - raise ValueError( - "Missing context project in environemt variable `AYON_PROJECT_NAME`." - ) - return get_project_settings(project_name) - - - diff --git a/client/ayon_core/settings/local_settings.md b/client/ayon_core/settings/local_settings.md deleted file mode 100644 index fbb5cf3df1..0000000000 --- a/client/ayon_core/settings/local_settings.md +++ /dev/null @@ -1,79 +0,0 @@ -# Structure of local settings -- local settings do not have any validation schemas right now this should help to see what is stored to local settings and how it works -- they are stored by identifier site_id which should be unified identifier of workstation -- all keys may and may not available on load -- contain main categories: `general`, `applications`, `projects` - -## Categories -### General -- ATM contain only label of site -```json -{ - "general": { - "site_label": "MySite" - } -} -``` - -### Applications -- modifications of application executables -- output should match application groups and variants -```json -{ - "applications": { - "": { - "": { - "executable": "/my/path/to/nuke_12_2" - } - } - } -} -``` - -### Projects -- project specific modifications -- default project is stored under constant key defined in `pype.settings.contants` -```json -{ - "projects": { - "": { - "active_site": "", - "remote_site": "", - "roots": { - "": { - "": "" - } - } - } - } -} -``` - -## Final document -```json -{ - "_id": "", - "site_id": "", - "general": { - "site_label": "MySite" - }, - "applications": { - "": { - "": { - "executable": "" - } - } - }, - "projects": { - "": { - "active_site": "", - "remote_site": "", - "roots": { - "": { - "": "" - } - } - } - } -} -``` From d98c055a048591168d64b3e0ec11243bbbf8bde6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 20:14:43 +0800 Subject: [PATCH 38/59] some tweaks on codes --- .../plugins/publish/validate_mesh_has_uv.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index 846e537b69..21339c1d41 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -33,9 +33,21 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, @classmethod def get_invalid(cls, instance): - invalid = [member for member in instance.data["members"] - if not member.mesh.numTVerts > 0] - return invalid + invalid_mesh_type = [member for member in instance.data["members"] + if not rt.isProperty(member, "mesh")] + if invalid_mesh_type: + cls.log.error("Non-mesh type objects detected:\n".join( + "-{}".format(invalid.name) for invalid + in invalid_mesh_type)) + return invalid_mesh_type + + invalid_uv = [member for member in instance.data["members"] + if not member.mesh.numTVerts > 0] + if invalid_uv: + cls.log.error("Meshes detected with invalid UVs:\n".join( + "-{}".format(invalid.name) for invalid + in invalid_uv)) + return invalid_uv def process(self, instance): invalid = self.get_invalid(instance) @@ -47,7 +59,7 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, report = ( "Model meshes are required to have UVs.\n\n" "Meshes detected with invalid or missing UVs:\n" - f"{bullet_point_invalid_statement}\n\n" + f"{bullet_point_invalid_statement}\n" ) raise PublishValidationError( report, From 446160f758f2bed3225bb898c9ca605808fbffa4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 20:15:31 +0800 Subject: [PATCH 39/59] cosmetic for docstring --- .../hosts/max/plugins/publish/validate_instance_in_context.py | 1 - 1 file changed, 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 788e9fb19c..93f977c15c 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 @@ -23,7 +23,6 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, Action on this validator will select invalid instances. """ - order = ValidateContentsOrder label = "Instance in same Context" optional = True From 05d308d975c33de04d01c24d8c58bbc1a2a3436a Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 28 Feb 2024 12:41:43 +0000 Subject: [PATCH 40/59] Added missing hosts to collect_audio --- client/ayon_core/plugins/publish/collect_audio.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_audio.py b/client/ayon_core/plugins/publish/collect_audio.py index f0efd546e7..af6598ad59 100644 --- a/client/ayon_core/plugins/publish/collect_audio.py +++ b/client/ayon_core/plugins/publish/collect_audio.py @@ -40,7 +40,10 @@ class CollectAudio(pyblish.api.ContextPlugin): "webpublisher", "aftereffects", "flame", - "unreal" + "unreal", + "blender", + "houdini", + "max", ] audio_product_name = "audioMain" From 0e35253bdee1a0bdef553ab7b8d05823280de6f9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 20:44:33 +0800 Subject: [PATCH 41/59] add a check on non-mesh objects in the validator --- .../max/plugins/publish/validate_mesh_has_uv.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index 21339c1d41..5594a6a789 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -36,17 +36,13 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, invalid_mesh_type = [member for member in instance.data["members"] if not rt.isProperty(member, "mesh")] if invalid_mesh_type: - cls.log.error("Non-mesh type objects detected:\n".join( - "-{}".format(invalid.name) for invalid - in invalid_mesh_type)) + cls.log.error("Non-mesh type objects detected") return invalid_mesh_type invalid_uv = [member for member in instance.data["members"] if not member.mesh.numTVerts > 0] if invalid_uv: - cls.log.error("Meshes detected with invalid UVs:\n".join( - "-{}".format(invalid.name) for invalid - in invalid_uv)) + cls.log.error("Meshes detected with invalid UVs") return invalid_uv def process(self, instance): @@ -57,13 +53,14 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, in invalid ) report = ( - "Model meshes are required to have UVs.\n\n" + "Non-mesh objects found or mesh objects" + " do not have UVs.\n\n" "Meshes detected with invalid or missing UVs:\n" f"{bullet_point_invalid_statement}\n" ) raise PublishValidationError( report, description=( - "Model meshes are required to have UVs.\n\n" + "Non-mesh objects detected or the meshes do not have any UVs.\n\n" "Meshes detected with no texture vertice or missing UVs"), - title="Mesh has missing UVs") + title="Non-mesh objects found or mesh has missing UVs") From ce94823a0f09a707bbcbc51f87941c4d756e0df4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 20:51:16 +0800 Subject: [PATCH 42/59] restore unrelated deletion of unrelated code --- client/ayon_core/settings/local_settings.md | 79 +++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 client/ayon_core/settings/local_settings.md diff --git a/client/ayon_core/settings/local_settings.md b/client/ayon_core/settings/local_settings.md new file mode 100644 index 0000000000..6694bb60a8 --- /dev/null +++ b/client/ayon_core/settings/local_settings.md @@ -0,0 +1,79 @@ +# Structure of local settings +- local settings do not have any validation schemas right now this should help to see what is stored to local settings and how it works +- they are stored by identifier site_id which should be unified identifier of workstation +- all keys may and may not available on load +- contain main categories: `general`, `applications`, `projects` + +## Categories +### General +- ATM contain only label of site +```json +{ + "general": { + "site_label": "MySite" + } +} +``` + +### Applications +- modifications of application executables +- output should match application groups and variants +```json +{ + "applications": { + "": { + "": { + "executable": "/my/path/to/nuke_12_2" + } + } + } +} +``` + +### Projects +- project specific modifications +- default project is stored under constant key defined in `pype.settings.contants` +```json +{ + "projects": { + "": { + "active_site": "", + "remote_site": "", + "roots": { + "": { + "": "" + } + } + } + } +} +``` + +## Final document +```json +{ + "_id": "", + "site_id": "", + "general": { + "site_label": "MySite" + }, + "applications": { + "": { + "": { + "executable": "" + } + } + }, + "projects": { + "": { + "active_site": "", + "remote_site": "", + "roots": { + "": { + "": "" + } + } + } + } +} +``` \ No newline at end of file From 3cf206a61622e5e99fcdfa5706e9d3da16fbfe50 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 20:54:00 +0800 Subject: [PATCH 43/59] restore unrelated deletion of unrelated code --- client/ayon_core/settings/local_settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/settings/local_settings.md b/client/ayon_core/settings/local_settings.md index 6694bb60a8..fbb5cf3df1 100644 --- a/client/ayon_core/settings/local_settings.md +++ b/client/ayon_core/settings/local_settings.md @@ -76,4 +76,4 @@ } } } -``` \ No newline at end of file +``` From 9ad258997b38968eb126d752ec588f955f6446b1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 29 Feb 2024 13:40:08 +0800 Subject: [PATCH 44/59] check instance data against context data rather than check user sertup properties against context data --- .../publish/validate_instance_in_context.py | 55 +++++++------------ 1 file changed, 21 insertions(+), 34 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 93f977c15c..dcd30da459 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 @@ -33,32 +33,18 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, if not self.is_active(instance.data): return - instance_node = rt.getNodeByName(instance.data.get( - "instance_node", "")) - if not instance_node: - return - asset = rt.getUserProp(instance_node, "folderPath") - context_asset = instance.context.data["folderPath"] - task = rt.getUserProp(instance_node, "task") - context_task = instance.context.data["task"] - invalid = [] - if asset != context_asset: - invalid.append( - "Instance '{}' publishes to different asset than current " - "context: {}. Current context: {}".format( - instance.name, asset, context_asset + folderPath = instance.data.get("folderPath") + task = instance.data.get("task") + context = self.get_context(instance) + if (folderPath, task) != context: + context_label = "{} > {}".format(*context) + instance_label = "{} > {}".format(folderPath, task) + message = ( + "Instance '{}' publishes to different folderPath than current" + "context: {}. Current context:{}".format( + instance.name, instance_label, context_label ) ) - if task != context_task: - invalid.append( - "Instance '{}' publishes to different task than current " - "context: {}. Current context: {}".format( - instance.name, task, context_task - ) - ) - - if invalid: - message = "\n".join(invalid) raise PublishValidationError( message=message, description=( @@ -75,16 +61,11 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, @classmethod def get_invalid(cls, instance): invalid = [] - node = rt.getNodeByName(instance.data["instance_node"]) - asset = rt.getUserProp(node, "folderPath") - context_asset = instance.context.data["folderPath"] - if asset != context_asset: - invalid.append(node) - task = rt.getUserProp(node, "task") - context_task = instance.context.data["task"] - if task != context_task: - invalid.append(node) - + folderPath = instance.data.get("folderPath") + task = instance.data.get("task") + context = cls.get_context(instance) + if (folderPath, task) != context: + invalid.append(rt.getNodeByName(instance.name)) return invalid @classmethod @@ -97,3 +78,9 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, return rt.SetUserProp(instance_node, "folderPath", context_asset) rt.SetUserProp(instance_node, "task", context_task) + + @staticmethod + def get_context(instance): + """Return asset, task from publishing context data""" + context = instance.context + return context.data["folderPath"], context.data["task"] From 56f4f04d1c1b94b8afc41da66eaff99ba8c49bd6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 29 Feb 2024 15:08:25 +0800 Subject: [PATCH 45/59] edit the code would pass only when the object is mesh type --- .../plugins/publish/validate_mesh_has_uv.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index 5594a6a789..4d1490ac8f 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -30,20 +30,17 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, actions = [SelectInvalidAction] optional = True - @classmethod def get_invalid(cls, instance): invalid_mesh_type = [member for member in instance.data["members"] - if not rt.isProperty(member, "mesh")] + if rt.isProperty(member, "mesh")] if invalid_mesh_type: - cls.log.error("Non-mesh type objects detected") - return invalid_mesh_type + invalid_uv = [member for member in instance.data["members"] + if not member.mesh.numTVerts > 0] + if invalid_uv: + cls.log.error("Meshes detected with invalid UVs") - invalid_uv = [member for member in instance.data["members"] - if not member.mesh.numTVerts > 0] - if invalid_uv: - cls.log.error("Meshes detected with invalid UVs") - return invalid_uv + return invalid_uv def process(self, instance): invalid = self.get_invalid(instance) @@ -53,14 +50,13 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, in invalid ) report = ( - "Non-mesh objects found or mesh objects" - " do not have UVs.\n\n" + "Model meshes are required to have UVs.\n\n" "Meshes detected with invalid or missing UVs:\n" f"{bullet_point_invalid_statement}\n" ) raise PublishValidationError( report, description=( - "Non-mesh objects detected or the meshes do not have any UVs.\n\n" + "Model meshes are required to have UVs.\n\n" "Meshes detected with no texture vertice or missing UVs"), title="Non-mesh objects found or mesh has missing UVs") From c7ba4be69c9e46f02b05976ab8aec4514de5306c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 29 Feb 2024 20:37:23 +0800 Subject: [PATCH 46/59] code clean up with bigroy's comment --- .../max/plugins/publish/validate_mesh_has_uv.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index 4d1490ac8f..cecc16c3c3 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -32,15 +32,11 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, @classmethod def get_invalid(cls, instance): - invalid_mesh_type = [member for member in instance.data["members"] - if rt.isProperty(member, "mesh")] - if invalid_mesh_type: - invalid_uv = [member for member in instance.data["members"] - if not member.mesh.numTVerts > 0] - if invalid_uv: - cls.log.error("Meshes detected with invalid UVs") - - return invalid_uv + meshes = [member for member in instance.data["members"] + if rt.isProperty(member, "mesh")] + invalid = [member for member in meshes + if not member.mesh.numTVerts > 0] + return invalid def process(self, instance): invalid = self.get_invalid(instance) From 34cca056153b0ae6d6805c90d14822851a0f21e5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 29 Feb 2024 20:42:32 +0800 Subject: [PATCH 47/59] code clean up --- .../hosts/max/plugins/publish/validate_instance_in_context.py | 4 +--- 1 file changed, 1 insertion(+), 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 dcd30da459..cf285bfab6 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 @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- """Validate if instance asset is the same as context asset.""" -from __future__ import absolute_import - import pyblish.api from ayon_core.pipeline.publish import ( RepairAction, @@ -41,7 +39,7 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, instance_label = "{} > {}".format(folderPath, task) message = ( "Instance '{}' publishes to different folderPath than current" - "context: {}. Current context:{}".format( + "context: {}. Current context: {}".format( instance.name, instance_label, context_label ) ) From 1bdac65841bc92d77c7df5353d7d96b5ab9f8b2d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 29 Feb 2024 20:43:13 +0800 Subject: [PATCH 48/59] code clean up --- .../ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py index cecc16c3c3..109b7fe0b5 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_mesh_has_uv.py @@ -35,7 +35,7 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin, meshes = [member for member in instance.data["members"] if rt.isProperty(member, "mesh")] invalid = [member for member in meshes - if not member.mesh.numTVerts > 0] + if member.mesh.numTVerts == 0] return invalid def process(self, instance): From 48c64c9cafa5e09edbeb10ec7d47329975a7b07d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 1 Mar 2024 17:44:01 +0800 Subject: [PATCH 49/59] cosmetic fix --- .../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 cf285bfab6..8104bfb5e3 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 folderPath than current" + "Instance '{}' publishes to different folderPath than current " "context: {}. Current context: {}".format( instance.name, instance_label, context_label ) From 3b455730892304edc299e10dd43eabe14bdbcdd7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 1 Mar 2024 17:54:43 +0800 Subject: [PATCH 50/59] make the docstring and comment more concise and align with ayon --- .../max/plugins/publish/validate_instance_in_context.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 8104bfb5e3..f116ec5609 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 @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Validate if instance asset is the same as context asset.""" +"""Validate if instance context is the same as current context.""" import pyblish.api from ayon_core.pipeline.publish import ( RepairAction, @@ -13,10 +13,10 @@ from pymxs import runtime as rt class ValidateInstanceInContext(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): - """Validator to check if instance asset match context asset. + """Validator to check if instance context match current context . When working in per-shot style you always publish data in context of - current asset (shot). This validator checks if this is so. It is optional + current context (shot). This validator checks if this is so. It is optional so it can be disabled when needed. Action on this validator will select invalid instances. @@ -38,7 +38,7 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, context_label = "{} > {}".format(*context) instance_label = "{} > {}".format(folderPath, task) message = ( - "Instance '{}' publishes to different folderPath than current " + "Instance '{}' publishes to different context than current " "context: {}. Current context: {}".format( instance.name, instance_label, context_label ) From 7ec4f6cc92d910ac2dbb0b1eb0614dc709500ff8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 1 Mar 2024 18:21:13 +0800 Subject: [PATCH 51/59] cosmetic --- .../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 f116ec5609..6f44356d9f 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 @@ -13,7 +13,7 @@ from pymxs import runtime as rt class ValidateInstanceInContext(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): - """Validator to check if instance context match current context . + """Validator to check if instance context match current context. When working in per-shot style you always publish data in context of current context (shot). This validator checks if this is so. It is optional From 1f2b531a908eb4825233296bfa49ccdc3ef84196 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 2 Mar 2024 00:46:06 +0800 Subject: [PATCH 52/59] tweak on the description comment of publish validation error --- .../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 6f44356d9f..963a601009 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,11 +46,11 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin, raise PublishValidationError( message=message, description=( - "## Publishing to a different asset\n" + "## Publishing to a different context data\n" "There are publish instances present which are publishing " - "into a different asset or task than your current context.\n\n" + "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 " - "where you might want to publish into another asset or " + "where you might want to publish into another context or " "shot. If that's the case you can disable the validation " "on the instance to ignore it." ) From a7ad7211b239040f89467c75dee90841f849aab8 Mon Sep 17 00:00:00 2001 From: murphy Date: Tue, 5 Mar 2024 11:56:47 +0100 Subject: [PATCH 53/59] updating tools defaults --- server_addon/applications/server/tools.json | 160 ++++++++++++++++---- server_addon/applications/server/version.py | 2 +- 2 files changed, 128 insertions(+), 34 deletions(-) diff --git a/server_addon/applications/server/tools.json b/server_addon/applications/server/tools.json index 54bee11cf7..3d8f400200 100644 --- a/server_addon/applications/server/tools.json +++ b/server_addon/applications/server/tools.json @@ -1,55 +1,149 @@ { "tool_groups": [ { - "environment": "{\n \"MTOA\": \"{STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}\",\n \"MAYA_RENDER_DESC_PATH\": \"{MTOA}\",\n \"MAYA_MODULE_PATH\": \"{MTOA}\",\n \"ARNOLD_PLUGIN_PATH\": \"{MTOA}/shaders\",\n \"MTOA_EXTENSIONS_PATH\": {\n \"darwin\": \"{MTOA}/extensions\",\n \"linux\": \"{MTOA}/extensions\",\n \"windows\": \"{MTOA}/extensions\"\n },\n \"MTOA_EXTENSIONS\": {\n \"darwin\": \"{MTOA}/extensions\",\n \"linux\": \"{MTOA}/extensions\",\n \"windows\": \"{MTOA}/extensions\"\n },\n \"DYLD_LIBRARY_PATH\": {\n \"darwin\": \"{MTOA}/bin\"\n },\n \"PATH\": {\n \"windows\": \"{PATH};{MTOA}/bin\"\n }\n}", - "name": "mtoa", - "label": "Autodesk Arnold", + "name": "htoa", + "label": "Arnold for Houdini (example)", "variants": [ { + "name": "5-4-2-7", + "label": "", + "host_names": [ + "houdini" + ], + "environment": "{\n \"HTOA_VERSION\": \"5.4.2.7\"\n}", + "app_variants": [] + } + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"HOUDINI_PATH\": [\n \"{STUDIO_SW}/APP/HTOA/{HTOA_VERSION}/HOUDINI{HOUDINI_VERSION}/WINDOWS/htoa-6.1.3.3_rdb15014_houdini-{HTOA_VERSION}\",\n \"{HOUDINI_PATH}\"\n ],\n \"PATH\": {\n \"windows\": [\n \"{STUDIO_SW}/APP/HTOA/{HTOA_VERSION}/HOUDINI{HOUDINI_VERSION}/WINDOWS/htoa-6.1.3.3_rdb15014_houdini-{HTOA_VERSION}/scripts/bin\",\n \"{PATH}\"\n ]\n }\n}" + }, + { + "name": "mtoa", + "label": "Arnold for Maya (example)", + "variants": [ + { + "name": "5-3-1-0", + "label": "", "host_names": [], - "app_variants": [], - "environment": "{\n \"MTOA_VERSION\": \"3.2\"\n}", - "name": "3-2", - "label": "3.2" + "environment": "{\n \"MTOA_VERSION\": \"5.3.1.0\"\n}", + "app_variants": [] }, { + "name": "5-3-4-1", + "label": "", "host_names": [], - "app_variants": [], - "environment": "{\n \"MTOA_VERSION\": \"3.1\"\n}", - "name": "3-1", - "label": "3.1" + "environment": "{\n \"MTOA_VERSION\": \"5.3.4.1\"\n}", + "app_variants": [] } - ] + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"MTOA\": {\n \"darwin\": \"{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/MAC\",\n \"linux\": \"{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/LINUX\",\n \"windows\": \"{STUDIO_SW}/APP/MTOA/{MTOA_VERSION}/MAYA{MAYA_VERSION}/WINDOWS\"\n },\n \"MAYA_MODULE_PATH\": [\n \"{STUDIO_SW}/APP/MTOA\",\n \"{MAYA_MODULE_PATH}\"\n ],\n \"DYLD_LIBRARY_PATH\": {\n \"darwin\": \"{MTOA}/bin\"\n },\n \"PATH\": {\n \"windows\": [\n \"{MTOA}/bin\",\n \"{PATH}\"\n ]\n },\n \"XBMLANGPATH\": [\n \"{MTOA}/icons\",\n \"{XBMLANGPATH}\"\n ],\n \"MAYA_RENDER_DESC_PATH\": [\n \"{MTOA}\",\n \"{MAYA_RENDER_DESC_PATH}\"\n ],\n \"MTOA_STARTUP_LOG_VERBOSITY\": \"3\"\n}" }, { - "environment": "{}", - "name": "vray", - "label": "Chaos Group Vray", - "variants": [] - }, - { - "environment": "{}", - "name": "yeti", - "label": "Peregrine Labs Yeti", - "variants": [] - }, - { - "environment": "{}", - "name": "renderman", - "label": "Pixar Renderman", + "name": "redshiftMaya", + "label": "Redshift for Maya (example)", "variants": [ { + "name": "3-5-23", + "label": "", + "host_names": [], + "environment": "{\n \"REDSHIFT_VERSION\": \"3.5.23\"\n}", + "app_variants": [] + } + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"REDSHIFT_COREDATAPATH\": {\n \"darwin\": \"{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/MAC\",\n \"linux\": \"{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/LINUX\",\n \"windows\": \"{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/WINDOWS\"\n },\n \"REDSHIFT_ABORTONLICENSEFAIL\": \"0\",\n \"MAYA_MODULE_PATH\": [\n \"{STUDIO_SW}/APP/REDSHIFT\",\n \"{MAYA_MODULE_PATH}\"\n ],\n \"MAYA_PLUG_IN_PATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/Plugins/Maya/{MAYA_VERSION}/nt-x86-64\",\n \"{MAYA_PLUG_IN_PATH}\"\n ],\n \"linux\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}\",\n \"{MAYA_PLUG_IN_PATH}\"\n ],\n \"darwin\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}\",\n \"{MAYA_PLUG_IN_PATH}\"\n ]\n },\n \"MAYA_SCRIPT_PATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/scripts\",\n \"{MAYA_SCRIPT_PATH}\"\n ],\n \"linux\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts\",\n \"{MAYA_SCRIPT_PATH}\"\n ],\n \"darwin\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts\",\n \"{MAYA_SCRIPT_PATH}\"\n ]\n },\n \"REDSHIFT_PROCEDURALSPATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/Procedurals\",\n \"{REDSHIFT_PROCEDURALSPATH}\"\n ],\n \"linux\": [\n \"{REDSHIFT_COREDATAPATH}/procedurals\",\n \"{REDSHIFT_PROCEDURALSPATH}\"\n ],\n \"darwin\": [\n \"{REDSHIFT_COREDATAPATH}/procedurals\",\n \"{REDSHIFT_PROCEDURALSPATH}\"\n ]\n },\n \"REDSHIFT_MAYAEXTENSIONSPATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/Plugins/Maya/{MAYA_VERSION}/nt-x86-64/extensions\",\n \"{REDSHIFT_MAYAEXTENSIONSPATH}\"\n ],\n \"linux\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}/extensions\",\n \"{REDSHIFT_MAYAEXTENSIONSPATH}\"\n ],\n \"darwin\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/{MAYA_VERSION}/extensions\",\n \"{REDSHIFT_MAYAEXTENSIONSPATH}\"\n ]\n },\n \"XBMLANGPATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/icons\",\n \"{XBMLANGPATH}\"\n ],\n \"linux\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/common/icons\",\n \"{XBMLANGPATH}\"\n ],\n \"darwin\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/common/icons\",\n \"{XBMLANGPATH}\"\n ]\n },\n \"MAYA_RENDER_DESC_PATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/rendererDesc\",\n \"{MAYA_RENDER_DESC_PATH}\"\n ],\n \"linux\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/common/rendererDesc\",\n \"{MAYA_RENDER_DESC_PATH}\"\n ],\n \"darwin\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/common/rendererDesc\",\n \"{MAYA_RENDER_DESC_PATH}\"\n ]\n },\n \"MAYA_CUSTOM_TEMPLATE_PATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/Plugins/Maya/Common/scripts/NETemplates\",\n \"{MAYA_CUSTOM_TEMPLATE_PATH}\"\n ],\n \"linux\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts/NETemplates\",\n \"{MAYA_CUSTOM_TEMPLATE_PATH}\"\n ],\n \"darwin\": [\n \"{REDSHIFT_COREDATAPATH}/redshift4maya/common/scripts/NETemplates\",\n \"{MAYA_CUSTOM_TEMPLATE_PATH}\"\n ]\n },\n \"PATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/bin\",\n \"{PATH}\"\n ]\n }\n}" + }, + { + "name": "redshift3dsmax", + "label": "Redshift for 3dsmax (example)", + "variants": [ + { + "name": "3-5-19", + "label": "", + "host_names": [ + "max" + ], + "environment": "{\n \"REDSHIFT_VERSION\": \"3.5.19\"\n}", + "app_variants": [] + } + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"REDSHIFT_COREDATAPATH\": {\n \"darwin\": \"{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/MAC\",\n \"linux\": \"{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/LINUX\",\n \"windows\": \"{STUDIO_SW}/APP/REDSHIFT/{REDSHIFT_VERSION}/WINDOWS\"\n },\n \"REDSHIFT_ABORTONLICENSEFAIL\": \"0\",\n \"REDSHIFT_PROCEDURALSPATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/Procedurals\",\n \"{REDSHIFT_PROCEDURALSPATH}\"\n ],\n \"linux\": [\n \"{REDSHIFT_COREDATAPATH}/procedurals\",\n \"{REDSHIFT_PROCEDURALSPATH}\"\n ],\n \"darwin\": [\n \"{REDSHIFT_COREDATAPATH}/procedurals\",\n \"{REDSHIFT_PROCEDURALSPATH}\"\n ]\n },\n \"PATH\": {\n \"windows\": [\n \"{REDSHIFT_COREDATAPATH}/bin\",\n \"{PATH}\"\n ]\n }\n}" + }, + { + "name": "rendermanMaya", + "label": "Renderman for Maya (example)", + "variants": [ + { + "name": "24-3-maya", + "label": "24.3 RFM", "host_names": [ "maya" ], + "environment": "{\n \"RFMTREE\": {\n \"windows\": \"C:\\\\Program Files\\\\Pixar\\\\RenderManForMaya-24.3\",\n \"darwin\": \"/Applications/Pixar/RenderManForMaya-24.3\",\n \"linux\": \"/opt/pixar/RenderManForMaya-24.3\"\n },\n \"RMANTREE\": {\n \"windows\": \"C:\\\\Program Files\\\\Pixar\\\\RenderManProServer-24.3\",\n \"darwin\": \"/Applications/Pixar/RenderManProServer-24.3\",\n \"linux\": \"/opt/pixar/RenderManProServer-24.3\"\n }\n}", "app_variants": [ "maya/2022" - ], - "environment": "{\n \"RFMTREE\": {\n \"windows\": \"C:\\\\Program Files\\\\Pixar\\\\RenderManForMaya-24.3\",\n \"darwin\": \"/Applications/Pixar/RenderManForMaya-24.3\",\n \"linux\": \"/opt/pixar/RenderManForMaya-24.3\"\n },\n \"RMANTREE\": {\n \"windows\": \"C:\\\\Program Files\\\\Pixar\\\\RenderManProServer-24.3\",\n \"darwin\": \"/Applications/Pixar/RenderManProServer-24.3\",\n \"linux\": \"/opt/pixar/RenderManProServer-24.3\"\n }\n}", - "name": "24-3-maya", - "label": "24.3 RFM" + ] } - ] + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"RFMTREE\": {\n \"darwin\": \"{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/MAC/MAYA\",\n \"linux\": \"{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/LINUX/MAYA\",\n \"windows\": \"{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/WINDOWS/MAYA\"\n },\n \"RMANTREE\": {\n \"darwin\": \"{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/MAC/RenderManProServer-{RM_VERSION}\",\n \"linux\": \"{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/LINUX/RenderManProServer-{RM_VERSION}\",\n \"windows\": \"{STUDIO_SW}/APP/RENDERMAN/{RM_VERSION}/WINDOWS/RenderManProServer-{RM_VERSION}\"\n },\n \"MAYA_MODULE_PATH\": [\n \"{STUDIO_SW}/APP/RENDERMAN\",\n \"{MAYA_MODULE_PATH}\"\n ],\n \"PIXAR_LICENSE_FILE\": \"{STUDIO_SW}/APP/RENDERMAN/pixar.license\",\n \"RFM_DO_NOT_CREATE_MODULE_FILE\": \"1\"\n}" + }, + { + "name": "mGear", + "label": "mGear for Maya (example)", + "variants": [ + { + "name": "4-0-7", + "label": "", + "host_names": [], + "environment": "{\n \"MGEAR_VERSION\": \"4.0.7\"\n}", + "app_variants": [] + } + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"MGEAR_ROOT\": \"{STUDIO_SW}/APP/MGEAR/{MGEAR_VERSION}/MAYA{MAYA_VERSION}/windows/x64\",\n \"MAYA_MODULE_PATH\": [\n \"{STUDIO_SW}/APP/MGEAR/{MGEAR_VERSION}/release\",\n \"{MAYA_MODULE_PATH}\"\n ]\n}" + }, + { + "name": "yetiMaya", + "label": "Yeti for Maya (example)", + "variants": [ + { + "name": "4.2.11", + "label": "", + "host_names": [], + "environment": "{\n \"YETI_VERSION\": \"4.2.11\"\n}", + "app_variants": [] + } + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"YETI_HOME\": {\n \"darwin\": \"{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/MAC\",\n \"linux\": \"{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/LINUX\",\n \"windows\": \"{STUDIO_SW}/APP/YETI/{YETI_VERSION}/MAYA{MAYA_VERSION}/WINDOWS\"\n },\n \"YETI_TMP\": {\n \"windows\": \"C:/temp\",\n \"darwin\": \"/tmp\",\n \"linux\": \"/tmp\"\n },\n \"peregrinel_LICENSE\": \"4202@35.158.197.250\",\n \"MAYA_MODULE_PATH\": [\n \"{STUDIO_SW}/APP/YETI\",\n \"{MAYA_MODULE_PATH}\"\n ]\n}" + }, + { + "name": "vrayMaya", + "label": "Vray for Maya (example)", + "variants": [ + { + "name": "6.10.01", + "label": "", + "host_names": [ + "" + ], + "environment": "{\n \"VRAY_VERSION\": \"6.10.01\"\n}", + "app_variants": [] + } + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"MAYA_MODULE_PATH\": {\n \"windows\": [\n \"{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/WINDOWS/maya_root/modules\",\n \"{MAYA_MODULE_PATH}\"\n ],\n \"linux\": [\n \"{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/LINUX/maya_root/modules\",\n \"{MAYA_MODULE_PATH}\"\n ],\n \"darwin\": [\n \"{STUDIO_SW}/APP/VRAY/{VRAY_VERSION}/MAYA{MAYA_VERSION}/MAC/maya_root/modules\",\n \"{MAYA_MODULE_PATH}\"\n ]\n },\n \"VRAY_AUTH_CLIENT_FILE_PATH\": \"{STUDIO_SW}/APP/VRAY\"\n}" + }, + { + "name": "vraynuke", + "label": "Vray for Nuke (example)", + "variants": [ + { + "name": "5-20-00", + "label": "", + "host_names": [ + "nuke" + ], + "environment": "{\n \"VRAYNUKE_VERSION\": \"5.20.00\"\n}", + "app_variants": [] + } + ], + "environment": "{\n \"_comment_\": \"{STUDIO_SW} points to software repository. Can be defined in Core addon globally\",\n\n \"VRAY_FOR_NUKE_13_0_PLUGINS\": {\n \"windows\": \"{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_vray/plugins/vray\"\n },\n \"NUKE_PATH\": {\n \"windows\": [\n \"{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_root\",\n \"{NUKE_PATH}\"\n ]\n },\n \"PATH\": {\n \"windows\": [\n \"{STUDIO_SW}/APP/VRAYNUKE/{VRAYNUKE_VERSION}/NUKE{NUKE_VRAY_VERSION}/WINDOWS/nuke_vray\",\n \"{PATH}\"\n ]\n },\n \"VRAY_AUTH_CLIENT_FILE_PATH\": \"{STUDIO_SW}/APP/VRAY\"\n}" } ] -} +} \ No newline at end of file diff --git a/server_addon/applications/server/version.py b/server_addon/applications/server/version.py index 0a8da88258..f1380eede2 100644 --- a/server_addon/applications/server/version.py +++ b/server_addon/applications/server/version.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.1.7" From dad4bbe9d41cc9a6af50bad4b5ca3a7ad52c7685 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Mar 2024 13:36:24 +0100 Subject: [PATCH 54/59] Update plugin settings title and version number. - Updated a plugin setting title to "Product types" from "Sync workfile versions for families". - Increased the version number from "0.1.9" to "0.1.10". --- server_addon/nuke/server/settings/publish_plugins.py | 2 +- server_addon/nuke/server/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py index 02ee9b3bab..7d9c914fee 100644 --- a/server_addon/nuke/server/settings/publish_plugins.py +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -56,7 +56,7 @@ class CollectInstanceDataModel(BaseSettingsModel): sync_workfile_version_on_product_types: list[str] = SettingsField( default_factory=list, enum_resolver=nuke_product_types_enum, - title="Sync workfile versions for familes" + title="Product types" ) diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py index c11f861afb..569b1212f7 100644 --- a/server_addon/nuke/server/version.py +++ b/server_addon/nuke/server/version.py @@ -1 +1 @@ -__version__ = "0.1.9" +__version__ = "0.1.10" From 2caf32c5b05f0153a8f1c25ad80cb08301d6d6e3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 6 Mar 2024 23:08:40 +0800 Subject: [PATCH 55/59] supports the check on only one keyframe in timeline --- .../plugins/publish/validate_no_animation.py | 19 ++++++++++++++++++- server_addon/max/server/version.py | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py b/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py index 0b7a296cd9..4b2a18d606 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_no_animation.py @@ -8,6 +8,23 @@ from ayon_core.pipeline import ( from ayon_core.hosts.max.api.action import SelectInvalidAction +def get_invalid_keys(obj): + """function to check on whether there is keyframe in + + Args: + obj (str): object needed to check if there is a keyframe + + Returns: + bool: whether invalid object(s) exist + """ + for transform in ["Position", "Rotation", "Scale"]: + num_of_key = rt.NumKeys(rt.getPropertyController( + obj.controller, transform)) + if num_of_key > 0: + return True + return False + + class ValidateNoAnimation(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Validates No Animation @@ -45,6 +62,6 @@ class ValidateNoAnimation(pyblish.api.InstancePlugin, list: list of invalid objects """ invalid = [invalid for invalid in instance.data["members"] - if invalid.isAnimated] + if invalid.isAnimated or get_invalid_keys(invalid)] return invalid diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index 0a8da88258..f1380eede2 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.1.7" From fd8bf2cc67d03d9e7b80c903e1d6ccb40d6c0df0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 6 Mar 2024 16:29:12 +0100 Subject: [PATCH 56/59] Adjust clip start if slate is present Trim the clip's start time by 1 frame and update its duration accordingly when a "slate" is detected in the version data families. Also, corrected calculation for `frame_end_handle` to use `frame_end`. --- client/ayon_core/hosts/resolve/api/plugin.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/hosts/resolve/api/plugin.py b/client/ayon_core/hosts/resolve/api/plugin.py index 157b8de363..dfce3ea37a 100644 --- a/client/ayon_core/hosts/resolve/api/plugin.py +++ b/client/ayon_core/hosts/resolve/api/plugin.py @@ -410,6 +410,11 @@ class ClipLoader: source_out = int(_clip_property("End")) source_duration = int(_clip_property("Frames")) + # Trim clip start if slate is present + if "slate" in self.data["versionData"]["families"]: + source_in += 1 + source_duration = source_out - source_in + 1 + if not self.with_handles: # Load file without the handles of the source media # We remove the handles from the source in and source out @@ -435,7 +440,7 @@ class ClipLoader: handle_start = version_data.get("handleStart", 0) handle_end = version_data.get("handleEnd", 0) frame_start_handle = frame_start - handle_start - frame_end_handle = frame_start + handle_end + frame_end_handle = frame_end + handle_end database_frame_duration = int( frame_end_handle - frame_start_handle + 1 ) From b5873d47a8c8b9f4d812421bee0221f4ad7b7030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 6 Mar 2024 16:44:18 +0100 Subject: [PATCH 57/59] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5a0ed1ae1d..e6badf936a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug Report description: File a bug report -title: '' +title: 'Your issue title here' labels: - 'type: bug' body: From 14db89cc359c2240727a24cc832e1733dbb67414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 6 Mar 2024 16:45:43 +0100 Subject: [PATCH 58/59] Update enhancement_request.yml --- .github/ISSUE_TEMPLATE/enhancement_request.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/enhancement_request.yml b/.github/ISSUE_TEMPLATE/enhancement_request.yml index da4d0d9319..31b2eb2edd 100644 --- a/.github/ISSUE_TEMPLATE/enhancement_request.yml +++ b/.github/ISSUE_TEMPLATE/enhancement_request.yml @@ -1,6 +1,6 @@ name: Enhancement Request description: Create a report to help us enhance a particular feature -title: "" +title: "Your issue title here" labels: - "type: enhancement" body: @@ -49,4 +49,4 @@ body: label: "Additional context:" description: Add any other context or screenshots about the enhancement request here. validations: - required: false \ No newline at end of file + required: false From 7b19e06787dc00681369126136e0cc76d7fa4d82 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Mar 2024 19:37:03 +0800 Subject: [PATCH 59/59] removing remove instances --- .../hosts/max/plugins/create/create_workfile.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/client/ayon_core/hosts/max/plugins/create/create_workfile.py b/client/ayon_core/hosts/max/plugins/create/create_workfile.py index 1552149413..058fe10eb2 100644 --- a/client/ayon_core/hosts/max/plugins/create/create_workfile.py +++ b/client/ayon_core/hosts/max/plugins/create/create_workfile.py @@ -98,21 +98,6 @@ class CreateWorkfile(plugin.MaxCreatorBase, AutoCreator): created_inst.data_to_store() ) - def remove_instances(self, instances): - """Remove specified instance from the scene. - - This is only removing `id` parameter so instance is no longer - instance, because it might contain valuable data for artist. - - """ - for instance in instances: - instance_node = rt.GetNodeByName( - instance.data.get("instance_node")) - if instance_node: - rt.Delete(instance_node) - - self._remove_instance_from_context(instance) - def create_node(self, product_name): if rt.getNodeByName(product_name): node = rt.getNodeByName(product_name)