import maya.cmds as cmds
from functools import partial
def export(txtField):
print "hello"
#print cmds.textField( txtField, q=1 )
if cmds.window( 'exporter', q=1, ex=1 ):
cmds.deleteUI( 'exporter' )
window = cmds.window( 'exporter' )
cmds.columnLayout( adjustableColumn=True )
name = cmds.textField( text='testing...' )
press = cmds.button( 'Export...', c=partial( export, name) )
cmds.showWindow( 'exporter' )
So Im getting error:
# Error: export() takes exactly 1 argument (2 given) #
So Im new to partial and I dont understand what they do and how they work. But I know it's possible to do what I want with partial. So just print out whatever I have in textField
In this case partial is probably overkill. #mapofemergence's answer will work fine, but you can just do this:
from maya import cmds
if cmds.window('exporter', q=1, ex=1):
cmds.deleteUI('exporter')
window = cmds.window('exporter')
cmds.columnLayout(adjustableColumn=True)
tf = cmds.textField(text='testing...')
def export(*_):
print "textfield says" , cmds.textField(tf, q=1, text=1)
press = cmds.button('Export...', c=export)
cmds.showWindow('exporter')
since export is defined after the textfield is created, it captures the variable value at creation time.
Here's something which should work for you:
from maya import cmds
from functools import partial
def export(txtField, *args):
print txtField, ':', args
print cmds.textField(txtField, q=1, text=1)
if cmds.window('exporter', q=1, ex=1):
cmds.deleteUI('exporter')
window = cmds.window('exporter')
cmds.columnLayout(adjustableColumn=True)
name = cmds.textField(text='testing...')
press = cmds.button('Export...', c=partial(export, name))
cmds.showWindow('exporter')
What is happening (and might be confusing) is that the command invoked by Maya's button already passes an argument by default, in this case False. As a consequence, partial is appending the extra argument to the one you specified and therefore export is expected to receive two arguments, not just one.
It is quite some time I don't use native Maya UI commands, so I'm not sure what False stands for and if you can expect any meaningful argument to be passed, in different conditions.
Anyway, you can work around this by adding an extra argument to your function definition.
You could write something like def export(txtField, _): but that would only work if you're sure that the parameter being passed is one and only one. For that reason I used def export(txtField, *args): instead. This way you can print args and troubleshoot what's being passed (regardless the number of arguments), while having the rest of your code working.
As a side note, for other uses of partial with Maya you might want to read this:
https://theodox.github.io/2014/maya_callbacks_cheat_sheet
Related
I have a python function here that is supposed to rename object in maya. But when the window showed up and i click the 'rename' button, nothing changed. not even bringing new window. please help
def renameObject():
a = cmds.ls(sl=True)
txt = cmds.textField('txtName', q=True, tx=True)
cmds.rename('a', txt)
cmds.confirmDialog(icn='information', message='Done!')
cmds.showWindow()
return
cmds.window(title='Rename Object')
cmds.columnLayout(adj=1)
cmds.text(label= 'Insert Name', w=300, h=30)
cmds.separator()
cmds.textField('txtName')
cmds.button(label='Rename', width=300, c=lambda*args:'renameObject()')
cmds.showWindow()
Two reasons:
Your lambda expression has a string 'renameObject()', it should be the function name without apostrope.
Even if the renameObject() function is called, it will fail because you assign the current selection to a variable called a. But in the rename function, you use again a string 'a'. So maya searches for an object called 'a' and tries to rename it what does not work unless you really have an object called 'a'.
And the confirmDialog() does not need a cmds.showWindow(), it works without.
def renameObject(*args):
a = cmds.ls(sl=True)
txt = cmds.textField(txt_field , q=True, tx=True)
cmds.rename(a[0], txt)
cmds.confirmDialog(icn='information', message='Done!')
cmds.showWindow()
cmds.window(title='Rename Object')
cmds.columnLayout(adj=1)
cmds.text(label= 'Insert Name', w=300, h=30)
cmds.separator()
txt_field = cmds.textField('txtName')
cmds.button(label='Rename', width=300, c=renameObject)
cmds.showWindow()
I've corrected your code, it shuold work when ran but there is lots of mistakes in your code, Haggi Krey has pointed lots of them. If you want to dive in UI design, you should look at partial module from functools. There is lots of examples here in Stack
I can't seem to figure out the syntax for triggering a function upon someone using a FileInput widget in a Parameterized class.
I understand that FileInput isn't a param itself, but I looked at the code for it and the value attribute is a generic param.Parameter, so I thought this would work. I also tried just depending on file (#param.depends('file')).
class MyFile(param.Parameterized):
file = pn.widgets.FileInput() # should be a param.?
file_data = None
#param.depends('file.value')
def process_file(self):
print('processing file')
self.file_data = self.file.value
my_file = MyFile()
Then after using the file widget, I would expect my_file.file_data to have the same contents of self.file.value.
panel_output
Appreciate any input or if anyone can point me to appropriate docs. Thanks!
https://github.com/pyviz/panel/issues/711
You are right, in this case your 'file' variable needs to be a param, not a panel widget.
All possible options there are for setting available params are here:
https://param.pyviz.org/Reference_Manual/param.html
So in your case I used param.FileSelector():
import param
import panel as pn
pn.extension()
class MyFile(param.Parameterized):
file = param.FileSelector() # changed this into a param
file_data = None
#param.depends('file', watch=True) # removed .value and added watch=True
def process_file(self):
print('processing file')
self.file_data = self.file # removed .value since this is a param so it's not needed
my_file = MyFile()
This FileSelector is however a box to type the filename yourself. This question is related to this and gives some more explanation: Get a different (non default) widget when using param in parameterized class (holoviz param panel) So you need to change this FileSelector still to the FileInput widget, by overwriting it like this:
pn.Param(
my_file.param['file'],
widgets={'file': pn.widgets.FileInput}
)
Please note that I also added watch=True. This makes sure that changes get picked up, when your 'file' param has changes. There's a bit more explanation of this in the following question: How do i automatically update a dropdown selection widget when another selection widget is changed? (Python panel pyviz)
Can you let me know if this helped?
Below you can see the code I have for a method in my GUI Class. I have been trying to create an option menu from a list however I am getting an error. It says that tkinter has no module 'apply'. In all the examples I can find people use Tkinter instead of tkinter, so has there been a change in the apply method from python 2.x to 3.x?
I have tried writing all of the following:
tk.apply, tk.Apply, apply. But nothing seems to work.
import tkinter as tk
class GUI:
def UploadHomeworkScreen(self):
self.masternew = tk.Tk()
self.framenew = tk.Frame(self.masternew)
self.HomeworkFileEntry = tk.Entry(self.framenew)
self.ClassVariable = tk.StringVar(self.masternew)
self.ClassVariable.set(Client.ListOfClasses[0])
self.ClassChoice = tk.apply(tk.OptionMenu, (self.framenew, self.ClassVariable) + tuple(Client.ListOfClasses))
self.SubmitButton = tk.Button(self.framenew, text = "Submit", command = self.SubmitHomework)
self.HomeworkFileEntry.pack(pady = 30, padx = 10)
self.ClassChoice.pack()
self.SubmitButton.pack()
self.framenew.pack()
self.masternew.mainloop()
I am open to creating the option menu another way if it is possible.
Thanks.
Note: apply() doesn't exist in Python 3. Any guide that uses it(notably, the tkinterbook on effbot.org) is horribly out of date.
Per tkinter's definition for class OptionMenu(Menubutton):
The OptionMenu is initialized as such:
def __init__(self, master, variable, value, *values, **kwargs):
"""Construct an optionmenu widget with the parent MASTER, with
the resource textvariable set to VARIABLE, the initially selected
value VALUE, the other menu values VALUES and an additional
keyword argument command."""
Taking that into account, your code line:
self.ClassChoice = tk.apply(tk.OptionMenu, (self.framenew, self.ClassVariable) + tuple(Client.ListOfClasses))
Should be changed to:
self.ClassChoice = tk.OptionMenu(self.framenew, self.ClassVariable, *Client.ListOfClasses)
Note the asterisk before Client.ListOfClasses. This is to pass in the menu VALUES needed by the OptionMenu as a list, per https://docs.python.org/3.7/tutorial/controlflow.html#unpacking-argument-lists.
The apply function has been removed in Python 3.[1] It was not a tkinter specific function. To fix this, use the first parameter of the apply function as the function name, like so:
Your code:
self.ClassChoice = tk.apply(tk.OptionMenu, (self.framenew, self.ClassVariable) + tuple(Client.ListOfClasses))
New:
self.ClassChoice = tk.OptionMenu((self.framenew, self.ClassVariable) + tuple(Client.ListOfClasses))
Or as the documentation for 2to3 says, "apply(function, *args, **kwargs) is converted to function(*args, **kwargs)."
folks! So, thanks to you guys I was able to figure out what it was I was doing wrong in my previous script of staggering animation for selected objects in a scene. I am now on part two of this little exercise: Creating a UI for it.
This involves creating a window with a button and user input of how much the animation will be staggered by. So, instead of me putting how much the stagger should increment by (which was two in my previous script), I'd now allow the user to decide.
The script I have so far created the window, button, and input correctly, though I am having some trouble with getting the UI to properly execute, meaning when I click on the button, no error pops up; in fact, nothing happens at all to change the scene. I get the feeling it's due to my not having my increment variable in the correct spot, or not utilizing it the right way, but I'm not sure where/how exactly to address it. Any help would be greatly appreciated.
The code I have (with suggested edits) is as follows:
import maya.cmds as cmds
spheres = cmds.ls(selection=True)
stagWin = cmds.window(title="Stagger Tool", wh=(300,100))
cmds.columnLayout()
button = cmds.button(label="My Life For Aiur!")
count = cmds.floatFieldGrp(fieldgroup, query=True, value=True)
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
cmds.showWindow(stagWin)
def stagger(fieldgroup):
for i in spheres:
cmds.selectKey(i)
cmds.keyframe(edit=True, relative=True, timeChange=count)
print "BLAH"
Moving the comments into an answer because I think I've got it all figured out finally:
First of all, the better practice is to pass the stagger object to the button command rather than the string. so that would be:
cmds.button(label="My Life For Aiur!", command=stagger)
Secondly, the count isn't getting updated, so it stays 0 as per your third line. To update that:
count = cmds.floatFieldGrp(fieldgroup, query=True, value=True)
But wait, where did fieldgroup come from? We need to pass it into the function. So go back to your button code and take out the command entirely, also saving the object to a variable:
button = cmds.button(label="My Life For Aiur!")
Now store the object for the fieldgroup when you make it:
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
Now that you have fieldgroup, you can pass that in the function for the button, like this:
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
I had to wrap the function in a lambda because we're passing fieldgroup, but if I just put stagger(fieldgroup) it would call that and pass the result of that into the command for the button
Also update stagger def with fieldgroup argument:
def stagger(fieldgroup):
One final note that won't actually affect this, but good to know:
when you shift the keyframes inside stagger you're using a DIFFERENT count variable than the one you declared as 0 up above. The outer one is global, and the inner is local scope. Generally it's best to avoid global in the first place, which fortunately for you means just taking out count = 0
Putting that all together:
import maya.cmds as cmds
spheres = cmds.ls(selection=True)
stagWin = cmds.window(title="Stagger Tool", wh=(300,100))
cmds.columnLayout()
button = cmds.button(label="My Life For Aiur!")
fieldgroup = cmds.floatFieldGrp(numberOfFields=1)
cmds.button(button, edit=True, command=lambda _:stagger(fieldgroup))
cmds.showWindow(stagWin)
def stagger(fieldgroup):
count = 0
increment = cmds.floatFieldGrp(fieldgroup, query=True, value=True)[0]
print count
for i in spheres:
cmds.selectKey(i)
cmds.keyframe(edit=True, relative=True, timeChange=count)
count += increment
print "BLAH"
I'm new to GUI and classes and I'm a just a bit confused, when I use a button in tkinter for python it's suppose to repeat it's command when pressed. but in my program it doesn't do that. is there something wrong with me codes that might counter it? I'm trying to make a simple program that echos whatever is typed.
-Thanks
from Tkinter import *
from PIL import Image, ImageTk
import tkMessageBox
class appsMain(Frame):
def __init__(self,parent):
Frame.__init__(self,parent)
self.parent=parent
self.initUI()
def initUI(self):
self.parent.title("OrganizedWindows")
self.send=Text(self,bg="white",height=3,width=35)
self.send.place(x=17,y=235)
self.msg=Text(self,width=35,height=12,state="disable")
self.msg.place(x=17,y=20)
sendbtn=Button(self,text=" Listen ",command=self.accept)
sendbtn.place(x=305,y=240)
self.pack(fill=BOTH, expand=1)
def accept(self,msg):
self.msg.configure(state="normal")
self.msg.insert(INSERT,msg+"\n")
self.msg.insert(INSERT,"BYE")
self.msg.configure(state="disable")
root=Tk()
root.geometry("350x300+300+300")
app=appsMain(root)
root.mainloop()
Your code has a few problems. The first is solved easily:
sendbtn=Button(self,text=" Listen ",command=self.accept)
doesn't work because when the button is clicked, self.accept is called with no additional arguments (accept expects 2 arguments, [self and msg], but it is only getting 1 [self]).
You can work around this with lambda:
sendbtn=Button(self,text=" Listen ",command=lambda : self.accept("some message here"))
(This is equivalent to):
def func:
self.accept("some message here")
sendbtn=Button(self,text=" Listen ",command=func)
But, I don't know if you want to constantly add different messages ... or where they come from, so it is difficult to give a general solution at this point.
Tkinter applications happily continue to run even after exceptions are raised. It is a good idea to watch the terminal for exceptions when you're developing a Tkinter application (In this case, it pointed me right to the source of the problem).
This is to better answer your Lambda comment question. Lambda is a quick, one-liner way to write a function. The variable you set it to is the same as the name of your function for def myFunction. Then you say the keyword lambda and the letter(s)/word(s) you put after the keyword lambda are just the parameters of your function. Next you put a colon (just like you would for a normal function-> def myFunction:). After that you write whatever you want the function to return. So if you wanted a function to square a given number, n, then you could write it normally like:
def square_num(n):
return n**2
OR as a cool Lambda:
square_num = lambda n: n**2
You can also have as many parameters as you wish, just like in a normal function, so for a given number raised to the x power you could write:
raise_num = lambda n, x: n**x