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 new file mode 100644 index 0000000000..a0cad4e454 --- /dev/null +++ b/client/ayon_core/hosts/max/plugins/publish/validate_model_name.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +"""Validate model nodes names.""" +import re + +import pyblish.api + +from ayon_core.hosts.max.api.action import SelectInvalidAction + +from ayon_core.pipeline.publish import ( + OptionalPyblishPluginMixin, + PublishXmlValidationError, + ValidateContentsOrder +) + +class ValidateModelName(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validate Model Name. + + 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 + order = ValidateContentsOrder + hosts = ["max"] + families = ["model"] + label = "Validate Model Name" + actions = [SelectInvalidAction] + # defined by settings + regex = r"(.*)_(?P.*)_(GEO)" + # cache + regex_compiled = None + + def process(self, instance): + if not self.is_active(instance.data): + 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: + return obj 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..4b2a18d606 --- /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 + + +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 + + 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: + raise PublishValidationError( + "Keyframes found on:\n\n{0}".format(invalid) + , + title="Keyframes on model" + ) + + @staticmethod + def get_invalid(instance): + """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 or get_invalid_keys(invalid)] + + return invalid diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index 729b8aa006..c23ddced30 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -49,6 +49,20 @@ class FamilyMappingItemModel(BaseSettingsModel): ) +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=( + "Regex for validating model name. You can use named " + " capturing groups:(?P.*) for Asset name" + ) + ) + + class ValidateLoadedPluginModel(BaseSettingsModel): enabled: bool = SettingsField(title="Enabled") optional: bool = SettingsField(title="Optional") @@ -86,6 +100,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" @@ -94,6 +112,10 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Mesh Has UVs" ) + ValidateModelName: ValidateModelNameModel = SettingsField( + default_factory=ValidateModelNameModel, + title="Validate Model Name" + ) ExtractModelObj: BasicValidateModel = SettingsField( default_factory=BasicValidateModel, title="Extract OBJ", @@ -142,6 +164,12 @@ DEFAULT_PUBLISH_SETTINGS = { "nearclip": 1.0, "farclip": 1000.0 }, + "ValidateModelName": { + "enabled": True, + "optional": True, + "active": False, + "regex": "(.*)_(?P.*)_(GEO)" + }, "ValidateLoadedPlugin": { "enabled": False, "optional": True, @@ -152,6 +180,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "active": False }, + "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 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"