How to make sure selection does not include anything except meshes and groups

To exclude some errors, which may occur in my script i need to make sure that selection, that user makes does not include anything except meshes or grouped meshes.

Using few examples i was able to figure out how to query type of selected object.
Attaching my code, which returns me type of each selected object (well, not type but rather more important categories for me - mesh, not mesh, group and empty group)

My initial idea was that i would use something like any to check if there is any no_meshes in my selection and would warn user that he needs to exclude those objects from the selection.

What i have now works fine with single objects, but i can’t wrap my head around the way i should deal with group hierarchy, since there might be meshes inside each group or combination of meshes and not meshes inside the groups.

Also it returns group name for each object in the group… which is obviously not the correct way to do it…

so, my first idea was to use listRelatives(allDescendents=True), but i am not sure how…

Would appreciate any piece of advice, i am kinda lost here.
Probably there are better ideas that will allow to accomplish my initial goal of making sure that user doesn’t have anything extra in the selection except meshes and groups.

import pymel.core as pm

current_selection = pm.selected()

# Check if anything selected
if not current_selection:
    print 'Nothing is selected'

elif current_selection:

    for each_object in current_selection:

        # First check if there is a Shape
        if each_object.getShape():
            if each_object.getShape().nodeType() == 'mesh':
                # object is a mesh
                print('{0} is a polygon mesh'.format(each_object))

            else:
                # object is not a mesh
                print('{0} is not a polygon mesh'.format(each_object))

        # Check if object is a group
        elif not each_object.getShape():
            children = each_object.getChildren()
            if children:
                 for each_child in children:
                     if type(each_child) is not pm.nodetypes.Transform:
                         # object is something else
                         print('{0} is something else'.format(each_object))

                     else:
                         # object is a group
                         print('{0} is a group'.format(each_object))

        else:
            # object is an empty group
            print('{0} is a an empty group'.format(each_object))

Here is my stab at it. Maybe there are some ideas in here for you? I didn’t test it thoroughly, but it seems to work.

I start by getting ALL the meshes, and then testing for selection.

When a user selects a group that contains meshes, do you want to return the meshes or the group? My version returns the meshes. It also ignores any nurbs that are selected, and only returns meshes.

The things I’d point out in this:

  • ls() can specify a type and an exactType flag.
  • getChildren has an allDescendents flag, for deeply nested hierarchies.
  • getTransform() finds the transform node of a shape. In this case, I am searching in the opposite direction than you did, which sometimes helps.
  • I use a fancy nested List Comprehension in here. The reason is that I am doing getChildren() and that gives me a list of lists, and I need to flatten it into a list. For more info, see this: https://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
  • At one points I use list(set(X)). Using set removes any duplicates in the list. And then list() turns it back into a list.
import pymel.core as pm

# First get ALL the meshes.
allMeshes = pm.ls(type='mesh')

# Then filter down the ones whose transform is in your selection
selectedMeshes = [x.getTransform() for x in allMeshes if x.getTransform() in pm.selected()]

# Then grab all the meshes you directly have selected. (The shape nodes.) Return their transforms.
selectedShapes = [x.getTransform() for x in pm.selected(type='mesh')]

# Then a too-fancy nested List Comprehension, which:
# 1. For each in the selection, it gets all the children of type "mesh".
# 2. If it finds meshes, it returns the getTransform() of it.
allChildMeshes = [mesh.getTransform() for x in pm.selected() for mesh in x.getChildren(ad=True, type='mesh')]

# Then combine the list
allSelectedMeshes = list(set(selectedMeshes + selectedShapes + allChildMeshes))
print(allSelectedMeshes)

The lazy way is to check the listRelatives(ad=True) results and make sure that all of the shapes it returns are meshes:

sel = cmds.ls(sl=True) 
descendants =  set(cmds.ls(cmds.listRelatives(sel, ad=True) or [], type='shape'))
mesh_descendants = set(cmds.ls(descendants, type='mesh'))
is_valid = (descendants == mesh_descendants)

As written it will allow the selection of just a group with no children, which may or may not be what you want. If you wanted to ensure there was a mesh in there somewhere,

is_valid = (descendants == mesh_descendants) and bool(descendants)

You might need to consider your scenario a bit more carefully in that the user could select multiple groups in the same hierarchy, or an empty hierarchy alongside a valid one… without knowing the use case its hard to be more specific.

Thank you @clesage and @Theodox!
Very cool examples, but not exactly what i was looking for, thanks anyway!

I think my initial post was very confusing.
So, in order to avoid possible bugs and issues in my core script, the idea was to restrict user in type of objects he can select and work with. Basically, i want to let him select only meshes or groups with meshes.

So i took another shot on this and here is my current version:

import pymel.core as pm

current_selection = pm.listRelatives(allDescendents=True)
node_type_list = []
type_check_results = []

# Check if the selection is not empty
if not current_selection:
    print 'Nothing is selected'


else:
    for each_object in current_selection:
        each_object = pm.PyNode(each_object)
        node_type_list.append(pm.nodeType(each_object))

        for each_node in node_type_list:
            if each_node == 'mesh':
                type_check_results.append('Mesh')

            elif each_node == 'transform':
                type_check_results.append('Transform')

            else:
                type_check_results.append('Not a mesh')

    if 'Mesh' not in type_check_results:
        print 'There are no meshes in the selection'


    else:
        # Check if overall count of meshes and transforms is equal to the length of the list
        # That means that there is nothing in the list except Meshes and Transforms
        if (type_check_results.count('Mesh') + type_check_results.count('Transform')) == len(type_check_results):

            # Check if there are empty groups in the initial selection
            list_of_found_empty_groups = []
            list_of_transforms = pm.selected(transforms=True)
            for each_transform in list_of_transforms:
                children = pm.listRelatives(each_transform, children=True)
                if not children:
                    list_of_found_empty_groups.append(each_transform)

            if not list_of_found_empty_groups:
                print 'There are only meshes and transforms in the selection'

            else:
                print 'There is an empty group in the selection'

        else:
            print 'There is something else except meshes and transforms in the selection'

it accomplishes all i wanted to achieve, i can clearly exclude cases when i have undesired objects in the selection and that helps me to avoid errors in my core script, but… there are still cases which can’t be avoided.
For example, you can select a mesh and a group, that has another empty group inside of it…
which is not that critical, i guess…

The core script itself is for applying materials and baking maps, that is why I wanted to restrict user to working only with meshes and groups.

Probably its me not checking for empty groups properly… or is it better to completely restrict user from selecting groups and leave him only with meshes?
How such cases are usually resolved in the industry?

Oh, by the way,
how to make syntax highlighting work here?
it is very hard to read.

Not sure how you did it, but I surrounded my code with triple quotes. Don’t use the “preformatted” button.

Your post made sense. It’s just what you are doing is searching the selection for the types meshes and groups.

What I am suggesting is that you flip it conceptually, and search meshes for type “selected”. Iterate through all meshes, and see if they are in the bounds of your user’s selection. (is the shape, transform, or any parent selected?)

Restricting your users selection might not be that user-friendly. But it would make it easier for you. Alternatively, you could present the user with a custom list of all the valid objects they could choose, and have them select from there.