1

As I continue to study For Loops: I've run into some annoying errors. The problem is the script does exactly what I want it to. It deletes the null groups under the demo joints: but unlike other loops I've made for renaming which can be closed with a transform flag in the cmds.ls command: cmds.listRelatives doesn't allow a transform flag to close out the loop. You run the script by simply clicking Build Examples then hitting Delete Waste Groups

I've tried every flag according to the Maya documentation: but nothing seems to be closing the loop. I dont know if I need another variable, or a combination of some flags: or if I am using the wrong type of wording: but ideally what I would like this script to do is simply close out the loop so I dont get the error Error: No object matches name: curve

'''
import DS_wasteGroup_cleanerDemo
reload (DS_wasteGroup_cleanerDemo)
DS_wasteGroup_cleanerDemo.gui()
'''

import re
import maya.cmds as cmds
import maya.mel as mel

if cmds.window("renameWin", exists =True):
    cmds.deleteUI("renameWin", window = True)

myWindow = cmds.window("renameWin",t='DS_wasteGroup_cleanerDemo',w=200, h=500, toolbox=True)
column = cmds.columnLayout(adj=True)

def gui():

    cmds.button( label="Build Examples", c = buildExamples)
    cmds.separator( w=200, h=3)
    cmds.button( label="Delete Waste Groups", c = deleteWasteGrp)
    cmds.separator( w=200, h=9)

    cmds.setParent('..')
    cmds.showWindow(myWindow)

def buildExamples(*args):

    cmds.group(n='exampleGroup1',world=True,empty=True)
    cmds.joint(n='demoJoint1')
    cmds.group(n='curve1',world=True,empty=True)
    cmds.parent('curve1','demoJoint1')

    cmds.joint(n='demoJoint2')
    cmds.parent('demoJoint2','exampleGroup1')
    cmds.group(n='curve2',world=True,empty=True)
    cmds.parent('curve2','demoJoint2')

    cmds.joint(n='demoJoint3')
    cmds.parent('demoJoint3','exampleGroup1')
    cmds.group(n='curve3',world=True,empty=True)
    cmds.parent('curve3','demoJoint3')

    cmds.joint(n='demoJoint4')
    cmds.parent('demoJoint4','exampleGroup1')
    cmds.group(n='curve4',world=True,empty=True)
    cmds.parent('curve4','demoJoint4')

    cmds.joint(n='demoJoint5')
    cmds.parent('demoJoint5','exampleGroup1')
    cmds.group(n='curve5',world=True,empty=True)
    cmds.parent('curve5','demoJoint5')


def deleteWasteGrp(*args):
    grpList = cmds.listRelatives('demoJoint*',p=True,f=True)
    for name in grpList:
        print(grpList)
        cmds.delete('curve*')

My apologies if I'm posting simple questions. I do write Python scripts to automate the most tedious tasks in rigging: but my knowledge is only intermediate. I want to learn more python so my scripts arent so clunky and brute forced: as well as the fact that I need them to be more adaptable to various types of characters: so any resources that dumb all this down would also be appreciated. Thank you for your help.

deadshot
  • 8,881
  • 4
  • 20
  • 39
Dasteel
  • 101
  • 18

3 Answers3

1

The error is correct, because the very first time the for loop executes, all "curve" obects are deleted, then in the next iteration, the same command does not find any curve objects because they are already deleted. If you place the delete command outside the for loop, the error should disappear.

haggi krey
  • 1,885
  • 1
  • 7
  • 9
  • That does work @haggi krey, the only problem is I just want it to delete the specific curve groups inside exampleGroup1: if I move the delete command outside the For Loop it deletes anything starting with curve outside exampleGroup1 which can be a problem – Dasteel Feb 27 '20 at 11:25
  • Then you need to know the exact name of the object you want to delete. The easiest way would be get the name by replacing e.g. "demoJoint" in "demoJoint5" by curve like this "demoJoint5".replace("demoJoint","curve"), then you get the correct name and you can delete it. But it only works if the names are always matching. – haggi krey Feb 27 '20 at 11:44
0

I feel like you're going about this whole process a bit wrong, and I would love to elaborate if you're interested, but for now here is a fix for your loop situation:

def deleteWasteGrp(*args):    
    curveList = cmds.ls('curve*',transforms=True)
    try:
        cmds.delete(curveList)
        print('Deleted the following objects: {}'.format(curveList))
    except Exception as e:
        cmds.warning('Cleanup failed: {}'.format(e))

The cmds.delete method accepts a list parameter, which in your case is the easiest way to get the job done. Keep in mind that when you delete a parent object, you also delete its children, so depending on your circumstances deleting objects can be order-specific.

Throwing any "likely to fail" calls in a try/except clause is generally a good idea, as it lets you handle the error gracefully. Be careful, however, to not suppress it and just move on -- you at the very least need to alert the user adequately.

Lastly, your buildExamples method will most likely fail if you run it more than once. Because you are addressing objects by string literals (hard coded names) instead of keeping track of their actual names (and full path). You will likely see this error eventually:

# Error: ValueError: file <maya console> line ??: More than one object matches name: demoJoint1 # 

Edit: Some elaborations as requested

The commands cmds.group and cmds.joint return a string value indicating the actual name of the object created (in create mode). It's usually a good idea of storing this value in case Maya decides to name your object slightly differently than what you are expecting, usually when there is a naming clash. Eg:

print cmds.group(name='test', world=True, empty=True)
# Returns: test

print cmds.group(name='test', world=True, empty=True)
# Returns: test1

Example of how to capture object names as you create them. I've concatenated your five identical(ish) calls to create joints and curves in this loop:

import maya.cmds as cmds

topGroupName = 'exampleGroup'
actualTopGroupName = None

# Create top level group
actualTopGroupName = cmds.group(n=topGroupName, world=True, empty=True)

# Loop through 5 times and do the following:
for i in range(5):
    # PS: hash character in name indicates "next available number"
    cmds.select(clear=True)
    jnt = cmds.joint(n='demoJoint#') 
    crv = cmds.group(n='curve#',world=True,empty=True)
    cmds.parent(crv, jnt)
    cmds.parent(jnt, actualTopGroupName)

Example of how to narrow down which objects to search for with cmds.ls:

topGroupName = 'exampleGroup'
print cmds.ls('|{}*|*|curve*'.format(topGroupName)) 
# Returns: [u'curve1', u'curve2', u'curve3', u'curve4', u'curve5']

# The string .format() expression is just a dynamic way of writing this:
# |exampleGroup*|*|curve*

Vertical pipes (|) indicate levels in a hierarchy, similar to how slashes (/) work in URLs. And asterisks/wildcards (*) indicate "any character, or none".

Hope this helps you along your way a little bit.

Daniel Skovli
  • 455
  • 3
  • 8
  • Thank you, Elaboration would be great. What do you mean by actual names? I know hardcoded is when you create specific objects with specific names, I mean you would have to use hardscoded if you want to names specific joints like "clavicle, shoulder, elbow, wrist, ect" I'm guessing you know a more efficient way however. Also is their any way to make your fix check inside of a specific group instead of the entire outliner? Just because if I have anything starting with "curve" outside of exampleGroup1: I dont want it editing or deleting that – Dasteel Feb 28 '20 at 00:56
  • Be careful about running select commands, especially in a loop. This causes the viewport to redraw, which can significantly slow down a script's performance. Not a big deal in this case, just bad habit to get into. – Green Cell Feb 28 '20 at 02:56
  • @GreenCell: In this case I was just getting around the quirk where `cmds.joint` insists on using the current selection (last created group) as a parent. If you know of a flag to use to discourage this behaviour, I'll edit the post with some updates. – Daniel Skovli Feb 28 '20 at 02:59
  • I ran into the same thing too, and it's why I used `cmds.createNode`. – Green Cell Feb 28 '20 at 03:00
0

Honestly I would take a whole different approach as you're hard-coding everything which could easily lead to disaster. When I mean hard-code, I mean you're trying to parent, let's say, "demoJoint2" to an object. This is bad because why are you assuming that "demoJoint2" even exists? If you create an object with a specific name that already exists, Maya will auto-rename the new object, and now you're referencing the wrong one right off the bat! Instead when you create your objects, capture their names in a variable then work with that, otherwise you'll be constantly shooting yourself in the foot.

Here's the same script with an approach I would try instead:

import maya.cmds as cmds


def gui():
    if cmds.window("renameWin", exists=True):
        cmds.deleteUI("renameWin", window=True)

    myWindow = cmds.window("renameWin", t="DS_wasteGroup_cleanerDemo", w=200, h=500, toolbox=True)
    column = cmds.columnLayout(adj=True)

    cmds.button(label="Build Examples", c=buildExamples)
    cmds.separator(w=200, h=3)
    cmds.button(label="Delete Waste Groups", c=deleteWasteGrp)
    cmds.separator(w=200, h=9)

    cmds.setParent("..")
    cmds.showWindow(myWindow)


def buildExamples(*args):
    root = cmds.group(n="exampleGroup1", world=True, empty=True)

    for i in range(5):  # Loop to the amount of joints you want to create.
        jnt = cmds.createNode("joint", name="demoJoint" + str(i + 1))  # Use `i` to help name the object.
        jnt = cmds.parent(jnt, root)[0]  # Parenting changes its long name, so recapture the joint in a variable.
        crv = cmds.group(n="curve" + str(i + 1), world=True, empty=True)  # Create empty group.
        cmds.parent(crv, jnt)  # Parent curve to joint.


def deleteWasteGrp(*args):
    jnts = cmds.ls("demoJoint*", long=True, type="joint")  # Get all `demoJoints`.
    children = cmds.listRelatives(jnts, f=True, children=True, type="transform") or []  # Get all of their children, and only get transform types.
    curves = [obj for obj in children if obj.split("|")[-1].startswith("curve")]  # Don't assume we got the right objects. Run a final loop to collect any object that starts with `curve`. Need to use split as we're looping through long names but need to check its short name.
    if curves:  # `cmds.delete` will error if this list is empty, so don't assume.
        cmds.delete(curves)  # Delete all curves at once.


gui()

Now I can hit the build button as much as I want with no issues, and delete all the curves when pressing the delete button.

A few more notes:

Notice in buildExamples I'm using a loop to create all the objects instead of reusing redundant code that does the same thing. You could even have a spinbox in your gui that defines how many joints it creates now, where as before it wasn't possible because the count was hard-coded.

cmds.listRelatives does have a way to filter objects by transforms by setting parameter type="transform". In fact you'll see many commands have this same parameter (again start checking docs).

cmds.listRelatives('demoJoint*',p=True,f=True) was grabbing the joint's parent, not its children. The docs clearly state this.

Running cmds.delete('curve*') is going to delete ALL objects with names that start with curve, and since you're running this in a loop it's trying to do this multiple times.

maya.cmds is not pymel. There's a whole separate module called pymel.

If you're unsure with any parts of the code try adding in a print statement to see what it's doing.

Green Cell
  • 4,677
  • 2
  • 18
  • 49
  • Thank you very much Green Cell, that functionally answers every question I have. I do have one thing I would like to know more about though. I noticed in build examples you have two variables named "jnt" if you have 2 variables with the same name, does Maya just follow the default logic of activating the first variable before the next one? Is there a certan way you wrote them to differentiate them? Honestly I've never stumbled across a script that uses the same variable twice: I am very curious.about this. – Dasteel Feb 28 '20 at 04:03
  • He's just re-defining the variable to reflect the new name of the object. – Daniel Skovli Feb 28 '20 at 04:32
  • You could technically name it to a different variable, but since it's the same object I'm just re-assigning it to the new name it spits out. This is necessary when multiple objects in the scene have the same name, but are under different hierarchies. – Green Cell Feb 28 '20 at 07:31