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"