Adverti horiz upsell
Creating Maya GUI for asset lighting
Creating Maya GUI for asset lighting
Alex Khan, updated 2013-03-14 20:03:27 UTC 17,871 views  Rating:
(3 ratings)
Page 1 of 1

The purpose of this tutorial is to show how to  build Maya GUI , using PyMEL. As an example I will use my own script, that helps to automate image based lighting setup  for Mental Ray.

 

You can find and download this free script from CreativeCrash. The name is  - Asset Lighting Maya GUI.

assetlighting

 

With this GUI you can create lighting setup - (IBL node and set of lights) and point it to the chosen camera.
There are two presets - for outdoor lighting (IBL and directional light) and for indoor (IBL and standard three area lights setup).
You can pick your own HDR image and individual colors for lights.

 

The position and scale of lights based  on dimensions of visible objects in the scene.

There are also few controls to manage selected objects. You can:
  - hide unselected objects
  - "frame" selection - move center of selected objects to zero point, preserving parenting
  -create turntable animation with specified duration
  -reset any of the above

 

This tutorial requires some basic knowledge of PyMEL. If you want to know more about it – visit official Autodesk page - https://autodesk.com/us/maya/2011help/PyMel/index.html

To download PyMEL – https://code.google.com/p/pymel/

 

GUI was tested in Maya 2011, and should work for versions 2010 and 2012 as well.

******************************** 

 

Lets get started. 

I will show just certain parts of code, breaking for comments, skipping repeated and unimportant.

You can download the script to see all code.

Basically, we are defining new class – AssetLightWindow:

import pymel.core as pm
 
class AssetLightWindow(object):
 
    @classmethod
    def showUI(cls):
        # classmethod to show example of class
        win=cls()
        win.create()
        return win
 
 
    def __init__(self):
 

It worth noting following:

The class method  showUI  is a special method that allows us to call it without creating the instance of class.

__init__ method is used to keep data attributes.  Python automatically call it as soon as instance is created.  I am skipping all definitions in this method. They are unimportant for this tutorial.

  def create(self):
        # create window "Asset Light"
        ## destroy the window if it already exists
        try:
            pm.deleteUI(self.WINDOW_NAME, window=True)
        except: pass
        # draw the window
        self.WINDOW_NAME = pm.window(
            self.WINDOW_NAME,
            title=self.WINDOW_TITLE,
            widthHeight=self.WINDOW_SIZE
        )
 

Create method will be called at the moment of creating the instance of class.

At the beginning we delete window if it already exists.

	self.mainForm = pm.formLayout(nd=100)
 
	## lighting control frame Layout
        self.modeGrpFrame=pm.frameLayout(
            label='Lighting Control',
            collapsable=True,
            width=400,
            borderStyle='etchedIn'
        )
 
        pm.formLayout(
            self.mainForm, e=True,
            attachForm=(
                  [self.modeGrpFrame,'top',16],
                  [self.modeGrpFrame,'left',6],
            )
        )
 
 

There are many layout commands or controls can be used to build your GUI.

FormLayout() is a very flexible  way to organize them.

It gives you a numbers of positioning options.

 AttachForm flag allows you to pin control’s edges to edges of layout form.

AttachPosition – allows to pin control’s edges to specific coordinates in layout form coordinate space(dimension  of which was passed as argument to FormLayout() command)

AttachControl –allows you to pin control’s edges to another control.

FormLayout()  is the basis you can put various controls on. Every control, defined after  FormLayout() will be automatically parented to it. To change current parent, you can use setParent() method.

	self.modeGrpForm = pm.formLayout(nd=100)
 
        ## lighting mode options menu
        self.lightModeGrpOptionMenu=pm.optionMenu(
            label='Lighting Mode',
            changeCommand=pm.Callback(self.on_mode_change)
        )
 
        for item in self.listOfLightModes:
            pm.menuItem(l=item)
 
        pm.formLayout(
            self.modeGrpForm, e=True,
            attachForm=(
                  [self.lightModeGrpOptionMenu,'top',10],
                  [self.lightModeGrpOptionMenu,'left',20],
            )
        )
 

As far as there are three sections in GUI, three additional form layouts created. First of them -

modeGrpForm is defined above. Then, new option menu control is created. It has all possible lighting mode options, defined in __init__ method in the list listOfLightModes[].

Method  on_mode_change() mentioned as a response to action of choosing new option.

 
	## render camera options menu
        self.renderCamGrpOptionMenu=pm.optionMenu(
            label='Render Cam',
            changeCommand=pm.Callback(self.on_cam_change)
        )
 
        self.cam_option_list()
 
        pm.formLayout(
            self.modeGrpForm, e=True,
            attachForm=(
                  [self.renderCamGrpOptionMenu,'top',10],
            ),
            attachControl=(
                  [self.renderCamGrpOptionMenu,'left',10,self.lightModeGrpOptionMenu],
            )
        )
 

There is new control created – option menu for existing cameras in the scene. Method  cam_option_list() searching for cameras and fills up option list.

        self.HdrPathTxt=pm.textFieldButtonGrp(
            label='HDRI file',
            text=self.currentHdrImgPath,
            buttonLabel='Browse',
            buttonCommand = pm.Callback(self.on_browse_btn)
        )
 
        pm.formLayout(
            self.modeGrpForm, e=True,
            attachForm=(
                  [self.HdrPathTxt,'left',-52],
            ),
            attachControl=(
                  [self.HdrPathTxt,'top',10,self.lightModeGrpOptionMenu],
             )
        )
 

textFieldButtonGrp () – new control, showing current HDR image path. It has button, that opens open file dialog.

        self.ColorPicker=pm.colorSliderGrp(label='KeyLight Color', height=14, rgbValue=self.listOfLightClr[0])
 
        pm.formLayout(
            self.modeGrpForm, e=True,
            attachForm=(
                    [self.ColorPicker,'left',-50],
            ),
            attachControl=(
                    [self.ColorPicker,'top',10,self.HdrPathTxt],
            )
        )
 

ColorSliderGrp()- color picker control, defining color of the light.

 

        self.Build_btn = pm.button(
            label='Build Lighting',
            command=pm.Callback(self.on_build_btn)
        )
 
        self.Reset_btn = pm.button(
            label='Reset Lighting',
            command=pm.Callback(self.on_reset_btn)
        )
 
        pm.formLayout(
            self.modeGrpForm, e=True,
            attachForm=(
                [self.Build_btn,'bottom',5],
                [self.Build_btn,'left',5],
                [self.Reset_btn,'bottom',5],
                [self.Reset_btn,'right',10],
            ),
            attachControl=(
                [self.Build_btn,'top',10,self.ColorPicker],
                [self.Reset_btn,'top',10,self.ColorPicker],
            ),
            attachPosition=(
                [self.Build_btn,'right',1,50],
                [self.Reset_btn,'left',1,50],
            )
 
        )
 

Two button controls, calling relevant methods that create and delete lighting rig.

 

I am skipping the part of code for another two sections of GUI controls.

They are using the same controls and methods mentioned above.

The last statement of create() method shows new window -

 

        pm.showWindow()

I am skipping description of all methods, except one, that builds lighting rig.

They are all pretty easy for understanding, and performs quite basic operations.

 

    def on_build_btn(self,*args):
        # lighting builder
        ## deletes lighting elements if they exist
        self.on_reset_btn()
        ## finds dimensions and center of visible objects
        if pm.pluginInfo("Mayatomr", query=True, loaded=True) != 1 :
            pm.loadPlugin( "Mayatomr" )
        if not pm.objExists('mentalrayGlobals'):
            pm.mel.eval("miCreateDefaultNodes")
 

First two if  clauses check if Mental Ray is loaded. And if it does – if mentalrayGlobals is active.

MentalrayGlobals is a node that contains all important mental ray attributes. We need to connect IBL node to MentalrayGlobals to have image based lighting works.

It worth mentioning that Maya keeps mentalrayGlobals inactive until you start configuring options in Features or Indirect Lighting tabs. MEL command miCreateDefaultNodes force the creation of all the mental ray nodes in the scene.

 

        visibleObjs=pm.ls(visible=True,geometry=True,cameras=False,lights=False)
        if len(visibleObjs) != 0:
            boundBoxOfVisbl=pm.exactWorldBoundingBox(visibleObjs)
            highOfVisbl=boundBoxOfVisbl[4]-boundBoxOfVisbl[1]
            largestDimOfVisibl=max([boundBoxOfVisbl[3]-boundBoxOfVisbl[0],
                                    boundBoxOfVisbl[4]-boundBoxOfVisbl[1],
                                    boundBoxOfVisbl[5]-boundBoxOfVisbl[2]]
            )
            centerPointOfVisbl=[(boundBoxOfVisbl[3]+boundBoxOfVisbl[0])/2,
                                (boundBoxOfVisbl[4]+boundBoxOfVisbl[1])/2,
                                (boundBoxOfVisbl[5]+boundBoxOfVisbl[2])/2
            ]
 

In order to scale and position lighting elements properly, dimensions of all visible objects is calculating.

 

        ## creates skyDome Node and connect to HDRI file
        skyDome=pm.createNode('mentalrayIblShape', name='EnvironmentShape')
        pm.connectAttr(skyDome.message,'mentalrayGlobals.imageBasedLighting',force=True)
        pm.setAttr(skyDome.visibleInFinalGather,1)
 
 
        self.listOfLightObjects.append(skyDome.getParent())
        pm.sets('defaultLightSet',addElement = skyDome)
        pm.xform(skyDome.getParent(),scale=[100,100,100])
 
        hdrFile=pm.createNode('file', name= 'HdrFile')
 
        place2dTxt=pm.createNode('place2dTexture', name='Place2dTxt')
 
        pm.connectAttr(place2dTxt.coverage, hdrFile.coverage)
        hdrFile.setAttr('fileTextureName',self.currentHdrImgPath)
 
        pm.setAttr(skyDome.texture,hdrFile.getAttr('fileTextureName'))
 
 

The code above creates IBL sphere and connect it to appropriate HDR image.

 

        ## builds Lights
 
        self.currentLightMode=self.lightModeGrpOptionMenu.getValue()
 
        lightsList=[]
 
        if self.currentLightMode in self.listOfLightModes[0]:
            dirLight=pm.directionalLight(name='Sun')
            pm.setAttr('Sun.intensity',5)
 
            dirLight.setAttr('color',self.get_currentClr()[0])
            pm.xform(dirLight.getParent(),
                     rotation =[-120,0,20],
                     scale=[largestDimOfVisibl/4,largestDimOfVisibl/4,largestDimOfVisibl/4]
            )
            self.listOfLightObjects.append(dirLight.getParent())
 
        else:
 
            keyLight=pm.createNode('areaLight',name='KeyLightShape')
            pm.rename(keyLight.getParent(),'KeyLight')
 
            pm.setAttr('KeyLight.intensity',2)
            lightsList.append(keyLight)
            pm.xform(keyLight.getParent(),
                     translation =[-largestDimOfVisibl*1.1,highOfVisbl/2,-largestDimOfVisibl*1.1],
                     scale=[largestDimOfVisibl/4,largestDimOfVisibl/4,largestDimOfVisibl/4]
            )
 
 
            fillLight=pm.createNode('areaLight',name='FillLightShape')
            pm.rename(fillLight.getParent(),'FillLight')
 
            pm.setAttr('FillLight.intensity',1)
            pm.xform(fillLight.getParent(),
                     translation =[largestDimOfVisibl*1.1,0,-largestDimOfVisibl*1.1/3],
                     scale=[largestDimOfVisibl/4,largestDimOfVisibl/4,largestDimOfVisibl/4]
            )
            lightsList.append(fillLight)
            #fillLight.setAttr('color',self.get_currentClr()[1])
            rimLight=pm.createNode('areaLight',name='RimLightShape')
            pm.rename(rimLight.getParent(),'RimLight')
 
            pm.setAttr('FillLight.intensity',2)
            pm.xform(rimLight.getParent(),
                     translation =[0,0,largestDimOfVisibl*1.1],
                     scale=[largestDimOfVisibl/4,largestDimOfVisibl/4,largestDimOfVisibl/4]
            )
            lightsList.append(rimLight)
            for i in lightsList:
                i.setAttr('color',self.get_currentClr()[lightsList.index(i)])
                self.listOfLightObjects.append(i.getParent())
 

This section creates lights, based on chosen option of lighting mode. For “Outdoor” mode only one directional light is created. In most cases its combination with IBL node is sufficient to light the scene.

In case of “Indoor” mode the standard three point lighting setup is created.

Lights are distributed and scaled based on dimensions  of visible objects in the scene.

 

        # group and constraint elements
        listconstr=[]
        lightGrp=pm.group(self.listOfLightObjects, name=self.currentLightMode+'_grp')
        pm.xform(lightGrp,pivots=[0,0,0],translation=centerPointOfVisbl)
 
 
        for i in lightsList:
            listconstr.append(pm.aimConstraint(lightGrp,i.getParent(),aimVector=[0,0,-1]))
 
        for i in listconstr:
            pm.delete(i)
 
        #point to render camera
        try:
            grpconstr=pm.aimConstraint(
                    self.renderCamGrpOptionMenu.getValue(),
                    lightGrp,
                    aimVector=[0,0,-1],
                    upVector=[0,1,0],
                    skip=['x','z']
                    )
            pm.delete(grpconstr)
        except:pass
 
 
        self.listOfLightObjects.append(hdrFile)
        self.listOfLightObjects.append(place2dTxt)
        self.listOfLightObjects.append(lightGrp)
 

Code above groups all lights and IBL sphere and points it to render camera chosen in option menu. Constraints are using temporary and have deleted,  after  aiming is completed.

  

At the end it fills up list of all lighting objects. It will be used in the following method to delete lighting objects in the scene:

 

    def on_reset_btn(self,*args):
        # deletes all objects created for lighting
        for i in self.listOfLightObjects :
            try:
                pm.delete(i)
 
            except: pass
        self.listOfLightObjects=[]
 

After defining class  and all methods, class method showUI() is called to create window:

 

AssetLightWindow.showUI()

That’s all. Thanks for your interest.

I hope you’ve got something new for yourself.