Skip to content

validate_rig_contents

ValidateRigContents

Bases: MayaInstancePlugin, OptionalPyblishPluginMixin

Ensure rig contains pipeline-critical content

Every rig must contain at least two object sets

"controls_SET" - Set of all animatable controls "out_SET" - Set of all cacheable meshes

Source code in client/ayon_maya/plugins/publish/validate_rig_contents.py
 11
 12
 13
 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
class ValidateRigContents(plugin.MayaInstancePlugin,
                          OptionalPyblishPluginMixin):
    """Ensure rig contains pipeline-critical content

    Every rig must contain at least two object sets:
        "controls_SET" - Set of all animatable controls
        "out_SET" - Set of all cacheable meshes

    """

    order = ValidateContentsOrder
    label = "Rig Contents"
    families = ["rig"]
    action = [ayon_maya.api.action.SelectInvalidAction]
    optional = True

    accepted_output = ["mesh", "transform"]
    accepted_controllers = ["transform"]

    def process(self, instance):
        if not self.is_active(instance.data):
            return
        invalid = self.get_invalid(instance)
        if invalid:
            raise PublishValidationError(
                "Invalid rig content. See log for details.")

    @classmethod
    def get_invalid(cls, instance):

        # Find required sets by suffix
        required, rig_sets = cls.get_nodes(instance)

        cls.validate_missing_objectsets(instance, required, rig_sets)

        controls_set = rig_sets["controls_SET"]
        out_set = rig_sets["out_SET"]

        # Ensure contents in sets and retrieve long path for all objects
        output_content = cmds.sets(out_set, query=True) or []
        if not output_content:
            raise PublishValidationError("Must have members in rig out_SET")
        output_content = cmds.ls(output_content, long=True)

        controls_content = cmds.sets(controls_set, query=True) or []
        if not controls_content:
            raise PublishValidationError(
                "Must have members in rig controls_SET"
            )
        controls_content = cmds.ls(controls_content, long=True)

        rig_content = output_content + controls_content
        invalid_hierarchy = cls.invalid_hierarchy(instance, rig_content)

        # Additional validations
        invalid_geometry = cls.validate_geometry(output_content)
        invalid_controls = cls.validate_controls(controls_content)

        error = False
        if invalid_hierarchy:
            cls.log.error("Found nodes which reside outside of root group "
                           "while they are set up for publishing."
                           "\n%s" % invalid_hierarchy)
            error = True

        if invalid_controls:
            cls.log.error("Only transforms can be part of the controls_SET."
                           "\n%s" % invalid_controls)
            error = True

        if invalid_geometry:
            cls.log.error("Only meshes can be part of the out_SET\n%s"
                           % invalid_geometry)
            error = True
        if error:
            return invalid_hierarchy + invalid_controls + invalid_geometry

    @classmethod
    def validate_missing_objectsets(cls, instance,
                                    required_objsets, rig_sets):
        """Validate missing objectsets in rig sets

        Args:
            instance (pyblish.api.Instance): instance
            required_objsets (list[str]): list of objectset names
            rig_sets (list[str]): list of rig sets

        Raises:
            PublishValidationError: When the error is raised, it will show
                which instance has the missing object sets
        """
        missing = [
            key for key in required_objsets if key not in rig_sets
        ]
        if missing:
            raise PublishValidationError(
                "%s is missing sets: %s" % (instance, ", ".join(missing))
            )

    @classmethod
    def invalid_hierarchy(cls, instance, content):
        """
        Check if all rig set members are within the hierarchy of the rig root

        Args:
            instance (pyblish.api.Instance): instance
            content (list[str]): list of content from rig sets

        Raises:
            PublishValidationError: It means no dag nodes in
                the rig instance

        Returns:
            List[str]: invalid hierarchy
        """
        # Ensure there are at least some transforms or dag nodes
        # in the rig instance
        set_members = instance.data['setMembers']
        if not cmds.ls(set_members, type="dagNode", long=True):
            raise PublishValidationError(
                "No dag nodes in the rig instance. "
                "(Empty instance?)"
            )
        # Validate members are inside the hierarchy from root node
        root_nodes = cmds.ls(set_members, assemblies=True, long=True)
        hierarchy = cmds.listRelatives(root_nodes, allDescendents=True,
                                       fullPath=True) + root_nodes
        hierarchy = set(hierarchy)
        invalid_hierarchy = []
        for node in content:
            if node not in hierarchy:
                invalid_hierarchy.append(node)
        return invalid_hierarchy

    @classmethod
    def validate_geometry(cls, set_members):
        """Checks if the node types of the set members valid

        Args:
            set_members (list[str]): nodes of the out_set

        Returns:
            list[str]: Nodes of invalid types.
        """

        # Validate all shape types
        invalid = []
        shapes = cmds.listRelatives(set_members,
                                    allDescendents=True,
                                    shapes=True,
                                    fullPath=True) or []
        all_shapes = cmds.ls(set_members + shapes, long=True, shapes=True)
        for shape in all_shapes:
            if cmds.nodeType(shape) not in cls.accepted_output:
                invalid.append(shape)

        return invalid

    @classmethod
    def validate_controls(cls, set_members):
        """Checks if the node types of the set members are valid for controls.

        Args:
            set_members (list[str]): list of nodes of the controls_set

        Returns:
            list: Controls of disallowed node types.
        """

        # Validate control types
        invalid = []
        for node in set_members:
            if cmds.nodeType(node) not in cls.accepted_controllers:
                invalid.append(node)

        return invalid

    @classmethod
    def get_nodes(cls, instance):
        """Get the target objectsets and rig sets nodes

        Args:
            instance (pyblish.api.Instance): instance

        Returns:
            tuple: 2-tuple of list of objectsets,
                list of rig sets nodes
        """
        objectsets = ["controls_SET", "out_SET"]
        rig_sets_nodes = instance.data.get("rig_sets", [])
        return objectsets, rig_sets_nodes

get_nodes(instance) classmethod

Get the target objectsets and rig sets nodes

Parameters:

Name Type Description Default
instance Instance

instance

required

Returns:

Name Type Description
tuple

2-tuple of list of objectsets, list of rig sets nodes

Source code in client/ayon_maya/plugins/publish/validate_rig_contents.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
@classmethod
def get_nodes(cls, instance):
    """Get the target objectsets and rig sets nodes

    Args:
        instance (pyblish.api.Instance): instance

    Returns:
        tuple: 2-tuple of list of objectsets,
            list of rig sets nodes
    """
    objectsets = ["controls_SET", "out_SET"]
    rig_sets_nodes = instance.data.get("rig_sets", [])
    return objectsets, rig_sets_nodes

invalid_hierarchy(instance, content) classmethod

Check if all rig set members are within the hierarchy of the rig root

Parameters:

Name Type Description Default
instance Instance

instance

required
content list[str]

list of content from rig sets

required

Raises:

Type Description
PublishValidationError

It means no dag nodes in the rig instance

Returns:

Type Description

List[str]: invalid hierarchy

Source code in client/ayon_maya/plugins/publish/validate_rig_contents.py
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
136
137
138
139
140
141
142
143
@classmethod
def invalid_hierarchy(cls, instance, content):
    """
    Check if all rig set members are within the hierarchy of the rig root

    Args:
        instance (pyblish.api.Instance): instance
        content (list[str]): list of content from rig sets

    Raises:
        PublishValidationError: It means no dag nodes in
            the rig instance

    Returns:
        List[str]: invalid hierarchy
    """
    # Ensure there are at least some transforms or dag nodes
    # in the rig instance
    set_members = instance.data['setMembers']
    if not cmds.ls(set_members, type="dagNode", long=True):
        raise PublishValidationError(
            "No dag nodes in the rig instance. "
            "(Empty instance?)"
        )
    # Validate members are inside the hierarchy from root node
    root_nodes = cmds.ls(set_members, assemblies=True, long=True)
    hierarchy = cmds.listRelatives(root_nodes, allDescendents=True,
                                   fullPath=True) + root_nodes
    hierarchy = set(hierarchy)
    invalid_hierarchy = []
    for node in content:
        if node not in hierarchy:
            invalid_hierarchy.append(node)
    return invalid_hierarchy

validate_controls(set_members) classmethod

Checks if the node types of the set members are valid for controls.

Parameters:

Name Type Description Default
set_members list[str]

list of nodes of the controls_set

required

Returns:

Name Type Description
list

Controls of disallowed node types.

Source code in client/ayon_maya/plugins/publish/validate_rig_contents.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
@classmethod
def validate_controls(cls, set_members):
    """Checks if the node types of the set members are valid for controls.

    Args:
        set_members (list[str]): list of nodes of the controls_set

    Returns:
        list: Controls of disallowed node types.
    """

    # Validate control types
    invalid = []
    for node in set_members:
        if cmds.nodeType(node) not in cls.accepted_controllers:
            invalid.append(node)

    return invalid

validate_geometry(set_members) classmethod

Checks if the node types of the set members valid

Parameters:

Name Type Description Default
set_members list[str]

nodes of the out_set

required

Returns:

Type Description

list[str]: Nodes of invalid types.

Source code in client/ayon_maya/plugins/publish/validate_rig_contents.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
@classmethod
def validate_geometry(cls, set_members):
    """Checks if the node types of the set members valid

    Args:
        set_members (list[str]): nodes of the out_set

    Returns:
        list[str]: Nodes of invalid types.
    """

    # Validate all shape types
    invalid = []
    shapes = cmds.listRelatives(set_members,
                                allDescendents=True,
                                shapes=True,
                                fullPath=True) or []
    all_shapes = cmds.ls(set_members + shapes, long=True, shapes=True)
    for shape in all_shapes:
        if cmds.nodeType(shape) not in cls.accepted_output:
            invalid.append(shape)

    return invalid

validate_missing_objectsets(instance, required_objsets, rig_sets) classmethod

Validate missing objectsets in rig sets

Parameters:

Name Type Description Default
instance Instance

instance

required
required_objsets list[str]

list of objectset names

required
rig_sets list[str]

list of rig sets

required

Raises:

Type Description
PublishValidationError

When the error is raised, it will show which instance has the missing object sets

Source code in client/ayon_maya/plugins/publish/validate_rig_contents.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@classmethod
def validate_missing_objectsets(cls, instance,
                                required_objsets, rig_sets):
    """Validate missing objectsets in rig sets

    Args:
        instance (pyblish.api.Instance): instance
        required_objsets (list[str]): list of objectset names
        rig_sets (list[str]): list of rig sets

    Raises:
        PublishValidationError: When the error is raised, it will show
            which instance has the missing object sets
    """
    missing = [
        key for key in required_objsets if key not in rig_sets
    ]
    if missing:
        raise PublishValidationError(
            "%s is missing sets: %s" % (instance, ", ".join(missing))
        )

ValidateSkeletonRigContents

Bases: ValidateRigContents

Ensure skeleton rigs contains pipeline-critical content

The rigs optionally contain at least two object sets

"skeletonMesh_SET" - Set of the skinned meshes with bone hierarchies

Source code in client/ayon_maya/plugins/publish/validate_rig_contents.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
class ValidateSkeletonRigContents(ValidateRigContents):
    """Ensure skeleton rigs contains pipeline-critical content

    The rigs optionally contain at least two object sets:
        "skeletonMesh_SET" - Set of the skinned meshes
                             with bone hierarchies

    """

    order = ValidateContentsOrder
    label = "Skeleton Rig Contents"
    hosts = ["maya"]
    families = ["rig.fbx"]
    optional = True

    @classmethod
    def get_invalid(cls, instance):
        objectsets, skeleton_mesh_nodes = cls.get_nodes(instance)
        cls.validate_missing_objectsets(
            instance, objectsets, instance.data["rig_sets"])

        # Ensure contents in sets and retrieve long path for all objects
        output_content = instance.data.get("skeleton_mesh", [])
        output_content = cmds.ls(skeleton_mesh_nodes, long=True)

        invalid_hierarchy = cls.invalid_hierarchy(
            instance, output_content)
        invalid_geometry = cls.validate_geometry(output_content)

        error = False
        if invalid_hierarchy:
            cls.log.error("Found nodes which reside outside of root group "
                          "while they are set up for publishing."
                          "\n%s" % invalid_hierarchy)
            error = True
        if invalid_geometry:
            cls.log.error("Found nodes which reside outside of root group "
                          "while they are set up for publishing."
                          "\n%s" % invalid_hierarchy)
            error = True
        if error:
            return invalid_hierarchy + invalid_geometry

    @classmethod
    def get_nodes(cls, instance):
        """Get the target objectsets and rig sets nodes

        Args:
            instance (pyblish.api.Instance): instance

        Returns:
            tuple: 2-tuple of list of objectsets, list of rig sets nodes
        """
        objectsets = ["skeletonMesh_SET"]
        skeleton_mesh_nodes = instance.data.get("skeleton_mesh", [])
        return objectsets, skeleton_mesh_nodes

get_nodes(instance) classmethod

Get the target objectsets and rig sets nodes

Parameters:

Name Type Description Default
instance Instance

instance

required

Returns:

Name Type Description
tuple

2-tuple of list of objectsets, list of rig sets nodes

Source code in client/ayon_maya/plugins/publish/validate_rig_contents.py
247
248
249
250
251
252
253
254
255
256
257
258
259
@classmethod
def get_nodes(cls, instance):
    """Get the target objectsets and rig sets nodes

    Args:
        instance (pyblish.api.Instance): instance

    Returns:
        tuple: 2-tuple of list of objectsets, list of rig sets nodes
    """
    objectsets = ["skeletonMesh_SET"]
    skeleton_mesh_nodes = instance.data.get("skeleton_mesh", [])
    return objectsets, skeleton_mesh_nodes