I was looking over a bit of code rooted in urwid:
import urwid
from functools import partial
from random import randint
class State(object):
def __init__(self, main_widget):
self.main_widget = main_widget
def handle_keystroke(app_state, key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
else:
loop.widget = urwid.Filler(urwid.Button('new rand int:' + str(randint(0, 100))))
app_state = State(urwid.Filler(urwid.Button('original widget')))
callback = partial(handle_keystroke, app_state)
loop = urwid.MainLoop(app_state.main_widget, unhandled_input=callback)
loop.run()
and noticed that loop is referenced in the function unhandled_input before it's defined. Furthermore, it's not passed as a parameter, it's just hard coded into the function by name. 1) Why is this possible, and: 2) is there a clearer alternative? It is difficult to do otherwise, as there is a circular dependencies of loop, app_state and callback.
I'm not sure how much of your sample code represents the original code, but it looks like you may want to get familiar with the technique of using urwid's custom widgets wrapping text widgets, as shown in the answer with an example widget that displays a text content one line at the time.
Here is an example of writing something similar to the sample code you provided, in a design that fits urwid and Python a bit better:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import, division
import urwid
from random import randint
class RandomNumberWidget(urwid.WidgetWrap):
def __init__(self):
self.random_number = None
self.text_widget = urwid.Text(u'')
super(RandomNumberWidget, self).__init__(self.text_widget)
def roll(self):
self.random_number = randint(0, 100)
self.update()
def update(self):
"""Update UI
"""
if self.random_number is None:
self.text_widget.set_text('No number set')
else:
self.text_widget.set_text('Random number: %s' % self.random_number)
class App(object):
def __init__(self):
self.random_number_widget = RandomNumberWidget()
top_message = 'Press any key to get a random number, or q to quit\n\n\n'
widget = urwid.Pile([
urwid.Padding(urwid.Text(top_message),
'center', width=('relative', len(top_message))),
self.random_number_widget,
])
self.widget = urwid.Filler(widget, 'top')
def play(self):
self.random_number_widget.roll()
def play_or_exit(self, key):
if key in ('q', 'Q', 'esc'):
raise urwid.ExitMainLoop()
app.play()
if __name__ == '__main__':
app = App()
loop = urwid.MainLoop(app.widget, unhandled_input=app.play_or_exit)
loop.run()
Depending also on what you actually want to do, it could make sense to make the custom widgets respond to the keyboard events, instead of doing it all in the global handler (which is totally fine for simple programs, IMO).
When python compiles a function, left-hand-side variables that are the target of assignment are treated as local and the rest are global. loop is not assigned, so when python runs loop.widget = urwid.Filler(...), it knows that loop is not a local variable and it will look the name up in the module's namespace.
Module namespaces are dynamic, so as long as loop = urwid.MainLoop(app_state.main_widget, unhandled_input=callback) runs before the lookup, loop is created and it works. Since the callback can't be executed until loop.run(), loop will be defined.
This is one of the classic risks of singletons and global state. Its not always easy to make sure the resource is created before it is used.
Related
I am a Maya user and I am currently writting an Auto-Rig.
I created different Classes for each major tasks of the tool. (ex: Class_UI, Class_Arms_Rig, etc..)
The problem I have is that I can't call a method from "Class_Joints" (the class that will generates every needed Joints) with my "Class_UI"
Here are the codes :
First the Class_UI
import sys
sys.path.append('G:\\3D2\\Script\\Auto_Rig')
import Class_Joints
import Class_Arms
import maya.cmds as mc
class Window_UI(object):
# Initializing global variables
def __init__(self):
# Getting acces to the different modules
self.Arms = Class_Arms.Arms_Rig()
self.Joints = Class_Joints.Gen_Joints()
# Create Ui
self.create_UI()
# Creating the UI
def create_UI(self):
# Create window
self.UI = mc.window(title='Auto-Rig Tool', w=(300), h=(350))
# Main layout
self.mainLayout = mc.menuBarLayout()
### Joints Option ###
# Create Joints Button
self.createJointsButton = mc.button(label='Create Joints', command=self.Joints.gen_arms_joints)
Window_UI()
mc.showWindow()
Then the Class_Joints :
import maya.cmds as mc
class Gen_Joints:
# Creating arm Jnts and the list of it
def gen_arms_joints(self):
self.shoulderJnt = mc.joint(absolute=True, position=[5,8,0], n='L_Shoulder_Jnt')
self.elbowJnt = mc.joint(absolute=True, position=[10,8,-1.5], n='L_Elbow_Jnt')
self.wristJnt = mc.joint(absolute=True, position=[15,8,0], n='L_Wrist_Jnt')
self.handcupJnt = mc.joint(absolute=True, position=[18,8,0], n='L_HandCup_Jnt')
self.jntList = mc.ls(self.shoulderJnt, self.elbowJnt, self.wristJnt, self.handcupJnt)
When I run the Class_UI Code, the button within the UI is supposed to run the gen_arms_joints method within the Class_Joints
But I get this error message : # Error: gen_arms_joints() takes exactly 1 argument (2 given) #
I know that self is an implicit argument here but I do not know how to avoid this error.
Thank you all for your time.
:D
Cordially, Luca.
Two things i would recommend you do. I dont use Maya but i have built apps with multiple different GUIs.
Every GUI I've used when it comes to buttons is the first argument is a reference to self, and then there is usually 1 or 2 more arguments passed in. Some pass the reference to the button itself while others pass a argument that holds event details. My guess is this is what is happening. When you click the button it is passing in an "event" object that hold details about what was clicked and other details.
To truly find out what is passed change your function signature to this and see what is logged.
def gen_arms_joints(self, mystery_second_arg):
print(type(mystery_second_arg), mystery_second_arg)
self.shoulderJnt = mc.joint(absolute=True, position=[5,8,0], n='L_Shoulder_Jnt')
self.elbowJnt = mc.joint(absolute=True, position=[10,8,-1.5], n='L_Elbow_Jnt')
self.wristJnt = mc.joint(absolute=True, position=[15,8,0], n='L_Wrist_Jnt')
self.handcupJnt = mc.joint(absolute=True, position=[18,8,0], n='L_HandCup_Jnt')
self.jntList = mc.ls(self.shoulderJnt, self.elbowJnt, self.wristJnt, self.handcupJnt)
The issue is that in the __init__() call of the Class_UI, you have defined the wrong class call for the actual function gen_arm_joints(self), it should be: self.Joints = Gen_Joints(), seems like you have different import classNames but in the code you have called the class as Gen_Joints. You can't pass in two self classes references i.e the error traceback.
You will have to fix the import class to import Gen_Joints.
#FishingCode
Look out what I tried based on what you told me :
import sys
sys.path.append('G:\\3D2\\Script\\Auto_Rig')
import Class_Joints
import Class_Arms
import maya.cmds as mc
class Window_UI(object):
# Initializing global variables
def __init__(self):
# Getting acces to the different modules
self.Arms = Class_Arms.Arms_Rig()
self.Joints = Gen_Joints()
# Create Ui
self.create_UI()
# Creating the UI
def create_UI(self):
# Create window
self.UI = mc.window(title='Auto-Rig Tool', w=(300), h=(350))
# Main layout
self.mainLayout = mc.menuBarLayout()
# Create Joints Button
self.createJointsButton = mc.button(label='Create Joints', command=self.Joints.gen_arms_joints)
#show window
Window_UI()
mc.showWindow()
It's exactly what I have on Visual Studio Code
import maya.cmds as mc
class Gen_Joints:
# Creating arm Jnts and the list of it
def gen_arms_joints(self):
self.shoulderJnt = mc.joint(absolute=True, position=[5,8,0], n='L_Shoulder_Jnt')
self.elbowJnt = mc.joint(absolute=True, position=[10,8,-1.5], n='L_Elbow_Jnt')
self.wristJnt = mc.joint(absolute=True, position=[15,8,0], n='L_Wrist_Jnt')
self.handcupJnt = mc.joint(absolute=True, position=[18,8,0], n='L_HandCup_Jnt')
self.jntList = mc.ls(self.shoulderJnt, self.elbowJnt, self.wristJnt, self.handcupJnt)
VS Code tells me Undefined variable 'Gen_Joints'
And within maya I get # Error: NameError: file <maya console> line 16: global name 'Gen_Joints' is not defined #
mc.button passes on an extra boolean value to the method, so since that's not defined in gen_arms_joints it throws that error and says it's the wrong number of arguments.
So simply add an extra parameter and it'll fix it:
def gen_arms_joints(self, extra_param):
Or better yet use *args instead to be a bit more generic:
def gen_arms_joints(self, *args):
It's honestly kind of sneaky because it's not really stated in the documentation, but you can see it being used in the example at the bottom of the page.
I’m writing a thing in maya and have run in to trouble. Really don’t know what I did, I was going to adress something else when this happened, the last thing I did was add a button to the layout.
I have been messing around with this for a long time now and as far as I can tell, the uiScript flag doesn’t like arguments passed in the method call…? what happens is, it never sets the restore flag to True so that bit never hits. resulting in it spawning multiple windows in maya. I’m also trying to figure out where the cmds.deleteUI try clause should go, not entrirely sure where I had it before this happened. If anyone could offer any insight I would be most grateful, cheers /S
in the script below, I have replaced an instance of my ui with just a button, it makes no difference on the behaviour.
code:
//////////
from PySide2 import QtWidgets, QtCore
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
import maya.OpenMayaUI as mui
import maya.cmds as cmds
import weakref
if not 'customMixinWindow' in globals():
customMixinWindow = None
class DockableWidget(MayaQWidgetDockableMixin, QtWidgets.QWidget):
instances = list()
CONTROL_NAME = 'customMixinWindow'
def __init__(self, parent=None):
super(DockableWidget, self).__init__(parent=parent)
DockableWidget.delete_instances()
self.__class__.instances.append(weakref.proxy(self))
self.main_layout = QtWidgets.QVBoxLayout()
self.button = QtWidgets.QPushButton()
self.main_layout.addWidget(self.button)
self.setLayout(self.main_layout)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
#staticmethod
def delete_instances():
print "deleting"
for ins in DockableWidget.instances:
try:
ins.setParent(None)
ins.deleteLater()
except:
pass
DockableWidget.instances.remove(ins)
del ins
def DockableWidgetUIScript(restore=False):
global customMixinWindow
if restore == True:
restoredControl = mui.MQtUtil.getCurrentParent()
customMixinWindow = DockableWidget()
if customMixinWindow is None:
#customMixinWindow = DockableWidget()
customMixinWindow.setObjectName('customMayaMixinWindow')
if restore == True:
mixinPtr = mui.MQtUtil.findControl(customMixinWindow.objectName())
mui.MQtUtil.addWidgetToMayaLayout(long(mixinPtr), long(restoredControl))
else:
try:
cmds.workspaceControl('customMayaMixinWindowWorkspaceControl', e=True, close=True)
cmds.deleteUI('customMayaMixinWindowWorkspaceControl')
except:
pass
customMixinWindow.show(dockable=True, restore=True, height=400, width=400, uiScript='import dockWin; dockWin.DockableWidgetUIScript(restore=True)')
def main():
ui = DockableWidgetUIScript()
return ui
if __name__ == 'dockWin':
main()
ok, problem solved.
after a lot of troubleshooting, it turned out to be a QSpacerItem in my ui that caused the window to open once and then crash maya the second time. which made me think it had something to do with the deleteUI stuff.
I had the QSpacerItem added like so:
self.spacerFive = QtWidgets.QSpacerItem(5 , 5)
self.myLayout.addSpacerItem(self.spacerFive)
this made maya crash out completely when deleteing the UI/workspaceControl…
no idea why, garbage collection?
this fixed it:
self.myLayout.addSpacerItem(QtWidgets.QSpacerItem(5, 5))
alright, everything working as expected again. cheers /S
I have a question when it comes to OOP in general, as well as Python in particular. Let's say that I have, for instance, priorities.py - a simple GUI program to manage priorities and there are three classes: Priority, Client, GuiPart:
# priorities.py
# GUI program to manage priorities
from tkinter import *
class Priority:
pass
class GuiPart:
def __init__(self):
self.root = self.createWindow()
def createWindow(self):
root = Tk()
root.resizable(width = False, height = False)
root.title("Priorities")
return root
def display(self):
Label(self.root,
text = "testes").grid(row = 0, column = 1)
class Client:
pass
def main():
g = GuiPart()
g.display()
root = g.root.mainloop()
main()
Should I put def main() outside of any classes, or should I put it in Client class?
Every module(python file) have a builtin __name__ variable, if this equal to "__main__" this means that this file ran directly, but if __name__ is equal to other things this means that current file imported to other python files.
if you running this file directly or as module, you can use __name__ variable to recognize type of code-file used, similar below:
# Some codes
if __name__ == '__main__':
main()
Now users can running this file directly and/or programmers can use this module in other codes without running main() function.
My preferred approach:
separate main file with the if __name__ == '__main__': directive
Reasons:
Application Logic and calling logic is separate. so you can scale easily
Can maintain and apply different environment settings effectively. so, we can seamlessly transition between dev/test/stage/prod setup
Increases code readability as well
class script(object):
def __init__(self, time_delay = 1.5):
self.time_delay = time_delay
self.lines = []
def run_script(self):
for line in self.lines:
print line
sleep(self.time_delay)
intro = script(1.5)
intro.lines = ["great", "boy"]
My guess would be that the sleep() function is from the time library. Just add
from time import *
at the beginning of the file. But, since the syntax above will import definitions as if they were declared in your file, you can use:
import time
...
time.sleep(self.time_delay)
But there is also another possibility. That sleep() has to be a function declared by you. If this is the case, you have to define it:
class script(object):
# ...
def sleep(delay):
# implementation
Note:
As #icktoofay commented, you are not using the run_script() method, so may want to add a call like:
intro.run_script()
There might be confusion on your part about "running" a class, since you don't "run" a class, but you can "run" a method of a class. Perhaps you just mean
intro.run_script()
I am updating some code from using libglade to GtkBuilder, which is supposed to be the way of the future.
With gtk.glade, you could call glade_xml.signal_autoconnect(...) repeatedly to connect signals onto objects of different classes corresponding to different windows in the program. However Builder.connect_signals seems to work only once, and (therefore) to give warnings about any handlers that aren't defined in the first class that's passed in.
I realize I can connect them manually but this seems a bit laborious. (Or for that matter I could use some getattr hackery to let it connect them through a proxy to all the objects...)
Is it a bug there's no function to hook up handlers across multiple objects? Or am I missing something?
Someone else has a similar problem http://www.gtkforums.com/about1514.html which I assume means this can't be done.
Here's what I currently have. Feel free to use it, or to suggest something better:
class HandlerFinder(object):
"""Searches for handler implementations across multiple objects.
"""
# See <http://stackoverflow.com/questions/4637792> for why this is
# necessary.
def __init__(self, backing_objects):
self.backing_objects = backing_objects
def __getattr__(self, name):
for o in self.backing_objects:
if hasattr(o, name):
return getattr(o, name)
else:
raise AttributeError("%r not found on any of %r"
% (name, self.backing_objects))
I have been looking for a solution to this for some time and found that it can be done by passing a dict of all the handlers to connect_signals.
The inspect module can extract methods using
inspect.getmembers(instance, predicate=inspect.ismethod
These can then be concatenated into a dictionary using d.update(d3), watching out for duplicate functions such as on_delete.
Example code:
import inspect
...
handlers = {}
for c in [win2, win3, win4, self]: # self is the main window
methods = inspect.getmembers(c, predicate=inspect.ismethod)
handlers.update(methods)
builder.connect_signals(handlers)
This will not pick up alias method names declared using #alias. For an example of how to do that, see the code for Builder.py, at def dict_from_callback_obj.
I'm only a novice but this is what I do, maybe it can inspire;-)
I instantiate the major components from a 'control' and pass the builder object so that the instantiated object can make use of any of the builder objects (mainwindow in example) or add to the builder (aboutDialog example). I also pass a dictionary (dic) where each component adds "signals" to it.
Then the 'connect_signals(dic)' is executed.
Of course I need to do some manual signal connecting when I need to pass user arguments to the callback method, but those are few.
#modules.control.py
class Control:
def __init__(self):
# Load the builder obj
guibuilder = gtk.Builder()
guibuilder.add_from_file("gui/mainwindow.ui")
# Create a dictionnary to store signal from loaded components
dic = {}
# Instanciate the components...
aboutdialog = modules.aboutdialog.AboutDialog(guibuilder, dic)
mainwin = modules.mainwindow.MainWindow(guibuilder, dic, self)
...
guibuilder.connect_signals(dic)
del dic
#modules/aboutdialog.py
class AboutDialog:
def __init__(self, builder, dic):
dic["on_OpenAboutWindow_activate"] = self.on_OpenAboutWindow_activate
self.builder = builder
def on_OpenAboutWindow_activate(self, menu_item):
self.builder.add_from_file("gui/aboutdialog.ui")
self.aboutdialog = self.builder.get_object("aboutdialog")
self.aboutdialog.run()
self.aboutdialog.destroy()
#modules/mainwindow.py
class MainWindow:
def __init__(self, builder, dic, controller):
self.control = controller
# get gui xml and/or signals
dic["on_file_new_activate"] = self.control.newFile
dic["on_file_open_activate"] = self.control.openFile
dic["on_file_save_activate"] = self.control.saveFile
dic["on_file_close_activate"] = self.control.closeFile
...
# get needed gui objects
self.mainWindow = builder.get_object("mainWindow")
...
Edit: alternative to auto attach signals to callbacks:
Untested code
def start_element(name, attrs):
if name == "signal":
if attrs["handler"]:
handler = attrs["handler"]
#Insert code to verify if handler is part of the collection
#we want.
self.handlerList.append(handler)
def extractSignals(uiFile)
import xml.parsers.expat
p = xml.parsers.expat.ParserCreate()
p.StartElementHandler = self.start_element
p.ParseFile(uiFile)
self.handlerList = []
extractSignals(uiFile)
for handler in handlerList:
dic[handler] = eval(''. join(["self.", handler, "_cb"]))
builder.connect_signals
({
"on_window_destroy" : gtk.main_quit,
"on_buttonQuit_clicked" : gtk.main_quit
})