Search code examples
pythonuser-interfacemaya

Trying to populate a window dynamically in Maya using separate py files to essentially build modules


So I'm trying build a window in Maya, that will have contents that will be populated dynamically. My folder structure is this: /scripts/modularMenu/ <-- which contains:

init.py

modMenu.py

and a /modules/ folder

in the modules folder I have: modList.py

mod1.py

mod2.py

mod3.py etc. etc.

In modMenu.py I tell Maya to draw the window, but also run the function that populates it based on the contents of the modules folder, with the goal being to create new modules that are, if tagged correctly, populated in the window.

import maya.cmds as cmds
import sys, os.path
from functools import partial
import modules.modList as mList

def modMenu():
    if (cmds.window('modMenu', exists=True)):
        cmds.deleteUI('modMenu')
    else:
        window = cmds.window( 'modMenu', title="Mod Menu v1.1", iconName='mRig', widthHeight=(400, 800))
        cmds.scrollLayout(width=400, cr=True)
        cmds.columnLayout(adj=True )
        #This all needs to be generated Dynamically.  
        mList.populateWindow()      
        cmds.showWindow( window )

In modList.py I have a list of categories and a function to populate the window.

typeList = ['Type One', 'Type Two', Type Three']

def populateWindow():
    for type in typeList:
        cmds.frameLayout(label = type, collapsable = True, borderStyle = 'etchedIn')
        cmds.text(label = type, al = 'center', height = 15)
        #Need to then go through each module and import the rest of the form here, each form will have to have a tag to determine if it
        #needs to go in this Category or not.  Perhaps the equivalent of...
        #for each mod.py in /modules folder if their tag == type then add
        cmds.setParent( '..' )

What I'm trying to figure out next is one, how to safely import the contents of each separate mod1.py, mod2.py, etc etc into this modList.py file and two how to tag each separate mod.py file so its placed within the menu system correctly. Ideally I'd like to include one identical function in each mod.py file and a string that correct tags it, that I could call in the modList.py file, but I'm not sure how to correctly import from those mod files en masse to successfully call that function. Any help would be welcome.


Solution

  • On one level, this is pretty simple. You can always add gui elements to a Maya layout if you have a string reference to it, using the setParent() command to tell Maya where the new stuff goes.

    In a case like this you just need to pass the shared layout to a bunch of functions --- it doesn't matter where they come froms -- and have each of them call 'setParent` to make the layout active and add to it. Here's an example of how it would go, using separate functions instead of separate modules -- it would not make a difference if these different functions had different module origins.

    def button_section(parent):
        cmds.columnLayout(adj=True)
        cmds.frameLayout(label='buttons') 
        cmds.columnLayout(adj=True)
        cmds.rowLayout(nc=2, cw = (200,200))
        cmds.button(label = 'red', backgroundColor=(1,0.5,0.5), width=100)
        cmds.button(label = 'blue', backgroundColor =(0.5, 0.5, 1), width=100)
    
    def text_section(parent):
        cmds.separator()
        cmds.text(label = 'time:')
        cmds.text(label = 'It is currently ' + str(datetime.datetime.now()))
        cmds.text(label = 'main layout is ' + parent)
        cmds.separator()
    
    def object_section(parent):
        cmds.columnLayout(adj=True)
        cmds.frameLayout(label = 'scene')
        cmds.columnLayout(adj=True, rs = 12, columnAttach = ('both', 8) )
        for object in cmds.ls(type='transform'):
            select_the_thing = lambda b: cmds.select(object)
            cmds.button(label = object, c = select_the_thing)   
    
    def create_window(*sections):
        window = cmds.window(title = 'example')
        main_layout = cmds.columnLayout(adj=True)
        for each_section in sections:
            cmds.setParent(main_layout)
            each_section(main_layout)
        cmds.setParent(main_layout)
        cmds.columnLayout(adj=1, columnAttach = ('both', 8))
        cmds.separator()
        cmds.text(label = 'here is a footer')
        cmds.showWindow(window)
    
    create_window(button_section, text_section, object_section)
    

    If you're not familiar with the syntax the create_window function with a * takes any number of arguments. In this case it's just taking the three individual section functions. You could, however write it to just take a list of functions. In any case the logic is the same -- just setParent back to the main layout and you'll be able to add new stuff to the layout.

    In this example I passed the name of the main layout into each of the different layout functions. This is useful so you can do things like get the width of a layout element that owns you, or work your way up to a higher level recursively.


    In general the thing you'll have to watch out for here is designing this so that the different sections really are independent of each other. Things will get complicated fast if a button in section A needs to know the state of a checkbox in section B. However this shows you the basics of how to compose layouts in Maya.

    I'd be very careful about trying to populate the menu based on the contents of your module folder -- if you delete a module but don't remember to delete the pyc file that is produced by it, you can end up with a phantom section of UI that you don't expect. It would be better to just organize the code as conventional modules and then have a simple script that asked for the modules explicitly. Then you can know exactly what to expect on a local install.