Python Dynamic UI

Hi gang!
I 've been wrestling with a UI for a tool I’m doing.
I’ve posted in google groups aswell, but my threads never seem to be posted and I do need some help.

Anyway, back on topic.
I was trying to create a dynamic UI to read in a file list and create layouts and radio buttons based on those. I started off in python, but I wasn’t going very pythonic about it since it’s just was a proof of concept. I got it to look like I wanted, but to retrieve the values for the radio buttons in each layout is such a hazzle now. The code is real f****d up!

However, for something like this I do need some structure so I’m recreating it using a more pythonic manner this time around(hopefully). But this also brings up some questions. I’m just doing a simple test now. This is what I have so far:
https://dl.dropboxusercontent.com/u/16029672/3D/python_dynUI.txt

Also I didn’t use any radioButtons in this example, which might make things trickier as I need to create them column by column.

My script is based on modules located in a folder.
You might want to create new styles, thus adding a new set of modules. That’s why I need it to be dynamic, but also to learn to create neater code.
For each style, I’m creating a formLayout. Then adding the modules for that style in that specific formLayout.

So for instance. My modules looks like this:
start_A_01, start_A_02, mid_A_01, end_A_01, start_B_01, mid_B_01, mid_B_02, end_B_01
For each module type I’m also creating a column in that layout with the radioCollection command.

So in this example, my column for style A looks like this:
start_A_01 mid_A_01 end_A_01
start_A_02

and for style B:
start_B_01 mid_B_01 end_B_01
mid_B_02

I’m utilizing layouts so I can easily set the visibility to 0 and switch to another layout with another set of modules. Not sure if this approach is smart, but the one I thought would be smoothest to implement.

please, have a look through my code. I’m trying to get the hang of the object oriented approach to UI development, but I’m not really utilizing it now, I know. Is there another way this could be written shorter and more pythonic?

Thanks for looking!

EDIT: Just thought I should post my proof of concept UI:
https://dl.dropboxusercontent.com/u/16029672/3D/buckleUP_UI.mp4

Looks cool! Do you need to see all of the module options displayed at once? If not, maybe have an option menu or two that contains the allowed modules and sub-options, then a checkbox/listview/etc that displays the specific options for the current selection. The dynamic-ness of your proof UI is cool, but if I was using it, I’d get annoyed that “Buckle” is constantly changing its location on the menu, depending on what options precede it. Buckle, etc should always be in the same location, me thinks. Just an idea!

Modules:

| module 2 | ^ |

Sub Options

| buckles from module 2 | ^ |

Choose:

|module2_buckle1 |
|module2_buckle2 |
|module2_buckle3 |

pants_not_on

Thanks!
Forgive me, but I haven’t really told you what the UI is for. Though it might look self explanatory, not sure. It’s a UI for a belt or strap creator.
The idea is that you select a curve and set your options, then execute the script and a belt or strap is created. Much like the belt insert mesh brush in ZBrush.
However, I needed some more control and easier setup. Doing the straps in ZBrush and modifying it took forever imo. Especially when I needed 18 of them…

Back to the UI.
So I’m on the right track?
I assumed you could do something a bit more pythonic, but wasn’t sure. I still need to create the buttons and layout on the fly so using a button method didn’t seem to be appropriate.
Still, my code comes out as cluttered, no?
Especially when adding the functionalities I want.

Like this ex from http://oferz.com/blog/blog4.php/dynamic-ui-in-maya-using-python
Here he is using a createButtons method:

mport maya.cmds as cmds
import maya.mel as mel


class dynamicUIElements:
    """A demo for creating dynamic UI elements in Maya using python."""
   
    # On initialize, create the UI and show it.
    def __init__(self):
        """Initialize."""
        
        self.createWindow()
        
    
    # I like to keep all the iportant UI elements in a dictionary.
    UIElements = {}  
    
    
    def createWindow(self):
        """This function creates the window."""
        
        # Create the window element
        self.UIElements['window'] = cmds.window()
        #Create a layout
        self.UIElements['main_layout'] = cmds.columnLayout( adjustableColumn=True )
        
        # Create the dynamic buttons
        self.createButtons()
        
        # Show the window.
        cmds.showWindow( self.UIElements['window'] )
        
        
    def createButtons(self):
        """Creates some buttons dynamically."""
        
        for i in range(1,6):
            self.UIElements['button'+str(i)] = cmds.button( label=('Button ' + str(i)), command=self.ehButtonPressed )
            
            
    def ehButtonPressed(self, *args):
        """An event handler for the button pressed event."""
        
        print args

win = dynamicUIElements()

Regarding the column issue. The idea is that you’ll always have the same number of module types(as the creator script is relying on the parts to function) and if not, the modules shouldn’t be loaded as in my case with the one buckle style as it is not a complete set. So in the end it shouldn’t be an issue, and spares me the hassle of creating code to handle that issue.

I’m only giving you the opportunity to change some of the modules as the rest must be the same and there are no real need to change them. Then you’d want to change the style completely loading in another set of modules.
I have a start(which you can change), mid(which is always the same), holes(also always the same for each style), end(when selecting the strap part, you can change how the end will look), buckle(which you can change).
Hope it makes sense.

Later I will need to query the radiobuttons for each column in the active layout. How should I go about doing so?
As it is now, I’ve added all my buttons to the same dictionary as the rest of the UI elements, but perhaps I should create a dictionary for each layout with a nested dictionary for each radioCollection?

So querying would be to query the active Layout
and for that layout query each radioCollection.

In my bashed up script I’m using a nested list:

listFInal = [[[u'dagUI|formLayout100|formLayout102|radioCollection57'], [u'dagUI|formLayout100|formLayout102|radioCollection58'], [u'dagUI|formLayout100|formLayout102|radioCollection59']], [[u'dagUI|formLayout100|formLayout103|radioCollection60'], [u'dagUI|formLayout100|formLayout103|radioCollection61'], [u'dagUI|formLayout100|formLayout103|radioCollection62']], [[u'dagUI|formLayout100|formLayout104|radioCollection63']]]

//For example looking at the first layout
for each in listFinal[0]:
    val = mc.radioCollection(each, q=True, sl=True)
    print val

This prints out the selected radioButtons, but then I need to retrieve the model that corresponds to that radioButton.

Just wanted to show you the script in action.
I’m still planning on creating a better UI for it, but in the meantime…

https://dl.dropboxusercontent.com/u/16029672/3D/buckleUP_workflow/buckleUP.html

In terms of cleanliness, you might want to consider refactoring so that the outermost code doesn’t handle UI elements directly at all. It’s a pain in the ass to know that controlX is a radioButton and control Y is a text field and so on. You can make a simple wrapper that hides that so you can assemble a list of controls and create/query/delete them in short order:



class uiProxy(object):
    CMD = cmds.control
    VALUE = None
    
    def __init__(self, *args, **kwargs):
        self.Args = args
        self.Kwargs = kwargs
        self.Control = None

    def create(self):
        self.Control = self.CMD(*self.Args, **self.Kwargs)
        
    def __repr__(self):      # for convenience, you can see the control path
        if self.Control:
            return self.Control
        else:
            return "<uninitialized UIProxy>"
    
    def edit(self, *args, **kwargs):
        kwargs['e'] = True
        return self.CMD(self.Control, *args, **kwargs)       

    def query(self, *args, **kwargs):
        kwargs['q'] = True
        return self.CMD(self.Control, *args, **kwargs)  
 
    def value(self):       # this gets the value for things like fields that have one.
                                 # it uses a class level flag since they don't always use the 
                                 # same flag from class to class
        if not self.Value : return None
        return self.query(**{self.VALUE:True})


# you derive it for the classes you need by changing CMD:
class uiButton(uiProxy):
    CMD = cmds.button
    
    def __init__(self, name, callback, **kwargs):
        kwargs['c'] = callback  # you can change the syntax this way for convenience - this always takes name, callback
                                          # since I always want a callback with my buttons....
        uiProxy.__init__(self, name, **kwargs)

class uiText(uiProxy):
    CMD = cmds.text
    VALUE = 'label'

class uiTextField (uiProxy):
   CMD = cmds.textField
   VALUE = 'tx'   


Doing it this way lets you push all the special case code down into individual classes. For example, instead of calling cmds.intField(field, q=True, v=True) on an intfield and cmds.textField(field, q=True, v=True) on a text field, you could just call the value() method on the wrappers. This saves a lot of very situation specific coding

You can also consider bundling your UI controls and the objects in the scene they work on into classes. Then you manage the list of bundles, knowing that each one already has the right context for scene operations instead of having to check a global list of ui elements against a global list of controls.

Here’s a simple example:



class Widget(uiProxy):
     CMD = cmds.RowLayout

     def __init__(self, target):
         uiProxy.__init__(self)
         self.Target = target
         self.Text = None
 
     def create(self):
        # create the rowlayout with a textfield displaying the name and 2 buttons
        self.Control = self.CMD(nc = 3, cw3 = (100,50,50)
        self.Text = uiText(label= self.Target).create() 
        uiButton('move', self.moveTarget).create()
        uiBotton('rotate', self.rotateTarget).create()
        cmds.setParent("..")  
        # no need to track those guys individually, they'll get deleted if the rowlayout is deleted...

     def moveTarget(self):
        cmds.xform(self.Target, t = (0,1,0), relative=True)

     def rotateTarget(self):
        cmds.xform(self.Target, rotate = (0,30,0), relative=True)
      
     def getText(self):
        return self.Text

# then, elsewhere I can make a bunch of these in a scrollLayout:

    dynamic_elements = {}
    scroll = cmds.scrollLayout(cr=False)
    column = cmds.columnLayout(width = 200)
    for item in target_objects:
        dynamic_elements[item] = Widget(item)
        dynamic_elements[item].create()
    cmds.setParent('..')


If you needed to find out the value of a UI item in one of the dynamic elements, you could just make sure the Widget class stored the wrapper you needed. In the above example:



text_of_item = dynamic_elements['example'].Text.value()

Awesome. Thank you so very much!
I’ll be back with updates.