Maya Python UI appear in Attribute Editor - python

It's my first time being here and I've been struggling with python coding dealing with figuring it out how to update stuff per action or mouse event.
Lately, whenever I tried to test out my script, I often see some of the buttons and layout panels in the Attribute Editor, when it's suppose to be in the window I've created. how can I make that stop?
I don't think I could post the code in here since it's about 1000 code long, but how can I find a way to prevent something like that? Is it because I used too much setParent('..') function?

If your buttons etc are appearing in the wrong layout, it is probably because you're calling UI commands after some other function has reset the existing parent.
If you want to be sure your controls are going to the right place you'll need to store the names of any windows/layouts/panels you've created and explicitly set them to be the parent before you start making widgets. Otherwise the parenting is basically 'whatever got created last'. You can verify that by something like this:
# make a button out of context
import maya.cmds as cmds
xxx = cmds.button('boo')
# ask the parent of what we just made....
print cmds.control(xxx, q=True, p=True)
## Result: u'MayaWindow|MainAttributeEditorLayout|formLayout2|AEmenuBarLayout|AErootLayout|AEselectAndCloseButtonLayout' #
Parentage will be switched if you create a top level container (a window or panel):
w = cmds.window()
c = cmds.columnLayout()
b = cmds.button("bar")
# ask b's parent....
print cmds.control(b, q=True, p=True)
## Result: window3|columnLayout49 #
You can also switch parents explicitly:
def make_a_layout(window_name):
w = cmds.window(window_name)
c = cmds.columnLayout()
return c
layout_a = make_a_layout('window_a')
# any future widgets go into this layout...
print cmds.button("layout 1 a")
# window_a|columnLayout55|layout_1_a
layout_b = make_a_layout('window_b')
# now this is the active layout
print cmds.button("layout 2 a ")
# window_b|columnLayout56|layout_2_a
# explicitly set the parent to the first layout
# now new widgets will be there
cmds.setParent(layout_a)
print cmds.button("layout 1 b")
# window_a|columnLayout56|layout_1_b
As you can see, current parent is set every time a new layout is created. You can pop up a level with setParent ('..') or set it to any layout explicitly with setParent('your_layout_here').

Related

PyQt: removing custom widgets from a QListWidget

I am loading in a picture into a QGraphicsScene then when you click on the photo it will place circles in the area that is clicked. Then I am adding a custom widget that will keep a list of the points. This widget has a couple of buttons. one of them will be able to move the circle and the other will be to delete it. I am currently stuck on the delete part.
Problem
I can delete the circle just fine and I can delete the widget from the list. The problem is there is still a space in the list from where the widget once was and since I'm using the button from the widget and not selecting the item I'm not sure how to delete that spot. Also if I delete a bunch and then try and add some Python its self will crash and I have no idea why.
I'm not sure if what I want can be done since there really is no reference or if I'm better off moving the buttons to the main window and removing them from the custom widget. If it is possible I would like to keep it the way I have it.
There are a few files so I will put it on GitHub so I am not to take up a ton of space. Any help is much appreciated.
GitHub Link
Link to GitHub Project
The relevant code (from Main.py):
class MainWindow(QMainWindow, Ui_MainWindow):
...
def Add_History(self,pos):
self.HistoryWidget = HistoryList()
self.HistoryWidget.setObjectName("HistoryWidget_"+ str(Constents.analysisCount))
myQListWidgetItem = QListWidgetItem(self.History_List)
myQListWidgetItem.setSizeHint(self.HistoryWidget.sizeHint())
self.History_List.addItem(myQListWidgetItem)
self.History_List.setItemWidget(myQListWidgetItem, self.HistoryWidget)
self.HistoryWidget.buttonPushed.connect(self.deleteObject)
self.HistoryWidget.setXpoint(str(pos.x()))
self.HistoryWidget.setYpoint(str(pos.y()))
self.HistoryWidget.setHistoryName("Point "+ str(Constents.analysisCount))
Constents.analysisCount = Constents.analysisCount + 1
def deleteObject(self):
sender = self.sender() #var of object that sent the request
row_number = sender.objectName().split("_")
number = row_number[1]
x,y = Constents.analysisDetails[str(number)]# getting xy for object
self.loadPicture.findAndRemoveAnalysisPoint(x,y) #calling the class with the scense to delete it
Constents.analysisDetails.pop(str(number)) # get rid of the object in the variables
HistoryWidget = self.findChildren(HistoryList, "HistoryWidget_"+number)[0] #find the actual object
HistoryWidget.deleteLater() #delete that object
#Simport pdb; pdb.set_trace()
#self.History_List.takeItem(HistoryWidget)
Pictures
You must delete both the item-widget and the item itself. To do that, a method is required for getting an item from an item-widget (or one of its child widgets). A clean way to do this is to use the list-widget's itemAt method, which can get an item from a point on the screen. The major benefit of doing it this way is that it does not require knowledge of the item's index, which can of course change when other items are deleted. It also means the item-widgets don't need to know anything about the specific list-widget items they are associated with.
Here is a re-write of your deleteObject method, which implements that:
def deleteObject(self):
sender = self.sender() #var of object that sent the request
row_number = sender.objectName().split("_")
number = row_number[1]
x,y = Constents.analysisDetails[str(number)]# getting xy for object
self.loadPicture.findAndRemoveAnalysisPoint(x,y) #calling the class with the scense to delete it
Constents.analysisDetails.pop(str(number)) # get rid of the object in the variables
# get the viewport coords of the item-widget
pos = self.History_List.viewport().mapFromGlobal(
sender.mapToGlobal(QtCore.QPoint(1, 1)))
if not pos.isNull():
# get the item from the coords
item = self.History_List.itemAt(pos)
if item is not None:
# delete both the widget and the item
widget = self.History_List.itemWidget(item)
self.History_List.removeItemWidget(item)
self.History_List.takeItem(self.History_List.row(item))
widget.deleteLater()

How can I unbind every single binding from a Tkinter root window

So I have an app in Tkinter that has a lot of buttons in the first screen and when you press one you pass into a new "Window" (basically destroying all widgets and drawing the ones that are needed for the 'window'). There is a standard function that uses some commands to destroy every child on the root. I would like to add some code that can unbind all of the bindings that are made in the root. Bindings that are on specific widgets get destroyed but those that are bind on the root stay there and cause error.
Here's the code for destroying the widgets.
#staticmethod
def clear():
for widget in guihandle.root.winfo_children():
widget.destroy()
#staticmethod
def set(db,table):
guihandle.clear()
curW = Window(db,table)
guihandle.current_Window = curW
curW.initialize()
guihandle.windows.push(curW)
(Yes, I make the base GUI from a sqlite3 database :P)
There is nothing in Tkinter to do what you want. Your app will need to keep track of the bindings it wants to remove.
That being said, depending on just how complex your real problem is, there may be other solutions. For example, instead of binding to the root window, bind to a custom binding tag (also called a bind tag or bindtag). You will then need to add that tag to every widget to enable the bindings, and remove the tag from any existing widgets to effectively disable the bindings.
I know I am VERY late but, if you go through your code and replace every widget.bind with the function below, which was taken and modified from here
def bindWidget(widget: Widget, event, all:bool=False, func=None):
# https://stackoverflow.com/a/226141/19581763
'''Set or retrieve the binding for an event on a widget'''
try:
_ = widget.__dict__['bindings']
except KeyError:
has_binding_key = False
else:
has_binding_key = True
if not has_binding_key:
setattr(widget, 'bindings', dict())
if func:
if not all:
widget.bind(event, func)
else:
widget.bind_all(event, func)
widget.bindings[event] = func
else:
return(widget.bindings.setdefault(event, None))
Than the function will keep track of every binding for you by setting a attribute called bindings which has both, the event and the function inside of it.
For example:
label.bind('<Button-1>', label.destroy)
Will become:
bindWidget(label, '<Button-1>', func=label.destroy)
After doing that, you can write a simple function which deletes all of the bindings and widgets:
def clear(self): # Self will be your Tk() instance
"""Clears everything on the window, including bindings"""
for child in self.winfo_children():
child.destroy()
if not hasattr(self, 'bindings'):
self.bindings = {}
for event, func in self.bindings.items():
self.unbind_all(event)
self.bindings = {}
There are only 2 caveats with this approach
Time
You will have to go through your code to replace every widget.bind and if you have lots of bindings, than it will take a lot of time
Readability
bindWidget(label, '<Button-1>', func=label.destroy) is less readable and less clear than label.bind('<Button-1>', label.destroy)
Edit 8/11/2022
Alternatively, you can destroy the entire window and recreate it like this:
window.destroy()
window = Tk()
However, I am not sure if creating a whole Tcl interpreter is good.

ipywidget interactive hiding visibility

I would like to make an interactive module with ipywidgets.
So far so good but I'm stuck.
I want to hide the visibility of a certain ipywidget object dependent on a certain situation, and I want my printed text to show up above the widget and stay there.
dropdown=widgets.Dropdown(
options={'Coffee machine': 1, 'Washing machine': 2, 'Water Heater': 3, 'Heating System': 4, 'Dryer': 5, 'Oven': 6, 'Microwave': 7, 'Other':8},
value=1,
description='Apparaat:',
)
text_new=widgets.Text()
def text_field(value):
if(value==8):
display(text_new)
text_new.on_submit(handle_submit)
else:
text_new.visible(False) #Doesn't work but I want something like this
print("Today you had an increase in electricity consumption, would you like to name this device?") #This just be above the dropdown menu and be stuck
i=widgets.interactive(text_field, value=dropdown)
display(i)
What this does now:
When "Other" is checked in the dropdown menu, a text box appears where the user can type something.
However, when checking another machine, the text box stays there.
I just need a "hide" function but I can't seem to find one that works.
Also, after checking another option on the dropdown, the print dissapears, not coming back.
Had same problem so i found in
boton.layout.visibility = 'hidden'
or
check.layout.display = 'none'
they made some changes... i got if from here
Cannot create a widget whose initial state is visible=False
Given a widget:
import ipywidgets
button = ipywidgets.Button()
There are two direct ways to hide the the widget, with a notable difference.
Hide and unhide the widget without affecting overall page layout:
# Turn the widget "invisible" without affecting layout
button.layout.visibility = "hidden"
# Make the widget visible again, layout unaffected
button.layout.visibility = "visible"
Hide and unhide the widget and collapse the space that the widget took up:
# Hide widget and collapse empty space
button.layout.display = "none"
# Re-add the widget, adjusting page layout as necessary.
button.layout.display = "block"
When to use each one? As a rule of thumb, use layout.visibility so the page layout is not constantly jumping around as visibility is toggled. However, for very large widgets, consider using layout.display to avoid huge blank spaces.
For more general CSS information that applies here, see What is the difference between visibility:hidden and display:none?
In addition to the accepted answer, if you want to dynamically change the visibility of a control, you can declare the layout variable and reuse.
layout_hidden = widgets.Layout(visibility = 'hidden')
layout_visible = widgets.Layout(visibility = 'visible')
Like attach to an event:
def visible_txt(b):
text_box.layout = layout_visible
def hidden_txt(b):
text_box.layout = layout_hidden
btn_visible.on_click(visible_txt)
btn_hidden.on_click(hidden_txt)

Creating A UI Window For My Previous Script

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"

Can I exclude specific controllers from the standard tab traversal?

I've made a custom dialog that contains a series of text controls. Every text control has a couple of buttons beside them for adding specific values more conveniently. I don't want these buttons to receive focus when the user it tab traversing through the dialog, since the user, in most cases, won't need to use the buttons.
Is there any convenient way to exclude specific controllers from the standard tab traversal?
A simple way to prevent a button from being focused with the keyboard is to derive from wx.lib.buttons.GenButton or wx.lib.buttons.ThemedGenButton which are based on wx.PyControl that supports overriding of AcceptsFocusFromKeyboard():
class NoFocusButton(wx.lib.buttons.ThemedGenButton):
def __init__(self, parent, id=wx.ID_ANY, label=wx.EmptyString, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, validator=wx.DefaultValidator, name=wx.ButtonNameStr):
wx.lib.buttons.ThemedGenButton.__init__(self,parent,id,label,pos,size,style,validator,name)
def AcceptsFocusFromKeyboard(self):
return False # does not accept focus
For more complex navigation rules or controls, you could handle wx.EVT_NAVIGATION_KEY and manage the navigation yourself. To get the list of windows to navigate, you can use self.GetChildren(). The index of the the currently focused window in the wx.WindowList can be obtained through .index(mywindow).
With that information, you can navigate through the list whenever the user presses the "navigation key" and set the focus to the next applicable control, skipping those that you don't want to focus.
To make navigating through the list easier, you could create a generator:
def CycleList(thelist,index,forward):
for unused in range(len(thelist)): # cycle through the list ONCE
if forward:
index = index+1 if index+1 < len(thelist) else 0
else:
index = index-1 if index-1 >= 0 else len(thelist)-1
yield thelist[index]
In the dialog, handle wx.EVT_NAVIGATION_KEY:
self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey)
def OnNavigationKey(self,event):
children = self.GetChildren() # list of child windows
focused = self.FindFocus() # current focus
# avoid accessing elements that do not exist
if not focused or focused not in children:
event.Skip() # use default behavior
return
index = children.index(focused)
for child in CycleList(children,index,event.GetDirection()):
# default behavior:
if child.AcceptsFocusFromKeyboard():
child.SetFocus()
return
The example above emulates the default behavior: it cycles through focusable controls (skipping unfocusable controls like static texts).
You could expand the check to exclude specific controls or create a custom button class that implements AcceptsFocusFromKeyboard returning False.
NOTE: While wx.PyWindow, wx.PyPanel and wx.PyControl implement the mechanism to allow overriding of AcceptsFocusFromKeyboard, the standard wxPython controls do not.
However, handling wx.EVT_NAVIGATION_KEY and checking AcceptsFocusFromKeyboard on the python side will access the actual python object which will invoke the overridden method.
If you were using C++ this problem has a straightforward solution as described in the remainder of this answer. In wxPython it seems you cannot specialize wxWidgets classes - which seems to me a fatal snag.
You could create a specialization of the button control which will be left out of the tab traversal by overriding AcceptsFocusFromKeyboard() to return FALSE.
http://docs.wxwidgets.org/trunk/classwx_window.html#a2370bdd3ab08e7ef3c7555c6aa8301b8
The following C++ code works fine: focus jumps from the first to the third button when tab is pressed
class cNoTabButton : public wxButton
{
public:
cNoTabButton(wxWindow *parent,
wxWindowID id,
const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0 )
: wxButton(parent,id,label,pos,size,style)
{}
bool AcceptsFocusFromKeyboard() const {
return false;
}
};
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title)
{
// set the frame icon
SetIcon(wxICON(sample));
wxPanel * panel = new wxPanel(this,-1,wxPoint(0,0),wxSize(500,500));
new wxButton(panel,-1,"TabPlease",wxPoint(20,20));
new cNoTabButton(panel,-1,"NoTabThanks",wxPoint(100,20));
new wxButton(panel,-1,"OKWithMe",wxPoint(200,20));
}
This is not a perfect solution but one way to do this is actually to delay your control's getting the focus back by half a second.
Your main window will get focus back and buttons still work. I use it because I want my main windows to handle all key presses but still to contain buttons on it that are used with the mouse.
So you bind your KILL_FOCUS event in the control that is supposed to preserve the focus and you create a list of controls that cannot get the focus from it.
First a helper function to get all children:
def GetAllChildren(control):
children = []
for c in control.GetChildren():
children.append(c)
children += GetAllChildren(c)
return children
In my case I want all the children of the window to not get focus
self.not_allowed_focus = GetAllChildren(self)
self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
In my KILL FOCUS handler I ask for the focus back after half a second
def OnKillFocus(self,evt):
print "OnKillFocus"
win = evt.GetWindow()
if win in self.not_allowed_focus:
wx.CallLater(500,self.SetFocus)

Categories

Resources