I'm finally in the end game of the Helper Joint script I'm working on and only 1 last problem stands in my way. Here is how the script is supposed to work. Create a joint, name it "Parent_Joint" then create a "multDoubleLinear" node in the node editor, name it "bob, Hit "Load Parent Joint" after selecting the joint you created and hit add attribute. In an ideal world where I'm smarter on this subject, The custom attribute added to the joint would be plugged into bob's "input1" instead I get an error saying "# Error: The source attribute 'Parent_Joint_HelperJntAttr' cannot be found."
In terms of what I've already tried, I put connectAttr beneath addAttr as common sense would dictate that first the attribute must be created before it's connected: but despite this it's just refusing to connect. I know the fault doesn't fall on the "bob.input1" node, because it only brings up the prefixed attribute name for 'Parent_Joint_HelperJntAttr: so my guess is it's just simply my lack of knowledge in writing this particular procedure.
import maya.cmds as cmds
if cmds.window(window, exists =True):
cmds.deleteUI(window)
window = cmds.window(title='DS Attribute adder')
column = cmds.columnLayout(adj=True)
sld_textFld = cmds.textField('sld_surfaceTextHJ', width =240)
def set_textfield(_):
sel = cmds.ls(selection=True)
cmds.textField(sld_textFld, edit=True, text=sel[0])
load_button = cmds.button( label='Load Parent Joint', c = set_textfield)
def add_Attribute(_):
text_value = cmds.textField(sld_textFld, q = True, text=True)
if text_value:
print "attrAdded:"
cmds.addAttr(ln=text_value +'_HelperJntAttr', defaultValue=5.0, minValue=0, attributeType='float', keyable=True)
cmds.connectAttr( text_value +"_HelperJntAttr", 'bob.input1')
else:
cmds.warning("select an object and add it to the window first!")
node_button = cmds.button( label='add attribute', c = add_Attribute)
cmds.showWindow(window)
I know how to use the connectAttr command on default attributes in maya, but where I fall flat is custom attributes. My hope is I come out of this knowing how to write code that both creates and connects the custom attributes of the joint. Thank you in advance for your help
The way you were using addAttr, it was including the joint's name in the attribute name. Attributes are separated with a ., not an _, so your connectAttr also fails because of that.
You also need to initialize your window variable to some default value or it fails on the line where you check if it exists (but window isn't defined at that point).
Here is the script adding the attribute and connecting it as expected:
import maya.cmds as cmds
window = "" # Need to initialize this variable first or it crashes on next line.
if cmds.window(window, exists =True):
cmds.deleteUI(window)
window = cmds.window(title='DS Attribute adder')
column = cmds.columnLayout(adj=True)
sld_textFld = cmds.textField('sld_surfaceTextHJ', width =240)
def set_textfield(_):
sel = cmds.ls(selection=True)
cmds.textField(sld_textFld, edit=True, text=sel[0])
load_button = cmds.button( label='Load Parent Joint', c = set_textfield)
def add_Attribute(_):
text_value = cmds.textField(sld_textFld, q = True, text=True)
if text_value:
print "attrAdded:"
# Attribute must be created this way.
cmds.addAttr(text_value, ln='HelperJntAttr', defaultValue=5.0, minValue=0, attributeType='float', keyable=True)
# Attribute is separated with a dot.
cmds.connectAttr(text_value + ".HelperJntAttr", 'bob.input1')
else:
cmds.warning("select an object and add it to the window first!")
node_button = cmds.button( label='add attribute', c = add_Attribute)
cmds.showWindow(window)
Related
In python I want to select one object and change repeatUV .
The code work for all objects and not for one specific object
import maya.cmds as cmds
import pymel.core as pmc
def UVTILECHANGE():
selected = cmds.ls(sl=True)
textField_obj_name = cmds.textField( 'textField_action_name',query=True,text=True )
textField_obj = cmds.textField( 'textField_action',query=True,text=True )
#print textField_obj
textField_obj2 = cmds.textField( 'textField_action2',query=True,text=True )
if textField_obj_name==selected[0]:
array = cmds.ls(type="place2dTexture")
#SELECT ALL PLACE2DTECTURE
#I WANT JUST FOR selected[0]
#print len(array)
i=0
while (i <= len(array)):
print
cmds.setAttr(str(array[i])+ ".repeatU", float(textField_obj))
cmds.setAttr(str(array[i])+ ".repeatV", float(textField_obj2))
i=i+1
def MakeWindow():
if (pmc.window('flattenVertwindow', exists=True)):
pmc.deleteUI('flattenVertwindow')
pmc.window('flattenVertwindow', title="TEST",widthHeight=(200,100))
pmc.columnLayout(adjustableColumn=True)
#pmc.text(label='select axis')
cmds.text("Choisir nom de l'objet")
cmds.separator(h=10)
textField00 = cmds.textField( 'textField_action_name' )
cmds.separator(h=10)
cmds.text("Taille des Tiles")
cmds.separator(h=10)
textField01 = cmds.textField( 'textField_action' )
textField02 = cmds.textField( 'textField_action2' )
red= pmc.button(label='Change UV TILE ', command= 'UVTILECHANGE()')
pmc.showWindow('flattenVertwindow')
MakeWindow()
Im not sure what doesn't work, Ive improved your code with comments.
I hope it will help, it works here :
import maya.cmds as cmds
import pymel.core as pmc
# if you dont know classes, you can create a dict to store all main controls
UI_DIC = {}
# for function used in ui, you need *args because maya will try to parse a default True
# argument as last argument
def picker(*args):
sel = cmds.ls(sl=True)[0]
cmds.textField(UI_DIC['textField00'], edit=True, text=sel )
# use lower case for functions, you can use camel casing like maya : uvTilesChange
# but keep the first letter lower case as uppercase for the first is used for Class
def uv_tile_change(*args):
# we parse fields with the value captured in the dictionnary
textField_obj_name = cmds.textField(UI_DIC['textField00'], query=True, text=True )
textField_obj = cmds.textField(UI_DIC['textField01'], query=True, text=True )
textField_obj2 = cmds.textField(UI_DIC['textField02'], query=True, text=True )
# if there is nothing in the ui, dont execute anything
if not textField_obj_name:
return
# we are using few funcitons to find the place2dTexture of an object
shape_mesh = cmds.ls(textField_obj_name, dag=True, type='shape', ni=True)
if not shape_mesh:
# if the object doesnt exists, stop here
return
# we look for the shading engine of the object
hist = cmds.listHistory(shape_mesh, future=True)
sgs = cmds.ls(hist, type="shadingEngine")
# from the sgs, we look for the place2dTexture
place2dTextures = [i for i in cmds.listHistory(sgs) if cmds.ls(i, type='place2dTexture')]
# instead of a while, just use a python for loop
for p2t in place2dTextures:
cmds.setAttr(p2t + ".repeatU", float(textField_obj))
cmds.setAttr(p2t + ".repeatV", float(textField_obj2))
def make_window():
width = 180
height = 150
if (pmc.window('flattenVertwindow', exists=True)):
pmc.deleteUI('flattenVertwindow')
# sizeable works for me
pmc.window('flattenVertwindow', title="TEST",widthHeight=(width,height), sizeable=False)
main_layout = pmc.columnLayout(adjustableColumn=True)
# use the flag parent, it is more clear
pmc.text("Choisir nom de l'objet", parent=main_layout)
# choose either cmds or pymel, do not use both
pmc.separator(h=10, parent=main_layout)
# here is an example on how parent flag is useful
# we create a sub layout and parent it to the main one
# we parent all the controls to the sub layout
picker_layout = pmc.rowLayout(nc=3, parent=main_layout, width=width, ad1=True)
# we capture the name of the control on creation inside the dict
UI_DIC['textField00'] = pmc.textField(parent=picker_layout, w=width)
# take the habit to not put string in the command flag
pmc.button(label='<', command= picker, parent=picker_layout)
pmc.separator(h=10, parent=main_layout)
pmc.text("Taille des Tiles", parent=main_layout)
pmc.separator(h=10)
UI_DIC['textField01'] = pmc.textField(parent=main_layout)
UI_DIC['textField02'] = pmc.textField(parent=main_layout)
red= pmc.button(label='Change UV TILE ', command= uv_tile_change, parent=main_layout)
pmc.showWindow('flattenVertwindow')
make_window()
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?
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
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.
Here is my plan. First create a joint and open your node editor. When you got your joint created, name it "A_Joint" hit "load joint" after running the script, then hit "test" upon hitting test, you should get a node created with the name "A_Joint_firstGuy"
the objective of this script is to create a node based on the name of whatever you loaded into the textField. It will take the name of the selected object and add it to the front of the name of the node
Atleast thats what should happen, but in truth I lack the knowledge to figure this out and every google search has thus far been fruitless. The script is down below for anyone willing to take a crack at it, thank you for your time and I hope to hear back from you:
https://drive.google.com/file/d/1NvL0MZCDJcKAnVu6voVNYbaW0HurZ6Rh/view?usp=sharing
Or here, in SO format:
import maya.cmds as cmds
if cmds.window(window, exists =True):
cmds.deleteUI(window)
window = cmds.window(title = "DS Selection Loader Demo", widthHeight=(300, 200) )
cmds.columnLayout(adjustableColumn=True)
def sld_loadHelperJoint():
sel = cmds.ls(selection=True)
selString = " ".join(sel)
add = cmds.textField('sld_surfaceTextHJ', edit=True, text=selString)
#def sld_loadParentJoint():
# sel = cmds.ls(selection=True)
# selString = " ".join(sel)
# add = cmds.textField('sld_surfaceTextPJ', edit=True, text=selString)
def createNode():
testNode = cmds.createNode( 'transform', selString = "sld_surfaceTextHJ", name = '_firstGuy' )
cmds.columnLayout(adjustableColumn=True)
sld_textFld = cmds.textField('sld_surfaceTextHJ', width =240)
cmds.button( label='Load Helper Joint', command = "sld_loadHelperJoint()")
cmds.setParent('..')
#cmds.columnLayout(adjustableColumn=True)
#name = cmds.textField('sld_surfaceTextPJ', width =240)
#cmds.button( label="Load Parent Joint", command = "sld_loadParentJoint()")
#cmds.setParent('..')
testNode = cmds.createNode( 'transform', name = textField +'_firstGuy' )
# you must first create "def" group for the attributes you
# want to be created via button, "testNode" is our example
# Connect the translation of two nodes together
#cmds.connectAttr( 'firstGuy.t', 'secondGuy.translate' )
# Connect the rotation of one node to the override colour
# of a second node.
#cmds.connectAttr( 'firstGuy.rotate', 'secondGuy.overrideColor' )
cmds.showWindow (window)
OK, there's a few things going on here to consider.
First, Maya gui widgets look like strings -- just like you make a polyCube and it comes back to you as the string 'pCube1', a widget will come back as a string like 'myWindow' or 'textField72'. Just like working with scene objects, you always need to capture the results of a command so you know what it's really called -- you can't guarantee you'll get the name you asked for, so always capture the results.
So you want to do something like this, just to get the graphics going:
window = cmds.window(title='selection loader demo')
column = cmds.columnLayout(adj=True)
sld_textFld = cmds.textField('sld_surfaceTextHJ', width =240)
load_button = cmds.button( label='Load Helper Joint')
cmds.show_window(window)
If you needed to ask what's in the textField, for example, you'd do:
text_contents = cmds.textField(sld_textFld, q=True, text=True)
You notice that's with the variable, not the string, so we're sure we have whatever worked.
To make the button use that information, though, the button script needs to have access to the variable. There are several ways to do this -- it's a common stack overflow question -- but the easiest one is just to define the button command where you already have that variable. So the above sample becomes:
window = cmds.window(title='selection loader demo')
column = cmds.columnLayout(adj=True)
sld_textFld = cmds.textField('sld_surfaceTextHJ', width =240)
def set_textfield(_):
sel = cmds.ls(selection=True)
add = cmds.textField(sld_textFld, edit=True, text=sel[0])
load_button = cmds.button( label='Load Helper Joint', c = set_textfield)
cmds.showWindow(window)
There are two bits here.
One is the _ in the function definition; Maya buttons always call their functions with one argument. There's nothing magical about the underscore, it's just Python slang for "ignore me" -- but if you don't have an argument in the function def, it will fail.
The more important bit is that the button is given the function definition directly, without quotes. You are saying call this function when clicked. If you use the string version -- a holdover from MEL -- you will run into problems later. The reasons are explained in detail here but the TLDR is don't use the the string form. Ever.
Now that the structural pieces are in place, you should be able to either the node creation to the function I called set_textfield() or make a second button\function combo that does the actual node creation, something like:
window = cmds.window(title='selection loader demo')
column = cmds.columnLayout(adj=True)
sld_textFld = cmds.textField('sld_surfaceTextHJ', width =240)
def set_textfield(_):
sel = cmds.ls(selection=True)
cmds.textField(sld_textFld, edit=True, text=sel[0])
load_button = cmds.button( label='Load Helper Joint', c = set_textfield)
def make_node(_):
text_value = cmds.textField(sld_textFld, q = True, text=True)
if text_value:
print "created:", cmds.createNode('transform', n=text_value +'_firstGuy')
else:
cmds.warning("select an object and add it to the window first!")
node_button = cmds.button( label='Make Node', c = make_node)
cmds.showWindow(window)
The code I made, despite double, triple, and quadruple checking is refusing to load selected items into the text fields, that is assuming you can get maya to get over it's newfound hatred of the cmds.windows function
The errors I'm getting are threefold, if you try to load the code into a new window you will just get
Error: name 'window' is not defined" assuming you make it past that hurdle you will run into 2 problems: the first is just from pasting the code below. The menu will load just fine, but if you hit either "Load A Node" or "Load B Node" you will get the error "# Error: Object 'window1|columnLayout9|sld_surfaceTextHJ' not found.
I never put "sld_surfaceTextHJ" in the code, so I dont know why maya keeps asking for it. If you humor it however and try to change the name 'sld_surfaceTextA' or 'sld_surfaceTextB' to any other name you will get the third error: # Error: name 'window1|columnLayout9|sld_surfaceTextHJ' is not defined.
The script used to work fine loading selections, but it seems that every script I made following this format is refusing to work.
Here it is if you want to take a crack at it
import maya.cmds as cmds
if cmds.window(window, exists =True):
cmds.deleteUI(window)
window = cmds.window(title='DS selection connector demo')
column = cmds.columnLayout(adj=True)
sld_textFldA = cmds.textField('sld_surfaceText1', width =240)
load_button = cmds.button( label='Load A Node', c = set_textfield)
sld_textFldB = cmds.textField('sld_surfaceText2', width =240)
load_button = cmds.button( label='Load B Node', c = set_textfield)
node_button = cmds.button( label='Connect Node', c = make_node)
def set_textfieldA(_):
sel = cmds.ls(selection=True)
cmds.textField(sld_textFldA, edit=True, text=sel[0])
def set_textfieldB(_):
sel = cmds.ls(selection=True)
cmds.textField(sld_textFldB, edit=True, text=sel[0])
def connect_node(_):
text_value = cmds.textField(sld_textFldA, q = True, text=True)
text_value = cmds.textField(sld_textFldB, q = True, text=True)
if text_value:
print "created:", cmds.connectAttr('transform', n=text_value +'_firstGuy')
print "created:", cmds.connectAttr('transform', n=text_value +'_secondGuy')
else:
cmds.warning("select an object and add it to the window first!")
cmds.showWindow( window )
The expected results are rather simple: you hit "Load A Node" on any node you create to load the first Node, Then you hit "Load B Node" on the second node you created: then upon hitting "Connect Node" the translate attribute of your first node should be connected to your second node like the connection editor.
Never mind guys, for anyone interested: here is the fixed code, I still haven't gotten the connect attributes part figured out yet: but for anyone looking for a simple textField selection loader demo, here you go:
import maya.cmds as cmds
if cmds.window(window, exists =True):
cmds.deleteUI(window)
window = cmds.window(title='DS selection connector demo')
column = cmds.columnLayout(adj=True)
sld_textFldA = cmds.textField('sld_surfaceText1', width =240)
load_button = cmds.button( label='Load A Node', c = set_textfield1)
sld_textFldB = cmds.textField('sld_surfaceText2', width =240)
load_button = cmds.button( label='Load B Node', c = set_textfield2)
def set_textfield1(_):
sel = cmds.ls(selection=True)
cmds.textField(sld_textFldA, edit=True, text=sel[0])
def set_textfield2(_):
sel = cmds.ls(selection=True)
cmds.textField(sld_textFldB, edit=True, text=sel[0])
cmds.showWindow( window )
I want to use the output of a textField to serve as a name for a newly created blenshape. However when I press the button to create my shape it only consideres the text that has been defined by me as a stock text that should be replaced. It doesn't consider new letters typed in the box :
def buildUI(self, *args):
self.widgets["bs"] = cmds.textField (tx= "Replace me", editable= True, )
self.widgets["blendshape_name"] = cmds.textField(self.widgets['bs'], q=True, text=True)
cmds.button(label="Create BlendShape ", w=295, h=30, al="center", c=self.blendShape)
def blendShape (self, *args):
cmds.blendShape ( cmds.ls(sl=True)[1], cmds.ls(sl=True)[0],frontOfChain=True, n= self.widgets["blendshape_name"] )
You query the blendshape_name only when you run buildUI function. You have to read the textfield at every click. So query the blendshape_name inside the function blendShape:
def blendShape(self, *args):
blendshape_name = cmds.textField(self.widgets['bs'], q=True, text=True)
cmds.blendShape(cmds.ls(sl=True)[1], cmds.ls(sl=True)[0], frontOfChain=True, n=blendshape_name)