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"