Executing different functions based on options selected - python

I wanted to know if there is a way of populating a option menu with choices and then each of the different choice gives the build button a different function
for eg:
Type = cmds.optionMenu('type',w = 300 ,label = 'Type of crowd:')
cmds.menuItem( label='Walking' )
cmds.menuItem( label='Running' )
cmds.menuItem( label='Cheering' )
cmds.button('Build',command = bld)
def walk(*args):
print (walking)
def run(*args)
print (running)
def cheer(*args)
print (cheer)
so if the menu item selected would be walking the button command would execute the command wak
and if the menu item selected would be running then the button command would execute the command run and so on....
is this even possible in maya python...????

There's three parts to the problem.
First, you want your options to be callables. I like functools.partial for that, so that you could give the same command different parameters and have it be treated as two different actions:
from functools import partial
bigcircle = functools.partial ( cmds.circle, radius = 10)
littleCircle = functools.partial (cmds.circle, radius = 1)
the second problem is that menuItems in OptionMenus don't fire their commands directly. They trigger the -cc change command on the owning optionMenu. So we need something that will turn the label back into a callable object. A little class will do:
class menuMgr(object):
'''call the function associated with a key in the **callables dictionary'''
def __init__(self, **callables):
self.Callables = callables
def __call__(self, *args):
self.Callables[args[-1]]()
The third part is to match these with a label. You can do this elegantly with the **kwargs syntax, where you can either pass in a whole dictionary or named keywords:
def menu_of_functions(**callables):
mmgr = menuMgr(**callables)
Main = cmds.optionMenu('type3',w = 300 ,label = 'Type of crowd:', cc = mmgr)
for key, partial in callables.items():
cmds.menuItem(label = key)
cmds.setParent("..")
Heres the whole thing in working form to inspect:
import maya.cmds as cmds
import functools
bigCircle = functools.partial ( cmds.circle, radius = 10)
littleCircle = functools.partial (cmds.circle, radius = 1)
class menuMgr(object):
def __init__(self, **callables):
self.Callables = callables
def __call__(self, *args):
self.Callables[args[-1]]()
def menu_of_functions(**callables):
mmgr = menuMgr(**callables)
Main = cmds.optionMenu('type3',w = 300 ,label = 'Type of crowd:', cc = mmgr)
for key, partial in callables.items():
cmds.menuItem(label = key)
cmds.setParent("..")
q = cmds.window()
cmds.columnLayout()
menu_of_functions(big = bigCircle, small = littleCircle)
cmds.showWindow(q)

Sure you can, since functions are first class objects in python.
Let's say you have a list of functions.
fns = [walk, run, cheer]
1) You'll need a mapping from a string key to the python function.
Let's use a dictionary comprehension.
options = dict((fn.__name__, fn) for fn in fns)
Alternatively you can build the dictionary with arbitrary keys.
options = {"Walking": walk, "Running": run, "Cheering": cheer}
2) Get a reference to the function by accessing the dictionary item with the functions name.
options['run'](*args)
3) ???
4) Profit

Had a chance to do a small bit of research on this today, but I don't actually use Maya Python so this may not be viable code -- at least it should be close!
def walk(*args):
print ("walking")
def run(*args)
print ("running")
def cheer(*args)
print ("cheer")
fncdict = {"Walking":walk,"Running":run,"Cheering":cheer}
def dofunc(funcname):
try: fncdict[funcname]()
except KeyError: print("adsmith doesn't really know how this crap works,
and gave you some really shoddy advice. Go downvote
his answer.")
Type = cmds.optionMenu('type',w = 300 ,label = 'Type of crowd:')
# Please don't name things this way! naming conventions in PEP 8
# make this look like a class not an instance of optionMenu
cmds.menuItem( label='Walking', parent = Type )
cmds.menuItem( label='Running', parent = Type )
cmds.menuItem( label='Cheering', parent = Type )
cmds.button('Build',command = lambda x: dofunc(Type.value))
# I'm assuming this is the button you want to use to say "GO", right?
From the little I've read, it looks like optionMenu.value refers to the text in the active menuItem, but I can't say for sure -- it may just be the text for that optionMenu in which case the button will call dofunc('Type of crowd:') which will return the exception I built to shame myself.
Here's an alternative that I KNOW will work, but it's ugly and unnecessary.
# all
# those
# functions
# go
# here
# including
# the dict and dofunc
activeMenuItem = None
Type = cmds.optionMenu('type',w = 300 ,label = 'Type of crowd:',
changeCommand = lambda x: activeMenuItem = x)
# this fails because you can't do assignments in a lambda -- OOPS!!
cmds.menuItem( label='Walking', parent = Type )
cmds.menuItem( label='Running', parent = Type )
cmds.menuItem( label='Cheering', parent = Type )
cmds.button('Build',command = lambda x: dofunc(activeMenuItem))
The changeCommand option in optionMenu gets called every time you change items. I've assigned it to a lambda that updates the variable activeMenuItem to the value in the newly-active menuItem, then had the button reference the variable instead of querying the optionMenu for its currently selected button, but let's be honest -- this is what menus are MADE to do. There is DEFINITELY a way to do it without storing every single selection.
EDIT: THIS LAST ONE WON'T WORK BECAUSE YOU CAN'T DO ASSIGNMENTS WITHIN LAMBDA EXPRESSIONS. MY FAULT!

I have kind of figured out an easier way to this problem
cmds.optionMenu('Greetings',Label = 'Greet')
cmds.menuItem(label= hi,parent = 'Greetings)
cmds.menuItem(label = Hello,parent = 'Greetings')
def Greet(*args)
menuItems = cmds.optionMenu('Greetings',q=True,v=True)
if menuItems = hi
print "hi"
menuItemsnew = cmds.optionMenu('Greetings',q=True,v=True)
if menuItemsnew = hello
print "hello"
This should work,it worked for me

Related

Set parent class attribute in child class constructor

This question is posed as a general Python question, but will be exemplified using Kubeflow Pipelines SDK, kfp==1.8.12. In this module, I want to create a helper class around the class dsl.ContainerOp to simplify a lot of my work. As a minimal example, below I use this class to create a component as such:
from kfp import dsl
name = 'My name'
image = 'My image'
docker_entrypoint = "/main.py"
docker_args = [
'--arg1', 'some arg',
'--arg2', 'some other arg'
]
component = dsl.ContainerOp(
name=name,
image=image,
arguments=[docker_entrypoint] + docker_args
)
Then, I would like to set one of its attributes that relates to caching as such;
use_caching = False
if use_caching:
staleness = "P30D"
else:
staleness = "P0D"
component.execution_options.caching_strategy.max_cache_staleness = staleness
which works fine, as expected. Now, I would like to create a ContainerOpHelper class, to simplify a lot of my argument passing (the "real" code has a lot of parameters). Problem: I need to access the attribute execution_options.caching_strategy.max_cache_staleness from the class, but I can't figure out how! Here is the helper class, and my attempt to access the attribute;
class ContainerOpHelper(dsl.ContainerOp):
def __init__(
self,
name: str,
image: str,
docker_entrypoint: str = None,
docker_args: list = None,
use_caching: bool = None
):
super().__init__(
name=name,
image=image,
arguments=([docker_entrypoint] if docker_entrypoint else []) + (docker_args if docker_args else [])
)
if use_caching:
staleness = "P30D"
else:
staleness = "P0D"
# Tried to be creative; but doesnt work
super.__setattr__("execution_options.caching_strategy.max_cache_staleness", staleness)
This helper class can then be used as such;
component = ContainerOpHelper(
name='My name',
image='My image',
docker_entrypoint="/main.py",
docker_args=[
'--arg1', 'some arg',
'--arg2', 'some other arg'
],
use_caching=False
)
Since the attribute execution_options.caching_strategy.max_cache_staleness is "many levels deep", I'm not sure how I can set it in my helper class. Any ideas?
The solution was fairly simple, as provided by the comment of #quamrana.
Simply set it straight in the child class constructor as such;
self.execution_options.caching_strategy.max_cache_staleness = staleness

Function creates buttons but they can't trigger other functions

I'm Trying to build a dynamic layout in Kivy, my function generates buttons but they are not able to trigger any other function that would be responsible for the creation of labels:
def candidate_builder(self):
file = open('GSUCandidates.txt', 'r')
for names in file:
names = names.rstrip()
if 'President' in names:
cbl_layout = self.ids['cs_grid']
cn_label = Label(bold=True, text=names)
cn_button = Button(id='pr', bold=True, text='Vote')
cn_button.on_release = show()
cbl_layout.add_widget(cn_label)
cbl_layout.add_widget(cn_button)
cbl_layout.height = cbl_layout.height + 250
def show():
vp_label = Label(bold=True, text=names)
cpl_layout = self.ids['csp_grid']
cpl_layout.add_widget(vp_label)
You can use:
cn_button = Button(id='pr', bold=True, text='Vote', on_release=show)
...
def show(*args):
Or in case you want to send some variables to your method to should use lambda:
cn_button = Button(id='pr', bold=True, text='Vote', on_release=lambda event: show()) # <- here you should have parentheses, where you can put anything you want
...
def show():
You don't want show in quotes (that makes it a string, not a function reference). I think you need to to use
cn_button.on_release = show
and I think it needs to appear after the def show(). Also, the Button instance will be passed to the show() method, so you need to define it as:
def show(butt_instance):

Returning PY_VARxxx instead of expected string

I'm currently creating a GUI in order to turn a lot of individual instruments into one complete system. In def smuSelect(self) I create a list self.smuChoices I can use to call individual choices such as smuChoices[0] and it will return "2410(1)".
Once I call def checkBoxSetup it returns PY_VARxxx. I've tried searching the different forums and everything. I've seen mentions using the .get() which just gives me the state of the individual choice. The reason I want the actual string itself is I would like to use it in def testSetup(self) for the user to assign specific names to the individual machine, for example, 2410 = Gate.
My initial attempt was to create another variable smuChoice2 but I believe this is still changing the original list self.smuChoices.
import tkinter as tk
import numpy as np
from tkinter import ttk
def checkBoxSetup(smuChoice2): #TK.INTVAR() IS CHANGING NAME OF SMUS NEED TO CREATE ANOTHER INSTANCE OF SELF.SMUCHOICES
for val, SMU in enumerate(smuChoice2):
smuChoice2[val] = tk.IntVar()
b = tk.Checkbutton(smuSelection,text=SMU,variable=smuChoice2[val])
b.grid()
root = tk.Tk()
root.title("SMU Selection")
"""
Selects the specific SMUs that are going to be used, only allow amount up to chosen terminals.
--> If only allow 590 if CV is picked, also only allow use of low voltage SMU (maybe dim options that aren't available)
--> Clear Checkboxes once complete
--> change checkbox selection method
"""
smuChoices = [
"2410(1)",
"2410(2)",
"6430",
"590 (CV)",
"2400",
"2420"
]
smuChoice2 = smuChoices
smuSelection = ttk.Frame(root)
selectInstruct = tk.Label(smuSelection,text="Choose SMUs").grid()
print(smuChoices[0]) #Accessing list prior to checkboxsetup resulting in 2410(1)
checkBoxSetup(smuChoice2)
print(smuChoices[0]) #Accessing list after check box setup resulting in PY_VAR376
variableSMUs = tk.StringVar()
w7_Button = tk.Button(smuSelection,text="Enter").grid()
w8_Button = tk.Button(smuSelection,text="Setup Window").grid()
root.mainloop()
I was able to solve the problem by changing my list, smuChoices, to a dictionary then modifying
def checkBoxSetup(smuChoice2):
for val, SMU in enumerate(smuChoice2):
smuChoice2[val] = tk.IntVar()
b = tk.Checkbutton(smuSelection,text=SMU,variable=smuChoice2[val])
b.grid()
to
def checkBoxSetup(self):
for i in self.smuChoices:
self.smuChoices[i] = tk.IntVar()
b = tk.Checkbutton(self.smuSelection,text=i,variable=self.smuChoices[i])
b.grid()
Previously I was replacing the variable with what I'm guessing is some identifier that tkinter uses to store the state which is why I was getting PYxxx.
First of all getting PY_VARXX instead of what's in a variable class indicates the lack of get().
replace:
print(self.smuChoices[0])
with:
print(self.smuChoices[0].get())
Secondly, if you want to display the value of a variable class on a label, button, etc. you could rather just use the textvariable option by simply assigning the variable class to it.
Replace:
tk.Label(self.smuName,text=SMU).grid()
with:
tk.Label(self.smuName, textvariable=self.smuChoices[val]).grid()
Your question is still a bit unclear to me but I will try to provide an answer to the best of my understanding.
As I understand it, you're trying to create a set of Checkbuttons for a given list of items. Below is an example of a method that takes items as an argument and returns a dictionary of checkboxes that have root as their parent:
import tkinter as tk
def dict_of_cbs(iterable, parent):
if iterable:
dict_of_cbs = dict()
for item in iterable:
dict_of_cbs[item] = tk.Checkbutton(parent)
dict_of_cbs[item]['text'] = item
dict_of_cbs[item].pack() # it's probably a better idea to manage
# geometry in the same place wherever
# the parent is customizing its
# children's layout
return dict_of_cbs
if __name__ == '__main__':
root = tk.Tk()
items = ("These", "are", "some", "items.")
my_checkboxes = dict_of_cbs(items, root)
root.mainloop()
Additionally note that I haven't used any variable classes (BooleanVar, DoubleVar, IntVar or StringVar) as they seem to be redundant in this particular case.

Problems with a bind function from tkinter in Python

I am working on an application that is supposed to support both running from a console and from a GUI. The application has several options to choose from, and since in both running modes the program is going to have the same options obviously, I made a generalisation:
class Option:
def __init__(self, par_name, par_desc):
self.name = par_name
self.desc = par_desc
class Mode():
def __init__(self):
self.options = []
self.options.append(Option('Option1', 'Desc1'))
self.options.append(Option('Option2', 'Desc2'))
self.options.append(Option('Option3', 'Desc3'))
self.options.append(Option('Option4', 'Desc4'))
self.options.append(Option('Option5', 'Desc5'))
#And so on
The problem is that in GUI, those options are going to be buttons, so I have to add a new field to an Option class and I'm doing it like this:
def onMouseEnter(par_event, par_option):
helpLabel.configure(text = par_option.desc)
return
def onMouseLeave(par_event):
helpLabel.configure(text = '')
return
class GUIMode(Mode):
#...
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name, bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event, iOption))
iOption.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
#...
There is also a "help label" showing the description of the option every time a mouse hovers over it, so there I am binding those functions.
What is happening is that while I am indeed successfully adding a new field with a button, the bind function seems to mess up and the result is this:
Help label is always showing the description of the last option added, no matter over which button I hover. The problem seems to go away if I directly modify the Option class instead, like this:
class Option:
def __init__(self, par_name, par_desc):
self.name = par_name
self.desc = par_desc
self.button = Button(wrapper, text = self.name, bg = '#004A7F', fg = 'white')
self.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event, self))
self.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
But I obviously can't keep it that way because the console mode will get those fields too which I don't really want. Isn't this the same thing, however? Why does it matter if I do it in a constructor with self or in a loop later? I therefore assume that the problem might be in a way I dynamically add the field to the class?
Here is the full minimal and runnable test code or whatever it is called, if you want to mess with it: http://pastebin.com/0PWnF2P0
Thank you for your time
The problem is that the value of iOption is evaluated after the
for iOption in self.option:
loops are complete. Since you reset iOption on each iteration, when the loop is completed iOption has the same value, namely the last element in self.options. You can demonstrate this at-event-time binding with the snippet:
def debug_late_bind(event):
print(iOption)
onMouseEnter(event, iOption)
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name,
bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', debug_late_bind)
which will show that all events that iOption has the same value.
I split out the use of iOption to debug_late_bind to show that iOption comes in from the class scope and is not evaluated when the bind() call is executed. A more simple example would be
def print_i():
print(i)
for i in range(5):
pass
print_i()
which prints "4" because that is the last value that was assigned to i. This is why every call in your code to onMouseEnter(par_event, iOption) has the same value for iOption; it is evaluated at the time of the event, not the time of the bind. I suggest that you read up on model view controller and understand how you've tangled the view and the controller. The primary reason this has happened is that you've got two views (console and tk) which should be less coupled with the model.
Extracting the .widget property of the event is a decent workaround, but better still would be to not overwrite the scalar iOption, but instead use list of individual buttons. The code
for n, iOption in enumerate(self.options):
would help in creating a list. In your proposed workaround, you are encoding too much of the iOption model in the tkinter view. That's bound to bite you again at some point.
I don't know what the actual problem was with my original code, but I kind of just bypassed it. I added a dictionary with button as a key and option as a value and I just used the par_event.widget to get the option and it's description, which is working fine:
buttonOption = {}
def onMouseEnter(par_event):
helpLabel.configure(text = buttonOption[par_event.widget].desc)
return
def onMouseLeave(par_event):
helpLabel.configure(text = '')
return
class GUIMode(Mode):
def run(self):
#...
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name, bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event))
iOption.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
buttonOption[iOption.button] = iOption
#...

Don't want to use global. Python [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 8 years ago.
Improve this question
I have been trying to use a variable which I defined within a function within a another but I don't want to use a Global.
import sys
from tkinter import *
from tkinter import messagebox
from tkinter import colorchooser
from tkinter import filedialog
x = "#000000"
#colour chooser
def mColour():
color = colorchooser.askcolor()
mlabel2 = Label(mGui,text=color).pack()
messagebox.showinfo(title = "Colour",message = "This feature has not been fully added yet.")
x = color[1]
print (color[1])
return x
#printing message
def mhello():
mtext = ment.get()
mlabel2 = Label(mGui,text=mtext, fg = (b)) # color varible = x
mlabel2.pack()
#message
def mNew():
messagebox.showerror(title = "(New) Error",message = "This feature has not been added yet.")
def mAbout():
messagebox.showinfo(title = "About",message = "This Jono's susure personal data base. (Will be)")
#question message
def mQuit():
mExit = messagebox.askokcancel(title = "Quit",message = "Are you sure")
if mExit > 0:
mGui.destroy()
#open
def mOpen():
myopen = filedialog.askopenfile()
mlabel3 = Label(mGui,text=myopen).pack()
messagebox.showinfo(title = "Open",message = "This feature has not been fully added yet.")
mGui = Tk()
ment = StringVar()
mGui.geometry("300x200+100+100")
mGui.title("Jono's Clock")
mlable = Label(mGui,text="My Label",fg = "red").pack()
mbutton = Button(mGui,text ="OK",command = mhello,fg = "red").pack()
mEntry = Entry(mGui,textvariable=ment).pack()
# Menu
menubar = Menu(mGui)
filemenu = Menu(menubar, tearoff = 0)
filemenu.add_command(label = "New",command = mNew)
filemenu.add_command(label = "Open",command = mOpen)
filemenu.add_command(label = "Colour",command = mColour)
filemenu.add_command(label = "About",command = mAbout)
filemenu.add_command(label = "Quit",command = mQuit)
menubar.add_cascade(label = "File",menu = filemenu)
mGui.config(menu = menubar)
mGui.mainloop()
I'm using python 3.3 on windows 7.
I have add the rest of the code as requested.
As the comments suggested, it would be good to pass the variable x as a parameter to the function.
def mColour(x):
...
def mHello(x):
...
Then when you want to call the functions:
x = "#000000"
x = mColour(x)
mhello(x)
As a rule of thumb, defining global variables inside a function is a bad idea (with rare circumstances being exceptions). You want to keep local and global namespaces seperate. Functions allow the passing of arguments for this reason.
#colour chooser
def mColour(x):
color = colorchooser.askcolor()
mlabel2 = Label(mGui,text=color).pack()
messagebox.showinfo(title = "Colour",message = "This feature has not been fully added yet.")
x = color[1]
print (color[1]) # Just a test
return x
x = "#000000" # default text colour
mColour(x)
You could use a class instead as I don't think there's a way to access local variables from another function.
class Example(object):
x="00006"
def c():
print(Example.x)
c()
So here you're not using any globals at all
EDIT: Now your question makes a lot more sense. Because your functions are connected to these UI controls, it becomes much more difficult to pass parameters to them.
As such, you need a structure in which to store these values that you get from the UI. Then, when mhello() needs it, it has access to a function that can get the color from whatever structure you choose to use.
Now, mHello still can't take the color or the structure as a parameter, so you've got three options.
1) You can make something global, which you've already said you don't want to do.
2) You can make something static so that you can access the same instance from within mHello.
3) But I think the best answer is to use the structure that's commonly used: Make the UI a class. It has member variables to which the member functions have access. If mColor and mHello are members of the same class, then they can access whatever they need, pretty easily. Then you create some means of getting stuff outside the UI class so that it can be accessed, processed, and returned by whatever application you're writing.
(old answer:)
There are two ways.
EDIT: Both of which have already been suggested.
You could either make both of those functions members of the same class, and then x (which you should name something more descriptive) is accessible to both of them without being global.
Or...
You could (as suggested by other commentors) pass x as a parameter to your second function. mColor() even returns x which means you can store that value in another variable that you then pass to mhello().
Incidentally, I'm not sure why you're prefacing your methods with m but that typically identifies them as members of a class. Are they?
It would also be helpful to see the rest of the code, where you're actually calling these functions.

Categories