From 45bb933e801d703f527bc5813c649f29b3c5312e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 25 Jan 2024 17:09:44 +0800 Subject: [PATCH 01/19] 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 1d83c6aaa8adcb77ba9b5395cceae064e4aa8e0a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 Jan 2024 17:19:04 +0800 Subject: [PATCH 02/19] 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 03/19] 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 04/19] 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 05/19] 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 6b9da9b1e83a1be30197d00d4aae198050590d33 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 2 Feb 2024 16:14:08 +0800 Subject: [PATCH 06/19] 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 12f78b17668c8f5034c60f8ba6921a5aa507c015 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Feb 2024 20:12:22 +0800 Subject: [PATCH 07/19] 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 08/19] 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 09/19] 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 10/19] 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 05b97437b8f28bb43dc6ed303cbb6049f42c1316 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Feb 2024 19:49:38 +0800 Subject: [PATCH 11/19] 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 12/19] 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 4a85b2ec45a05f066758b6be4e17588cdfd51598 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Feb 2024 21:01:12 +0800 Subject: [PATCH 13/19] 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 c25161d454ca05dc21abff0eacf885c2e6cddbd8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 22 Feb 2024 18:26:54 +0800 Subject: [PATCH 14/19] 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 344e244a993a3082b8836afdb87f5883e63ff0e8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 26 Feb 2024 18:04:40 +0800 Subject: [PATCH 15/19] 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 16/19] 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 ef654cd7d8d1cbe3aea4238328aced4082bd9d4a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 26 Feb 2024 20:05:09 +0800 Subject: [PATCH 17/19] 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 fadf820fea6bcd4c40dd7a2ce43f43cf322a3b02 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 28 Feb 2024 18:53:44 +0800 Subject: [PATCH 18/19] 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 2caf32c5b05f0153a8f1c25ad80cb08301d6d6e3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 6 Mar 2024 23:08:40 +0800 Subject: [PATCH 19/19] 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"