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.
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.
Author: Alex Khan
Submitted: 2013-02-23 20:10:18 UTC
Tags: GUI, Mental Ray, pymel, and Python
Software: Maya
Views: 17,857
Related Items
-
Mass Instancer for Maya 1.6.0 (maya plugin)
$299.00 (USD) -
Baby Walker Cartoon 01 3D Model
$20.00 (USD) -
Baby Walker Cartoon 02 3D Model
$20.00 (USD) -
Baby Walker Cartoon 03 3D Model
$20.00 (USD) -
Cessna TTx 400 3D Model
$139.00 (USD) -
Bucker Bu131 Jungmann 3D Model
$109.00 (USD) -
Tree Cartoon 3D Model
$20.00 (USD) -
Diamond DA62 3D Model
$199.00 (USD) -
Generic Future Helicopter 3D Model
$99.00 (USD)