theodox / mgui Goto Github PK
View Code? Open in Web Editor NEWPython module for cleaner maya GUI layout syntax
License: MIT License
Python module for cleaner maya GUI layout syntax
License: MIT License
Maya's default tabLayout management is pretty nasty: like formLayout it involves a lot of retrospective editing
What's the right way to clean it up?
right now you could do
with TabLayout("fred") as tabs:
with FormLayout("xxx") as tab1:
Button("example")
with FormLayout("yyy") as tab2:
Button("example")
tabs.tabLabels = ((tab1, "XXX"), (tab2, "YYY"))
but it would be nice if you did not have to. Could we:
with TabLayout("fred") as tabs:
with FormLayout("xxx") as tab1:
Button("example")
tabs.setname(tab1, "xxx")
other ideass?
What do people think about a Yaml-like declarative layout format for saving GUI as data? Is that a step forward or back? mGui.MenuLoader
is great for menus, since there's a lot of boilerplate in making menus. Is it worth extending as whole layouts?
HI!:
It looks like the main window is auto-named (window8, window9, etc.) when instantiated . How can I implement a single instance window? The usual way is to use window name together with deleteUI command to ensure that there is only a single instance window.
There are couple idea I am thinking about:
Maybe I miss something here, can you guys help out?
Thanks.
Would be interested to hear if people have enough familiarity with 'real' CSS to warrant using an acual, compliant CSS parser like tinyCSS instead of the homegrown CSS classes...
If we're going to keep things like the mGui.qt
going forward we'll need to transparently support post Maya 2017. I think QTpy is the likeliest way forward. Thoughts?
The default TextScrollList is just the maya one, which does not allow bulk set of items. Need to come up with a property wrapper that allows binding to a collection and under the hood deletes and rebuilds the collection without making user do that every time.
It looks like everything happening in a LayoutDialogForm is deferred, so the UI is gone before any handlers are invoked. This is true even if you explicitly replace the MayaEvents with Events, which makes no sense to me. Need to investigate further
mGui/mGui/examples/filtered_collection.py
cmds.scriptJob(lj=True)
# Error: ImportError: file <maya console> line 9: No module named ul_profile #
mGui/mGui/examples/filtered_collection.py ImportError
seems fixed by removing line 9
from ul_profile import do_cprofile
I just noticed that the CSS feature targeting keys is not working in 2.2. I think this probably broke with 2.0, since the problem is that CSS.applies
is looking for keys -- but since 2.0 the keys on an mGui control are set retroactively, not during the __init__
of any particular widget.
So this does not work:
example = CSS ('named_item', backgroundColor=(1,0,0)
with example:
with FillForm():
named_item = Button('should be red')
but this still does
example = CSS ('named_item', backgroundColor=(1,0,0)
with FillForm() as outer:
named_item = Button('should be red')
example.apply_recursive(outer)
and so does this:
example = CSS ('named_item', backgroundColor=(1,0,0)
with FillForm():
named_item = Button('should be red', css = example)
I'm not sure if its worth resurrecting this feature. It's supposed to be an analogy to the "id" selector in regular CSS. However since a CSS object can be passed directly to a constructor, user can work around it like that. alternatively we could add an explict 'selector' kwarg, but that seems like overkill. I don't make much use of the string id target feature, so I won't mind if we just remove the documentation that talks about it .
What do other people think?
right now it just says 'auto-generated', but if we copy all of the flags into help it might make it easier to work with.
It would be nice to have something akin to mGui.menuloader
for shelves, allowing good support for data-driven shelves
I really like the access to children: man.root.header.button1 is a nice, natural idiom. But I hate all the quotes involved in typing
Button("my key")
So I'm thinking about using the context manager hack in plugger to make variable style assignments instead
with FormLayout() as root:
button = Button(label = 'xx')
field = TextField()
were the context manager close-over does what is currently done by the keys
these files look almost identical, and controls
is in a wierd state. Safe to delete?
I'm dynamically generating controls under a VerticalForm. I've been trying to use the form's clear() method when I need to refresh the UI, but some of the controls aren't removed. I tried poking around in the code, and it looks to me like the culprit is the generator expression in mgui/core/init.py line 350. It looks like it's removing controls from the list as it's reading from the list, and some controls get skipped as a result.
Replacing the generator expression with a list seems to work. I'll create a pull request with that change.
By default mGui widgets are created with MayaEvent()
objects as their event delegates. Ordinarily that's nice because it minimizes damage from UI reacting across threads...
but it also means that the events for a widget created using cmds.layoutDialog
don't fire until after the dialog closes, since we never hit the idle state while the dialog is open.
The workaround it to replace the MayaEvent()
with an Event()
-- that's synchronous so it doesn't get hung up. Here's an example:
def _layout(self):
with forms.LayoutDialogForm('root') as self.root:
with forms.VerticalThreePane(None):
gui.Text( label='Please log into Perforce. This is only required once per project.')
with forms.VerticalExpandForm(None):
gui.Text( label='Enter your p4 client workspace. E.G. "bryce-class4"')
self.p4Client = gui.TextField('P4ClientWorkspace')
gui.Text( label='Enter p4 user name. E.G. "Bryce"')
self.p4User = gui.TextField('P4UserName')
gui.Text( label='Enter p4 password.')
self.p4Password = gui.TextField('P4Password')
gui.Text( label='Enter p4 port.')
self.p4Port = gui.TextField('p4port', text='p4proxy.undeadlabs.net:1667')
login_button = gui.Button('login', label='Login')
login_button.command = Event()
login_button.command += self.attempt_p4_login
where the login_button
is post-edited to switch types. That's an annoying piece of trivia for users to remembr
This is probably an edge case, and may not affect everyone, but it is worth fixing in my opinion.
The mel variable $tmp is being used to get $gMainProgressBar. Since $tmp is a common variable for getting global variables from MEL to Python, this could cause variable type clashes, causing the Main Progress Bar code to fail if that variable was already declared in some other tool/code.
I've been toying with an idea for getting rid of dead ref errors without needing users to make classes.
Basically it looks like this:
with BindingWindow(title = 'example window') as test_window:
bound = ObservableCollection("A", "B")
with VerticalForm() as main:
Text(label = "The following items don't have vertex colors")
list_view = VerticalList()
list_view.bind.collection < bind() < bound
with HorizontalStretchForm('buttons'):
refresh = Button('refresh', l='Refresh')
close = Button('close', l='Close')
def close_window(*_, **__):
cmds.deleteUI(test_window)
def refresh_window(*_, **__):
list_view.redraw()
test_window.refresh = refresh_window
test_window.close = close_window
refresh.command += test_window.refresh
close.command += test_window.close
by making test_window.refresh
and test_window.close
members of the object test_window
, they don't fall out of scope but they don't need to be regular instance methods with self
. I was thinking we could extend the handler syntax so it automatically did this for the user, so maybe:
refresh.command *= refresh_window
would add the static function refresh_window
as a member variable (on refresh
maybe instead of on the window), keeping it alive as long as the widget lived.
Thoughts?
Maya's text field does not send a notification on content changes - only for enter/return key or when the user changes focus. This means that callbacks on changes to text content don't get fired if the user does not tab/return out of the field. So, if you have a button that closes your window, and are relying on bindings to get your text fields, you will never be notified that the text in a text field has changed if you ciick the window close button or another piece of UI that tries to close the window.
AFAIK the only way around this would be to use PySide to grab the underlying QT widget and manually hook the appropriate event (the QT widget should have more events than the Maya wrapper around it does).
Thoughts? Suggestions?
in a couple of places the current code uses this idiom to work with existing controls:
@classmethod
def from_existing(cls, widget):
try:
_cache = cls.CMD
def fake_CMD(*args, **kwargs):
return widget
cls.CMD = fake_CMD
return cls(widget, widget)
finally:
cls.CMD = _cache
This seems to work, but I'm nervous about it. It won't be thread safe (not a major concern - the gui stuff is thread hell no matter what) and it will need extra work for things like parsing existing menus with submenus....
The current style is pep 8 with the exception of field names, which are captialised. Nowadays this bugs me. Fixing is a pretty major refactor, however, since it touches pretty much all existing code.
Who cares? Sound off!
We currently support Maya's limited internal drag-and-drop callbacks. however these don't work for drags from outside of Maya -- you can't for example drop a folder from Explorer onto a button and get a callback to fire.
I'm ambivalent about supporting this, because it's another QT-only feature. how complicated would it be to implement, assuming we decided to do it?
Noticed we haven't yet wrapped cmds.optionMenuGrp
, I was going to do an initial pass on it, but noticed that all of our MenuGrp
related classes inherit from Labeled
, which is over in controls
.
Would it make sense to move Labeled
over to core? Or should I just import it from controls
?
I'm getting this error when trying to load a shelf:
# AttributeError: 'MenuItem' object has no attribute 'itemArray' //
It looks like the problem might be with the change to a self.proxy.wrap
call in the previous line. Reverting that to gui.PopupMenu.wrap
seems to fix it.
because the event handlers need a live reference to something they will die if you create a window in a script and it falls out of scope:
class XXX(Window):
def__init__(key, *args, **kwargs):
# etc
def show(self):
self.Window.show()
def test():
XXX().show(): # window appears but handlers fail, since the XXX we make
# here falls out of scope immediately
You can work around this easily:
class XXX(Window):
ALIVE = []
def__init__(key, _args, *_kwargs):
# etc
def show(self):
self.Window.show()
self.ALIVE.append(self) # new instances stay in scope forever
but this keeps all instance around forever, which is equally annoying.
What to do?
def _set_stylesheet(self, css):
if not isinstance(css, CSS):
css = CSS(**css) # <<-- Lacks a target argument
self._style = css
css.apply_recursive(self)
Not sure what an appropriate substitute key would be here, though maybe self.key
is workable?
Or we could raise TypeError
if not passed a CSS
instance.
This currently breaks BindingWindow when operating with the key-less idiom, but would apply to anything else that might override Nested.__exit__
.
import mGui.gui as mg
import mGui.forms as mf
with mg.BindingWindow() as win:
with mf.VerticalExpandForm() as main:
btn = mg.Button()
win.show()
win.main.btn.label = 'I fail here'
# AttributeError: 'BindingWindow'' has no attribute 'main' #
So in the above example when win's BindingWindow.__exit__
method is called, it then calls into Nested.__exit__
, from there we grab the parent frame, which turns out to be BindingWindow.__exit__
, and not the actual context scope we were hoping for.
Original report here:
am trying to run mGui.menu_loader with a yaml file, copying the example you show in your post,
and yet I can't.
I have the error
Error: AttributeError: file D:\packages\mayaBase\mGui\menu_loader.py line 173: 'str' object has no attribute 'instantiate'
Maybe I forgot something ...
I have the latest Pyyaml version installed (3.12)
The disqus link for the blog post this was attached to is busted, so creating this for tracking.
There seems to be no VerticalListForm as per your code in the readme.
import mGui.gui
import mGui.observable as obs
from mGui.bindings import bind, BindingContext
bound = obs.ViewCollection(pm.PyNode("pCube1"), pm.PyNode("pPlane2"))
with gui.Window('window', title = 'fred') as example_window:
with BindingContext() as bind_ctx:
with gui.VerticalForm('main') as main:
Text(None, label = "The following items don't have vertex colors")
VerticalListForm('lister' ).Collection < bind() < bound
with HorizontalStretchForm('buttons'):
Button('refresh', l='Refresh')
Button('close', l='Close')
example_window.show()
In the threaded branch, it appears safe to, say, asynchronously update an observableCollection and have it update the UI - however making all the property accessors use EvalDeferredInMainThreadWithResult means that you get long hangs and some dependent layout items don't work properly because, I suspect, the layout items don't have sizes yet.
It would be really nice not to have to think about this at the user level at all. How to get there?
I noticed and fixed an issue with mGui 2.2 and popup menus . The new and improved menu syntax had the side effect of making popups not get added to their owning widgets -- which made them get garbage collected, so any references to themselves (like kwargs['sender']
) would result in dead reference errors.
I've fixed it already in e8378c0 but if you run into that you may need to catch up on that commit
it seems impossible to trigger a del on a class derived from Window, at least if there are events attached to it. Somebody other than event handler is maintaining a link to the class and thus even del(self) does not remove it from memory. This should be cleaned up ASAP
testing example files:
executing
\mGui\examples\simple.py
produces
# Error: AttributeError: file line 46: 'module' object has no attribute 'HorizontalStretchForm' #
It seems mGui.Forms is not being imported. There are a few other forms objects referenced in the code.
We track names for path access. We should also track allow some method to generate variables on the top level control of a group that tells parent controls to collect it. Here's the idiom i have in mind:
with Window('root') as w:
with FillForm('ignoreme'):
with HeaderForm("ignoremetoo")
Button ("@important")
w.important.command += do_something
but we'd keep existing behavior so w.important
would be the same as
w.ignoreme.ignoremetoo.important
It would be good to have a simple, generic method to bind a CSS style to a value so that the value could be updated using bindings without extra imperative code.
Maybe make the a style_binding property that applied an incoming value?
Right now Events are created on controls when they are first attached. If you want to parameterize them you need to get them from the Callbacks dictionary:
with gui.Window('main') as example:
with gui.VerticalForm('form'):
for item in cmds.ls(type='transform'):
b = gui.Button('b_' + item, label = item)
b.command += move_down
b.Callbacks['command'].Data['target'] = item
It would be great to be able to explicitly create a paramterized event:
with gui.Window('main') as example:
with gui.VerticalForm('form'):
for item in cmds.ls(type='transform'):
b = gui.Button('b_' + item, label = item)
b.command = MayaEvent(target = item)
but this won't work with the current setup. How to fix...?
How do we want to handle version specific codes?
I was looking at adding a wrapper from cmds.workspaceControl
and it's related friends, but they only exist in 2017+ so we'd need some way to signify that constraint.
Easiest option would be inside an if MAYA_VERSION >= 2017
style block.
Though a separate module could also work.
Currently submenu creation is very clunky. Is there a way to leverage nesting or something similar in the Yaml to make it more natural?
would be nice to have LayoutDialog
class to go with LayoutDialogForm
and make modal dialogs a bit less wonky
TODO: have to write better examples of how lists.Templated
and onWidgetCreated
handle event forwarding from lists. The documentation there is correct as far as it goes -- but far from detailed enough for a N00b
Text("key").bind.label < bind() < (something, attr)
is concise but still feels wierd. Maybe
Text("key").attr.label < bind() < (something, attr)
Do you think we should replace all print statements of tracebacks and user feedback to use Python's logging API?
Hi, I have a problem with TreeView control.
I want to connect a function to the TreeView buttons like you do with the normal Button.
My sample code:
treeViewTest = gui.TreeView('Viewer', numberOfButtons=2)
treeViewTest .addItem = ('Work1', '')
treeViewTest .pressCommand += self.buttonAction
def buttonAction(self, _args, *_kwargs):
template = kwargs['sender']
print template
ERROR MESSAGE:
TypeError: Invalid arguments for flag 'pressCommand'. Expected ( int, string or function ), got MayaEvent
But if I do like this:
treeViewTest .pressCommand =[(1,self.buttonAction),(2,self.buttonAction),(3,self.buttonAction)]
It does work but I cant get the sender. Which button I clicked on etc...
Only the arguments the controller sends.
This is the example when you connect with button controller and that works:
buttonTest = gui.Button('Reset to Default')
buttonTest .command += self.buttonAction
Probably I write something totally stupid wrong, so please teach me why this do not work.
And how you would solve this situation.
Best regards, Mattias Eriksson.
CallbackProxy instances created via the get_weak_reference factory method don't have the correct name attribute of their internal function object.
Traceback (most recent call last):
File "C:\ULDev\source\ulMaya\external\mGui\mGui\menu_loader.py", line 165, in instantiate
new_item.command += cp
File "C:\ULDev\source\ulMaya\external\mGui\mGui\events.py", line 87, in _add_handler
self._Handlers.add(get_weak_reference(handler))
File "C:\ULDev\source\ulMaya\external\mGui\mGui\events.py", line 237, in get_weak_reference
return WeakMethodFree(f)
File "C:\ULDev\source\ulMaya\external\mGui\mGui\events.py", line 213, in __init__
self._ref_name = f.__name__
AttributeError: 'CallbackProxy' object has no attribute '__name__'
so,,, which of these is the right one? Looks like a merge issue creating two versions of the same idea
@classmethod
def wrap(cls, control_name, key=None):
def _spoof_create(*_, **__):
return control_name
try:
cache_CMD = cls.CMD
cls.CMD = _spoof_create
return cls(key=control_name)
finally:
cls.CMD = cache_CMD
@classmethod
def from_existing(cls, key, widget):
"""
Create an instance of <cls> from an existing widgets
"""
def fake_init(self, *_, **__):
return widget
_cmd = cls.CMD
try:
cls.CMD = fake_init
return cls(key)
finally:
cls.CMD = _cmd
```
This is just a notification: I reworked the way lists work, and they appear to be way, way faster. It turned out that QT really did not like lots of edits on child controls: just deleting containers and rebuilding them was far easier.
It would be great to have a way of verifying between builds that mGui is visually consistent. Perhaps doing a pixel diff on screenshot dumps or something like that? Howe would this work?
Is it worth looking into packaging mGui for the maya app store? What's involved?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.