0

I'm building a toolbox UI using python in Maya, and I keep on getting a Nonetype error when I call one of the imported functions. This is the script for the toolbox:

class Toolbox():

    import maya.cmds as cmds


    def __init__(self):

        self.window_name = "mlToolbox"
    def create(self): 
        self.delete()
        self.window_name = cmds.window(self.window_name)
        self.m_column = cmds.columnLayout(p = self.window_name, adj = True)
        cmds.button(p=self.m_column,label = 'MyButton', c=lambda *arg: cmds.polySphere(r = 2))



        cmds.button(p=self.m_column, label = 'Make_Control', command = lambda *args: self.ControlBTN())

        cmds.button(p=self.m_column, label = 'Find Center of All Selected', command = lambda *args: self.CenterBTN())

        cmds.button(p=self.m_column, label = 'Find Center of Each Selected Object', command = lambda *args: self.IndiCenterBTN())

        self.colorname = cmds.textField(placeholderText = 'Enter color name...')
        cmds.button(p=self.m_column, label = 'ChangeColor', command = lambda *args: self.colorBtn())


        self.MinAndMax = cmds.textField()
        cmds.button(p=self.m_column, label = 'Random Scatter', command = lambda *args: self.ScatterBTN())

        cmds.showWindow(self.window_name)

        cmds.button(p=self.m_column, label = 'Select Everything', command = lambda *args: self.selectBTN())

    def CenterBTN(self):
        import CenterSelected
        CenterSelected.Locator()

    def ScatterBTN(self):
        import Scatter
        value = cmds.textField(self.MinAndMax, q=True)
        Scatter.RandomScatter(value)
        cmds.intField(self.moveMin, self.moveMax, self.rotMin, self.rotMax, self.scaleMin, self.scaleMax, e=True, text='')


    def IndiCenterBTN(self):
        import ManySelected
        ManySelected.LocatorMany()

    def colorBtn(self):
        import ColorControl
        value = cmds.textField(self.colorname, q=True, text = True)
        ColorControl.colorControl(value)
        cmds.textField(self.colorname, e=True, text='')

    def selectBTN(self): 
        import tools
        tools.selectAll()

    def delete(self): 
        if cmds.window(self.window_name, exists=True):
            cmds.deleteUI(self.window_name)

    def ControlBTN(self):

        import CreateControl
        CreateControl.createControl()

myTool = Toolbox()    

myTool.create()   

And this is the function that I'm having trouble with:

def RandomScatter(MinAndMax):

    import random

    import maya.cmds as cmds



    Stuff = cmds.ls(sl=True)

    i=0



    for i in range(random.randint(1,100)):

        Stuff.append(cmds.duplicate(Stuff))

        cmds.move( (random.randint(MinAndMax[0], MinAndMax[1])), (random.randint(MinAndMax[0], MinAndMax[1])), (random.randint(MinAndMax[0], MinAndMax[1])), Stuff[i], absolute=True ) 

        cmds.rotate( (random.randint(MinAndMax[2], MinAndMax[3])), (random.randint(MinAndMax[2], MinAndMax[3])), (random.randint(MinAndMax[2], MinAndMax[3])), Stuff[i], absolute=True )

        cmds.scale( (random.randint(MinAndMax[4], MinAndMax[5])), (random.randint(MinAndMax[4], MinAndMax[5])), (random.randint(MinAndMax[4], MinAndMax[5])), Stuff[i], absolute=True )

        i = i+1

RandomScatter() works fine as long as I call it on it's own using a RandomScatter([a, b, c, d, e, f]) format, but when I try to call it Toolbox(), I get "Scatter.py line 21: 'NoneType' object has no attribute 'getitem'" as an error. It also happens when I try to use the intField() command instead of textField(). The UI window builds just fine; the error only happens after I enter input into the text field and press the button that's supposed to call RandomScatter(). It seems like the input isn't making it to the MinAndMax list, so when it reaches "cmds.move( (random.randint(MinAndMax[0]," it can't find anything to put in the MinAndMax[0] slot, or any of the slots after that, but I can't figure out why. Does anyone have any advice?

dms
  • 1,260
  • 7
  • 12
  • You're not specifying what you're querying from the `textField`. I guess you want to read its text, so you should use `cmds.textField(self.MinAndMax, q=True, text=True)`. – dms Dec 12 '19 at 01:08
  • the lambda *args is probably just a shorthand for swallowing the useless first argument in button callbacks – theodox Jan 03 '20 at 19:40

2 Answers2

1

I didn't test your code and didn't read it totally, but I can already say that your strange "lambda" usage doesn't make any sens.

lambda *args: self.ControlBTN()

this lambda execute self.ControlBTN during the cmds.button definition and provide to it a function which return None. that like doing that:

self.ControlBTN()
function = def func(): return None
cmds.button(p=self.m_column, label = 'Make_Control', command=function)

I advise you to reread the documentation about the "python lambda".

replace this by:

cmds.button(p=self.m_column, label = 'Make_Control', command=self.ControlBTN)
...
def ControlBTN(self, *args)
...

That should help

good luck

lionel
  • 11
  • 1
1

As written self.MinAndMax is a text field; even if you get the value from it it'll be a string and you won't be able to index into it to get the individual items -- and your indexing will be thrown off if any of the numbers are negative or have decimals. The lazy solution is to use a FloatFieldGrp which lets you have 2-4 numberic inputs. It's a bit annoying to get at all of the values at once (see the way it's done below) but it will avoid many issues with trying to parse the text field.

Also, this line doesn't make sense in context:

    cmds.intField(self.moveMin, self.moveMax, self.rotMin, self.rotMax, self.scaleMin, self.scaleMax, e=True, text='')

You're not seeing the error because it's failing in the previous line, but the first argument ought to be the name of an existing intField.

In any case, I'd refactor this a bit to keep the argument parsing out of the scatter function, so you can separate out the working logic from the UI parsing logic. It'll make it much easier to spot where things have gone off the rails:

import random
import maya.cmds as cmds

class TestGUI(object):

    def __init__(self):
        self.window = cmds.window()
        self.layout = cmds.rowLayout(nc=3)
        self.min_xyz = cmds.floatFieldGrp( numberOfFields=3, label='min', value1=-10, value2=-10, value3=-10 )
        self.max_xyz= cmds.floatFieldGrp( numberOfFields=3, label='min', value1=10, value2=10, value3=10 )
        cmds.button(label='scatter', c = self.scatter)
        cmds.showWindow(self.window)

    def scatter(self, *_):
        selected = cmds.ls(sl=True)
        if not selected:
            cmds.warning("select something")
            return

        min_xyz = (
            cmds.floatFieldGrp(self.min_xyz, q=True, v1=True),
            cmds.floatFieldGrp(self.min_xyz, q=True, v2=True),
            cmds.floatFieldGrp(self.min_xyz, q=True, v3=True)
        )

       max_xyz = (
            cmds.floatFieldGrp(self.max_xyz, q=True, v1=True),
            cmds.floatFieldGrp(self.max_xyz, q=True, v2=True),
            cmds.floatFieldGrp(self.max_xyz, q=True, v3=True)
        )

        print "scatter settings:", min_xyz, max_xyz
        rand_scatter(selected, min_xyz, max_xyz)



def rand_scatter(selection, min_xyz, max_xyz):

    dupe_count = random.randint(1, 10)
    duplicates = [cmds.duplicate(selection) for n in range(dupe_count)]

    for dupe in duplicates:
        destination = [random.randint(min_xyz[k], max_xyz[k]) for k in range(3)]
        cmds.xform(dupe, t=destination, absolute=True)
        print (dupe, destination)


t = TestGUI() # shows the window; hit 'scatter' to test

My version changes your logic a bit -- you were duplicating your selection list, which would cause the number of items to grow exponentially (as each duplication would also duplicate the previous dupes). I'm not sure if that's inline with your intention or not.

You can avoid the extra lambdas by including a *_ in the actual button callback; it's a minor maya irritant that buttons always have that useless first argument.

As an aside, I'd try not to do imports inside of function bodies. If the imported module is not available, it's better to know at the time this file is imported rather than only when the user clicks a button -- it's much easier to spot a missing module if you do all your imports in a block at the top.

theodox
  • 12,028
  • 3
  • 23
  • 36