Skip to content

validate_model_content

ValidateModelContent

Bases: MayaInstancePlugin, OptionalPyblishPluginMixin

Adheres to the content of 'model' product type

See get_description for more details.

Source code in client/ayon_maya/plugins/publish/validate_model_content.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
class ValidateModelContent(plugin.MayaInstancePlugin,
                           OptionalPyblishPluginMixin):
    """Adheres to the content of 'model' product type

    See `get_description` for more details.

    """

    order = ValidateContentsOrder
    families = ["model"]
    label = "Model Content"
    actions = [ayon_maya.api.action.SelectInvalidAction]

    validate_top_group = True
    optional = False

    allowed = ('mesh', 'transform', 'nurbsCurve', 'nurbsSurface', 'locator')

    @classmethod
    def get_invalid(cls, instance):

        content_instance = instance.data.get("setMembers", None)
        if not content_instance:
            cls.log.error("Model instance has no nodes. "
                          "It is not allowed to be empty")
            return [instance.data["instance_node"]]

        # All children will be included in the extracted export so we also
        # validate *all* descendents of the set members and we skip any
        # intermediate shapes
        descendants = cmds.listRelatives(content_instance,
                                         allDescendents=True,
                                         fullPath=True) or []
        descendants = cmds.ls(descendants, noIntermediate=True, long=True)
        content_instance = list(set(content_instance + descendants))

        # Ensure only valid node types
        nodes = cmds.ls(content_instance, long=True)
        valid = cmds.ls(content_instance, long=True, type=cls.allowed)
        invalid = set(nodes) - set(valid)

        if invalid:
            # List as bullet points
            invalid_bullets = "\n".join(f"- {node}" for node in invalid)

            cls.log.error(
                "These nodes are not allowed:\n{}\n\n"
                "The valid node types are: {}".format(
                    invalid_bullets, ", ".join(cls.allowed))
            )
            return list(invalid)

        if not valid:
            cls.log.error(
                "No valid nodes in the model instance.\n"
                "The valid node types are: {}".format(", ".join(cls.allowed))
            )
            return [instance.data["instance_node"]]

        # Ensure it has shapes
        shapes = cmds.ls(valid, long=True, shapes=True)
        if not shapes:
            cls.log.error("No shapes in the model instance")
            return [instance.data["instance_node"]]

        # Ensure single top group
        top_parents = {"|" + x.split("|", 2)[1] for x in content_instance}
        if cls.validate_top_group and len(top_parents) != 1:
            cls.log.error(
                "A model instance must have exactly one top group. "
                "Found top groups: {}".format(", ".join(top_parents))
            )
            return list(top_parents)

        def _is_visible(node):
            """Return whether node is visible"""
            return lib.is_visible(node,
                                  displayLayer=False,
                                  intermediateObject=True,
                                  parentHidden=True,
                                  visibility=True)

        # The roots must be visible (the assemblies)
        for parent in top_parents:
            if not _is_visible(parent):
                cls.log.error("Invisible parent (root node) is not "
                              "allowed: {0}".format(parent))
                invalid.add(parent)

        # Ensure at least one shape is visible
        if not any(_is_visible(shape) for shape in shapes):
            cls.log.error("No visible shapes in the model instance")
            invalid.update(shapes)

        return list(invalid)

    def process(self, instance):
        if not self.is_active(instance.data):
            return
        invalid = self.get_invalid(instance)

        if invalid:
            raise PublishValidationError(
                title="Model content is invalid",
                message="Model content is invalid. See log for more details.",
                description=self.get_description()
            )

    @classmethod
    def get_description(cls):
        return inspect.cleandoc(f"""
            ### Model content is invalid

            Your model instance does not adhere to the rules of a
            model product type:

            - Must have at least one visible shape in it, like a mesh.
            - Must have one root node. When exporting multiple meshes they
              must be inside a group.
            - May only contain the following node types:
            {", ".join(cls.allowed)}
        """)