I am trying to show a wx.TipWindow at the mouse location when the scroll wheel changes a RadioBox item (I want the newly selected item to show at the mouse).
Here is simply what I have:
wx.TipWindow(self, self.rdb.GetString(nxt)) <- Works perfectly. Self is an instance of wx.Frame
However, if the above line gets called more than once, (ie, I scroll more than once ~5 times) it constantly makes new windows on top of one another. Eventually, the program crashes with this error:
Traceback (most recent call last):
File "C:\Users\Seth\anaconda3\envs\DLC-GPU\lib\site-packages\matplotlib\backends\backend_wx.py", line 989, in _onMouseWheel
x = evt.GetX()
AttributeError: 'MouseEvent' object has no attribute 'GetX'
Can I not get this to work? I tried closing and destroying the TipWindow before showing the next one, but same error.
I have thought "why not make my own" but sheesh thats annoying lol.
Code block where wx.TipWindow is shown:
def MouseWheel(self, event):
assert isinstance(event, wx.MouseEvent)
whlRot = event.GetWheelRotation()
if whlRot < 0:
nxt = self.rdb.GetSelection() + 1
if nxt < self.rdb.GetCount():
self.rdb.SetSelection(nxt) # Sets the next object in the RadioBox
wx.TipWindow(self, self.rdb.GetString(nxt))
if whlRot > 0:
prv = self.rdb.GetSelection() - 1
if prv >= 0:
self.rdb.SetSelection(prv) # Sets the previous object in the RadioBox
wx.TipWindow(self, self.rdb.GetString(prv))
As you say "...scroll wheel changes RadioBox item..." but that is not a native function of a radiobox, it's something that you have engineered via code that we cannot see.
I assume that it looks something like this:
import wx
class RadioBoxFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Radio Box Frame', size=(400, 400))
panel = wx.Panel(self, -1, pos=(10,10), size=(300,300), style=wx.BORDER_RAISED)
sampleList = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']
self.option=wx.RadioBox(panel, -1, "Select an option", pos=(10, 10), size=wx.DefaultSize, choices=sampleList, majorDimension=3, style=wx.RA_SPECIFY_COLS)
self.Bind(wx.EVT_RADIOBOX, self.getOption)
panel.Bind(wx.EVT_MOUSEWHEEL, self.getWheelOption)
for i in range(len(sampleList)):
self.option.SetItemToolTip(i, "Option "+sampleList[i])
self.CreateStatusBar()
self.SetStatusText("Status area")
self.Show()
def getWheelOption(self, event):
assert isinstance(event, wx.MouseEvent)
whlRot = event.GetWheelRotation()
if whlRot < 0:
nxt = self.option.GetSelection() + 1
else:
nxt = self.option.GetSelection() - 1
if nxt > self.option.GetCount() - 1:
nxt = 0
if nxt < 0:
nxt = self.option.GetCount() - 1
self.option.SetSelection(nxt) # Sets the next object in the RadioBox
txt = "Option " + self.option.GetString(nxt) + " Selected"
tw = wx.TipWindow(self, txt)
tw.SetBoundingRect(wx.Rect(1,1,1,1)) #restrict action required to cancel tipwindow
self.SetStatusText(txt)
def getOption(self, event):
state1 = self.option.GetSelection()
txt = self.option.GetString(state1)
self.SetStatusText(txt+" is Selected")
if __name__ == '__main__':
app = wx.App()
RadioBoxFrame()
app.MainLoop()
Which produces the following:
Play with this code a bit and see you you are still getting the same error, because using wxPython 4.1.1 on Linux, the code above doesn't give an error. You may need to look at your environment if the above code fails.
Finally found an answer to this that does not require updating wxPython (as this broke other things in the code that isn't mine).
Here is what I ended up doing:
if self.popUp is None:
self.popUp = wx.TipWindow(self, wht, maxLength=1000000)
else:
try: # Try is needed for if the TipWindow is closed by clicking elsewhere
self.popUp.Show(show=False) # This was the key line. For some reason, hiding the window was required before destroying
self.popUp.Destroy()
except:
pass
self.popUp = wx.TipWindow(self, wht, maxLength=1000000)
This works perfectly and is what I want for the results.
I noticed different TipWindow behavior on Windows between wxPython 4.1.0 and 4.1.1. In the 4.1.1 version, the TipWIndow is not be destroyed when moving out of a BoundingRect. Similar issues as described above but with the difference that I experience this only when upgrading to 4.1.1. See snippet below to reproduce this behavior + picture of effect in 4.1.1. Anybody had similar experience and know what the fix is?
import wx
class RadioBoxFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1)
panel = wx.Panel(self, -1, pos=(10, 10))
sampleList = ['Zero', 'One', 'Two']
self.option = wx.RadioBox(panel, -1, "Select an option", pos=(10, 10),
size=wx.DefaultSize, choices=sampleList,
majorDimension=3)
self.Bind(wx.EVT_RADIOBOX, self.getOption)
panel.Bind(wx.EVT_MOUSEWHEEL, self.getWheelOption)
self.Show()
def getWheelOption(self, event):
whlRot = event.GetWheelRotation()
if whlRot < 0:
nxt = self.option.GetSelection() + 1
else:
nxt = self.option.GetSelection() - 1
if nxt > self.option.GetCount() - 1:
nxt = 0
if nxt < 0:
nxt = self.option.GetCount() - 1
self.option.SetSelection(nxt)
txt = "Option " + self.option.GetString(nxt) + " Selected"
tw = wx.TipWindow(self, txt)
tw.SetBoundingRect(wx.Rect(1, 1, 1, 1))
def getOption(self, event):
state1 = self.option.GetSelection()
if __name__ == '__main__':
app = wx.App()
RadioBoxFrame()
app.MainLoop()
I am doing a rigging tool and I want to assign a different color to the controllers for right and left using a slider. the idea is to select en item from the menu and get the color for that. but for now it is working just the first item and none the others.
My code is inside a class, at this point I have the window, the function to select a joint and a function to change the color. I am querying the selection for the option menu. This information is use by the three conditionals to set a color for each menu item. However, it works just the first option and none the other two. I tried to do the same code but without a class just defining the function and it works. What can I be doing wrong inside the class? I am kind of new in python and this is my first time doing a tool for ringing.
import maya.cmds as cmds
import maya.OpenMaya as om
import maya.mel as mel
import sys
import math
class rigCreator:
def __init__(self, *args):
#self.startFunction()
self.window = "uiWindow"
self.title = "Rigging Tool Bipeds"
self.winSize = (150, 200)
self.createUI()
def createUI(self, *args):
#check if window and prefs exist. If yes, delete
if cmds.window(self.window, ex=True):
cmds.deleteUI(self.window, wnd=True)
elif cmds.windowPref(self.window, ex=True):
cmds.windowPref(self.window, r=True)
self.window = cmds.window(self.window, t=self.title, wh = self.winSize, s=1, mnb=1, mxb=1)
self.mainForm = cmds.formLayout(nd=100)
self.tagLine= cmds.text(label = "Rig Tool")
cmds.frameLayout(label="1. Choose the root joint")
self.Layout = cmds.columnLayout(adj=1)
#Add a saparator to the window
cmds.separator()
# button to select the first joint
cmds.rowColumnLayout (nc = 2, cs=[(1,6), (2,6)])
rootBt = cmds.textField ('rootJnt', tx = 'First joint of your Arm chain', w = 250)
#cmds.textFieldButtonGrp('rootJnt', width=380, cal=(8, "center"), cw3=(100, 200, 75), h=40, pht="First joint of your Arm chain", l="First Joint", bl="Select", bc = lambda x: findJnt(), p=self.Layout)
cmds.button(l = 'Select', c = lambda x:self.findJnt())
cmds.setParent(self.Layout)
frame = cmds.frameLayout("2. Name options", lv=1, li=1, w=250)
#cmds.text(label="", align = "left")
cmds.rowColumnLayout(nc=4, cs=[(1,6), (2,6), (3,6), (4,6)])
#cmds.text('Side', l='Side:')
cmds.optionMenu('Part_Side', l='Side:', cc=lambda x:self.colorChange(), acc=1, ni=1)
cmds.menuItem(label='L_')
cmds.menuItem(label='R_')
cmds.menuItem(label='_')
cmds.text('Part', l='Part:')
cmds.optionMenu('part_Body')
cmds.menuItem(label='Arm')
cmds.menuItem(label='Leg')
cmds.menuItem(label='Spine')
cmds.setParent(self.Layout)
frame2 = cmds.frameLayout("3. Type of rig", lv=True, li=1, w=250)
cmds.rowColumnLayout(nc=3, cs=[(1,6), (2,6), (3,6)])
cmds.radioCollection("limb side")
cmds.radioButton(label='IK/FK', select=True)
cmds.radioButton(label='IK')
cmds.radioButton(label='FK')
cmds.setParent(self.Layout)
frame3 = cmds.frameLayout("4. Thick if you want to apply stretch", lv=True, li=1, w=250)
cmds.rowColumnLayout(nc=1, cs=[(1,6)])
cmds.checkBox( label='Stretchy limb', align='center' )
cmds.setParent(self.Layout)
cmds.setParent(self.Layout)
frame4 = cmds.frameLayout("5. Pick icon color", lv=True, li=1, w=250)
cmds.gridLayout(nr=1, nc=5, cwh=[62,20])
cmds.iconTextButton('darkBlue_Btn', bgc=[.000,.016,.373])
cmds.iconTextButton('lightBlue_Btn', bgc=[0,0,1])
cmds.iconTextButton('Brown_Btn', bgc=[.537,.278,.2])
cmds.iconTextButton('red_Btn', bgc=[1,0,0])
cmds.iconTextButton('Yellow_Btn', bgc=[1,1,0])
cmds.setParent(self.Layout)
colorBt = cmds.colorIndexSliderGrp('rigColor', w=250, h=50, cw2=(150,0), min=0, max=31, v=7)
#This button will creat the chain of joins with streatch and squach
cmds.button('b_create', label='Create', h=30, c='create()')
#show the window
cmds.showWindow(self.window)
def findJnt(self, *args):
self.root = cmds.ls(sl=True)
if len(self.root) == 1:
selRoot = self.root[0]
rootBt = cmds.textField ('rootJnt', e=1, tx=selRoot)
else:
cmds.warning ('Please select only the first joint!')
def colorChange(self, *args):
self.limbSide = cmds.optionMenu('Part_Side', q=1, sl=1)
if self.limbSide ==1:
self.sideColor = 7
if self.limbSide == 2:
self.sideColor = 14
if self.limbSide ==3:
self.sideColor = 18
colorBt = cmds.colorIndexSliderGrp('rigColor', e=1, v=self.sideColor)
def partBody(self, *args):
pass
def rigType(self, *args):
pass
rigCreator()
I did just clean the lambda and comma function and it was working as expected for me.
Because you don't seem to be familiar with maya ui/class, I've cleaned your whole script with some line comments beginning with DW :
So you can find how to manage maya ui more easily !
Don't hesitate to look at partial, I've lots of posts with some tips, hope it helps :
import maya.cmds as cmds
import maya.OpenMaya as om
import maya.mel as mel
import sys
import math
from functools import partial
def create(rigType, sideColor, isStretch, *args):
# DW : refreshed every time by property that is executing before being passed
if rigType == 0:
print('IK/FK mode')
elif rigType == 1:
print('IK mode')
elif rigType == 2:
print('FK mode')
# DW : in this state of the script, it will return always 7 because there is no refresh query
# so you can see to pass outside your class some arguments
# below an example for an alternative of property
print('color picked is {}'.format(sideColor))
# DW : in this example the function for getting the status is passed
# it is different from property because we still use to execute the command
if isStretch():
print('rig is stretchy')
else:
print('no stretch')
# Dw : note that if you create an instance of your window inside a variable :
# ui = rigCreator()
# ui.ikfkPick is something you can print at any moment to debug
# any variable with 'self' would be replaced by 'ui' in this case
class rigCreator:
# DW : default colour, self don't need to be specify but it is like so
# personally I put the important parameters or parameters that set default values on top
# it is easier when parsing the code few month later
sideColor = 7 #: DW : don't hesitate to do inline comment to say this is RED color (im followng google doctstring https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)
isStretch = 0
ikfkPick = 0 #: pick IK/FK option, 1 for IK, 2 for FK
def __init__(self, *args):
#self.startFunction()
self.window = "uiWindow"
self.title = "Rigging Tool Bipeds"
self.winSize = (150, 200)
self.createUI()
def createUI(self):
# DW : only put *args if you are using maya command flags, otherwise no need
#check if window and prefs exist. If yes, delete
if cmds.window(self.window, ex=True):
cmds.deleteUI(self.window, wnd=True)
elif cmds.windowPref(self.window, ex=True):
cmds.windowPref(self.window, r=True)
self.window = cmds.window(self.window, t=self.title, wh = self.winSize, s=1, mnb=1, mxb=1)
self.mainForm = cmds.formLayout(nd=100)
self.tagLine= cmds.text(label = "Rig Tool")
cmds.frameLayout(label="1. Choose the root joint")
self.Layout = cmds.columnLayout(adj=1)
#Add a saparator to the window
cmds.separator()
# button to select the first joint
cmds.rowColumnLayout (nc = 2, cs=[(1,6), (2,6)])
self.tf_rootBt = cmds.textField ('rootJnt', tx = 'First joint of your Arm chain', w = 250)
#cmds.textFieldButtonGrp('rootJnt', width=380, cal=(8, "center"), cw3=(100, 200, 75), h=40, pht="First joint of your Arm chain", l="First Joint", bl="Select", bc = lambda x: findJnt(), p=self.Layout)
# DW : use lambda only to parse arguments, the better way to not make confusion is to use partial module
cmds.button(l = 'Select', c = self.findJnt)
cmds.setParent(self.Layout)
frame = cmds.frameLayout("2. Name options", lv=1, li=1, w=250)
#cmds.text(label="", align = "left")
cmds.rowColumnLayout(nc=4, cs=[(1,6), (2,6), (3,6), (4,6)])
#cmds.text('Side', l='Side:')
# DW : string naming can be dangerous, if another script create this type of name, it will conflict
# put your ctrl name into a variable or a class variable if you are using it somewhere else
# Same don't use lambda if you are not parsing arguments
self.om_partside = cmds.optionMenu('Part_Side', l='Side:', cc=self.colorChange, acc=1, ni=1)
cmds.menuItem(label='L_')
cmds.menuItem(label='R_')
cmds.menuItem(label='_')
cmds.text('Part', l='Part:')
cmds.optionMenu('part_Body')
cmds.menuItem(label='Arm')
cmds.menuItem(label='Leg')
cmds.menuItem(label='Spine')
cmds.setParent(self.Layout)
frame2 = cmds.frameLayout("3. Type of rig", lv=True, li=1, w=250)
cmds.rowColumnLayout(nc=3, cs=[(1,6), (2,6), (3,6)])
# DW :conforming to the default user settings on top of the class
# demonstrate property in class
self.rc_ikfk = cmds.radioCollection("limb side")
for x, lb in enumerate(['IK/FK', 'IK', 'FK']):
if x == self.ikfkPick:
defvalue = True
else:
defvalue = False
cmds.radioButton(label=lb, select=defvalue)
cmds.setParent(self.Layout)
frame3 = cmds.frameLayout("4. Thick if you want to apply stretch", lv=True, li=1, w=250)
cmds.rowColumnLayout(nc=1, cs=[(1,6)])
# DW : adding default value, class variable naming
self.ckb_stretch = cmds.checkBox( label='Stretchy limb', align='center', value=self.isStretch, cc=self.getStretch)
cmds.setParent(self.Layout)
cmds.setParent(self.Layout)
frame4 = cmds.frameLayout("5. Pick icon color", lv=True, li=1, w=250)
cmds.gridLayout(nr=1, nc=5, cwh=[62,20])
cmds.iconTextButton('darkBlue_Btn', bgc=[.000,.016,.373])
cmds.iconTextButton('lightBlue_Btn', bgc=[0,0,1])
cmds.iconTextButton('Brown_Btn', bgc=[.537,.278,.2])
cmds.iconTextButton('red_Btn', bgc=[1,0,0])
cmds.iconTextButton('Yellow_Btn', bgc=[1,1,0])
cmds.setParent(self.Layout)
self.sl_colorBt = cmds.colorIndexSliderGrp('rigColor', w=250, h=50, cw2=(150,0), min=0, max=31, v= self.sideColor)
#This button will creat the chain of joins with streatch and squach
# DW : never use comma for executing some function, if you use this script as a module afterward, you will have problems dealing with python namespacing
# maya is always passing a default argument True, so put *args in any command that is used by your ui controls
cmds.button('b_create', label='Create', h=30, c=partial(create, self.rigType, self.sideColor, self.getStretch))
#show the window
cmds.showWindow(self.window)
def findJnt(self, *args):
self.root = cmds.ls(sl=True, type='joint', l=True)
# DW : just becaue I was toying around the concept of root, sorry... ignor below
rootAbove = [i for i in self.root[0].split('|')[1:] if cmds.nodeType(i) == 'joint']
if rootAbove[0] != self.root[0].split('|')[-1]:
cmds.warning('you didn\'t choose the top joint !')
if len(self.root) == 1:
selRoot = self.root[0]
# DW : you dont have to store your edit, use class variable instead of string name
cmds.textField (self.tf_rootBt, e=1, tx=selRoot.split('|')[-1])
else:
cmds.warning ('Please select only the first joint!')
def colorChange(self, *args):
# DW : you don't need to put self on limbSide has you are not using it anywhere else
limbSide = cmds.optionMenu(self.om_partside, q=1, sl=1)
# DW : putting some elif
if limbSide == 1:
self.sideColor = 7
elif limbSide == 2:
self.sideColor = 14
elif limbSide ==3:
self.sideColor = 18
# DW : conforming editing controls
cmds.colorIndexSliderGrp(self.sl_colorBt, e=1, v=self.sideColor)
def partBody(self, *args):
pass
#property
def rigType(self):
'''Returns:
int: option selected in ui'''
# DW : demonstrating property usage and partial
rb = cmds.radioCollection(self.rc_ikfk, q=True, select=True)
cia = [i.split('|')[-1] for i in cmds.radioCollection(self.rc_ikfk, q=True, cia=True)]
return cia.index(rb)
def getStretch(self, *args):
"""function to get if the user need stretchy things
Note:
*args is just here to bypass maya default added True argument
Returns:
bool: use stretch or not
"""
# dw making some example function with some docstring
self.isStretch = cmds.checkBox(self.ckb_stretch, q=True, value=True)
return self.isStretch
rigCreator()
I am trying to code a menu routine for an alarm-clock in python, which displays the relevant information on a 7-segment display according to some inputs via pushbuttons.
I managed to create a loop which displays the current time, and whe the button "debMenu" is clicked 3 menu options are displayed. This works well only for the first time untill the 3rd. menu option is reached. When I push the button again the routine does not work - so the function "main_menu" is not called again.
What I am doing wrong...? Thanks !!
stop_loop = [False]
def main_menu(stop_loop, n=[0]):
stop_loop[0] = True
debSelect = DebouncedSwitch(butSelect, cbButSelect, "butSelect")
menuList = ['HO ', 'AL ', 'UP ']
if n[0] < 3:
display.scroll(menuList[n[0]], 200) #display menu on 7-seg display
display.show(menuList[n[0]])
n[0] += 1
elif n[0] == 3:
n=[0]
stop_loop[0] = False
main()
def main():
stop_loop[0] = False
debMenu = DebouncedSwitch(butMenu, main_menu, stop_loop)
while not stop_loop[0]: # display current time on 7-seg display
curTime = rtc.datetime()
display.numbers(curTime.hour, curTime.minute, False)
time.sleep_ms(500)
display.numbers(curTime.hour, curTime.minute)
time.sleep_ms(500)
main()
I guess the default value mutable variable is the one killing you. Check here: http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments.
I don't even understand why you need a list variable in this case, why not just n=0 without having to use a list?.
maybe make a new global variable or class to encapsulate both state-related variables (stop_loop and n). Like:
loop = dict(stop=False, n=0)
now you pass this instead of stop_loop to main_menu.
Or use a class that encapsulates a circular array for menu like:
class LoopMenu:
""" circular array menu list with pause/continue/next"""
def __init__(self, menu):
self.menu = menu
self.stop = False
self.n = 0
def next(self):
menu = self.menu[self.n]
self.n += 1
if self.n > len(self.menu):
self.n = 0
return menu
def pause(self):
self.stop = True
def play(self):
self.stop = False
then you main_menu method could be:
my_menu_loop = LoopMenu(['HO ', 'AL ', 'UP '])
def main_menu(menu_loop):
menu_loop.pause()
debSelect = DebouncedSwitch(butSelect, cbButSelect, "butSelect")
menu = menu_loop.next()
display.scroll(menu, 200) #display menu on 7-seg display
display.show(menu)
main()
def main():
my_menu_loop.play()
debMenu = DebouncedSwitch(butMenu, main_menu, my_menu_loop)
while not loop_menu.stop: # display current time on 7-seg display
curTime = rtc.datetime()
display.numbers(curTime.hour, curTime.minute, False)
time.sleep_ms(500)
display.numbers(curTime.hour, curTime.minute)
time.sleep_ms(500)
main()