Thursday, April 18, 2013
My own "Hello, World!" Maya plug-in
Yestarday, I used Maya devkit example to learn Maya plug-ins. Today, I will create my own alternative Hello World plug-in!
import maya.OpenMaya as om
import maya.OpenMayaMPx as ommpx
class AR_HelloWorldCmd(ommpx.MPxCommand):
"""
A command that prints a string to the console.
"""
## the name of the command
kPluginCmdName = 'ar_helloWorld'
def __init__(self):
"""
Initialize the instance.
"""
ommpx.MPxCommand.__init__(self)
def doIt(self, args):
"""
Print string to the console.
"""
print('Hello, world!')
@classmethod
def cmdCreator(cls):
"""
Return pointer to proxy object.It has to always call this method when creating MPx objects in scripted plug-ins.
"""
return ommpx.asMPxPtr(cls())
def initializePlugin(obj):
"""
Initialize the plug-in.
"""
plugin = ommpx.MFnPlugin(
obj,
'Meng',
'1.0',
'Any'
)
try:
plugin.registerCommand(
AR_HelloWorldCmd.kPluginCmdName,
AR_HelloWorldCmd.cmdCreator
)
except:
raise Exception(
'Failed to register command: %s'%
AR_HelloWorldCmd.kPluginCmdName
)
def uninitializePlugin(obj):
"""
Uninitialize the plug-in.
"""
plugin = ommpx.MFnPlugin(obj)
try:
plugin.deregisterCommand(AR_HelloWorldCmd.kPluginCmdName)
except:
raise Exception(
'Failed to unregister command: %s'%
AR_HelloWorldCmd.kPluginCmdName
)
It is critical to note again that you must unload and then reload a plug-in to see changes that you are making in an external text editor become active in Maya.
Wednesday, April 17, 2013
Maya Plug-In
The simplest way to start developing commands by using plug-ins is to look at the simplest possible example. The Maya devkit folder contains sevel example commands.
So I add helloworld plug-in by using Plug-in Manager:
Then print “Hello World!” should be as simple as type:
import maya.cmds as cmds;
cmds.spHelloWorld()
Then print “Hello World!” should be as simple as type:
import maya.cmds as cmds;
cmds.spHelloWorld()
Sunday, April 14, 2013
Formal Categorization of Problem solving Strategies
The following is a more Formal
Categorization of Problem solving Strategies:
• Abstraction: solving the
problem in a model of the system before applying it to the real system
• Analogy: using a
solution that solves an analogous problem
• Brainstorming:
(especially among groups of people) suggesting a large number of solutions or
ideas and combining and developing them until an optimum is found
• Divide and conquer:
breaking down a large, complex problem into smaller, solvable problems
• Hypothesis testing:
assuming a possible explanation to the problem and trying to prove (or, in some
contexts, disprove) the assumption
• Lateral thinking:
approaching solutions indirectly and creatively
• Means-ends analysis:
choosing an action at each step to move closer to the goal
• Method of focal objects:
synthesizing seemingly non-matching characteristics of different objects into
something new
• Morphological analysis:
assessing the output and interactions of an entire system
• Proof: try to prove that
the problem cannot be solved. The point where the proof fails will be the
starting point for solving it
• Reduction: transforming
the problem into another problem for which solutions exist
• Research: employing
existing ideas or adapting existing solutions to similar problems
• Root cause analysis:
identifying the cause of a problem
• Trial-and-error: testing
possible solutions until the right one is found
Thursday, April 11, 2013
General Problem Solving Strategies
General Problem Solving Strategies:
1. Read and try to understand asserts
diagnostics or symptoms if available.
2. Form clear hypothesis about cause of
problem.
3. Compare and contrast a working case with
a non-working case.
4. Trace flow if possible.
5. Isolate working and non-working parts. (Divide and
conquer) This is different from 3 above
in that 3 describes comparing a working whole vs. a non-working whole. Here, we are separating parts of a
non-working whole into working and non-working parts.
6. Time Box Trouble Shooting Efforts and
ask for help
7. Form new hypothesis if necessary --walk
away and return after a break.
Saturday, April 6, 2013
Reading Material
Since I'm a little ahead of my schedule, so,
I decided to read more materials related to technical artist instead of keep
reading Maya Python. Here are some materials I read.
1.
A great book to read on controlling
flow in complex systems. This book helps me to naturally involve objective
analysis, economics and an understanding of flow in my problem solving process.
2.
Several links describing
Technical Art: Since reality is composed of multiple perspectives, it’s
important to encounter multiple points of view.
No single description is definitive.
Sunday, March 31, 2013
Chapter 9 Conclution
Chapter 9 is all about understanding Maya API. Since the API documentation is written by C++, so I have to at least understand some basic knowledge about C++.
Maya's C++ API contains many classes and function sets that are accessible through various Python modules. These modules use SWIG to automatically convert Python parameters into data for C++, as well as a lack of perfect correspondence between C++ and Python, programmers working with the Python API must have some basic understanding of C++ to read the documentation, understand some limitations they will face, and better grasp classes like MScriptUtil.
Though it is somewhat different from ordinary Python scripting in Maya, the Maya Python API provides an accessible way to rapidly develop powerful tools for Maya.
PS: Maya API Reference: http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/index.html
Saturday, March 23, 2013
Cannot install Qt SDK
Chapter 8 is all about Qt. So first, I have to install it. But I encountered some problems because the book only told us how to install it for Maya 2011 and Maya 2012. I followed the instruction but it cannot work.
Then, I searched for a long time and found out that many people have same problem with me. Finally, I found the reason: PyQt 4.9.1 built against Qt4.7.1.
Unfortunately, I didn't find a solution to solve this problem. But I can keep on learning without studying Chapter 8. So, next week, I will move on to Chapter 9: Understanding C++ and the API Documentation. From this week's learning, I knew Qt is a WYSIWYG (what you see is what you get) tool. So, it must be very powerful and easy to use. I would like to learn it later on if I have chance.
Then, I searched for a long time and found out that many people have same problem with me. Finally, I found the reason: PyQt 4.9.1 built against Qt4.7.1.
Unfortunately, I didn't find a solution to solve this problem. But I can keep on learning without studying Chapter 8. So, next week, I will move on to Chapter 9: Understanding C++ and the API Documentation. From this week's learning, I knew Qt is a WYSIWYG (what you see is what you get) tool. So, it must be very powerful and easy to use. I would like to learn it later on if I have chance.
Monday, March 18, 2013
Chapter 7 Conclution
To be honest, this is the longest chapter I have learned so far. I nearly spent my whole week (spring break, OMG!) to finish reading it. But it's really useful, and I developed two awesome tools which satisfied me. So in the beginning of this chapter, I first learnt some core technical concepts related to Maya's GUI and then develop a base class for tool option windows. Then, explore some of Maya's built-in GUI controls and demonstrate how could easily extend this class to quickly create new tools. Finally, I learnt some advanced topics related to tool creation, such as serializing data and working with files.
In addition to have mastered some basic GUI commands, I have another hands-on example of some of the benefits of object-oriented programming in Maya using Python. By creating basic classes for common tool GUIs, I can introduce new tools much more easily than using MEL or manually creating new windows for every new tool. Moreover, I have learned best practices for executing commands with GUI controls, organizing GUI windows, and architecting GUI code in a larger code base.
In addition to have mastered some basic GUI commands, I have another hands-on example of some of the benefits of object-oriented programming in Maya using Python. By creating basic classes for common tool GUIs, I can introduce new tools much more easily than using MEL or manually creating new windows for every new tool. Moreover, I have learned best practices for executing commands with GUI controls, organizing GUI windows, and architecting GUI code in a larger code base.
Saturday, March 16, 2013
Pose Management Tool
import maya.cmds as cmds
import maya.mel as mel
import os, cPickle, sys, time
kPoseFileExtension = 'pse'
def showUI():
"""A function to instantiate the pose manager window"""
return AR_PoseManagerWindow.showUI()
class AR_PoseManagerWindow(object):
"""A class for a basic pose manager window"""
@classmethod
def showUI(cls):
win = cls()
win.create()
return win
def __init__(self):
"""Initialize data attributes"""
## a unique window handle
self.window = 'ar_poseManagerWindow'
## window title
self.title = 'Pose Manager'
## window size
self.size = (300, 174)
if mel.eval('getApplicationVersionAsFloat()') > 2010.0:
self.size = (300, 150)
## a temporary file in a writable location for storing a pose
self.tempFile = os.path.join(
os.path.expanduser('~'),
'temp_pose.%s'%kPoseFileExtension
)
## current clipboard status message
self.clipboardStat = 'No pose currently copied.'
if (os.path.exists(self.tempFile)):
self.clipboardStat = 'Old pose currently copied to clipboard.'
## file filter to display in file browsers
self.fileFilter = 'Pose (*.%s)'%kPoseFileExtension
def create(self):
"""Draw the window"""
# delete the window if its handle exists
if(cmds.window(self.window, exists=True)):
cmds.deleteUI(self.window, window=True)
# initialize the window
self.window = cmds.window(self.window, title=self.title, wh=self.size, s=False)
# main form layout
self.mainForm = cmds.formLayout()
# frame for copy/paste
self.copyPasteFrame = cmds.frameLayout(l='Copy and Paste Poses')
# form layout inside of frame
self.copyPasteForm = cmds.formLayout()
# create buttons in a 2-column grid
self.copyPasteGrid = cmds.gridLayout(cw=self.size[0]/2-2, nc=2)
self.copyBtn = cmds.button(l='Copy Pose', c=self.copyBtnCmd)
self.pasteBtn = cmds.button(l='Paste Pose', c=self.pasteBtnCmd)
# scroll view with label for clipboard status
cmds.setParent(self.copyPasteForm)
self.clipboardLayout = cmds.scrollLayout(h=42, w=self.size[0]-4)
self.clipboardLbl = cmds.text(l=self.clipboardStat)
# attach controls in the copyPaste form
ac = []; af = []
ac.append([self.clipboardLayout,'top',0,self.copyPasteGrid])
af.append([self.copyPasteGrid,'top',0])
af.append([self.clipboardLayout,'bottom',0])
cmds.formLayout(
self.copyPasteForm, e=True,
attachControl=ac, attachForm=af
)
# frame for save/load
cmds.setParent(self.mainForm)
self.loadSaveFrame = cmds.frameLayout(l='Save and Load Poses')
# create buttons in a 2-column grid
self.loadSaveBtnLayout = cmds.gridLayout(cw=self.size[0]/2-2, nc=2)
self.saveBtn = cmds.button(l='Save Pose', c=self.saveBtnCmd)
self.loadBtn = cmds.button(l='Load Pose', c=self.loadBtnCmd)
# attach frames to main form
ac = []; af = []
ac.append([self.loadSaveFrame,'top',0,self.copyPasteFrame])
af.append([self.copyPasteFrame,'top',0])
af.append([self.copyPasteFrame,'left',0])
af.append([self.copyPasteFrame,'right',0])
af.append([self.loadSaveFrame,'bottom',0])
af.append([self.loadSaveFrame,'left',0])
af.append([self.loadSaveFrame,'right',0])
cmds.formLayout(
self.mainForm, e=True,
attachControl=ac, attachForm=af
)
# show the window
cmds.showWindow(self.window)
# force window size
cmds.window(self.window, e=True, wh=self.size)
def getSelection(self):
rootNodes = cmds.ls(sl=True, type='transform')
if rootNodes is None or len(rootNodes) < 1:
cmds.confirmDialog(t='Error', b=['OK'],
m='Please select one or more transform nodes.')
return None
else: return rootNodes
def copyBtnCmd(self, *args):
"""Called when the Copy Pose button is pressed"""
rootNodes = self.getSelection()
if rootNodes is None: return
cmds.text(
self.clipboardLbl, e=True,
l='Pose copied at %s for %s.'%(
time.strftime('%I:%M'),
''.join('%s, '%t for t in rootNodes)[:-3]
)
)
exportPose(self.tempFile, rootNodes)
def pasteBtnCmd(self, *args):
"""Called when the Paste Pose button is pressed"""
if not os.path.exists(self.tempFile): return
importPose(self.tempFile)
def saveBtnCmd(self, *args):
"""Called when the Save Pose button is pressed"""
rootNodes = self.getSelection()
if rootNodes is None: return
filePath = ''
# Maya 2011 and newer use fileDialog2
try:
filePath = cmds.fileDialog2(
ff=self.fileFilter, fileMode=0
)
# BUG: Maya 2008 and older may, on some versions of OS X, return the
# path with no separator between the directory and file names:
# e.g., /users/adam/Desktopuntitled.pse
except:
filePath = cmds.fileDialog(
dm='*.%s'%kPoseFileExtension, mode=1
)
# early out of the dialog was canceled
if filePath is None or len(filePath) < 1: return
if isinstance(filePath, list): filePath = filePath[0]
exportPose(filePath, cmds.ls(sl=True, type='transform'))
def loadBtnCmd(self, *args):
"""Called when the Load Pose button is pressed"""
filePath = ''
# Maya 2011 and newer use fileDialog2
try:
filePath = cmds.fileDialog2(
ff=self.fileFilter, fileMode=1
)
except:
filePath = cmds.fileDialog(
dm='*.%s'%kPoseFileExtension, mode=0
)
# early out of the dialog was canceled
if filePath is None or len(filePath) < 1: return
if isinstance(filePath, list): filePath = filePath[0]
importPose(filePath)
def exportPose(filePath, rootNodes):
"""Save a pose file at filePath for rootNodes and their children"""
# try to open the file, w=write
try: f = open(filePath, 'w')
except:
cmds.confirmDialog(
t='Error', b=['OK'],
m='Unable to write file: %s'%filePath
)
raise
# built a list of hierarchy data
data = saveHiearchy(rootNodes, [])
# save the serialized data
cPickle.dump(data, f)
# close the file
f.close()
def saveHiearchy(rootNodes, data):
"""Append attribute values for all keyable attributes to data array"""
# iterate through supplied nodes
for node in rootNodes:
# skip non-transform nodes
nodeType = cmds.nodeType(node)
if not (nodeType=='transform' or
nodeType=='joint'): continue
# get animated attributes
keyableAttrs = cmds.listAttr(node, keyable=True)
if keyableAttrs is not None:
for attr in keyableAttrs:
data.append(['%s.%s'%(node,attr),
cmds.getAttr('%s.%s'%(node,attr))])
# if there are children, repeat the same process and append their data
children = cmds.listRelatives(node, children=True)
if children is not None: saveHiearchy(children, data)
return data
def importPose(filePath):
"""Import the pose data stored in filePath"""
# try to open the file, r=read
try: f = open(filePath, 'r')
except:
cmds.confirmDialog(
t='Error', b=['OK'],
m='Unable to open file: %s'%filePath
)
raise
# uncPickle the data
pose = cPickle.load(f)
# close the file
f.close()
# set the attributes to the stored pose
errAttrs = []
for attrValue in pose:
try: cmds.setAttr(attrValue[0], attrValue[1])
except:
try: errAttrs.append(attrValue[0])
except: errAttrs.append(attrValue)
# display error message if needed
if len(errAttrs) > 0:
importErrorWindow(errAttrs)
sys.stderr.write('Not all attributes could be loaded.')
def importErrorWindow(errAttrs):
"""An error window to display if there are unknown attributes when importing a pose"""
win='ar_errorWindow'
# a function to dismiss the window
def dismiss(*args):
cmds.deleteUI(win, window=True)
# destroy the window if it exists
if cmds.window(win, exists=True):
dismiss()
# create the window
size = (300, 200)
cmds.window(
win, wh=size, s=False,
t='Unknown Attributes'
)
mainForm = cmds.formLayout()
# info label
infoLbl = cmds.text(l='The following attributes could not be found.\nThey are being ignored.', al='left')
# display a list of attributes that could not be loaded
scroller = cmds.scrollLayout(w=size[0])
errStr = ''.join('\t- %s\n'%a for a in errAttrs).rstrip()
cmds.text(l=errStr, al='left')
# dismiss button
btn = cmds.button(l='OK', c=dismiss, p=mainForm, h=26)
# attach controls
ac = []; af=[];
ac.append([scroller,'top',5,infoLbl])
ac.append([scroller,'bottom',5,btn])
af.append([infoLbl,'top',5])
af.append([infoLbl,'left',5])
af.append([infoLbl,'right',5])
af.append([scroller,'left',0])
af.append([scroller,'right',0])
af.append([btn,'left',5])
af.append([btn,'right',5])
af.append([btn,'bottom',5])
cmds.formLayout(
mainForm, e=True,
attachControl=ac, attachForm=af
)
# show the window
cmds.window(win, e=True, wh=size)
cmds.showWindow(win)
Result:
Thursday, March 14, 2013
Polygon Creation Tool
import maya.cmds as cmds
from optwin import AR_OptionsWindow;
class AR_PolyOptionsWindow(AR_OptionsWindow):
def __init__(self):
AR_OptionsWindow.__init__(self);
self.title = 'Polygon Creation Options'; //reassign name
self.actionName = 'Create'; //reassign action button name
def displayOptions(self):
self.objType = cmds.radioButtonGrp(
label = 'Object Type: ',
labelArray4=[
'Cube',
'Cone',
'Cylinder',
'Sphere'
],
numberOfRadioButtons=4,
select=1 //set default selected item to 1(Cube)
);
self.xformGrp = cmds.frameLayout( //create a frame layout and stores it in the xformGrp attribute.
label='Transformations',
collapsable=True
);
cmds.formLayout(
self.optionsForm, e=True,
attachControl=(
[self.xformGrp, 'top',2,self.objType]
),
attachForm=(
[self.xformGrp, 'left',0],
[self.xformGrp, 'right',0]
)
);
self.xformCol = cmds.columnLayout();
self.position = cmds.floatFieldGrp(
label='Position: ',
numberOfFields=3
);
self.rotation = cmds.floatFieldGrp(
label='Rotation(XYZ): ',
numberOfFields=3
);
self.scale = cmds.floatFieldGrp(
label='Scale: ',
numberOfFields=3,
value=[1.0,1.0,1.0,1.0] //set default value
);
cmds.setParent(self.optionsForm); //call to the setParent command to make optionsForm the current parent again.
self.color = cmds.colorSliderGrp( //add a color picker control, the colorSliderGrp command creates a color picker with a luminance slider next to it.
label='Polygon Color: '
);
cmds.formLayout(
self.optionsForm, e=True,
attachControl=(
[self.color, 'top',0,self.xformGrp]
),
attachForm=(
[self.color, 'left',0]
)
);
def applyBtnCmd(self, *args):
self.objIndAsCmd = { //create a dictionary to map the radio indices to different function pointers.
1:cmds.polyCube,
2:cmds.polyCone,
3:cmds.polyCylinder,
4:cmds.polySphere
};
objIndex = cmds.radioButtonGrp( //query the objType radio button group and use the dictionary to execute the command that corresponds to the currently selected index.
self.objType, q=True,
select=True
);
newObject = self.objIndAsCmd[objIndex]();
pos = cmds.floatFieldGrp( //apply translation to the newly created object's transform node
self.position, q=True,
value=True
);
rot = cmds.floatFieldGrp( //apply rotation to the newly created object's transform node
self.rotation, q=True,
value=True
);
scale = cmds.floatFieldGrp( //apply scale to the newly created object's transform node
self.scale, q=True,
value=True
);
cmds.xform(newObject[0], t=pos, ro=rot, s=scale);
col = cmds.colorSliderGrp( //apply the selected color to the new model's vertices.
self.color, q=True,
rgbValue=True
);
cmds.polyColorPerVertex(
newObject[0],
colorRGB=col,
colorDisplayOption=True
);
AR_PolyOptionsWindow.showUI();
Result:
from optwin import AR_OptionsWindow;
class AR_PolyOptionsWindow(AR_OptionsWindow):
def __init__(self):
AR_OptionsWindow.__init__(self);
self.title = 'Polygon Creation Options'; //reassign name
self.actionName = 'Create'; //reassign action button name
def displayOptions(self):
self.objType = cmds.radioButtonGrp(
label = 'Object Type: ',
labelArray4=[
'Cube',
'Cone',
'Cylinder',
'Sphere'
],
numberOfRadioButtons=4,
select=1 //set default selected item to 1(Cube)
);
self.xformGrp = cmds.frameLayout( //create a frame layout and stores it in the xformGrp attribute.
label='Transformations',
collapsable=True
);
cmds.formLayout(
self.optionsForm, e=True,
attachControl=(
[self.xformGrp, 'top',2,self.objType]
),
attachForm=(
[self.xformGrp, 'left',0],
[self.xformGrp, 'right',0]
)
);
self.xformCol = cmds.columnLayout();
self.position = cmds.floatFieldGrp(
label='Position: ',
numberOfFields=3
);
self.rotation = cmds.floatFieldGrp(
label='Rotation(XYZ): ',
numberOfFields=3
);
self.scale = cmds.floatFieldGrp(
label='Scale: ',
numberOfFields=3,
value=[1.0,1.0,1.0,1.0] //set default value
);
cmds.setParent(self.optionsForm); //call to the setParent command to make optionsForm the current parent again.
self.color = cmds.colorSliderGrp( //add a color picker control, the colorSliderGrp command creates a color picker with a luminance slider next to it.
label='Polygon Color: '
);
cmds.formLayout(
self.optionsForm, e=True,
attachControl=(
[self.color, 'top',0,self.xformGrp]
),
attachForm=(
[self.color, 'left',0]
)
);
def applyBtnCmd(self, *args):
self.objIndAsCmd = { //create a dictionary to map the radio indices to different function pointers.
1:cmds.polyCube,
2:cmds.polyCone,
3:cmds.polyCylinder,
4:cmds.polySphere
};
objIndex = cmds.radioButtonGrp( //query the objType radio button group and use the dictionary to execute the command that corresponds to the currently selected index.
self.objType, q=True,
select=True
);
newObject = self.objIndAsCmd[objIndex]();
pos = cmds.floatFieldGrp( //apply translation to the newly created object's transform node
self.position, q=True,
value=True
);
rot = cmds.floatFieldGrp( //apply rotation to the newly created object's transform node
self.rotation, q=True,
value=True
);
scale = cmds.floatFieldGrp( //apply scale to the newly created object's transform node
self.scale, q=True,
value=True
);
cmds.xform(newObject[0], t=pos, ro=rot, s=scale);
col = cmds.colorSliderGrp( //apply the selected color to the new model's vertices.
self.color, q=True,
rgbValue=True
);
cmds.polyColorPerVertex(
newObject[0],
colorRGB=col,
colorDisplayOption=True
);
AR_PolyOptionsWindow.showUI();
Result:
Wednesday, March 13, 2013
Executing Commands with GUI Objects
class AR_OptionsWindow(object):
def showUI(cls): //provides a shortcut for creating and displaying a new window instance.
win = cls();
win.create();
return win;
def __init__(self):
self.window = 'ar_optionsWindow';
self.title = 'Meng\'s Window';
self.size = (546, 350);
self.supportsToolAction = False;
self.actionName = 'Apply and Close'; //set Apply and Close as the name that will display on the window's action button.Apply button and Close button do not need to set because most windows in Maya default to something like Apply and Close.
def actionBtnCmd(self, *args): //invoke the Apply behavior and then the Close behavior.
self.applyBtnCmd();
self.closeBtnCmd();
def applyBtnCmd(self, *args): pass //subclasses will need to override this method.
def closeBtnCmd(self, *args): //simply closes the window
cmds.deleteUI(self.window, window=True);
def commonMenu(self):
self.editMenu = cmds.menu(label='Edit');
self.editMenuSave = cmds.menuItem(
label='Save Settings',
command=self.editMenuSaveCmd //pass the pointer to the editMenuSaveCmd() method.
);
self.editMenuReset = cmds.menuItem(
label='Reset Settings',
command=self.editMenuResetCmd //pass the pointer to the editMenuResetCmd() method.
);
self.editMenuDiv = cmds.menuItem(d=True);
self.editMenuRadio = cmds.radioMenuItemCollection();
self.editMenuTool = cmds.menuItem(
label='As Tool',
radioButton=True,
enable=self.supportsToolAction,
command=self.editMenuActionCmd //pass the pointer to the editMenuToolCmd() method.
);
self.editMenuAction = cmds.menuItem(
label='As Action',
radioButton=True,
enable=self.supportsToolAction,
command=self.editMenuActionCmd //pass the pointer to the editMenuActionCmd() method.
);
self.helpMenu = cmds.menu(label='Help');
self.helpMenuItem = cmds.menuItem(
label='Help on %s'%self.title,
command=self.helpMenuCmd //pass the pointer to the helpMenuCmd() method.
);
def commonButtons(self):
self.commonBtnSize = ((self.size[0]-18)/3, 26); //specify a size for buttons as a tuple: (width,height)
//the commonBtnLayout assignment (rowLayout call) do not scale when adjust the size of the window. Sacling in the width of the window simply clips the buttons on the right side. To fix this problem, I use a more advanced layout: formLayout as below:
self.actionBtn = cmds.button(
label=self.actionName,
height=self.commonBtnSize[1],
command=self.actionBtnCmd
);
self.applyBtn = cmds.button(
label='Apply',
height=self.commonBtnSize[1],
command=self.applyBtnCmd
);
self.closeBtn = cmds.button(
label='Close',
height=self.commonBtnSize[1],
command=self.closeBtnCmd
);
cmds.formLayout( //the formLayout command in edit mode to configure how the buttons need to be attached in mainForm
self.mainForm, e=True,
attachForm=( //specifies edges on controls that pin to the bounds of the form we passed to the command.
[self.actionBtn, 'left', 5],
[self.actionBtn, 'bottom', 5],
[self.applyBtn, 'bottom', 5],
[self.closeBtn, 'right', 5],
[self.closeBtn, 'bottom', 5]
),
attachPosition=(
[self.actionBtn, 'right',1,33],//correspond to points approximately one-third of the form's width(33/100). The middle number(1) represents a pixel offset for the attachment.
[self.closeBtn, 'left',0,67]
),
attachControl=( //specifies edges on a control that attach to another control, with an optional offset.
[self.applyBtn, 'left',4,self.actionBtn],
[self.applyBtn, 'right',4,self.closeBtn]
),
attachNone=( //specifies edges on controls that should not attach to anything, and hence should not scale.
[self.actionBtn, 'top'],
[self.applyBtn, 'top'],
[self.closeBtn, 'top']
)
);
def helpMenuCmd(self, *args): //this method will load the Python Standard Library web site in a browser.
cmds.launch(web='http://docs.python.org/2/library/');
def editMenuSaveCmd(self, *args): pass //add four placeholder methods for child classes to override: editMenuSaveCmd(), editMenuResetCmd(), editMenuToolCmd(), editMenuActionCmd().
def editMenuResetCmd(self, *args): pass
def editMenuToolCmd(self, *args): pass
def editMenuActionCmd(self, *args): pass
def displayOptions(self): pass //This method will be overridden in child classes to actually display the contents in the main part of the options window.
def create(self):
if cmds.window(self.window, exists=True):
cmds.deleteUI(self.window, window=True);
self.window = cmds.window(
self.window,
title=self.title,
widthHeight=self.size,
menuBar=True
);
self.mainForm = cmds.formLayout(nd=100); //The mainForm attribute will be the parent for the next layout defined in the window (the row layout), which will be positioned at the top left of the form by default. The nd (numberOfDivisions) flag that passed to the formLayout command allows to use relative coordinates when specifying attachment positions of controls.
self.commonMenu();
self.commonButtons(); //add a call to commonButtons() in the create() method
self.optionsBorder = cmds.tabLayout( //add a border for the window
scrollable=True,
tabsVisible=False,
height=1
);
cmds.formLayout(
self.mainForm, e=True,
attachForm=(
[self.optionsBorder,'top',0],
[self.optionsBorder,'left',2],
[self.optionsBorder,'right',2]
),
attachControl=(
[self.optionsBorder,'bottom',5,self.applyBtn]
)
);
self.optionsForm = cmds.formLayout(nd=100);
self.displayOptions();
cmds.showWindow();
testWindow = AR_OptionsWindow();
testWindow.create();
Result:
Tuesday, March 12, 2013
Menus and menu items
class AR_OptionsWindow(object):
def __init__(self):
self.window = 'ar_optionsWindow';
self.title = 'Meng\'s Window';
self.size = (546, 350);
self.supportsToolAction = False;
def commonMenu(self):
self.editMenu = cmds.menu(label='Edit');
self.editMenuSave = cmds.menuItem(
label='Save Settings'
);
self.editMenuReset = cmds.menuItem(
label='Reset Settings'
);
self.editMenuDiv = cmds.menuItem(d=True); //create a divider
self.editMenuRadio = cmds.radioMenuItemCollection(); //call the radioMenuItemCollection command to initiate a sequence of items in a radio button group.
self.editMenuTool = cmds.menuItem(
label='As Tool',
radioButton=True,
enable=self.supportsToolAction
);
self.editMenuAction = cmds.menuItem(
label='As Action',
radioButton=True,
enable=self.supportsToolAction
);
self.helpMenu = cmds.menu(label='Help');
self.helpMenuItem = cmds.menuItem(
label='Help on %s'%self.title
);
def create(self):
if cmds.window(self.window, exists=True):
cmds.deleteUI(self.window, window=True);
self.window = cmds.window(
self.window,
title=self.title,
widthHeight=self.size,
menuBar=True
);
self.commonMenu();
cmds.showWindow();
testWindow = AR_OptionsWindow();
testWindow.create();
PS: Although this menu looks pretty good by Autodesk's standards, none of its menu items actually do anything yet. At this point, I should look into adding some commands. I will do it tomorrow following the book.
Saturday, March 9, 2013
Base Window Class
class AR_OptionsWindow(object): //create the AR_OptionsWindow class
def __init__(self):
self.window = 'ar_optionsWindow';
self.title = 'Meng\'s Window';
self.size = (546, 350);
def create(self):
if cmds.window(self.window, exists=True):
cmds.deleteUI(self.window, window=True);
self.window = cmds.window(
self.window,
title=self.title,
widthHeight=self.size
);
cmds.showWindow();
testWindow = AR_OptionsWindow(); //create a new instance of the AR_OptionsWindow class
testWindow.create(); //call its create() method.
Friday, March 8, 2013
Create Windows
import maya.cmds as cmds;
win = cmds.window(
'ar_optionsWindow',
title='Meng\'s First Window', //a title bar string,\is the escape character(转义字符)
widthHeight=(546,350) //window's size
);
cmds.showWindow(win); //to display the window
cmds.deleteUI(win, window=True); //delete exiting window
win = cmds.window(
'ar_optionsWindow',
title='Meng\'s Second Window',
widthHeight=(546,350)
);
cmds.showWindow(win);
Thursday, March 7, 2013
Moving UVs Tool
Just a simple example to show how to create your own marking menu.
1) In the main application window, open the marking menu editor by select window -- setting/preferences -- marking menu editor.
2) In the marking menus window, click the create marking menu button.
3) In the create marking menu window, the top displays a group of icons representing the different locations in the marking menu. RMB click the icon for the east item and select edit menu item from the context menu.
4) Enter the following lines in the commands input field:
python("import maya.cmds as cmds");
python("cmds.polyEditUV(u=1.0, v=0.0)");
5) In the label field, enter the word "Right" and press the save and close button.
6) In the marking menus window, edit the west menu item to have the following command input. Similar to the command created in step 5, this marking menu item will move the currently selected UVs one unit to the left.
python("import maya.cmds as cmds");
python("cmds.polyEditUV(u=-1.0, v=0.0)");
7) In the label field, enter the word "Left" and press the save and close button.
1) In the main application window, open the marking menu editor by select window -- setting/preferences -- marking menu editor.
2) In the marking menus window, click the create marking menu button.
3) In the create marking menu window, the top displays a group of icons representing the different locations in the marking menu. RMB click the icon for the east item and select edit menu item from the context menu.
4) Enter the following lines in the commands input field:
python("import maya.cmds as cmds");
python("cmds.polyEditUV(u=1.0, v=0.0)");
5) In the label field, enter the word "Right" and press the save and close button.
6) In the marking menus window, edit the west menu item to have the following command input. Similar to the command created in step 5, this marking menu item will move the currently selected UVs one unit to the left.
python("import maya.cmds as cmds");
python("cmds.polyEditUV(u=-1.0, v=0.0)");
7) In the label field, enter the word "Left" and press the save and close button.
8) Keeping the create marking menu window open, create a cube and enter UV editing mode (RMB+east)
9) Open the UV texture editor window (window -- UV texture editor).
10) Select all the cube's UVs.
11) In the create marking menu window, use the LMB in the test area (lower left) to try out the new marking menu on the cube's UVs.
12) Give this custom marking menu a name and save it.
Tuesday, March 5, 2013
Tips when designing for users
Communication
Identifying and communicating with your customers during your tool development process is a central task.
Observation
Observing your users, either directly or indirectly, can sometimes be more illuminating than an email or even a meeting. By observing your users directly, you can often help them uncover better solutions to a problem than they originally thought they wanted.
Ready, Set, Plan!
As you plan, remember so stay focused on the problem!
Simplify and Educate
You may frequently have to strike a balance between making a tool easy to use while also leaving it open for users to apply in corner cases or even possibly extend for special uses.
Identifying and communicating with your customers during your tool development process is a central task.
Observation
Observing your users, either directly or indirectly, can sometimes be more illuminating than an email or even a meeting. By observing your users directly, you can often help them uncover better solutions to a problem than they originally thought they wanted.
Ready, Set, Plan!
As you plan, remember so stay focused on the problem!
Simplify and Educate
You may frequently have to strike a balance between making a tool easy to use while also leaving it open for users to apply in corner cases or even possibly extend for special uses.
Monday, March 4, 2013
LOD Window (PyMEL)
I wrote a simple PyMEL example to manage the level of detail tagging for a game using PyMEL. The basic premise is that objects can be selected and have an attribute applied to them that determines the level of detail. Once this tag has been applied, objects can be selected and shown or hidden.
1)from lodwindow import LODWindow;
win = LODWindow();
win.create();
1)from lodwindow import LODWindow;
win = LODWindow();
win.create();
2)import pymel.core as pm;
for res in range(4)[1:]:
for i in range(3):
cyl = pm.polyCylinder(sa=res*6, n='barrel1');
cyl[0].tx.set(i*cyl[1].radius.get()*2);
cyl[0].tz.set((res-1)*cyl[1].radius.get()*2);
3)select all of the low-resolution cylinders in the back row, select the Low option from the LOD window dropdown menu, and press the Set LOD button. Repeat the same steps for the corresponding medium and high resolution cylinders.
Then, you could play around with all the buttons to your liking.
PS: here is the code in lodwindow.py
import pymel.core as pm
class LODWindow(object):
"""A pymel class for an level-of-detail editing window"""
## unique handle for the window
WINDOW_NAME = 'LOD Window'
def tag_nodes(self, node_list, res='Low'):
"""tag the supplied nodes with the supplied resolution"""
for node in node_list:
# add gameRes attribute if needed
if not node.hasAttr('gameRes'):
node.addAttr('gameRes', dataType='string')
node.gameRes.set(res, type='string')
def create(self):
# destroy the window if it already exists
try:
pm.deleteUI(self.WINDOW_NAME, window=True)
except: pass
# draw the window
with pm.window(self.WINDOW_NAME) as res_window:
with pm.columnLayout(adjustableColumn=True):
with pm.horizontalLayout():
pm.text(label='Resolution')
with pm.optionMenu() as self.res_menu:
pm.menuItem(l='Low')
pm.menuItem(l='Med')
pm.menuItem(l='Hi')
set_res_btn = pm.button(
label='Set LOD',
command=pm.Callback(self.on_set_res_btn)
)
pm.separator(style='in', height=4)
with pm.horizontalLayout() as h1:
pm.text(label='Low')
select_low_btn = pm.button(
label='Select All',
command=pm.Callback(
self.on_select_btn,
'Low'
)
)
toggle_low_btn = pm.button(
label='Toggle Visibility',
command=pm.Callback(
self.on_vis_btn,
'Low'
)
)
with pm.horizontalLayout() as h1:
pm.text(label='Medium')
select_med_btn = pm.button(
label='Select All',
command=pm.Callback(
self.on_select_btn,
'Med'
)
)
toggle_med_btn = pm.button(
label='Toggle Visibility',
command=pm.Callback(
self.on_vis_btn,
'Med'
)
)
with pm.horizontalLayout() as h1:
pm.text(label='High')
select_hi_btn = pm.button(
label='Select All',
command=pm.Callback(
self.on_select_btn,
'Hi'
)
)
toggle_hi_btn = pm.button(
label='Toggle Visibility',
command=pm.Callback(
self.on_vis_btn,
'Hi'
)
)
self.status_line = pm.textField(editable=False)
res_window.setWidthHeight((350,140))
def on_set_res_btn(self, *args):
"""action to execute when Set LOD button is pressed"""
# filter selection to only include meshes
selected = [
i for i in pm.ls(sl=True) if (
type(i.getShape())==pm.nt.Mesh)
]
res = self.res_menu.getValue()
if selected:
self.tag_nodes(selected, res)
self.status_line.setText(
'Set selection to resolution %s'%res
)
else:
self.status_line.setText('No selection processed.')
def on_select_btn(self, *args):
"""action to execute when Select All button is pressed"""
# get all the meshes in the scene
poly_meshes = [
i for i in pm.ls(
type=pm.nt.Transform
) if type(i.getShape())==pm.nt.Mesh
]
if poly_meshes:
# select anything with the gameRes attribute and the appropriate value
pm.select(
[i for i in poly_meshes if (
i.hasAttr('gameRes') and
i.gameRes.get()==args[0])
]
)
self.status_line.setText(
'Selected %s resolution meshes'%args[0]
)
else:
self.status('Nothing else selected')
def on_vis_btn(self, *args):
"""action to execute when the Toggle Visiblity button is pressed"""
# filter list to only include meshes
poly_meshes = [
i for i in pm.ls(type=pm.nt.Transform) if (
type(i.getShape())==pm.nt.Mesh)
]
if poly_meshes:
# get everything with the current resolution
res = [i for i in poly_meshes if (
i.hasAttr('gameRes') and i.gameRes.get()==args[0])
]
if res:
for j in res:
# flip visibility
j.visibility.set(1-int(j.visibility.get()))
PS: here is the code in lodwindow.py
import pymel.core as pm
class LODWindow(object):
"""A pymel class for an level-of-detail editing window"""
## unique handle for the window
WINDOW_NAME = 'LOD Window'
def tag_nodes(self, node_list, res='Low'):
"""tag the supplied nodes with the supplied resolution"""
for node in node_list:
# add gameRes attribute if needed
if not node.hasAttr('gameRes'):
node.addAttr('gameRes', dataType='string')
node.gameRes.set(res, type='string')
def create(self):
# destroy the window if it already exists
try:
pm.deleteUI(self.WINDOW_NAME, window=True)
except: pass
# draw the window
with pm.window(self.WINDOW_NAME) as res_window:
with pm.columnLayout(adjustableColumn=True):
with pm.horizontalLayout():
pm.text(label='Resolution')
with pm.optionMenu() as self.res_menu:
pm.menuItem(l='Low')
pm.menuItem(l='Med')
pm.menuItem(l='Hi')
set_res_btn = pm.button(
label='Set LOD',
command=pm.Callback(self.on_set_res_btn)
)
pm.separator(style='in', height=4)
with pm.horizontalLayout() as h1:
pm.text(label='Low')
select_low_btn = pm.button(
label='Select All',
command=pm.Callback(
self.on_select_btn,
'Low'
)
)
toggle_low_btn = pm.button(
label='Toggle Visibility',
command=pm.Callback(
self.on_vis_btn,
'Low'
)
)
with pm.horizontalLayout() as h1:
pm.text(label='Medium')
select_med_btn = pm.button(
label='Select All',
command=pm.Callback(
self.on_select_btn,
'Med'
)
)
toggle_med_btn = pm.button(
label='Toggle Visibility',
command=pm.Callback(
self.on_vis_btn,
'Med'
)
)
with pm.horizontalLayout() as h1:
pm.text(label='High')
select_hi_btn = pm.button(
label='Select All',
command=pm.Callback(
self.on_select_btn,
'Hi'
)
)
toggle_hi_btn = pm.button(
label='Toggle Visibility',
command=pm.Callback(
self.on_vis_btn,
'Hi'
)
)
self.status_line = pm.textField(editable=False)
res_window.setWidthHeight((350,140))
def on_set_res_btn(self, *args):
"""action to execute when Set LOD button is pressed"""
# filter selection to only include meshes
selected = [
i for i in pm.ls(sl=True) if (
type(i.getShape())==pm.nt.Mesh)
]
res = self.res_menu.getValue()
if selected:
self.tag_nodes(selected, res)
self.status_line.setText(
'Set selection to resolution %s'%res
)
else:
self.status_line.setText('No selection processed.')
def on_select_btn(self, *args):
"""action to execute when Select All button is pressed"""
# get all the meshes in the scene
poly_meshes = [
i for i in pm.ls(
type=pm.nt.Transform
) if type(i.getShape())==pm.nt.Mesh
]
if poly_meshes:
# select anything with the gameRes attribute and the appropriate value
pm.select(
[i for i in poly_meshes if (
i.hasAttr('gameRes') and
i.gameRes.get()==args[0])
]
)
self.status_line.setText(
'Selected %s resolution meshes'%args[0]
)
else:
self.status('Nothing else selected')
def on_vis_btn(self, *args):
"""action to execute when the Toggle Visiblity button is pressed"""
# filter list to only include meshes
poly_meshes = [
i for i in pm.ls(type=pm.nt.Transform) if (
type(i.getShape())==pm.nt.Mesh)
]
if poly_meshes:
# get everything with the current resolution
res = [i for i in poly_meshes if (
i.hasAttr('gameRes') and i.gameRes.get()==args[0])
]
if res:
for j in res:
# flip visibility
j.visibility.set(1-int(j.visibility.get()))
Sunday, March 3, 2013
PyMEL
1) Installing PyMEL
PyNodes:
written more pythonically, faster than maya.cmds counterparts, tracking identities in code much simpler and more reliable than working with object names, speed up node and attribute comparisons.
Advantages:
- Experienced Python programmers may have a much easier time learning PyMEL due to its object-oriented nature.
- PyMEL syntax tends to be a bit cleaner and creates neater code.
- Speed is greater in some cases due to API hybridization.
- The pymel package is open source, meaning studios can pull their own branch and add their own features and fixes.
Disadvantages:
- PyMEL's object-oriented nature can present a learning curve to MEL programmers. Switching from MEL to Python and learning a new programming paradigm at the same time can be daunting.
- PyMEL is not very widely adopted yet. A small community does mean that sometimes getting questions answered is difficult. Nevertheless, the development team is always eager to answer questions online.
- In some cases, speed can be degraded. Processing large numbers of components, for instance, can be much slower using PyMEL depending on the specific operation.
- Autodesk's ling-term roadmap for PyMEL is unclear.
- Because the pymel package is open source, it is possible (though rare) to get into a situation where a custom branch is quite divergent from the main one.
Saturday, March 2, 2013
INHERITANCE
In object-oriented programming (OOP),
inheritance is a way to reuse code of existing objects, or to establish a
subtype from an existing object, or both, depending upon programming language
support. In classical inheritance where objects are defined by classes, classes
can inherit attributes and behavior from pre-existing classes called base
classes, superclasses, or parent classes. The resulting classes are known as
derived classes, subclasses, or child classes. The relationships of classes
through inheritance gives rise to a hierarchy. In prototype-based programming,
objects can be defined directly from other objects without the need to define
any classes, in which case this feature is called differential inheritance.
Friday, March 1, 2013
Human Class
This little program I wrote for practicing basics of class implementation in Python including attributes, methods (static methods and class methods) and properties. Of course, all the statistics I wrote are fake, especially my height and weight~lol~
class Human(object):
"""A basic class to demonstrate some properties of Python classes"""
## constant factor to convert pounds to kilograms
kPoundsToKg = 0.4536;
## constant factor to convert feet to meters
kFeetToMeters = 0.3048;
def __init__(self, *args, **kwargs):
"""initialize data attributes from keyword arguments"""
self.first_name = kwargs.setdefault('first');
self.last_name = kwargs.setdefault('last');
self.height = kwargs.setdefault('height');
self.weight = kwargs.setdefault('weight');
def bmi(self):
"""compute body mass index assuming metric units"""
return self.weight / float(self.height)**2;
@staticmethod
def get_taller_person(human1, human2):
"""return which of the two instances is taller"""
if (human1.height > human2.height):
return human1;
else: return human2;
@classmethod
def create_meng(cls):
"""constructor to create Meng Xie"""
return cls(
first='Meng',
last='Xie',
height=6.083*cls.kFeetToMeters,
weight=158*cls.kPoundsToKg
);
# Begin properties
def fn_getter(self):
"""getter for full name"""
return '%s %s'%(self.first_name, self.last_name)
def fn_setter(self, val):
"""setter for full name"""
self.first_name, self.last_name = val.split()
## property for getting and setting the full name
full_name = property(fn_getter, fn_setter);
# End properties
# Alternate property defs for Maya 2010+
"""
@property
def full_name(self):
return '%s %s'%(self.first_name, self.last_name);
@full_name.setter
def full_name(self, val):
self.first_name, self.last_name = val.split();
"""
def __str__(self):
"""print the full name"""
return self.full_name;
def __repr__(self):
"""return a string that can be evaluated"""
return "Human(%s='%s', %s='%s', %s=%s, %s=%s)"%(
'first', self.first_name,
'last', self.last_name,
'height', self.height,
'weight', self.weight
);
Wednesday, February 27, 2013
Top 10 Autodesk Maya Python API Gotchas When Getting Started
I found an awesome article today from last year's GDC session by Kristion Middlemiss. I want to share it with you. It is really useful for Maya Python beginners.
10)
Not Understanding the
Difference between Python Script and Python API
Maya has four programming interfaces, two
of which are using the Python language. It’s important to know the distinction
between the terminology of Python Script and the Python API, because they each
access different functionality within Maya (with very little overlap). The
other two interfaces are Maya Embedded Language (MEL) and the C++ API.
9) Python Plug-in Disclosure
You cannot hide your Python API code from
users! If you’re a games developer looking to create Python plug-ins in-house
and not worried about protecting your code, then feel free to code away. But it
you want to protect, or commercialize and sell your hard work, you should
consider using C++ to create your plug-ins.
8) Must use Autodesk Specific Version of Qt
for Building PyQt
Building PyQt to work with Maya is a three
step process. First you need to compile Qt using the Autodesk's Modified Qt
Source Code (new for 2012) that is located on the Autodesk.com site. Then you
need to compile SIP (generates Python bindings for C++) against your built Qt
libraries. Last, you need to compile PyQt against your built SIP libraries.
Check the website for the correct versions or you could have some difficulties
down the road in your tools.
7) Awareness of Potential Python API
Performance Penalties
In certain situations when computation
involves complex or many API calls, the Python API may take a performance
penalty compared to the C++ API. This is because the Python API sits on top of
the C++ API. This results in an extra level of conversion through C++ and as
well as Python is an interpreted language which is slower by nature. For
example if you are thinking of doing a Python API shader, C++ will be the
better choice, but for most other tasks Python is perfect!
6) Working with Multiple Versions of Maya
Equals Multiple Versions of Python
If you are working on multiple versions of
Maya, then you need to keep in mind that you will need to have multiple version
of Python. Also if you are using PyQt, multiple versions of PyQt will need to
be installed on your computer. As nice as it would be to use the same version
of Python and PyQt for every version of Maya, it’s just not realistic as things
are always improving in the Python and PyQt releases. Keep yourself educated in
the Maya documentation for the versions that are used in each major release,
and on which platform.
5) Your Python API Code is not Working, and
you cannot find the Problem
When working with the Python API, like all
humans, you will have errors or code you want to debug; but how the heck do you
debug it? Unfortunately, there are no out-of-the-box tools in Maya, but there
are some good solutions available. Cyrille Fauvel from Autodesk has integrated
a Maya Python debugger into the Eclipse IDE. The details are available on
Autodesk.com, at the Maya Developer Center page. Dean Edmonds also from
Autodesk has integrated the PDB debugger into the Script Editor within Maya by
overriding the stdin and stout functions. Also, if you Google “Python in Maya”
group you will see other solutions.
4) Knowing when and how to Use MScriptUtil
Class
Many of the API methods in Maya require
that one or more of their parameters be passed as pointers or references
(return values can be pointers or references as well). Because Python does not
have the concept of references or pointers, you will need the utility class
called MScriptUtil for working with those pointers and references in Python.
MScriptUtil bridges this gap between Python and its underlying C++ API. When
you see the characters * or & in the documentation for simple data types
like integers and floats, think MScriptUtil!
3) The Reference Documentation is written
for C++, but you are Using Python
If you do not know C++, at first glance the
reference documentation will be very confusing!
But don’t worry; just put your eye blinders on to certain things when
reading it. Because Python is a more simplified language than C++ (but none the
less powerful), there are certain components in the documentation you can
ignore. For example the class MString and MStringArray are not available in
Python and instead you will work with strings as they are in native Python.
Another example is that there is no MStatus class in Python and instead you
will use standard Python error checking (try, except, catch). Try to think of
this as having a mini translator going on in your brain, you see MStatus, so
you just ignore it!
2) Trying to live in a Bubble and Teach
Yourself Everything
There is no possible way to memorize or
learn every single Maya API class, so don’t be shy to use the resources
available. As they say “Be Resourceful” and learn from others successes,
mistakes and questions. Maya has been around for over ten years, and there is
lots of API information out there (www.autodesk.com/developmaya). Also, you can
check out the new book “Maya Python for Games and Film: A Complete Reference
for Maya Python and the Maya Python API” by Adam
1) Mechtley and Ryan
Trowbridge.
Not Fully Grasping the Maya Architecture
and How the Python API Leverages It
If you don’t completely understanding the
Maya architecture, command engine and the Dependency Graph, you will run into
trouble as your tools get more complex. Take the time to learn these components
as they are very different than any other animation product. Because of this
complexity, it makes Maya more flexible and powerful than any other product.
Check out the free Maya Python API webcast recording located here
(www.autodesk.com/developmaya)
Subscribe to:
Posts (Atom)