Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association
''WHAT IS IT?''
*This page is all about how to do things in Maya using its scripting language "mel" (Maya Embedded Language). And in addition to that "elf" (mel's "Extended Layer Format"), for UI creation.
*This page mainly lists code samples, and "how-to's" with mel. There are no "scripts" to 'download'. The purpose of this page is to be a resource for creating mel procedures and scripts.
*More info on Maya can be found at the new Autodesk [[web site|http://usa.autodesk.com]] (yes, they bought Alias) or [[Highend3D.com|http://www.highend3d.com/maya/]].
*//This page used to be called ''"How to find stuff using mel"'' on other Wiki's://
**{{{www.openwiki.com}}} (which appears to be dead) & {{{warpcat.pbwiki.com}}} are some past sites.
''WHAT IT ISN'T:''
*Instructions on how to script\program. I'm presuming you already know a bit of that. But since there are plenty of examples listed, they may help.
''HISTORY:''
*This page started years ago as a personal web page with a bunch of random thoughts as I learned mel. So a lot of these notes are from the gamut of versions... 1.0 through currently, 8.5. You will see some pretty simple things listed, and some more complex things as well. I've thought about nixing the simple stuff, but for others that are learning, that would be counterproductive.
*I have posted it as a Wiki primarily because they're so easy to edit, I can add stuff very quickly while in development, from work, home, or off-site. I'm currently using [[TiddlyWiki|http://tiddlywiki.com/]] due to the fact it's so easy to search (based on the Tags), and update (since I only have to work on one tiddler at a time, rather than the whole page).
''DISCLAIMER''
*I can't be blamed for anything misrepresented on this page! If it doesn't work, no finger-waving my direction.
*Since I work on a Windows system, the majority of the command line stuff I call out to is Win-centric. If you're using Lunix\Unix\OSX etc, I'm fairly confident those args won't work.... (replace with what is appropriate for your OS)
''CONTRIBUTE YOUR BRAIN:''
*If you'd like to add something to this page, please [[email me|WarpCat]] with your addition, and I'll add it in if appropriate, thanks!
*I give credit to those I get info from, whenever possible.
*If you want to be a full-time contributor/editor, that's an option too. Let me know if you have interest.
''TAKE IT WITH YOU''
*Since this is a tiddlywiki, it's a 'self contained' wiki. No servers required to run it, no other installs needed (other than a valid web browser). Using the //right column// you can download this file (it's only one file), and run it off any other location (memory-stick, cd, hard drive, another web site, etc). Of course it won't update if you do that, so keep checking back on a regular basis.
----
And if you like how this TiddlyWiki works, check out the home page:
*http://tiddlywiki.com/ -- download TiddlyWiki
*http://tiddlyspot.com/ -- Get your TiddlyWiki hosted online for free (what you're using right now)
----
[[wiki help]]
While reading a book on the [[Processing|http://www.processing.org/]] language, they had a different take on the standard Maya {{{for}}} loop. A normal {{{for}}} loop has this format:
{{{
for (initialization; condition; change of condition){
statement;
statement;
...
}
}}}
So a simple example:
{{{
for($i=0;$i<5;$i++){
print($i + " ");
};
0 1 2 3 4
}}}
I learned that you can actually have //multiple// __initialization__ parameters, along with multiple __conditions__, and multiple __change of conditions__:
{{{
for($i=0, $j=5; $i<$j, $j<10; $i++, $j++){
print($i + " " + $j + "\n");
}
0 5
1 6
2 7
3 8
4 9
}}}
I'm really not too sure what I'm going to do with this info yet, but... I think it's good to know! Talking with other programmers, their reply was "oh yeh, //many// other languages do that". So, I guess I've been left out of the.... //loop//. (so bad, but so good).
Retrospective blog entry. mel wiki is created!
''Happy New Year!'' I added this new blog section.
I figured out how to move the "mel wiki" icon to the SiteTitle (very exciting). I also added my first bit of Python tagging, and setup a main [[PYTHON]] category. Still stuck on Maya 7 though...
Finally, made the 'Tags' tab always be visible in the right hand column, which should make it easier for users to navigate the site by default.
Man, there is a pile of rain coming down here in the San Francisco area!
Tiddlyspot.com is down for some reason, so all the latest updates are being made offline...
Tiddlywiki is back up again... I wonder what was going on...
Robots! Not directly related to mel, but I made my first couple of solar powered robots:
The Symet
[img[http://farm3.static.flickr.com/2232/2170294205_36506d0c98_m.jpg][http://www.flickr.com/photos/8064698@N03/sets/72157603647631805/]]
The Hexpummer
[img[http://farm3.static.flickr.com/2129/2174047884_2b66e455ea_m.jpg][http://www.flickr.com/photos/8064698@N03/sets/72157603656270159/]]
Added a new helpful section, the [[Visual Guide of UI Controls]]. Will be updating it slowly over time, in alphabetical order. Lets the user see the type of UI control before they build it... so you can make your UI's much faster!
Added the new [[All Subjects]] tiddler in the main menu (on the left) of the screen: It will show ALL the subjects (tiddlers) in the wiki. Now you can search through the categories, or just __one big page__ of subjects.
I recently got turned onto ''Processing'' through the ''Make'' blogs:
[img[http://processing.org/reference/environment/images/ide.gif]]
*Processing: http://www.processing.org/
*Make: http://www.makezine.com/ (which you should read all the time, FYI)
It's a cool (free) language that sort of 'wrappers up' Java. From their site:
<<<
"Processing is an open source programming language and environment for people who want to program images, animation, and interactions. It is used by students, artists, designers, researchers, and hobbyists for learning, prototyping, and production. It is created to teach fundamentals of computer programming within a visual context and to serve as a software sketchbook and professional production tool. Processing is developed by artists and designers as an alternative to proprietary software tools in the same domain."
<<<
----
Here's some fantastic examples by [[Jared Tarbell|http://www.complexification.net/programmer.html]]. One of the coolest things: He also gives the ''Processing'' source code away for each of them. That is awesome. Share the knowledge!
*http://www.complexification.net/gallery/
[img[http://www.complexification.net/timeline/WTsubstrate.jpg]]
[img[http://www.complexification.net/timeline/WThappyPlaceWideB.jpg]]
[img[http://www.complexification.net/timeline/WTintersectionMomentary.jpg]]
[img[http://www.complexification.net/timeline/WTboxFittingImg.jpg]]
(many more in the above link)
I added a new category (on the left) called [[TROUBLESHOOTING]]. It will take some time to organize other stuff into it, but it's a start.
Added a new section called [[Other Links]]. Thought I'd share some of the other web sites that I enjoy, that have some correlation to brains that enjoy Maya and mel.
[[A month or so ago|2008 02 18]] I talked about [[Processing|http://www.processing.org]]. Since then I've started to learn it, and have been posting some of my first sketches on [[Flickr|http://www.flickr.com/photos/8064698@N03/collections/72157604136742471/]]. Enjoy.
I've FINALLY gotten around to re-learning Python. In lite of that, I've made a new Python Wiki:
http://pythonwiki.tiddlyspot.com/
While there is still Maya-centric Python stuff on this page, non-Maya related Python stuff I"ll start posting over there. I'm still a Python noob, so there's nothing too amazing over there yet. It's like how this wiki started yeaaaars ago.
Finally got around to updating to tiddlywiki v2.4. No one will really care but me. But it's the new hottness.
I've finally made a blog to tie together my various wiki's and projects. It can be found here:
http://warpcat.blogspot.com/
A buddy of mine has started his own mel tiddlywiki as he learns the languange. Everyone should! ;)
http://brac.tiddlyspot.com/
I thought I'd try and experiment, and create a [[Guest Book|GuestBook]]. I really have no idea how many people use my wiki, how often it is viewed, etc. I do get an occasional email telling me how someone has used it, which gives me the warm-fuzzies.
If you'd like to 'sign' the [[Guest Book|GuestBook]], go to that link, and follow the directions. Since his wiki isn't open for public editing (one of the reasons I set it up that way: I used to get spammed all the time when anyone could edit it), you'll need to //email [[me|WarpCat]] your guest book comment. That can be a departure from regular website guest book signing, so I could see a reluctance on the part of some users.
As I said, this is an //experiment// ;-)
I've done some retagging when it comes to Python subjects: I already have an upper-case '[[PYTHON]]' category on the left-column. Before, //all// subjects that were either about Python specifically, or others that had Python code in their examples received that tag. I've now made a 'lowercase' '[[python]]' tag as well to help differentiate:
*Subjects that are //specifically related to Python features// will get the upper-case //category// tag.
*Subjects that have example Python code in them (or call to the {{{python}}} //mel// command), but aren't 'about' Python specifically, will get the 'lowercase tag'.
I think this will help make searching for //Python-specific// issues easier, and not clutter it with code simply written //in// Python.
Since I'm forcing myself to learn the API (very slowly), I added an [[API]] category. I'm mainly learning the API via Python, since I don't know much of c++ at all.
Added the [[INFO]] category. Haven't yet fully populated it, but it was missing: A place on subjects about querying information about things.
Added the [[HARDWARE]] category. Currently only has one item. But it's a cool item. I'm not sure how I'd use it, but I think it deserves its own category ;)
I really know nothing about the API yet... so this is just my starting scratchpad ;) A lot of this is just pulled directly from the Maya docs. And it should be noted this is entirely based on the @@//Python//@@ implementation of the API. No c++ stuff in here (other than helpful comparisons for learning the Python side of things).
----
I would be nice to link to this, but....
*"Maya Help -> Developer Resources -> API Guide -> ''Using the Maya Python API''"
You need to import Python's ~OpenMaya module to get at most of the functionality. Import others to do other things: like {{{OpenMayaMPx}}} if you're going to author a scripted pluigin
{{{
import maya.OpenMaya as om
import maya.OpenMayaMPx as ompx
}}}
----
Online Resources:
*http://groups.google.com/group/python_inside_maya
*[[This thread|http://groups.google.com/group/python_inside_maya/browse_thread/thread/9fa689ab6edf834a#]] in the 'python_inside_maya' Google groups.
*http://www.comet-cartoons.com/3ddocs/mayaAPI/
*http://www.rtrowbridge.com/blog/category/maya-python-api/
Good quality official documentation (seems better than what ships with Maya)
*http://download.autodesk.com/us/maya/2009help/API/
*http://download.autodesk.com/us/maya/2010help/API/
*http://download.autodesk.com/us/maya/2011help/API/
If you have an Autodesk support subscription, you can download their "__API and Game Dev__" podcasts and associated files here:
*http://subscription.autodesk.com -> Training -> Autodesk Maya -> Maya Video Podcasts -> Scripting and the API
This appears to be free, I have yet to actually look at it yet:
*http://download.autodesk.com/media/adn/Maya_Intermediate_Webcast.zip
----
''Differences between {{{c++}}} and {{{Python}}}'' (based on my ignorance of c++, take this with a grain of salt.)
*Pointers:
**See online reference for an overview: http://oreilly.com/catalog/pcp3/chapter/ch13.html
*Namespaces:
**c++ uses double colon {{{::}}} to define namespaces, Python uses single period '{{{.}}}':
{{{
# c++:
MGlobal::displayInfo()
# python:
MGlobal.displayInfo()
}}}
*Passing in args:
**In the c++ docs, if you see something like this:
{{{
MFnMesh ( const MObject & object, MStatus * ReturnStatus )
}}}
**What do the arg symbols mean?
**''{{{&}}}'' : The ampersand specify that the function is expecting an object to be passed in as a reference. The object can be modified, but not always.
**''{{{*}}}'' : The asterisk specifies a //pointer to an object// (in c++ terms). This object //will be modified// by the function. Often times you need to use the {{{OpenMaya.MScriptUtil}}} class to create pointers to be passed in.
**Some pseudo c++ code:
{{{
MObject foo;
MStatus result;
MFnMesh(foo, result);
if result.success():
do something involving foo maybe...
}}}
----
Constants / Enumerators:
*Any name with a {{{k}}} in the front is a //constant//, or an //enumerator//. These could be considered variables with pre-defined values.
*A listing can be found [[here|http://download.autodesk.com/us/maya/2009help/API/functions_eval_0x6b.html#index_k]].
Examples:
{{{
MItDag.kDepthFirst
MFn.kMesh
}}}
----
''API Classes:''
All Maya classes are contained in different Python libraries (which closely mirror the c++ ones). All the below classes in ''{{{bold}}}'' are children of the {{{maya}}} package (as in {{{maya.OpenMaya}}}).
''{{{maya.OpenMaya}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya.html]])
*@@{{{M}}}@@ classes are Maya's base utility classes. I'm going to go out on a limb here and guess that ''M'' stands for Maya. Most, although not all, of these classes are “Wrappers”. Examples of this class are: {{{MVector}}}, {{{MIntArray}}}, and so forth.
**@@{{{MObject}}}@@ is the base class of all objects. They are operated on via {{{MFn}}} function sets, and {{{MIt}}} iterators.
**@@{{{MPlug}}}@@ is the API interface for an attribute
**@@{{{MSelectionList}}}@@ handles everything to do with selections
**@@{{{MScriptUtil}}}@@ is key when doing API work from Python
**@@{{{MGlobal}}}@@ has a lot of random goodies. (could be called "m-misc.")
**many more...
*@@{{{MFn}}}@@ classes ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___m_fn.html]]) are wrappers to make accessing common object easier. 'Fn' = 'Function'. They wrapper "functionality". Any class with this prefix is a //''function set''// used to operate on {{{MObject}}}s of a particular type.
**@@{{{MFnDag}}}@@ / @@{{{MFnDependencyNode}}}@@ give you access to the DG/DAG.
**@@{{{MFnMesh}}}@@ makes mesh access easy.
**@@{{{MFnLambertShader}}}@@ exposes lots of material goodness
**many more...
*@@{{{MIt}}}@@ (mit) classes are for iterating though data (for looping through things). 'It' = 'Iterator'. They wrapper "iteration". These classes are iterators and work on ~MObjects similar to the way a function set (~{{{MFn}}} classes) does. For example, {{{MItCurveCV}}} is used to operate on an individual NURBS curve CV (there is no ~MFnNurbsCurveCV), or, iteratively, on all the CV's of a curve. They work on ~MObjects similar to the way a function set does.
**@@{{{MItDagNode}}}@@ iterates through the entire DAG (all nodes)
**@@{{{MITMesh}}}@@
**@@{{{MItPolygonMesh}}}@@ iterates through all aspects of a mesh.
**many more...
''{{{maya.OpenMayaMPx}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___m_px.html]])
*@@{{{MPx}}}@@ classes are for creating custom plugins. 'Px' = 'Proxy'. See [[Authoring Scripted Plugins via Python]]. They are API classes designed for you to derive from and create your own object types.
**@@{{{MPxCommand}}}@@ : Allows you to create custom //mel commands//.
**@@{{{MPxFileTranslator}}}@@ : custom file import/export
**@@{{{MPxNode}}}@@ : For making custom DG nodes.
**@@{{{MPxContext}}}@@ : Custom context creation
**@@{{{MPxLocatorNode}}}@@ : Custom locator type creation.
**many more...
''{{{maya.OpenMayaAnim}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_anim.html]]) Animation related classes
*Notes forthcoming...
''{{{maya.OpenMayaUI}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_u_i.html]]) Clases for manipulating the UI
*Notes forthcoming...
''{{{maya.OpenMayaFX}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_f_x.html]]) API modules for FX.
*Notes forthcoming...
''{{{maya.OpenMayaRender}}}''([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_render.html]]) API module for rendering.
*Notes forthcoming...
''{{{maya.OpenMayaCloth}}}'' API for cloth systems
*Notes forthcoming...
----
Example of ~OpenMaya ''M'' Class at work:
{{{
import maya.OpenMaya as om
vector1 = om.MVector(0,1,0)
vector2 = om.MVector(1,0,0)
vector3 = om.MVector(0,0,2)
newV = vector1 + vector2 + vector3
print "newVector %f, %f, %f " % (newV.x, newV.y, newV.z)
# newVector 1.000000, 1.000000, 2.000000
}}}
----
Maya's API object hierarchy is handled a bit differently from a standard OOP approach of object inheritance: In standard OOP, objects hold both the data (attributes) and functions (methods). Conceptually, the Maya API has a split, with attributes (the data) living on one side of the fence and methods (functions) acting on that data on the other. These are referred to as {{{function sets}}}, and in OOP these classes are called {{{functors}}}. Using some {{{c++}}} pseudo-code:
First, presume we have this object hierarchy, and function hierarchy (each is a class):
{{{
Object (superclass)
---- FooObj (subclass)
---- GooObj (subclass)
ObjFunc (superclass)
---- FooFunc (subclass)
---- GooFunc (subclass)
}}}
The {{{Object}}} objects contain data, plus member functions to access their data. The {{{ObjFunc}}} objects have corresponding member functions to access the {{{Object}}} data, but they themselves don't contain any of that data: The data is passed to them:
{{{
FooObj fooData;
FooFunc fooFn;
fooFn.setObj(fooData);
fooFn.doStuff();
}}}
As you can see, you make an {{{FooObj}}} type object called {{{fooData}}} (which inherits from {{{Object}}}) , and a corresponding {{{FooFunc}}} object (inheriting from {{{ObjFunc}}}) called {{{fooFn}}}. You pass {{{objData}}} into {{{objFunc}}}, and then do something to it. {{{FooFunc}}} objects only work on {{{FooObj}}} objects, {{{GooFunc}}} objects only work on {{{GooObj}}} objects.
It should be noted that while the function class define an interface for doing work on the data, it's the data classes that do the real work. Wacky!
''In Maya, you are only ever given access to an class called @@~MObject@@''. This is the "root" object type in Maya. Accessing all the hiden data in Maya is done through its {{function set}}} hierarchy. In actuality, ~MObject is just a //handle// (or a pointer) to another object inside the Maya core.
There is a root 'function' as well, which is @@''~MFnBase''@@.
----
Code snippets:
{{{
import maya.OpenMaya as om
# make us a cube:
mc.polyCube()
meshIter = om.MItDag( om.MItDag.kDepthFirst, om.MFn.kMesh )
while not meshIter.isDone():
dagPath = om.MDagPath()
meshIter.getPath(dagPath)
meshNode = om.MFnMesh(dagPath)
print meshNode
meshIter.next()
}}}
Details:
{{{MItDag}}} returns an //iterator//, which will iterate over the {{{DAG}}} (all nodes in the scene).
*{{{om.MItDag.kDepthFirst}}} is a traversal option that we don't care about (really?).
*{{{om.MFn.kMesh}}} is an optional filter type, and in this case it will filter meshes.
{{{
meshIter = om.MItDag( om.MItDag.kDepthFirst, om.MFn.kMesh )
}}}
{{{MItDag}}} iterator objects have a {{{.isDone()}}} method that can be used to check the loop
{{{
while not meshIter.isDone():
}}}
Create a {{{MDagPath}}} object, but current it doesn't do anything useful:
{{{
dagPath = om.MDagPath()
}}}
Based on the current mesh we're iterating over, update our {{{MDagPath}}} object ({{{dagPath}}}) to that:
{{{
meshIter.getPath(dagPath)
}}}
MAke a new {{{MFnMesh}}} object called {{{meshNode}}}, that is based on the current object {{{dagPath}}}:
{{{
meshNode = om.MFnMesh(dagPath)
}}}
Print the name of the {{{meshNode}}} object:
{{{
print meshNode
}}}
And iterate onto the next node:
{{{
meshIter.next()
}}}
Now, when this prints, presuming there was only a cube in the scene, it would print something //very usefull// like this:
{{{
<maya.OpenMaya.MFnMesh; proxy of <Swig Object of type 'MFnMesh *' at 0x132517b0> >
}}}
This is because we're printing the Python object name, not the Maya object name (coming soon...).
----
http://packages.python.org/MRV/
*"MRV is a multi-platform python development environment to ease rapid development of maintainable, reliable and high-performance code to be used in and around Autodesk Maya."
''~OpenMaya Python'':
{{{
import maya.OpenMaya as om
# Make a MSelectionList container object:
selList = om.MSelectionList()
# Fill our container object with the active selection:
om.MGlobal.getActiveSelectionList(selList)
# Make a list to gather the results:
sel = []
# Fill the result list with strings of the selection:
selList.getSelectionStrings(sel)
print sel
}}}
Man, that is clunky! :-P Compared to:
''Python'':
{{{
import maya.cmds as mc
sel = mc.ls(selection=True)
print sel
}}}
''MEL'':
{{{
string $sel[] = `ls - selection`;
print $sel;
}}}
(Referenced from 'Complete Maya Programming') Waaaay exciting!
{{{
# Python code
import maya.OpenMaya as om
}}}
{{{
# optionA:
transFn = om.MFnTransform()
transObj = transFn.create()
name = transFn.name()
}}}
{{{
# optionB:
transFn = om.MFnTransform()
transObj = transFn.create()
# Illustrating inheritance:
dagFn = om.MFnDagNode(transObj)
name = dagFn.name()
}}}
{{{
print name
# transform1
}}}
Class hierarchy:
*{{{MFnBase}}}
**{{{MFnDependencyNode}}}
***{{{MFnDagNode}}}
****{{{MFnTransform}}}
Presuming you need to call to //mel// someplace in your scripted plugin, the {{{maya.OpenMaya.MGlobal.executeCommand()}}} command will do the trick:
{{{
# Python code:
import maya.OpenMaya as om
om.MGlobal.executeCommand("cylinder")
}}}
Of course, in the case of the above example, you could call directly to the {{{cylinder()}}} //python// command via {{{maya.cmds.cylinder()}}}:
{{{
# Python code
import maya.cmds as mc
mc.cylinder()
}}}
But {{{MGlobal.executeCommand()}}} is obviously good if you physically need to call to //mel// for which there is no Python {{{maya.cmds}}} equivalent.
Pseudo code:
{{{
# Some scripted plugin...
import maya.OpenMaya as om
import maya.OpenMayaMPx as ompx
class Test(ompx.MPxCommand):
def doIt(self, args):
defaultArg = 5.0
index = args.flagIndex("d", "default")
if om.MArgList.kInvalidArgIndex != index:
defaultArg = args.asDouble(index+1)
# lots more code...
}}}
So what object type is {{{args}}}?
The passed in args are turned into an object type of {{{OpenMaya.MArgList}}}, so you have direct access to all of its methods (simple example above querying for a flag).
Docs:
http://download.autodesk.com/us/maya/2009help/API/class_m_arg_list.html
<<gradient horiz #ddddff #ffffff >>''WHAT IS IT?''
*This page is all about how to do things in Maya using its scripting language "mel" (Maya Embedded Language). And in addition to that "elf" (mel's "Extended Layer Format"), for UI creation. Starting with Maya v8.5, I've been adding [[Python|http://www.python.org]] examples as well, since you can now script with Python //in// Maya.
*This page mainly lists code samples, and "how-to's" with mel. There are no "scripts" for download. The purpose of this page is to be a resource for creating mel procedures and scripts.
*More info on Maya can be found in the [[Maya Links]] section.
*//This page used to be called ''"How to find stuff using mel"'' on other Wiki's://
**[[www.openwiki.com|http://www.openwiki.com/ow.asp?How+to+find+stuff+using+mel]] (which appears to be down half the time...) & [[warpcat.pbwiki.com|http://warpcat.pbwiki.com/HowToFindStuffUsingMel]] are some past sites.
''WHAT IT ISN'T:''
*Instructions on how to script\program. I'm presuming you already know a bit of that. But since there are plenty of examples listed, they may help.
''HISTORY:''
*This page started years ago as a personal web page with a bunch of random thoughts as I learned mel. So a lot of these notes are from the gamut of versions... 1.0 through currently, >insert current version here<. You will see some pretty simple things listed, and some more complex things as well. I've thought about nixing the simple stuff, but for others that are learning, that would be counterproductive.
*I have posted it as a Wiki primarily because they're so easy to edit, I can add stuff very quickly while in development, from work, home, or off-site. I'm currently using [[TiddlyWiki|http://tiddlywiki.com/]] due to the fact it's so easy to search (based on the Tags), and update (since I only have to work on one tiddler at a time, rather than the whole page).
''DISCLAIMER''
*I can't be blamed for anything misrepresented on this page! If it doesn't work, no finger-waving my direction.
*Since I work on a Windows system, the majority of the command line stuff I call out to is Win-centric. If you're using Lunix\Unix\OSX etc, I'm fairly confident those args won't work.... (replace with what is appropriate for your OS)
''CONTRIBUTE YOUR BRAIN:''
*If you'd like to add something to this page, please [[email me|WarpCat]] with your addition, and I'll add it in if appropriate, thanks!
*I give credit to those I get info from, whenever possible.
*If you want to be a full-time contributor/editor, that's an option too. Let me know if you have interest.
''TAKE IT WITH YOU''
*Since this is a tiddlywiki, it's a 'self contained' wiki. No servers required to run it, no other installs needed (other than a valid web browser). Using the //right column// you can download this file (it's only one file), and run it off any other location (memory-stick, cd, hard drive, another web site, etc). Of course it won't update if you do that, so keep checking back on a regular basis.
----
And if you like how this TiddlyWiki works, check out the home page:
*http://tiddlywiki.com/ -- download TiddlyWiki
*http://tiddlyspot.com/ -- Get your TiddlyWiki hosted online for free (what you're using right now)
----
Images hosted by [[flickr|http://www.flickr.com/]]>>
All of these will open the documentation in a web browser.
{{{
# Python code
import maya.cmds as mc
# show Python docs for xform:
mc.help('xform', language='python', doc=True)
# show mel docs for ls:
mc.help('ls', language='mel', doc=True)
# show the global documentation for mel:
mc.help(documentation=True, language='mel')
# show the global documentation for Python:
mc.help(documentation=True, language='python')
# show the global documentation for the API:
mc.showHelp("API/main.html", docs=True)
# show the global documentation for node types:
mc.showHelp("Nodes/index.html", docs=True)
}}}
When you access the Maya menu command here:
*Skin -> Edit Smooth Skin -> Paint Skin Weights Tool [Options]
What code does it execute?
That menu command calls to this {{{runTimeCommand}}}:
{{{
ArtPaintSkinWeightsToolOptions;
}}}
Which in turn is a wrapper for this command:
{{{
artAttrSkinToolScript 3;
}}}
Which is a global procedure called to in this script (on Vista 64):
{{{
C:\Program Files\Autodesk\Maya<version>\scripts\others\artAttrSkinToolScript.mel
}}}
Which has this code:
{{{
// Creation Date: May 2000
//
// Procedure Name:
// artAttrSkinToolScript
//
// Description:
// Create wrapper around the artAttrSkinPaintCtx command.
//
// Input Arguments:
// 4 ==> enter the tool (create if necessary)
// 3 ==> property sheet box
//
// Return Value:
// None.
//
global proc string artAttrSkinToolScript( int $setToTool )
//
// Description :
// 4 ==> enter the tool
{
// Deformer Paint Weight Tool.
string $tool = "artAttrSkinContext";
makePaintable -activateAll false;
makePaintable -activate true "skinCluster" "*";
if( ! `artAttrSkinPaintCtx -exists $tool` ) {
rememberCtxSettings `artAttrSkinPaintCtx -i1 "paintSkinWeights.xpm" -whichTool "skinWeights" $tool`;
}
setToolTo $tool;
if( 3 == $setToTool ) {
toolPropertyWindow;
}
else if( 4 != $setToTool ) {
warning( (uiRes("m_artAttrSkinToolScript.kWrongInput")));
}
return $tool;
}
}}}
Below are a list of //all// the subjects (tiddlers) in the wiki. Note that there will be ones for site maintenance etc, that should be disregarded.
----
<<list all>>
I really know nothing about this yet... so this is just my starting scratchpad ;) A lot of this is just pulled directly from the Maya docs.
More notes over on [[API basics]]
Also see official examples online here:
http://download.autodesk.com/us/maya/2009help/API/examples.html
Search for {{{.py}}} extensions.
----
Notes:
*May be good to previx all scripted plugin .py files with something... like "sp", so you know they're different from regulard Python scripts? Scripted Plugins can be loaded in the Maya plugin manager, regular Python scripts can't.
----
If you have Autodesk gold support, you can access their podcasts [[here|http://subscription.autodesk.com/sp/servlet/elearning/course?catID=11484791&cfID=Autodesk+Maya&siteID=11564774]]. There are several called (such as) "API & Game Dev I" and "API & Game Dev II" by Nathan Martz, lead programmer over at Double Fine Productions, that cover a lot of the API basics (starting with the second video).
----
Writing a scripted plug-in requires the definition of some specialized functions within the plug-in. The scripted plug-in must:
*Define initializePlugin and uninitializePlugin entry points.
*Register and unregister the proxy class within these entry points.
*Implement creator and initialize methods (as required) which Maya calls to build the proxy class.
*Implement the required functionality of the proxy class. This requires importing the necessary modules.
----
Plugins have different 'flavors', or classes.
Plugins have different categories: (all are sub-classes of {{{maya.OpenMayaMPx}}})
http://download.autodesk.com/us/maya/2009help/API/group___m_px.html
>In the documentation, the section listed as "__Public Member Functions__" are the superclass methods at your disposal when writing your own commands.
*{{{MPxCommand}}} : make custom mel commands.
**http://download.autodesk.com/us/maya/2009help/API/class_m_px_command.html
**Can be created to run in {{{creation}}}, {{{query}}}, and {{{edit}}} modes.
*{{{MPxFileTranslatorCommand}}} : custom file format import/export, read/write. These interface with Maya's UI, allowing you to select the custom file types in file browsers, etc...
**http://download.autodesk.com/us/maya/2009help/API/class_m_px_file_translator.html
*{{{MPxNode}}} : For making custom DG nodes.
**http://download.autodesk.com/us/maya/2009help/API/class_m_px_node.html
**{{{MPxLocator}}} : A type of {{{MPxNode}}} for custom drawing
**http://download.autodesk.com/us/maya/2009help/API/class_m_px_locator_node.html
*{{{MPXContext}}} : (and {{{ContextCreatorCmd}}}) : For handling mouse\user inputs
**http://download.autodesk.com/us/maya/2009help/API/class_m_px_context.html
* more!
Each category has similar basic structure, but with extra rules.
----
''Top level overview of plugin structure:''
*Plugin class/classes implements core functionality.
**Derives from an ~MPx class. Each type of plugin is implemented as python class that will be derived from.
**Implements functionality by overriding interface functions. Maya gives us a template, and we override that template based on our needs.
**Will contain any instance data, helper funcs, etc.
*Creator Function:
**Returns a new instance of the class
**Needs to be one per plugin class being authored.
*'doIt' function:
**What actually does the work in the instance of the plugin object.
*Registration/Deregistration:
**If multiple plugins classes are being authored in the same Python module, they can share the same regis\deregis block
**Tells Maya: about new plugins, their types, hooks needed to instance them.
Common Python plugin modules:
{{{
import maya.OpenMaya as om
# All plugin functionality lives here:
import maya.OpenMayaMPx as ompx
}}}
----
''Very simple scripted plugin''
I pulled this entirely from the Podcast '//Maya API For Game Development Session3: Of Plugins and Python//' by Nathan Martz (which I link to above). I've added my own comments, changed it a wee bit, and fixed a few bugs. Presuming you add it to Maya's plugin path, you should see it in the Plugin Manager.
{{{
# spHelloWorldCmd.py
import sys
import maya.OpenMayaMPx as ompx
# -----------------------------------
# Plugin code
# We're building a *command* (rather than say, a DAG node), so we
# instance the MPxCommand object:
class helloWorldCommand(ompx.MPxCommand):
# plugin initializer:
def __init__(self):
# initalize our superclass:
ompx.MPxCommand.__init__(self)
# 'Creator Function' needs to return a new instance of this type of class.
# This is a static method, it belongs to the class, not an instance
# of the class (note: there is no 'self' arg). '@staticmethod' is a 'Python
# decorator'. It should also be noted that this method could be authored
# outside the class as a function.
@staticmethod
def cmdCreator():
return ompx.asMPxPtr(helloWorldCommand())
# Define what our mel command name is. Assigned to a static member
# variable, meaning it's a class attribute, not an instance attribute.
commandName = "helloWorld"
# Define the instance method that will actually do work when the plugin
# command is called:
def doIt(self, argList):
print "Hello World!",
# -----------------------------------
# Plugin registration\degregistration block
# initializePlugin & uninitializePlugin are *required functions*
def initializePlugin(mobject):
# create a MFnPlugin
fnPlugin = ompx.MFnPlugin(mobject)
try:
# Try to register our plug by passing in the commandname, and the command creator:
# we use the '.registerCommand()' method since we're trying to build a
# command. Other plugin types use different registration functions. The
# args do differ, check docs.
fnPlugin.registerCommand(helloWorldCommand.commandName, helloWorldCommand.cmdCreator)
except(Exception), e:
sys.stderr.write(e)
sys.stderr.write("\nhelloWorldCommand registration failed\n")
pass
def uninitializePlugin(mobject):
# create a MFnPlugin
fnPlugin = ompx.MFnPlugin(mobject)
try:
# Try to deregister our plug by passing in the commandname, and the command creator:
fnPlugin.deregisterCommand(helloWorldCommand.commandName)
except(Exception), e:
sys.stderr.write(e)
sys.stderr.write("\nhelloWorldCommand deregistration failed\n")
pass
}}}
After this is loaded in the plugin manager, you should be able to type at the //mel// prompt:
{{{
helloWorld;
// Hello World!
}}}
----
''{{{doIt()}}}''
The {{{doIt()}}} function in your plugin class appears to be a special reserved name: It is what is executed when your new command is ran. Because this allows us to make new //mel commands//, which can take flags and flag arguments, there is a method for authoring those as well. From another example:
{{{
def doIt(self,argList):
syntax = om.MSyntax()
pathFlagShort = "p"
pathFlagLong = "path"
syntax.addFlag(pathFlagShort, pathFlagLong, om.MSyntax.kString)
parser = om.MArgParser(syntax, argList)
if parser.isFlagSet(pathFlagShort):
path = parser.flagArgumentString(pathFlagLong, 0)
doSomeWork(path)
}}}
In the above example, we add a flag called {{{path}}}, and it's short name {{{p}}} (because as you are aware, flags can have both versions).
*First, an {{{MSyntax}}} node is created, to which the two flags are added, as strings (based on the constant {{{om.MSyntax.kString}}}).
*Then a {{{MArgParser}}} object is created, at which point the code checks to see a flag is set.
*If the flag is set, it queries the path value (passed into the command as the flag arg), and then some code is ran on that path.
----
Snippet: Returning results
If you have a plugin that you can query help on, and you make your own custom help string, you can return it this way (for example):
{{{
# This is called inside of doIt(), when querying your argument data:
if argData.isFlagSet(MyCmd.helpFlag):
ompx.MPxCommand.setResult(MyCmd.helpText)
return om.MStatus.kSuccess
}}}
Presuming you have a selected hierarchy like below, how can you get back all the 'parent' nodes. Meaning, any transform node that has a child?
*group1
**some poly mesh A
**some poly mesh B
***some poly mesh C
*group2
**some poly meshD
{{{
string $selObjects[] = `ls -sl`;
string $parents[] = `listRelatives -p $selObjects`;
$parents= `stringArrayRemoveDuplicates $parents`;
print $parents;
//group1
//group2
//some poly mesh B
}}}
*Thanks to a post from Johan Ramstrom
Also see:
[[Based on a selection of nodes, how can I return only the 'groups'?]]
Presuming you have a selected hierarchy like below, how can you get back just the "group" nodes. Meaning, transforms with no shapes? (just the group# nodes)
*group1
**some poly mesh A
**some poly mesh B
***some poly mesh C
*group2
**some poly meshD
{{{
string $selObjects[] = `ls -sl`;
string $groups[]; clear $groups;
for($i=0;$i<size($selObjects);$i++)
{
string $shapes[] = `listRelatives -s $selObjects[$i]`;
if(size($shapes)==0)
$groups[size($groups)] = $selObjects[$i];
}
print $groups;
// group1
// group2
}}}
Also see:
[[Based on a selection of nodes, how can I return back all the 'parents'?]]
{{{lsThroughFilter}}}
For example, find all the default shading groups and materials in a new scene:
{{{
string $nodes[] = `lsThroughFilter DefaultMaterialsFilter`;
// Result: lambert1 particleCloud1 //
}}}
Also see:
*[[How can I query all the default itemFilters in Maya?]]
*[[How can I use itemFilter or itemFilterAttr?]]
<<gradient horiz #ffffff #ddddff #8888ff >>
The mel wiki blog.
*[[2010 01 20]] - Added the HARDWARE category.
*[[2010 01 13]] - Added the INFO category.
*[[2009 05 13]] - Added API category
*[[2008 11 19]] - Updated Python tagging
*[[2008 08 14]] - Created the Mel Wiki Guest Book
*[[2008 07 05]] - A new mel tiddlywiki online!
*[[2008 06 08]] - Blog about my new blog (blog blog blog)
*[[2008 05 21]] - tiddlywiki upgraded
*[[2008 05 20]] - Python Wiki!
*[[2008 03 31]] - More Processing.
*[[2008 03 11]] - Added 'Other Links'
*[[2008 03 07]] - Added the TROUBLESHOOTING category.
*[[2008 02 18]] - Processing!
*[[2008 01 29]] - Added [[All Subjects]] tiddler.
*[[2008 01 08]] - New feature: [[Visual Guide of UI Controls]]
*[[2008 01 07]] - Tiddlyspot back up. And... robots!
*[[2008 01 04]] - rain! Tiddlyspot down...
*[[2008 01 02]] - New [[PYTHON]] category, update wiki UI formatting.
*[[2007 04 27]] - mel wiki is born
Where can you download Maya's 'bonus tools'?
http://area.autodesk.com/bonus_tools
The below code was originally on [[Brian Ewerts 'match' web page|http://ewertb.soundlinker.com/mel/mel.094.htm]], which went down for a while. It appears to be back up! But, I've left this info in here.
http://ewertb.soundlinker.com/maya.htm
*Strip component
{{{
string $node = "pCube1.f[2]";
string $no_component = `match "^[^\.]*" $node`;
// Result: "pCube1" //
}}}
*Extract component or attribute, with '.'
{{{
string $node = "pCube1.f[2]";
string $component = `match "\\..*" $node`;
// Result: ".f[2]" //
string $nodeAttr = "blinn1.color";
string $attrName = `match "\\..*" $nodeAttr`;
// Result: ".color" //
}}}
*Extract attribute name, without '.'
{{{
string $node = "pCube1.f[2]";
string $component = `substitute "^[^.]*\\." $node ""`;
// Result: "f[2]" //
string $nodeAttr = "blinn1.color";
string $attrName = `substitute "^[^.]*\\." $nodeAttr ""`;
// Result: "color" //
}}}
*Extract parent UI control from full path
{{{
string $uiControl = "OptionBoxWindow|formLayout52|formLayout55|button6";
string $uiParent = `substitute "|[^|]*$" $uiControl ""`;
// Result: OptionBoxWindow|formLayout52|formLayout55 //
}}}
*Strip trailing CR, if any. This is useful when processing text input read from a file using 'fgetline'. (or you could just use the 'strip' command, but the concept is important)
{{{
string $input = "line\n";
$string $line = `match "^[^(\r\n)]*" $input`;
// Result: "line" //
}}}
*Extract directory from path Note that this leaves a trailing slash. This is typically desired, as it makes it easy to simply append a filename, and the slash is required for the ‘getFileList?’ command to return anything useful.
{{{
string $path = "C:/AW/Maya5.0/bin/maya.exe";
string $dir = `match "^.*/" $path`;
// Result: "C:/AW/Maya5.0/bin/"
}}}
*Extract file from path
{{{
string $path = "C:/AW/Maya5.0/bin/maya.exe";
string $filepart = `match "[^/\\]*$" $path`;
// Result: "maya.exe"
}}}
*Strip numeric suffix
{{{
string $node = "pCube1|pCubeShape223";
string $noSuffix = `match ".*[^0-9]" $node`;
// Result: "pCube1|pCubeShape"
}}}
*Extract numeric suffix
{{{
string $node = "pCube1|pCubeShape223";
string $suffix = `match "[0-9]+$" $node`;
// Result: "223" //
}}}
*Extract short name of DAG or control path
{{{
string $dagPath = "pCube1|pCubeShape223";
string $shortName = `match "[^|]*$" $dagPath`;
// Result: pCubeShape223 //
}}}
As of Maya 2008, when you hit {{{tab}}} in the Python Script Editor, it adds an 'actual tab' ({{{\t}}}), that is '8 spaces' long. Python's [[PEP8 Style Guide|http://www.python.org/dev/peps/pep-0008/]] says that tabs in general should be avoided (but if you have to use them, they should be '4 spaces' in length). You should be using '4 spaces' in place of tabs (most external IDE's have the ability to insert spaces instead of tabs when the tab button is pressed). So the Script Editor has this //completely wrong//.
After searching online, and pinging various user groups, this appears to be a fix value. In fact, this was reported as a bug to Autodesk in Maya8.5, and I'm told it's //still// not fixed in Maya2009. :-( Another reason to use an external IDE.
This tool has actually been around for some time, and while I've never used it, my friends have and vouched for it's abilities:
[[JoystickServer|http://www.geocities.com/jlimperials/realtime.html]] by Jorge Imperial
Download from Highend3d.com [[here|http://www.highend3d.com/f/1086.html?sheet=bg4]]
It basically turns your joystick into a mocap device, which Maya can then interface with.
[img[http://static.highend3d.com/downloadsimages/1086/screen1.jpg]]
If you have variable data defined in mel, how can you get that into Python?
For example, you want to get the name of the global playback slider, which you know is stored in a ''mel'' string variable:
{{{
# Python code
import maya.mel as mm
gPlayBackSlider = mm.eval("$g = $gPlayBackSlider")
print gPlayBackSlider
}}}
{{{
MayaWindow|mayaMainWindowForm|TimeSliderForm|formLayout16|formLayout17|formLayout46|frameLayout2|timeControl1
}}}
As you can see, you need to pass the variable you're after into another temp variable ({{{$g}}}), so Python can catch the return from it.
----
Also see:
*[[Casting variable data from Python to Maya]]
I've recently ran across a nasty... bug, in the ~Maya-Python integration. It appears, that if you have a Python module return a list, Maya will convert it to an array. //That's// as expected. However, if the Python list is //empty//, Maya converts it to a //string// with two brackets inside of it: "{{{[]}}}", rather than converting it to an empty array.
<<<
I should note, as an update, that this initial presumption (that this is a 'bug') is wrong: Maya variables are always typed (''{{{string}}}''{{{ $foo = "word";}}}), while Python's aren't, they're just pointers ({{{foo = "word"}}}). Maya has to auto-detect what type of data (string, float, etc) exists in the passed-in Python object and make a determination for what the conversion should be. If it just gets an 'empty list', it really has no idea what type of Maya array (float, string, int, vector) it should be.
<<<
Obviously, this can cause some real headaches if you're trying to capture expected data from Python in Maya.
I found a fix, that I'm... not.... very... happy with. Explained below the example.
Example: First, a Python module that will return different types of data, based on the passed in arg:
{{{
# Python code
def variableReturn(varType):
if varType == "str":
return "string"
if varType == "listSingle":
return ["value1"]
if varType == "listMult":
return ["value1", "value2"]
if varType == "listNone":
return [None]
if varType == "listEmpty":
return []
}}}
Now, some mel code to capture that data. I've already setup the Maya variable types to be the correct ones based on what the Python module spits out... but you can see that one of them is clearly wrong:
{{{
// Mel code:
string $str = python("variableReturn('str')");
string $lstSingle[] = python("variableReturn('listSingle')");
string $listMult[] = python("variableReturn('listMult')");
string $listNone[] = python("variableReturn('listNone')");
string $listEmpty = python("variableReturn('listEmpty')");
print ("Type of 'str' : " + `whatIs "$str"` + "\n");
print ("Type of 'listSingle' : " + `whatIs "$lstSingle"` + "\n");
print ("Type or 'listMult' : " + `whatIs "$listMult"` + "\n");
print ("Type of 'listNone' : " + `whatIs "$listNone"` +"\n");
print ("Type of 'listEmpty' : " + `whatIs "$listEmpty"` + " <--- aaaagh!!!\n");
}}}
Prints:
{{{
Type of 'str' : string variable
Type of 'listSingle' : string[] variable
Type or 'listMult' : string[] variable
Type of 'listNone' : string[] variable
Type of 'listEmpty' : string variable <--- aaaagh!!!
}}}
As you can see, the workaround is to have your Python code return a list with a 'None' object in it:
{{{
# Python
return [None]
}}}
Presumably, you'd do some sort of test before hand in your Python code to know to return things this way:
{{{
# Python
if len(myVal) == 0:
myVal = [None]
return myVal
}}}
Of course, in Maya you'd then have to detect if the first item of the list was empty, if it's size was 1:
{{{
// mel
string $result[] = python("myPyFunc()");
if(size($result) && $result[0] != ""){
// start doing something...
}
}}}
----
''Update:''
After reading through the Maya docs on Python integration, this may make a bit of sense:
| ''Python Return Value'' | ''MEL Conversion'' |
| string | string |
| unicode | string |
| int | int |
| float | float |
| list containing numbers, including at least one float | float[] |
| list containing only integers or longs | int[] |
| list containing a non-number | string[] |
| anything else | string |
I'm guessing an 'empty list' falls into the 'anything else' category, and thus converts it to a "string". Even though our above example returns an empty list, and we //presume// to have string data, it could actually contain float data, int data, etc. But how is Maya to know this if all it gets is an un-typed empty list? It doesn't, so it turns it into a string.
----
Also see:
*[[Casting variable data from Maya to Python]]
Use for finding attribute information based on a node class. Meaning, can find attributes on "transform" nodes, on "joint" nodes, etc. Can't be used to query info on nodes that you specify by name, like attributeQuery does.
*list internal attrs
*list hidden attrs
*list multi attrs
*list leaf attrs
*list writeable attrs
*list boolean attrs
*list enumerated attrs
*list "UI friendly" attr names rather than Maya Ascii names.
On an individual attribute on an individual node, use to find:
*If the attribute exists
*If it is a mulit-attribute
*find the range of its value
*find any "sibling" attributes
*find its parent attribute
*find any children attributes
*and several more....
{{{
// how can I query if an attr exists?
attributeQuery -ex -n "objectName" "attrName";
}}}
Find\set the current LINEAR (meters, feet, etc.) units, ANGULAR (degrees, radians) units, or FRAME RATE (film, video, etc.) that Maya uses.
Given an object, will return lists of:
*Readable attrs
*Writable attrs
*Array attrs
*Visible attrs
*Connectable attrs
*Keyable attrs
*Locked\unlocked attrs
*User defined attrs
*And many others....
*List both "source" and "destination" connections:
{{{
// this will only return object names thaat connect to your object:
listConnections "object";
// this will list object.attr namess that connect to your object:
listConnections -p 1 "object";
// this will list in pairs, your object.attr's and the connecting object.attr's:
listConnections -p 1 -c 1 "object";
// this will list in pairs, your specifieed object.attr, and it's connecting object.attr's:
listConnections -p 1 -c 1 "object.tx";
}}}
*List "source" or upstream connections only:
{{{
// this will only return object names:
listConnections -s 1 -d 0 "object";
// this will list object.attr names of soource connections to your object:
listConnections -s 1 -d 0 -p 1 "object";
// this will list in pairs, your object.attr's connecting to the source's object.attr's:
listConnections -s 1 -d 0 -p 1 -c 1 "object";
// this will list in pairs, your specified object.attr, and it's connecting source object.attr's:
listConnections -s 1 -d 0 -p 1 -c 1 "object.tx";
}}}
*List "destination" or downstream connections only:
{{{
// this will only return object names:
listConnections -s 0 -d 1 "object";
// this will list object.attr names of deestination connections to your object:
listConnections -s 0 -d 1 -p 1 "object";
// this will list in pairs, your object.attr's and connecting destination object.attr's:
listConnections -s 0 -d 1 -p 1 -c 1 "object";
// this will list in pairs, your specified object.attr, and its connecting destination object.attr's:
listConnections -s 0 -d 1 -p 1 -c 1 "object.tx";
}}}
*Important: Adding an attribute value to any of the queries filters the query to only check only on that object.attr.
*The "-t" "type" flag can also be used to query connections to only certain types of nodes. Very handy.
Query or set the current frame range.
Also: Query\set: playback speed, frames rate, and loopability.
Maya8 and later: Set the max playback speed
On a polygonal surface, how can I find component level items?
Object\component count: vertex, edge, face, uv, triangle, shell?
Bounding box size, uvSetName?, or surface area?
{{{polyEvaluate}}}
How can I find the specific poly components connected to another poly component?
Find non-manifold vertices, edges, and lamina faces?
How can I find the "normal" value of face?
{{{polyInfo}}}
*Example, get a face's normal value:
{{{
polyInfo -fn;
// however this will return a string that will need to be tokenized.
}}}
''Commands'': (WIP)
*[[commands|Commands]]
**__Can accept__ (when executed, depending on the command):
***command [[flags|Flags]] (& flag arguments), command [[arguments|Arguments]]
**__Can provide__ (when executed, depending on the command):
***[[return values|Return Values]]
----
*A command is one of the fundamental building blocks that make up mel.
*Many commands can have flags listed after them, or arguments, or both, or neither (it depends on the individual command):
**Arguments accept variables you pass into them, and expose those variables to the inner-workings of the command. They are like doors that allow external data into a command.
**Flags have several different behaviors: Some flags, if present, modify behavior of the command (their presence acts like a boolean switch, on|off), but take no arguments. Other flags accept arguments.
**//Usually//, command arguments and flags are optional. Sometimes certain commands require certain arguments. However, if a flag is used, and takes its own arguments, they are required as well. This is one way commands differ from scripted procedures: If a procedure defines an argument, then the argument is always required. And procedures can't be authored with flags.
**Command flags are listed first, then command arguments. It is the way of things. Of course if a flag has its own arguments, they're entered immediately after the flag.
*Using the below command, it will open a web browser to list all of the Maya commands. {{{-doc}}} is a flag (which takes no arguments), and {{{"index.html?MEL"}}} is an argument to that //command// (it's //not// an argument to the flag).
{{{
showHelp -docs "index.html?MEL";
}}}
*In the html help, some of the "commands" will have a "''M''" after them. This signifies that in fact they are //not// commands, but mel [[scripts|Scripts]] that ship with Maya. But they lump them in with the commands, since they provide similar functionality.
*These [[scripts|Scripts]] behave like commands in the sense they can usually take [[arguments]] (not [[flags]]), and provide [[return values]]. They provide this functionality by having a global procedure defined //inside// the script, with the //same name// as the script (see 'scripts' above).
*What is the difference between a command and a script?
**A command (to the best of my knowledge) is a 'programmed' bit of code, probably in c++. A //script// on the other hand, is a bunch of commands working together for some other goal. Custom user-made plugins can provide new commands.
**{{{xform}}} for example, is a command, and will move objects about. {{{randomMoveAllObjectsInScene.mel}}} could be a script, that mostly likely calls to {{{xform}}} and many other commands, to have a much broader effect on the scene.
*Commands can be entered in two different modes: 'command syntax', and 'function syntax'.
<<<
*__Command Syntax__: In command syntax you can leave off quotation marks around single-word strings and separate arguments & flags with spaces instead of commas:
{{{
setAttr mySphere1.translateX 10;
move -absolute 1 1 1;
// '-absolute' is a flag, '1' is an argument.
}}}
*__Function Syntax__: In function syntax, commands are treated like functions (procedures): Strings must be enclosed in quotes, and all arguments must have commas between them. Furthermore, any flags must be encased in quotes, turning them into strings, any arguments must be separated with commas. Finally, all the arguments and flags need to be surrounded by parenthesis:
{{{
setAttr("mySphere1.translateX", 10);
move("-absolute", 1, 1, 1);
}}}
<<<
''Returning Values:''
*Many commands provide [[return values]]. How you capture the return value of a script, and pass it into a [[variable]] depends on which syntax you're using:
**When in __command syntax__, values aren't returned unless you surround the statement with back-ticks \ back-quotes.
**When in __function syntax__, values are returned automatically.
A back-tick \ back-quote is the key to the left of ''1'': {{{`}}}
{{{
//Command syntax capturing return values:
string $all[] = `ls`;
string $selected[] = `ls -selection`;
string $onlyJoints[] = `ls -type "joint"`;
}}}
{{{
//Function syntax capturing return values:
string $all[] = ls();
string $selected[] = ls("-selection");
string $onlyJoints[] = ls("-type", "joint");
}}}
By using function syntax, it allows you to 'nest' your commands in ways that you can't using command syntax. This isn't to say you //should//, it just means you //can//. :-) See notes [[here|How can I nest my mel commands?]].
This is in Maya 2008:
Still trying to figure out what the top history layout is called.
*{{{scriptEditorPanel1Window}}} -- window
**{{{TearOffPane}}} -- paneLayout
***{{{scriptEditorPanel1}}} -- panel -- This and the next one are a bit confusing, being by the same name...
****{{{scriptEditorPanel1}}} -- scriptedPanel
*****{{{formLayout52}}} -- formLayout
******{{{frameLayout10}}} -- frameLayout
*******{{{formLayout53}}} -- formLayout
********All the buttons across the top of the Script Editor
******{{{formLayout54}}}
*******{{{paneLayout1}}} -- paneLayout
********{{{cmdScrollFieldReporter1}}} -- cmdScrollFieldReporter (the bottom half of the Script Editor. This value is also held in {{{global string $gCommandReporter}}}
********{{{formLayout55}}}
*********{{{tabLayout2}}} -- tabLayout
**********All the current mel and python tabs in the Script Editor
*********{{{tabLayout3}}} -- tabLayout
**********{{{formLayout66}}} -- formLayout
***********{{{textField5}}} -- textField
***********{{{textScrollList1}}} -- textScrollList
You can use the {{{cmdScrollFieldExecuter}}} command to create the bottom half of the Script Editor, in a new window.
Mel:
{{{C:/Program Files/Autodesk/Maya2008/scripts/startup/scriptEditorPanel.mel}}}
I've pretty-much made the switch from mel (Maya's scripting language) to Python. And what I describe below is standard in Python (implemented differently, but similar visually). But I just recently learned that you can include dots (periods) '.' in mel procedure names. While this doesn't in any way change their behavior, it does let you organize your procs in a more intelligent way based on their names.
Background: In Maya, if you have a global procedure in a mel script, and the global proc and the script share the same name, when you call to the name (via some other proc\script), Maya will search in its 'script path' for the script with the name, then search in that script for the global proc with that name, and (if it finds it) execute the proc. Furthermore, any other global procs in that script are now sourced and can be executed outside of the script by other procedures. Nothing new here. The 'dot in name' convention though lets you associate the script name with the other global procs in the script. When calling them in other procedures/scripts, you'll have a visual reminder of where they came from via their name. To explain this a bit better, an example:
{{{
// foo.mel
global proc foo.boo()
{
print "doing .boo() work\n";
}
global proc foo.shoe()
{
print "doing .shoe() work\n";
}
global proc foo()
{
print "foo.mel -- I do nothing.\n";
print "My procs:\n";
print "\t.boo()\n";
print "\t.shoe()\n";
}
}}}
The above code resides in 'foo.mel'. Inside, there is a global proc called 'foo()', that doesn't really do anything other than print some info. But its there so when the user calls to 'foo()', the other global proces will become sourced. There are other global procs in the script that do the actual work. When you execute 'foo()', the other global procs 'foo.boo()' and 'foo.shoe()' are sourced, and can be called to inside of that script, or other scripts\procs. If you're calling to them inside that script, it's fairly obvious where they're coming from. But say you have some other script called 'goo.mel':
{{{
// goo.mel
// Execute foo(), making its other global
// procs available in this script.
// Could have optionally executed 'source foo;'
foo;
// Execute foo's global procs:
foo.boo();
foo.shoe();
}}}
'goo.mel' executes (or optionally sources) 'foo.mel', thus giving 'goo.mel' access to 'foo.mel's' global procs, and then can call to each of them. By having 'foo.' in front of each proc, it's plainly clear to the user where they originally came from, which is a great aid in troubleshooting. This is a simple example, but imagine if you have goo executing procs from foo, moo, hoo, doo, etc, and you can see where this can come in handy.
So again, the 'dot in proc' is by no means required, but it's another tool in your organizational toolkit.
''Note'':
<<<
I have a newer subject listed here:
[[Positional args in Python authored UI's]]
It doesn't invalidate anything listed below, but puts another spin on it. When I authored the below section, I didn't quite understand 'positional arguments', and thought the data I was seeing was a bug. So I put a bit of a negative spin on it. I have re-written bits of below to reflect the new knowledge, but you should keep that in mind when reading it.
<<<
----
----
Python has a similar issues to mel when it comes to building a UI that calls to an external command:
*In //mel//, say you have a button that references a procedure in the same script as the UI (or it could be in a different script). If that procedure isn't a //global procedure//, the UI will never be able to find it after UI creation.
*Python has the same issue: In a Python module you author a Maya UI. In that UI you create a button that tries to execute a Python function. But when you run the UI, it can't find the function even though the function was authored in the same module as the UI. How can you make it work?
I've found --two-- six different solutions. Part of this ''update'' are three new solutions, now listed first.
*The first example shows a clean way of having a function-authored UI call to a function in the same module, via hard-coded UI control names.
*The second example shows how to author a UI in a class, and call to methods in that class via hard-coded UI control names.
*The third example shows how to author a UI in a class, and call to methods in that class via control object name pointers, rather than hard-coded names.
*The fourth example illustrates the {{{button}}}-argument bug (also see 'one last bug' below), and some solutions around it.
*The fifth (one of the original) example shows how you can create UI's via classes, and pass object pointers back as the commands.
*The sixth (last original) example uses more basic functions, and embeds the commands as strings.
I would recommend using methods 1-4, but I've kept the last two since they do show yet another way of approaching things.
----
''{{{lambda}}}'':
I should call out how important the {{{lambda}}} expression (//Python// expression, not Maya expression) is to this system. See {{{lambda}}} notes on my [[Python wiki|http://pythonwiki.tiddlyspot.com/#%5B%5BUnderstanding%20lambda%5D%5D]]. I'll touch on it briefly here: A button command in a Maya UI expects to have either a //string//, or a //function// passed into it. If you pass something else, you'll raise a {{{TypeError}}} exception like this:
{{{
# Passing in a 'None' object to a button command:
# TypeError: Invalid arguments for flag 'buttonCommand'. Expected string or function, got NoneType #
}}}
Why is this important?
*{{{lambda}}}'s are 'Python expressions' that return //function objects//. This means you can use a {{{lambda}}} to wrapper a command you're passing into a UI button command, without having to jump through the hoops of turning that command into a string to be evaluated later. And because of this, the {{{lambda}}} remembers the 'scope' it was authored in, thus allowing you to continue to search through it's parental namespaces in the containing module.
To put it another way:
*If you make the button command a string, when the command is executed, the string is evaluated in the 'most global scope' of Python. Technically this is the {{{__builtin__}}} scope of Python, which is the parental scope of modules (which define global scopes) and functions (local scopes). So that string really has //no// idea about the module, or function, it was authored in. {{{lambda}}} however isn't an anonymous string: it creates a live function object, fully aware of its scope. And because of that, makes you authoring life much easier. If you want more info understanding Python's scope, see my Python wiki [[here|http://pythonwiki.tiddlyspot.com/#%5B%5BPython%20variable%20scope%5D%5D]].
----
''Positional args, what you need to know:''
<<<
I used to think this was a bug in the system, but I've finally tracked this issue down. I explain this in great detail here: [[Positional args in Python authored UI's]], but I will cover it briefly here:
When a UI control is executed, either by changing its value (via a floatSlider, textField, floatField, etc), or by "pressing" it (via a button), the control will return //positional arguments// to its command flags. This means if you author the {{{-changeCommand}}} of a control to call to a function, the control will pass any positional arguments into that function. For example, if you have a {{{floatFieldGrp}}}, with three fields, and you map a function into the {{{-changeCommand}}} of that control, any time you change one of the three arguments, it will return all three //positional arguments// to the function defined by the {{{-changeCommand}}}. If you're not expecting this, it can seem like a bug. But it's not, and actually, its quite handy.
It should be noted though that this behavior can vary between controls: The {{{button}}} control always returns back an empty string {{{""}}} as its positional arg. The 'button' of a {{{textFieldButtonGrp}}} returns back nothing at all as its positional arg. That difference in behavior I //do// consider a bug.
<<<
----
!Example #1
We make a module called mayaWindowTest01.py. In it we author two functions, one to build the window, and one that is executed when the button in the window is pressed.
Here, three important things are happening:
#When the UI creates itself, it hard-codes the name of the {{{textFieldButtonGrp}}} as '{{{TFBG_stuff}}}'.
#The {{{buttonCommand}}} of the control is calling to a {{{lambda}}} expression, that actually ends up executing our button command. You can see an example above it (commented out) for the solution you'd need if you //weren't// using the {{{lambda}}} expression.
#Inside the {{{buttonCmd}}} function, it finds the control to query via the hard-coded name:
{{{
# mayaWindowTest01.py
import maya.cmds as mc
# Pass UI values by hard-coded UI names in functions outside a class:
def buttonCmd(word):
text = mc.textFieldButtonGrp("TFBG_stuff", query=True, text=True)
print text + " " + str(word)
def mainWindow():
if mc.window("pyWin", exists=True):
mc.deleteUI("pyWin")
mc.window("pyWin", title="Python Window", resizeToFitChildren=True)
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
# Example of passing our button-commad as a string, rather than lambda:
# mc.textFieldButtonGrp("TFBG_stuff", buttonCommand="mayaWindowTest01.buttonCmd('Success!')")
mc.textFieldButtonGrp("TFBG_stuff", buttonCommand=lambda:buttonCmd('Success!'))
mc.showWindow()
mainWindow()
}}}
Then to execute:
{{{
import mayaWindowTest01
}}}
The {{{buttonCmd}}} both queries the UI control value, and an incoming argument, just to illustrate possible usages. When you hit the button, it queries the UI control text, adds the text passed in as an arg, and prints the result.
Notice the commented-out example above. If you pass the command in as a string, you need to use the fully-qualified namespace of {{{buttonCmd}}}: '{{{mayaWindowTest01.buttonCmd}}}', since as a string, in a sense the command has no idea where to find the data its executing. But by using {{{lambda}}}, it is no longer a string, but an actual function, and it finds {{{buttonCmd}}} in the global scope of the module automatically.
!Example #2
Here, we author the UI and the command it calls to inside of a class. Like Example #1, we hard-code the UI control name for our {{{textFieldButtonGrp}}}. The {{{buttonCmd}}} method has the same functionality as the {{{buttonCmd}}} function from Example #1.
We still use a {{{lambda}}} to assign our command to our {{{buttonCommand}}} argument. But notice that the lambda points to '{{{self.buttonCmd}}}', since this is a method of the class:
{{{
# mayaWindowTest02.py
import maya.cmds as mc
# Pass UI values by hard-coded UI names inside a class:
class mainWindow(object):
def __init__(self):
self.showWindow()
def buttonCmd(self, word):
# pull UI control name from our hard-coded name:
text = mc.textFieldButtonGrp("TFBG_stuff", query=True, text=True)
print text + " " + str(word)
def showWindow(self):
if mc.window("pyWin", exists=True):
mc.deleteUI("pyWin")
mc.window("pyWin", title="Python Window", resizeToFitChildren=True)
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
# Hard-code our UI name:
mc.textFieldButtonGrp("TFBG_stuff", buttonCommand=lambda:self.buttonCmd('Success!'))
mc.showWindow()
}}}
Then to execute:
{{{
import mayaWindowTest02
win = mayaWindowTest02.mainWindow()
}}}
!Example #3
This is probably the most elegant example, since there is no hard-coding of UI control names. While nearly identical to Example #2, here we create a class attribute called {{{self.tfbg}}} that stores the object reference created when we author the {{{textFieldButtonGrp}}}. Inside of our {{{buttonCmd}}} method, we then call to that class attr for our control name. Never have to wory about UI name clashing!
{{{
# mayaWindowTest03.py
import maya.cmds as mc
# Pass UI values by object pointers inside a class:
class mainWindow(object):
def __init__(self):
self.showWindow()
def buttonCmd(self, word):
# Pull in the control name via the class attr:
text = mc.textFieldButtonGrp(self.tfbg, query=True, text=True)
print text + " " + str(word)
def showWindow(self):
if mc.window("pyWin", exists=True):
mc.deleteUI("pyWin")
mc.window("pyWin", title="Python Window", resizeToFitChildren=True)
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
# Create a class attribute, to store the UI name
self.tfbg = mc.textFieldButtonGrp(buttonCommand=lambda:self.buttonCmd('Success!'))
mc.showWindow()
}}}
To execute:
{{{
import mayaWindowTest03
win = mayaWindowTest03.mainWindow()
}}}
!Example #4
(I authored this example when I didn't quite understand //positional arguments//, and though they were a bug. While I've updated it to reflect that newer knowledge, it was still originally authored to show what I thought the problem was.)
When the {{{button}}} control is pressed, it passes a single empty string back as a //positional argument//. When it is pressed, and it tries to execute its {{{command}}}, it also passes and empty string as an argument to the command. If the command isn't expecting this... bad things.
We can still use {{{lambda}}}, but as you can see, we need to implement the concept of {{{*args}}}, which will let {{{lambda}}} get any number of external args during its execution.
In the below example, a window is created with four buttons. It shows how you can call to a method generated in an external object, and methods in the same object that created the UI, using {{{lambda}}} or not. The class {{{MyCmd}}} and it's method {{{printer}}} are designed to illustrate how the {{{button}}} command will pass its empty arg, if not caught by {{{lambda *args}}} first. To learn more about {{{*args}}}, see my Python wiki [[here|http://pythonwiki.tiddlyspot.com/#argument]].
{{{
# mayaWindowTest04.py
import maya.cmds as mc
class MyCmd(object):
def printer(self, *args):
self.args = args
print "External object, length of positional args: " + str(len(self.args))
class mainWindow(object):
def __init__(self):
self.showWindow()
def mainCmd(self):
print "Internal object, success"
def showWindow(self):
if mc.window("pyWin", exists=True):
mc.deleteUI("pyWin")
mc.window("pyWin", title="Python Window", resizeToFitChildren=True)
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
# make an object that will execute our external command:
cmdObject = MyCmd()
# Here we use the button command, with and without lambda. The button
# command always passes an empty string as an arg to the command, so we
# need to catch it with lambda using *args:
mc.button(command=lambda *args:cmdObject.printer(), label="Button, external object, lambda")
mc.button(command=lambda *args:self.mainCmd(), label="Button, this object, lambda")
mc.button(command=cmdObject.printer, label="Button, external object, no lambda")
mc.button(command=self.mainCmd, label="Button, this object, no lambda (will fail)")
# textFieldButtonGrp's button command doesn't return any positional args,
# which I actually think is a bug. But we can handle it differently because of that
mc.textFieldButtonGrp(buttonCommand=lambda:cmdObject.printer(), buttonLabel="textFieldButtonGrp, external object, lambda" )
mc.textFieldButtonGrp(buttonCommand=lambda:self.mainCmd(), buttonLabel="textFieldButtonGrp, this object, lambda" )
mc.textFieldButtonGrp(buttonCommand=cmdObject.printer, buttonLabel="textFieldButtonGrp, external object, no lambda" )
mc.textFieldButtonGrp(buttonCommand=self.mainCmd, buttonLabel="textFieldButtonGrp, this object, no lambda" )
mc.showWindow()
}}}
To execute:
{{{
import mayaWindowTest04
win = mayaWindowTest04.mainWindow()
}}}
----
''These are two older examples:''
----
!Example #5
(big shout out to Doug Brooks for helping me figure this out...)
Make sure that this module is saved in part of your Maya Python path.
{{{
# myWin.py
import maya.cmds as mc
class MyCmd(object):
# Create a object that will do the work the button requests
def __init__(self, word):
self.val = word
def printer(self, *arg):
print self.val
class MyWin(object):
# Our window object
def mainWindow(self):
# Display the window:
if mc.window("pyWin", exists=True):
mc.deleteUI("pyWin")
mc.window("pyWin", title="Python Window")
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
# make our command object, with argument:
cmdObject = MyCmd("success!")
mc.button(label="super button!", command=cmdObject.printer)
mc.showWindow()
}}}
In the above example, we create two objects, both existing in the same module, {{{myWin.py}}}. Key things to note:
*Before we make our button, we first create an object that will do the work, including the arguments that should be used:
{{{
cmdObject = MyCmd("success!")
}}}
*Next, when we create the button, we pass a pointer to the object.method we want to execute. Note that I have left //off// the trailing parenthesis '{{{()}}}' that one would normally include.
{{{
mc.button(label="super button!", command=cmdObject.printer)
}}}
*Now, inspect the {{{MyCmd}}} class: It has a standard {{{__init__}}} method for turning the passed in variable into an object attribute. But what is interesting, is the {{{printer}}} method:
{{{
def printer(self, *arg):
}}}
*See the 2nd argument, {{{*arg}}}? This is a //positional argument//, and can capture arbitrary passed in variables as a {{{tupple}}} (a Python immutable list). Even though the {{{printer}}} method doesn't call to the {{{*arg}}} inside of it, if you were to remove this argument, it would fail. Why? It //seems// that when Maya's {{{mc.button}}} command is ran, it passes an //empty string// as an argument //to the command//. I have no idea //why// it's doing this, but you need to catch this rouge argument via {{{*args}}}, and then just ignore it.
Finally, you can execute the code:
{{{
import maya.cmds as mc
import myWin
win = myWin.MyWin()
win.mainWindow()
}}}
This will launch the UI. When you hit the '{{{super button!}}}' button, it //should// print to Maya, {{{success!}}}.
!Example #6
Make sure that this module is saved in part of your Maya Python path:
{{{
# myWin2.py
import maya.cmds as mc
def printer(p):
# our buttom command function
print p
def mainWindow():
if mc.window("pyWin", exists=True):
mc.deleteUI("pyWin")
mc.window("pyWin", title="Python Window")
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
mc.button(label="super button!", command="import myWin2 as mw; mw.printer('success!')")
mc.showWindow()
}}}
This example is a bit more simplistic that the first example. Here, we have no class objects being created. Really, the only important thing to note is on the button line:
{{{
mc.button(label="super button!", command="import myWin2 as mw; mw.printer('success!')")
}}}
*Note how for the command, there are actually two separate commands: The first imports the module that the window was authored in ("{{{import myWin2 as mw}}}"), and the second executes the {{{printer}}} function defined there within ("{{{mw.printer('success!')}}}"). If the import didn't happen, when this command was executed, it'd have no knowledge of what '{{{printer}}}' was.
To execute the code:
{{{
import maya.cmds as mc
import myWin2
myWin2.mainWindow()
}}}
This will launch the UI. When you hit the '{{{super button!}}}' button, it //should// print to Maya, {{{success!}}}.
Since you're embedding the command as a string in this example, it's not as flexible as the first (in case you wanted a command to run on arbitrarily changing values), but, it is easier to wrap your head around.
For a long time I've been interested in figuring out the euler rotation angles of a given matrix. I'm not sure why I've wanted to know this. I have yet to actually deduce the actual math behind it, but base on [[this post|http://forums.cgsociety.org/archive/index.php/t-617014.html]], it gave me the knowledge of how to tap into Maya's API (via Python) to find a solution.
{{{
# Python code
import math
import maya.cmds as mc
import maya.OpenMaya as om
# Define a node to pull a matrix from. No reason you
# can't use your own matrix of course.
node = 'pCube1'
# Get the world matrix as a list
matrixList = mc.getAttr('%s.worldMatrix'%node)
# Convert list to MMatrix object
mMatrix = om.MMatrix()
om.MScriptUtil.createMatrixFromList(matrixList, mMatrix)
# Convert matrix to quaternion (MQuaternion object)
quatRotate = om.MQuaternion()
quatRotate.assign(mMatrix)
# Pull Euler rotation values from quaternion (MEulerRotation object)
eulers = quatRotate.asEulerRotation()
# Convert from radians to degrees:
angles = [math.degrees(angle) for angle in (eulers.x, eulers.y, eulers.z)]
print angles
#[-26.706627346943492, -79.553892761509957, -58.387916674342371]
}}}
Matrices store rotations as vectors, and because of this (and due to conversion to quaternions) they have no concept of rotations past 360 degrees.
Later while reading the API docs, I saw that the {{{OpenMaya.MTransformationMatrix}}} has a {{{getRotation()}}} method. I thought it would be better using this object so I could skip the {{{MQuaternion}}} conversion. However, I wasn't ever able to get it to work. Doing some research, I ran across [[this post|http://www.rtrowbridge.com/blog/2009/02/python-api-mtransformationmatrixgetrotation-bug/]] that confirms that it's actually a bug.
I recently ran into this problem: I had code written that would snap one object to another via their matrices quite successfully. I was using this in a rigging 'snap' tool. Then, the design of the rig changed, and there became an offset needed between the source and target nodes (in my case some rotational values). Since matrices are such nice things to use for transformational snapping, I wanted to keep using this method, but needed a way to calculate the difference of two matrices, store that value, then later re-apply it after the snap to make my target have the same relative transformations to the source based on their initial state. This is hard to describe, so here's the proof of concept example I came up with:
*Two nodes (cubes) in Maya.
*Transform them both to some new location in space (both in the same location).
*Now apply an //additional// transformation to the target node... maybe a rotational offset?
Our goal is to allow us to transform the source to any new value, and have the target match the new transformation with relative offset previously assigned. Meaning, if you'd rotated the target cube 90 deg's off from the source, wherever we transform the source, we want the target to match, rotated the same 90 deg's off.
----
This is all implemented through Python, and the code for the imported {{{matrixutils}}} module can be found in my subject [[here|Working with matrices with Python]]. It accesses the API's (~OpenMaya) {{{MMatrix}}} object and provides wrapper code for using it in Python.
{{{
# Python code
import maya.cmds as mc
import matrixutils as matrix
# Setup names for our source node, and our target node.
source = 'src'
target = 'dest'
}}}
As described above, transform both nodes to some location in space. Then apply an additional transformation to the target.
Find the worldspace inverse matrix of our source object, and the worldspace matrix of our target:
{{{
srcWorldInverseMatrix= matrix.getMMatrix(source, 'worldInverseMatrix')
targetWorldMatrix= matrix.getMMatrix(target, 'worldMatrix')
}}}
Create the difference matrix. This is done by multiplying the target world matrix by the source world inverse matrix... sort of like subtracting the source from the target to find the difference:
{{{
differenceMatrix = targetWorldMatrix * srcWorldInverseMatrix
}}}
Set target to the difference matrix, just for visualization: Should snap it to (or near, depending on the initial difference between the nodes) the origin, showing the difference in trans, rot, scale, and shear vals that were stored:
{{{
matrix.matrixXform(target, differenceMatrix, 'worldSpace')
}}}
Now move the source node somewhere new in the Maya scene.
Execute this code to find the //new// source worldspace matrix:
{{{
newSrcWorldMatrix = matrix.getMMatrix(source, 'worldMatrix')
}}}
Finally move our target to the new location, with the {{{differenceMatrix}}} applied.
{{{
offsetMatrix = differenceMatrix * newSrcWorldMatrix
matrix.matrixXform(target, offsetMatrix, 'worldSpace')
}}}
It should snap to the new location of the source node, but have the additional difference matrix applied, putting it into the same relative state that was originally captured.
I'd been looking for a LONG time, trying to find an IDE for Python that mirrored //one// important feature from Maya's Script Editor: ''Evaluate Selection'', which is the ability to @@highlight blocks of code@@, and execute just that code block interactively.
//Maya's// implementation of Python in the Script Editor can do this (since version 8.5), but what if you want to author Python //outside// of Maya, but still have this functionality?
After asking a lot of people, and doing a lot of searching, I finally found Wing:
http://www.wingware.com/
[img[https://wingware.com/images/wingide2-personal-screenshot-qtr.png]]
''Wings docs on integrating with Maya'' [[here|http://www.wingware.com/doc/howtos/maya]] (which actually links back to this wiki :-) )
It has three versions:
*Wing IDE 101 (free, but //doesn't// have 'Evaluate Selection')
**Update: It appears you can hack it: I saw this post in a forum, from Wingware itself:
<<<
It looks like the keyboard config pref is omitted in 101. You could
hack it by editing the keyboard.normal file in the Wing installation
and adding:
{{{'Ctrl-F5': 'evaluate-file-in-shell'}}}
Or to evaluate the current selection:
{{{'Ctrl-Shift-F5': 'evaluate-sel-in-shell'}}}
You'll need to restart Wing to read those and you can of course alter
the bindings. Also, if you're using one of the other keyboard
personalities you should edit the associated keyboard.* file instead.
<<<
*Wing IDE Personal ($35)
*Wing IDE Professional ($179 - Lets you access the Wing API via Python, to write your own plugins for the tool + other features)
----
Compare version features:
http://www.wingware.com/wingide/features
----
@@Interaction With Maya@@:
*I've come up with three different ways that allow Wing to interact with Maya and vice-versa (Maya Script Editor replacement, Remote debugging Maya data in Wing, Run Maya in Wings Python shell). See that subject here:
**[[Interaction between Wing and Maya]]
----
Notes:
*Wing itself runs a different version of Python than you may have installed. The current version of Wing (as of this update) runs Python 2.5.4. This is only really important if you get the "Pro" version, and start making your own 'plugin scripts': The plugins you author will be running Wings version of Python, not your installed version of Python. Furthermore, the Wing distribution isn't the full install of Python, so not all modules are available. This isn't a huge deal, but it's important to be aware of if your plugins don't work. For example, I had one that called to the {{{webbrowser}}} module: That module isn't distributed with Wing. Luckily, they have their own function ({{{wingapi.gApplication.OpenURL(url)}}}) that could be used in its place.
----
Presuming you know the name of your camera, and the name of the panel:
{{{
modelPanel -e -cam $camera $panel;
}}}
And if you need to know the name of the currently active panel:
{{{
string $panel = `getPanel -withFocus`;
}}}
Also see:
*[[How do I query the name of the camera in the active panel?]]
Say you have a selection of verts, and it's important you process the odd and even numbered verts differently. How can you generate those two lists?
{{{
// get our vert list:
string $sel[] = `ls -fl -sl`;
// make our buckets
string $odd[]; clear $odd;
string $even[]; clear $even;
for($i=0;$i<size($sel);$i++) // print $sel[$i]
{
// get just the END number, inside of brackets:
string $endNum = `match "\\[[0-9]+\\]$" $sel[$i]`;
// just num without brackets:
int $justNum = `match "[0-9]+" $endNum`;
// if we divide in half, do we have a remainder? Magic with the modulus operator:
float $remainder = $justNum%2;
if($remainder)
$odd[size($odd)] = $sel[$i];
else
$even[size($even)] = $sel[$i];
}
print $even;
print $odd;
}}}
would result something like:
{{{
...
pSphere1.vtx[240]
pSphere1.vtx[260]
pSphere1.vtx[143]
pSphere1.vtx[163]
...
}}}
Thanks to Eric Vignola
*Multiply the unit-vector by the distance, and add that to your original point:
{{{
vector $point = <<1, 1, 0>>;
vector $vector = <<1, 1, 0>>;
float $distance = 2;
vector $result = ($point + (unit($vector)* $distance));
// Result: <<2.414214, 2.414214, 0>> //
}}}
{{{filterExpand}}}
*Example, given a selection of polygon and nurbs surfaces, generate a list of only the polygon surfaces:
{{{
filterExpand -sm 12;
}}}
{{{
string $inf[] = `listConnections "skinCluster1.matrix"`;
}}}
Check out the replaceNode.mel script. Located here:
{{{
.../MayaX.X/scripts/others/replaceNode.mel
}}}
''The Mel Wiki Guest Book''
Since this wiki isn't opening to public editing (got too much spam), please email me your guest book entry, and I'll post it! -- {{{warpcat}}} (at) {{{sbcglobal}}} (dot) {{{net}}} --
Please use this format in your mail:
{{{
Name\Handle: Joe Bobson
Email (optional): joebob@bob.com
Comment: Wow, what a great wiki!
}}}
----
----
----
Date: Feb 08, 2010
Name: Zhi Min Chen
Comment: Great website. I use it on a regular basis to help me on most my projects at work. Your wiki's question format is very well thought out and it makes finding answers extremely quick. Keep up the great work!
----
Date: Aug 15, 2008
Name: Ben Brac
Email: brac@sonic.net
Comment: Awesome wiki ~WarpCat! I use it all the time when the usual documentation leaves much to be desired. Keep the updates coming!
----
Date: Aug 06, 2008
Name: Bob Aunum
"Thank you sincerely. The subject says it all. since i decided to teach myself mel, i've been looking for a good web resource, but i really didn't expect to find anything as elegantly functional as your wiki. It's great. Really."
----
Date: May 19th, 2008
Name: Fridi Kühn
I am a student from Germany and have recently started helping a games developer to automate their process of animating. Since I am a beginner to MEL I wanted to state that your mel wiki helped me alot to overcome the mysteries of that language. I just wanted to thank you for your website and hope you will keep it alive! Please know that I will frequently return and see what's new...
----
{{{lockNode}}}
Note: This is a different kind of "lock"than a "reference" node, which is read-only, or certain system nodes (like lightLinkers) that are read-only.
Requested\suggestion from Matti Grüner:
You can use the {{{addRecentFile.mel}}} script that comes with Maya. Lives here by default:
{{{
C:\Program Files\Autodesk\Maya<VERSION>\scripts\others\addRecentFile.mel
}}}
Easy to call to:
{{{
addRecentFile(string $file, "mayaAscii"); // or "mayaBinary"
}}}
If that succeeded, and you go to {{{File -> Recent Files -> }}}You should see the newly added entry.
In Python, this concept is handled by the {{{reload}}} command. The first time you {{{import}}} a module (Python's equivalent of a mel script), its loaded in memory. No amount of re-importing will change it in memory. If you make changes to your module //after// its imported, you need to use {{{reload}}} to update it in memory:
{{{
import myModule
# do work
reload(myModule)
}}}
(Suggested addition by Chris Mills)
Maya seems to provide no easy way to 'uninstance' a shape node, once instanced. This has caused a lot of frustration. The common solution I've seen is to simply duplicate the instance, delete the old one, and rename the duplicate. This is fine, unless there is something parented to the instanced node. The below code jumps through a lot of hoops, but it does so as to not delete the original transform the instance shape node is associated with. In a nutshell:
*Find the shape node
*Make a dummy shape (mesh) and associated transform
*Parent the dummy shape to the same parental transform as the instance. This is silly, and important, so we can get rid of the instance, and not have Maya also delete the transform.
*Parent the instance shape to the dummy transform
*Duplicate the dummy transform (and thus duplicating the instance shape)
*Parent the new uninstanced shape back to original transform
*Delete all the dummy stuff.
It's still a lot more steps than it should be...
{{{
// To start: Select the instanced objects
// Get a list of all of our objects
string $sel[] = `ls -l -sl`;
for($i=0;$i<size($sel);$i++)
{
// get a list of the current children of our selection:
string $kids[] = `listRelatives -f -s $sel[$i]`;
for($j=0;$j<size($kids);$j++)
{
// create a dummy mesh: a dummy shape node, and find its parent name
string $dum = `createNode mesh -name instanceDummy`;
string $dumPare[] = `listRelatives -p $dum`;
// parent that dummy shape to our current object. This lets us later
// unparent the instance, and not have the object delete itself.
parent -s -r $dum $sel[$i];
// Now parent the instance under our dummy node, and delete it.
string $reParent[] = `parent -s -r $kids[$j] $dumPare[0]`; // print $reParent[0]
delete $dum;
// duplicate the instance shape node (that was just reparented a moment ago),
// and then parent the new, uninstaned shape, back to the original parent.
string $newShapeParent[] = `duplicate -rc $reParent[0]`;
string $newUninstancedShape[] = `listRelatives -s -f $newShapeParent[0]`;
parent -s -r $newUninstancedShape[0] $sel[$i];
// finally, clean up by deleting the dummy parent node, and the transform left over
// from the duplication of our shape node
delete $dumPare;
delete $newShapeParent;
}
}
}}}
In the Maya Menu, you can execute:
*Modify -> Evaluate Nodes -> Evaluate All \ Ignore All
To enable\disable certain types of node evaluations. Those node types include:
*constraints, expressions, fluids, globalstitch, iksolvers, particles, rigidbodies, & snapshots.
But what code does that UI actually call to?
The UI calls to two maya {{{runTimeCommand}}}s:
{{{
EnableAll;
DisableAll;
}}}
{{{runTimeCommand}}}s are simply small wrappers for other bits of mel. Those {{{runTimeCommand}}}s actually execute:
{{{
// DisableAll:
doEnableNodeItems false all;
// EnableAll:
doEnableNodeItems true all;
}}}
{{{doEnableNodeItems}}} is a "startup" mel script found below. That means (to my understanding) that it is executed every time Maya starts up.
*{{{../Maya7.0/scripts/startup/doEnableNodeItems.mel}}}
But this mel script is mainly just a wrapper for this one:
*{{{../MayaX.X/scripts/others/setState.mel}}}
So while you could call to any of the mel listed above to do this, the cleanest way is to call to {{{setState}}}. It's arguements are:
{{{
setState(string $type, int $state);
}}}
The acceptable $types are (as listed above):
*{{{"all"}}}
*{{{"constraint"}}}
*{{{"expression"}}}
*{{{"fluid"}}}
*{{{"globalstitch"}}}
*{{{"iksolver"}}}
*{{{"particle"}}}
*{{{"rigidbody"}}}
*{{{"snapshot"}}}
Note that I have found that if the code tries to set the state on something that is "locked" it will fail. This will also fail at Maya startup, which is bad news. For example, one time I had locked a particle shape node's ".isDynamic" attribute. Whenever I'd later open that rig file, none of the constraints would evaluate, since the code to enable their evaulation at startup would crash. Crazy.
There is a handy command for this:
{{{toolPropertyWindow}}}
----
Also see:
*[[I want to add a UI to the tool settings window, how do I do that?]]
In this example, I'll add a multi attr of type 'message' to a {{{null1}}} node, and then start connecting the message attrs of other nodes to it:
{{{
# Python code
import maya.cmds as mc
mc.addAttr('null1', longName='myMulti', attributeType='message', multi=True )
mc.connectAttr('object1.message', 'null1.myMulti[0]')
mc.connectAttr('object1.message', 'null1.myMulti[1]')
}}}
To get a list of which attr indices are currently in use, and their values:
{{{
# indices are in the form of ["myMulti[0]", "myMulti[1]", etc...]
indices = mc.listAttr('null1.myMulti', multi=True)
for i in indices:
print "null1."+i, " --> ", mc.listConnections('null1.'+i)[0]
}}}
{{{
null1.myMulti[0] --> object1
null1.myMulti[1] --> object2
}}}
Notice how you use {{{listConnections}}}, not {{{getAttr}}} to query {{{message}}} attributes?
{{{scriptedPanel}}}s seem like magical things: Popping between UI's, docking in the UI and floating on top. How can you easily add one to your own UI?
If they're already authored by Maya, it's a simple act of finding the name, then adding it to your own UI with the {{{scriptedPanel}}} command. Below, I create a window that has both a graph editor and the dope sheet, with a time-slider below them. And, it's actually really easy.
How can you find the names of the existing scriptedPanels? In the Script Editor, turn on "echo all commands". Then pop off a window you want to use in your own ui. It should print a bunch of stuff, including a line like this:
{{{
addDopeSheetPanel dopeSheetPanel1;
}}}
In this case, "{{{dopeSheetPanel1}}}" is the name of the scriptedPanel we care about, and can now embed into our own UI:
{{{
# Python code:
import maya.cmds as mc
class App(object):
"""
Create the Maya UI object.
"""
def __init__(self):
self.name = "scriptedPanelTest"
self.title = "scriptedPanel test"
if mc.window(self.name, exists=True):
mc.deleteUI(self.name)
mc.window(self.name, title=self.title, resizeToFitChildren=True)
form = mc.formLayout()
self.paneLayout = mc.paneLayout( configuration='horizontal2' )
mc.scriptedPanel( 'graphEditor1', e=True, parent=self.paneLayout)
mc.scriptedPanel( 'dopeSheetPanel1', e=True, parent=self.paneLayout)
mc.setParent(form)
self.timePort = mc.timePort()
mc.formLayout(form, edit=True, attachForm=[(self.paneLayout, "top", 1),
(self.paneLayout, "left", 1),
(self.paneLayout, "right", 1),
(self.timePort, "left", 1),
(self.timePort, "right", 1),
(self.timePort, "bottom", 1)],
attachControl=[(self.paneLayout, "bottom", 0, self.timePort)])
mc.showWindow()
App()
}}}
Just like anything else:
{{{
sets -add setName object.attribute;
}}}
It's fun to add images to your UI's, but the biggest problem is when you give your UI to someone else, usually the hard-coded paths to the imagery break.
!!!Python
One nice thing about authoring modules in Python is they come with a 'special' {{{__file__}}} attribute. This can let the module itself (or any module that calls to it) know its save location. If you store your images in the same directory as the module, you're good to go.
In the below example, presume there is a {{{smiley.bmp}}} image saved in the same directory as the module:
{{{
# iconTextButtonTest.py
import os
import maya.cmds as mc
class App(object):
def __init__(self):
# define save location of this module:
self.path = os.path.dirname(os.path.abspath(__file__))
if mc.window(self.name, exists=True):
mc.deleteUI(self.name)
self.window = mc.window(self.name, resizeToFitChildren=True)
self.rootLayout = mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
# Create icon based on relative path:
self.button = mc.iconTextButton( style='iconOnly', image=os.path.join(self.path, "smiley.bmp"))
mc.showWindow()
App()
}}}
!!!Mel
A older mel convention I've used is this: Author an "image finder" script, and save it in the same dir as your images. You can then use that script to define your image path during UI creation.
{{{
// save this as imageFinder.mel, and save it in the same directory as your images.
global proc string imageFinder()
{
string $description = `whatIs imageFinder`;
string $path = `substitute "Mel procedure found in: " $description ""`;
string $dir = dirname($path);
return $dir;
}
}}}
Now, inside of your UI, you can call to this script to find the location of your images, and embed that path at UI creation time:
{{{
// UI code here......
string $imagePath = `imageFinder`;
string $image = ($imagePath + "/foo.bmp");
image -image $image -h 128 -w 128;
// finish UI code....
}}}
Notes:
*This is just and example: You will need a uniquely named "imageFinder.mel" script for each directory you'll be searching for images in. I usually name them based on the script that is calling to the images.
*The first time you enter the 'imageFinder' script in Maya, it won't work when called to, since Maya will think the imageFinder //procudrue// was 'entered interactively'. You'll either need to call the {{{source}}} command on the imageFinder script after it's been saved, or simply restart Maya. It will work from that point on.
*You can use this same technique to have a script's "help" menu\button point to a 'help.html' file; again, with non hard-coded paths.
----
*Also see [[How can I have a script return where it lives on disk?]]
*{{{skinCluster}}} : name of a given {{{skinCluster}}} node.
*{{{joints}}} : List of joints
{{{
# Python code
import maya.cmds as mc
mc.skinCluster(skinCluster, edit=True, addInfluence=joints)
}}}
Say you have a set hierarchy in the outliner, and you want to quickly pick one or more sets, add select one or more objects, and add the selected objects to the selected sets. Maya has the 'Sets Relationship Editor', but I find it's implementation very clunky.
{{{
# Python code
import maya.cmds as mc
sel = mc.ls(selection=True)
nodes = []
sets = []
for item in sel:
if mc.objectType(item) == "objectSet":
sets.append(item)
else:
nodes.append(item)
for s in sets:
mc.sets(nodes, addElement=s)
}}}
{{{select}}}
Example: Using a wildcard:
{{{
select "foo*";
}}}
In the Attribute Editor for materials, there is a field for "Hardware Texturing". In that field, there is an option to adjust the "Texture resolution". It makes it //look// like this is a //material// property, but in fact, this is done on the '//texture// node level'. When you adjust that UI drop-down, Maya is actually calling to this proc:
*{{{AEhardwareTextureQualityCB}}}, which lives in this script:
*{{{../MayaX.X/scripts/AETemplates/AEhardwareTextureTemplate.mel}}}
That code does all the magic to make this happen. But if you want to do this on your own, you'd need to:
*Find all the "texture" nodes that are connected to a material. This could be file, ramp, noise, etc. ({{{ls -tex}}})
*For each node, you'd need to add a new {{{.resolution}}} attr, and then update it's value. Like below (how the actuall ~MayaAscii stores it). What's interesting, is that as soon as that attr exists, you'll see the "resolution" of the texture change in the viewport. Maya must have some scriptJob running that detects for it(?)
{{{
addAttr -ci true -sn "resolution" -ln "resolution" -dv 128 -at "long";
}}}
The {{{-dv}}} (default value) is what the new resolution should be. If you set the resolution to "default" in the Attribute Editor, it seems to actually delete the attr completely. The other values are powers of two:
*Low: 32
*Med: 64
*High: 128
*Highest: 256
I wonder if you can enter in higher vals... (haven't tried yet)
What is interesting: Open the Attribute Editor for a given texture node, and "copy tab" (at the bottom of the UI) to rip off a dupe of it, expand the "Extra Attributes" field. If you then go back to the material's Attribute Editor, and start changing the Hardware Texturing values, you'll see the new attributes get built on the fly on the texture node. Nifty.
The {{{controlPoint}}} node (a parental node type of {{{nurbsCurve}}}) has a {{{.weight[]}}} array attr. Each index of that array corresponds to a CV on the curve. The default weights are 1.0 I believe.
{{{
setAttr curveShape1.wt[0] .5;
}}}
I've been told if there is history on the curve, it can reset the values, FYI.
After you create a {{{parentConstraint}}}, Maya gives you no easy way to adjust their offset values per the attribute editor. But you still can of course:
{{{
setAttr ($pareCon + ".tg[0].tot") -type "double3" 0 2 0;
setAttr ($pareCon + ".tg[0].tor") -type "double3" 0 0 45;
}}}
{{{.tg}}} is the target list. In the above example, I'm adjusting the offset of the first target.
{{{.tot}}} is for offseting translation, and {{{.tor}}} for offsetting rotation.
Maya is a //statically typed// language (like c++, Java), meaning, when you declare a variable, you have to define what //type// of data the variable is. {{{string $s}}}, {{{int $i}}}, {{{float $nums[]}}} etc. Python (like Ruby) is an example of a language that is //dynamically typed//: The type of the data is a part of the stored object, and has nothing to do with the variable name. The variable name is simply a pointer to a memory location which defines what type of data it is.
You can run into instances in mel where you may need to capture data, but you're not too sure what //type// of data the source is. An example of this is accessing Maya's {{{optionVar}}} system, which makes a good example.
{{{optionVar}}}s are variables stored on disk, usually for UI preferences. But you can use them for anything else you need to stay persistent between Maya sessions. I use them... a lot. But you can run into instances in your code where you may make an optionVar of 'type string', and then later turn it into a 'string array'. This is a blessing, an a curse: While {{{optionVar}}}s //are// typed, you can change their type at any point - they're just text stored on disk that can be modified at any point. But if you want to pull that data into your mel scripts, and the type ihas changed, you can obviously run into problems.
In //Python//, this is a non-issue (see example at bottom). But in mel this is a big issue. The below example explains it:
Make a optionVar of type string, called 'ovString', and filled with the text 'firstItem':
{{{
optionVar -stringValue ovString firstItem;
string $str = `optionVar -q ovString`;
// Result: firstItem //
}}}
Remove it from disk:
{{{
optionVar -remove ovString;
}}}
Remake ovString, but this time make it an array by default:
{{{
optionVar -stringValueAppend ovString firstItem;
string $str = `optionVar -q ovString`;
// Error: line 1: Cannot convert data of type string[] to type string. //
}}}
Now our code can't capture its value, since its a string array, and not type string.
To address this, we can make a procedure that can {{{catch}}} problems, and based on the result handle them. In this case, the code tries to pass a {{{optionVar}}} into a //string array//. If that fails, it knows it must be of type //string//, and acts accordingly.
{{{
global proc string varTest(string $optionVar){
// define our return value
string $stringVar;
// try to turn our optionvar into a string array, catch if fail:
int $bad = catch(eval("string $temp_array[] = `optionVar -q "+$optionVar+"`"));
if($bad){
// failed, this must be a string then
$stringVar = `optionVar -q $optionVar`;
}
else{
// didn't fail, this must be a list
string $local_ov[] = `optionVar -q $optionVar`;
$stringVar = $local_ov[0];
}
// return our single string
return $stringVar;
}
}}}
Lets see if it works:
{{{
string $str = varTest("ovString");
// Result: firstItem //
}}}
Even though the {{{optionVar}}} ovString is of type string array, our code catches the error, and processes the data correctly.
I should also note that Maya has the {{{whatIs}}} command, to tell you what type a variable is:
{{{
whatIs("$str")
// Result: string variable //
}}}
----
For comparison, here's the same thing in Python, showing the dynamic typing at work:
{{{
# Python code
import maya.cmds as mc
mc.optionVar(stringValue=('ovString', 'firstItem'))
str = mc.optionVar(query='ovString')
# firstItem
}}}
Above, variable {{{str}}} points to a {{{string}}} object, with value "firstItem"
{{{
# delete optionVar
mc.optionVar(remove='ovString')
}}}
Now remake our {{{optionVar}}} as a string array (Python list object):
{{{
mc.optionVar(stringValueAppend=('ovString', 'firstItem'))
str = mc.optionVar(query='ovString')
# [u'firstItem']
}}}
Now, based on Python dynamic typing system, the {{{str}}} variable points to a {{{list}}} object (emulating Maya's array type). The {{{list}}} object holds one {{{string}}} object at index {{{[0]}}}, which value is "firstItem".
So much easier to deal with than mel ;)
Like in mel, Python also has a command to tell you what type of object a variable points to:
{{{
type(str)
# Result: <type 'list'> #
# query the string inside of the list:
type(str[0])
# Result: <type 'unicode'> #
}}}
[img[http://farm3.static.flickr.com/2067/2197352149_10f912b166.jpg?v=0]]
{{{gradientControl}}}'s are cool, since they're a small little UI showing you a ramp (or curve) that can be manipulated by the user and ties into some object.attr. Problem though, the docs on it leave a //lot// to be desired.
(I pulled most of this info from a post by 'ewerybody' on highend3d.com [[here|http://www.highend3d.com/boards/lofiversion/index.php/t214296.html]])
There's two ways I've found to interact with this type of control: Via a ramp, and via a 'ui curve', both described below. The 'curve' method is really similar in process and look as a [[gradientControllNoAttr|How can I author a 'gradientControlNoAttr' into my UI?]]. However, the curve method stores its values as attributes on an object, while the {{{gradientControllNoAttr}}} stores its values in an [[optionVar|How can I store and query variables in Maya, that are stored on the hard drive?]].
''RAMP:''
{{{
// first we need any object to add the attribute to:
string $obj[] = `polyCube`;
string $attrName = "gradientControl";
// add the attrs to our object that the gradientControl will interact with:
string $attrName = "gradientControl";
addAttr -ln ($attrName) -numberOfChildren 3 -at compound -multi $obj[0];
addAttr -sn ($attrName + "Pos") -at "float" -parent $attrName $obj[0];
addAttr -sn ($attrName + "Value") -at "float3" -parent $attrName -storable 1 $obj[0];
addAttr -sn ($attrName + "ValueR") -at "float" -parent ($attrName + "Value") $obj[0];
addAttr -sn ($attrName + "ValueG") -at "float" -parent ($attrName + "Value") $obj[0];
addAttr -sn ($attrName + "ValueB") -at "float" -parent ($attrName + "Value") $obj[0];
addAttr -sn ($attrName + "Interpolation") -at "enum" -enumName "0:1:2:3"
-parent $attrName $obj[0];
//give it some default vals, or we'll get an error when the UI tries to build below:
setAttr ($obj[0] + "." + $attrName + "[0]." + $attrName + "Value") -type double3 1 0 0;
setAttr ($obj[0] + "." + $attrName + "[0]." + $attrName + "Pos") 0;
setAttr ($obj[0] + "." + $attrName + "[0]." + $attrName + "Interpolation") 3;
setAttr ($obj[0] + "." + $attrName + "[1]." + $attrName + "Value") -type double3 0 1 0;
setAttr ($obj[0] + "." + $attrName + "[1]." + $attrName + "Pos") .5;
setAttr ($obj[0] + "." + $attrName + "[1]." + $attrName + "Interpolation") 3;
setAttr ($obj[0] + "." + $attrName + "[2]." + $attrName + "Value") -type double3 0 0 1;
setAttr ($obj[0] + "." + $attrName + "[2]." + $attrName + "Pos") 1;
setAttr ($obj[0] + "." + $attrName + "[2]." + $attrName + "Interpolation") 3;
// now we can open a simple window that contains a control for this attributes:
if (`window -ex testWin`)
deleteUI testWin;
window testWin;
columnLayout;
string $colorControl = `attrColorSliderGrp -label "color"`;
gradientControl -at ($obj[0] + "." + $attrName) -selectedColorControl $colorControl;
showWindow testWin;
}}}
Now we have an attrColorSliderGrp that will control the color of whatever part of the gradientControl you have selected. You could make another one to control their position as well.
Of course the big problem is you can't query the value of the gradientControl itself, which is bothersome:
You can query the attr values on your object, but you really can't find the //interpolated// values that you'd be after. For that, you'd need to write a another proc, probably using the {{{hermite}}} function, to find the interpolated values. If I ever get that code authored, I'll update here.
For something similar, check out the [[rampColorPort|How can I make a UI control to edit my ramp?]] command.
----
''CURVE:''
[img[http://farm3.static.flickr.com/2270/2198047046_1b6111924d.jpg?v=0]]
(above image says 'gradientControlNoAttr', but this 'curve' method of the gradientControl looks the same)
{{{
// first we need any object to add the attribute to:
string $obj[] = `polyCube`;
string $attrName = "gradientControl";
// add the attrs to our object that the gradientControl will interact with:
// The enumName stands for the type of curve interpolation provided:
// flat, linear, spline plateau, and spline.
addAttr -ln ($attrName) -numberOfChildren 3 -at compound -multi $obj[0];
addAttr -sn ($attrName + "Pos") -at "float" -parent $attrName $obj[0];
addAttr -sn ($attrName + "Value") -at "float" -parent $attrName $obj[0];
addAttr -sn ($attrName + "Interpolation") -at "enum" -enumName "0:1:2:3"
-parent $attrName $obj[0];
// and if you want, you can add some default values:
setAttr ($obj[0] + "." + $attrName + "[0]." + $attrName + "Value") 1;
setAttr ($obj[0] + "." + $attrName + "[0]." + $attrName + "Pos") 0;
setAttr ($obj[0] + "." + $attrName + "[0]." + $attrName + "Interpolation") 3;
setAttr ($obj[0] + "." + $attrName + "[1]." + $attrName + "Value") .5;
setAttr ($obj[0] + "." + $attrName + "[1]." + $attrName + "Pos") .5;
setAttr ($obj[0] + "." + $attrName + "[1]." + $attrName + "Interpolation") 3;
setAttr ($obj[0] + "." + $attrName + "[2]." + $attrName + "Value") 0;
setAttr ($obj[0] + "." + $attrName + "[2]." + $attrName + "Pos") 1;
setAttr ($obj[0] + "." + $attrName + "[2]." + $attrName + "Interpolation") 3;
// now we can open a simple window that contains a control for this attributes:
if (`window -ex testWin`)
deleteUI testWin;
window testWin;
columnLayout;
gradientControl -at ($obj[0] + "." + $attrName) -h 75 testRamp;
showWindow testWin;
}}}
If you open the attribute editor for your object, under extra attributes you can see them change, as you add\remove\modify the gradientControl
[img[http://farm3.static.flickr.com/2270/2198047046_1b6111924d.jpg?v=0]]
{{{gradientControlNoAttr}}}'s are cool, since they're a small little UI showing you a curve that can be manipulated by the user. Problem though, the docs on it leave a //lot// to be desired. There is also the [[gradientControll|How can I author a 'gradientControl' into my UI?]] which pretty much does the same thing, but ties into an object's attributes.
Here's how to get started:
{{{
// To get more docs on it than the html page, run this:
help gradientControlNoAttr;
// standard docs:
help -doc gradientControlNoAttr;
}}}
To first set it up, you'll need to create an [[optionVar|How can I store and query variables in Maya, that are stored on the hard drive?]] to store\set up it's values. We'll call our {{{optionVar}}} '{{{GCNA_test}}}':
{{{
// remove the optionVar if it already exists:
optionVar -rm "GCNA_test";
// add some default values:
optionVar -stringValueAppend "GCNA_test" "1,0,3";
optionVar -stringValueAppend "GCNA_test" ".5,.5,3";
optionVar -stringValueAppend "GCNA_test" "0,1,3";
}}}
The data format stored in the optionVars is:
*yPos, xPos, curveType
The curveType is:
*0 = flat
*1 = linear
*2 = spline plateau
*3 = spline
*4+ = weird
Once the optionVar is in existance, you can build you UI, to take control of it:
{{{
global proc tempWin()
{
if ( `window -exists tempWin` )
deleteUI tempWin;
window -rtf 1 tempWin;
columnLayout -adj 1 -co both 5;
gradientControlNoAttr -optionVar "GCNA_test" GC_test;
showWindow;
}
tempWin;
}}}
Now you can adjust and add points to the {{{gradientControlNoAttr}}}. To query the values:
{{{
string $vals[] = `optionVar -q GCNA_test`;
print $vals;
}}}
Of course, this just returns back the values you set either in the initial {{{optionVar}}} declaration, or based on updating the {{{gradientControlNoAttr}}}, not the //interpolated// values at any random point. For that, you'd need to write a another proc, probably using the {{{hermite}}} function, to find the interpolated values. If I ever get that code authored, I'll update here.
For something similar, check out the {{{rampColorPort}}} command.
For more info on the {{{gradientControl}}}, see this other posting [[here|http://www.highend3d.com/boards/lofiversion/index.php/t214296.html]]
Credit to 'Vlad Dubovoy' on highend3d.com for his posting on this subject that I was able to pull from.
*This is a general concept I came up with which I'm still roughing out.
*A script can hold multiple procedures. Procedures can take arguments. But if a user doesn't define the proper arguments when executing a procedure, they'll get an error. Or, if you change the procedure to have additional arguments, or remove some arguments, any scripts that call to it will need to be updated to pass the correct arguments, or error.
*Current Maya Example:
**Here is my procedure, with arguments. Let's pretend it's saved in a file called foo.mel
{{{
global proc foo(string $a, string $b, string $c)
{
print ($a + "\n");
print ($b + "\n");
print ($c + "\n");
}
}}}
**And here is our code that calls to it:
{{{
foo("please", "don't", "break");
// result:
please
don't
break
}}}
**Now, pretend you change the arguments of foo to this:
{{{
global proc foo(string $a, string $c)
{
print ($a + "\n");
print ($c + "\n");
}
}}}
**When executing foo based on the old args, (hoping to now see "please break"), instead Maya will error:
{{{
// Error: foo("please", "don't", "break"); //
// Error: Line 1.31: Wrong number of arguments on call to foo. //
}}}
*Other scripting languages like Python allow the authoring of procedures with arguments, but you can author them such that you can assign default values to the arguments, or if you leave an argument out, it won't freak. Maya is much more rigid. Here is the concept I have working to sidestep this limitation. I'm sure it has some bugs, but this is just a prototype. First I'll show the scripts, and then describe what they do:
**'Arguemnt-less' script example:
{{{
// this file is saved as "argLess.mel" It is our script that has no hard-coded
// argument definitions, but can still take passed-in arguments:
{
// this defines our list of arguments that the script cares about:
global string $arglist[];
// now, if it finds an argument it cares about, do something:
if(stringArrayCount("$printme", $arglist))
print ($printme + "\n");
if(stringArrayCount("$addme", $arglist))
{
float $val = $addme + $addme;
print ("Added vals = " + $val + "\n");
}
}
}}}
{{{
// this is an example script that would make a call to our "argument-less" script above:
// it is saved as a script called "call_argLess.mel"
{
// Define our arguments:
global string $printme = "printing!!!!!";
global float $addme = 2.5;
global string $arglist[];
// here we define our global list of argurments. Note we've added one that argLess.mel isn't expecting
$arglist = {"$printme", "$addme", "$notused"};
// and execute our "argumentless" script
source argless;
}
}}}
***To execute call_argLess.mel, you have to source it, since there is no internal procedure definition:
{{{
source call_argLess;
// and the result in the Script Editor:
printing!!!!!
Added vals = 5
}}}
**So what's going on here? We have a script called argLess.mel that has no actual arguemnt definitions (since no procedures have been defined inside of it), yet you can call to it and pass in pre-defined arguments. It's common sense but it had just never occured to me before:
**In call_argLess.mel we setup a global string array called 'global string $arglist[]'. Into this we pass the names of the other global variables we want to send to our argLess.mel script.
**In argLess.mel we "pull-in" the global $arglist[] so it's seen inside of the script. Then we stimply check to see if it has data that it can deal with.
**In the case of the example, it knows to print something called $printme, and to add something call $addme. Even though we've 'passed' in something it isn't expecting ($notused), it doesn't matter, since argLess.mel isn't looking for it.
**Furthermore, if say we removed an argument from our $arglist[], the script could handle that as well, since it only executes on code that it can find.
*I hope this makes some sense! In a nutshell you have your "arg-less" proc that looks to a global list of arguments to work on. If they exist, then it does stuff. When you "call" to your "arg-less" script, first you define what the args values are, and then point your "global list of arguemnts" to their variable names. Since all the data is "global", they are seen between scripts.
*The only downside I've see so far is the way you need to author your "arg-less" script. You basically have a bunch of "if" statements querying if a variable they care about exists in the 'global var list', and if so, executes on it's contents. In most script\procedure writing you can use your variables all over the code, but using this method it's a bit more compartmentalized. Still, it may have some benefits.
*This will open a new Command Prompt:
{{{
>> maya -batch -file someMayaFile.ma -command "file -save"
}}}
*Same as "maya -batch" but this runs in the same Command Prompt as it was executed in:
{{{
>> mayaBatch -file someMayaFile.ma -command "file -save"
}}}
{{{fileBrowserDialog}}}
*Notes: The docs on this suck, so this is what you need to do:
----
This subject shows how to save the last directory accessed, so you can consistently open your dialogs to the same dir:
[[How can I define a start location for my fileBrowserDialog?]]
----
Example with Python, for picking directories. Since you can nest functions in Python, it makes it easier to wrapper the functionality of this command together into one cohesive unit.
{{{
import maya.cmds as mc
def fbd():
def fileCommand(directory, ignore):
# This is called by the fileBrowserDialog command after a dir is picked.
print directory
mc.fileBrowserDialog(mode=4, fileCommand=fileCommand, fileType='directory',
actionName='Choose a directory:')
fbd()
}}}
----
*Mel Examples:
**First, built the file brower dialog: -mode is telling it what to do:
{{{
fileBrowserDialog -mode 2 -fileCommand "procName"
-actionName "whatYouAreDoing";
}}}
**-mode 0 is for reading files, so if you type in a name that dosn't exist, the browers will give an error
**-mode 1 & 2 are pretty much the same and are for for writing to file, so you can speficy your own filename in the browers.
**-mode 4 is for selecting directories only.
**-fileCommand is the name of the below proc, that the fileBrowserDialog? passes its info to.
**-actionName is simply what is printed in the UI, telling the user what is going on
**If you're using mode 1 or 2, and you want to specify your own "file types" for filtering, you can use the below code snippet (thanks to Mason Sheffield).
***The {{{-filterList}}} can be called to multiple times to define different file types. The {{{-filterType}}} is simply the default UI filterList choice. Also, you //must// set {{{-dialogStyle 1}}} for some reason, or the filtering won't work :(
{{{
-filterList "My file type(*.mft),*.mft"
-filterList "otherFileType(*.oft),*.oft"
-fileType "My file type(*.mft)"
-dialogStyle 1
}}}
*Next, you need to build a proc, that can take the return from the FBD, and then do something with it. It needs to be in the below format, but the proc name, and var names can change.
{{{
global proc procName(string $result, string $type){
textFieldButtonGrp -e -tx $result controlName;}
}}}
**So, the {{{fileBrowserDialog}}} passes its info, via the {{{$result}}} argument, into the "procName" proc. Then procName will update controlName with $result.
*Another issue: By default you can't specify WHERE the dialog will open too. It appears that it bases where it opens on where Maya's current workspace is. SO, to tell it where to go, you'd need to do something like this:
{{{
// query Maya's current workspace:
string $mayaWorkspace = `workspace -q -dir`;
// set the workspace to where you want the dialog to open:
workspace -dir $myCustomPath;
// run the dialog code:
fileBrowserDialog ....
// and set the workspace back again:
workspace -dir $mayaWorkspace;
}}}
*Annoying!
----
----
I got an emailed suggestion from Nicolas Combecave. Thanks Nicolas!
*After scrutining into the fileBrowser mel //command//, I found that the //script// {{{fileBrowserWithFilter}}}, does both in one single command, plus adds an items filtering mechanism. Here is its syntax from the procedure declaration:
{{{
global proc int fileBrowserWithFilter(
string $callBack,
string $action,
string $title,
string $type,
int $mode,
string $filters[],
string $dir)
}}}
*We can translate it into a mode understandable text:
**{{{string $callBack}}} > your procedure to launch upon validation
**{{{string $action}}} > text label on the validation button
**{{{string $title}}} > browser title
**{{{string $type}}} > a filetype to select in the dropdown list. Even custom ones (see below).
**{{{int $mode}}} > choose for read/write/listDirs... (see description above)
**{{{string $filters[]}}} > fileTypes in the dropdown menu. You can define yours here: {"myFileType,*.mft":"yourFileType,*.yft":"theirFileType,*.tft":etc...}
**{{{string $dir}}} > the starting directory
*So you can use it when you want to specify a file that doesn't exists, into a specified destination, filtering only special file types, even custom ones:
{{{
string $startDir ="c:/tmp/";
fileBrowserWithFilter(
"procName",
"myLabel",
"myTitle",
"afileType",
1,
{"aFileType,*.aft","anotherFileType,*.anft"},
$startDir);
}}}
Given the 'Focal Length' and 'Horizontal Film Aperature'? (since it isn't an actual attribute)
*Code pulled from Maya's {{{AEcameraTemplate.mel}}} script:
{{{
float $focal = `getAttr "cameraShape1.focalLength"`;
float $aperture = `getAttr "cameraShape1.horizontalFilmAperture"`;
float $fov = (0.5 * $aperture) / ($focal * 0.03937);
$fov = 2.0 * atan ($fov);
$fov = 57.29578 * $fov;
}}}
Given the 'Horizontal Film Aperature' and 'Angle of View' (Field of View)?
*Code pulled from Maya's {{{AEcameraTemplate.mel}}} script. In this example we query the camera's Attribute Editor floatSliderGrp for the FOV, since the FOV isn't an actual camera attribute.
{{{
float $fov = `floatSliderGrp -q -value fovGrp`;
float $aperture = `getAttr "cameraShape1.horizontalFilmAperture")`;
float $focal = tan (0.00872665 * $fov);
$focal = (0.5 * $aperture) / ($focal * 0.03937);
}}}
Given the 'Focal Length' and 'Angle of View'?
* I don't know... someone please tell me....
Pseudo-code for the time being:
{{{
# Normalize the vectors:
vecA.normalize()
vecB.normalize()
# Find the dot product:
dot = vecA.dot(vecB)
#Get the angle in degrees from the acos of the dot:
angle = degrees(acos(dot))
}}}
Scripting languages like Python allow a procedure to be authored such that if it is expecting an argument, but none is passed in, it can use a default value. Mel doesn't allow that. :-(
But, you can use a technique to //simulate// the functionality. In a nutshell, rather than defining a default argument in the //procedure//, you define it when you pass in an //argument//.
To explain, a simple proc:
{{{
global proc string foofoo(string $arg)
{
return $arg;
}
}}}
So, whatever you pass in as $arg will be returned. But what if you don't want to pass anything in as $arg, but you still want it to return something by default? You can use a {{{conditional statement}}} //as// the argument:
{{{
// passing in an arg:
string $arg = "MY ARG";
foofoo((size($arg) > 0 ? $arg : "NO ARG"));
// Result: MY ARG //
// no arg passed in, using default value:
string $arg = "";
foofoo((size($arg) > 0 ? $arg : "NO ARG"));
// Result: NO ARG //
}}}
So either way, you have to pass in $arg, there's no way around that. But, it allows the user to write code to pass in a //different// default value if the argument (in this case) is empty. The conditional statement can be changed to support int values, etc.
Also see:
*[[How can I use operators outside of conditional statements?]]
*[[How can I define a variable outside 'if statement', but change its value within the statment?]]
When I'm rigging, I will often create an initial 'rig-skeleton' that fits inside the mesh to be deformed. To speed things along, I would first just rough in the joint positions. But later I like to get them centered into a selection of verts.
The below code will center a joint based on an edge loop. To use the code, pick a joint, then add-select an edge from your mesh (RMB on the mesh -> Edge). The code will (try to) find the edge loop, and center the joint in it.
{{{
// Center joint in edge loop.
// Select one joint, and one edge.
// first, find edge loop:
string $sel[] = `ls -fl -sl`;
string $joints[] = `ls -type "joint" $sel`;
string $edges[] = `polyListComponentConversion -te`;
int $edgeNum=0;
if(size($sel)!=2)
error("Please select exactly one edge, and one joint");
string $buffer[];
tokenize $edges[0] ".[]" $buffer;
$edgeNum = $buffer[size($buffer)-1];
if(size($joints)==0)
error "You didn't pick a joint";
if(size($edges)==0)
error "You didn't pick an edge";
polySelect -el $edgeNum;
// convert that to a list of verts:
string $verts[] = `polyListComponentConversion -tv`;
$verts = `ls -fl $verts`;
// Find center of vert positions:
float $xyz[] = {0,0,0};
float $x=0;
float $y=0;
float $z=0;
for($i=0;$i<size($verts);$i++)
{
// for some reason, no matter what is picked, or nothing is picked,
// pointPosition likes to toss a warning and say that more than one
// think is picked. So we just hide that, since it shouldn't be
// warning at all... :-S
scriptEditorInfo -e -sw 1;
float $pos[] = `pointPosition -w $verts[$i]`;
scriptEditorInfo -e -sw 0;
$x = $x + $pos[0];
$y = $y + $pos[1];
$z = $z + $pos[2];
}
$xyz = {($x / size($verts)), ($y / size($verts)), ($z / size($verts))};
// Move joint pivot to new location:
move -a -ws -rpr $xyz[0] $xyz[1] $xyz[2] ($joints[0] + ".rotatePivot") ($joints[0] + ".scalePivot");
print ("Centered " + $joints[0] + " in edge loop\n");
// Finally pick our edge loop, and joint:
select -r $verts;
select -add $joints[0];
}}}
There is a mel script that lives here, that will do the switching for you (very handy):
{{{
../MayaX.X/scripts/startup/setNamedPanelLayout.mel
}}}
Which has this argument structure:
{{{
setNamedPanelLayout( string $whichNamedLayout );
}}}
So you can call to it like this:
{{{
setNamedPanelLayout "Single Perspective View";
}}}
And it will switch to that view. You can get a list of these by accessing the Menu: {{{'Panels -> Saved Layouts -> (list of layouts)'}}}
The little bit of code that is interesting in that script is this line: (presuming: {{{string $whichNamedLayout = "Single Perspective View";}}})
{{{
string $configName = `getPanel -cwl $whichNamedLayout`;
// Result: panelConfiguration2 //
}}}
Which actually returns something useful for the code to work with.
__Presuming you have Maya //Unlimited// installed__ (and properly licensed), there are two ways to change what features are available to you.
----
One way is to add this line to your {{{Maya.env}}} file:
{{{
MAYA_LICENSE = complete
}}}
or
{{{
MAYA_LICENSE = unlimited
}}}
Every time you start Maya, it will run in the defined mode.
----
Another way allows you to launch Maya from the command line (probably with a .bat file) with the license defined. This allows you to say, run //temporarily// in unlimited when you normally only run in complete (via the {{{Maya.env}}}).
{{{
maya -lic=unlimited
}}}
That can be handled through the:
*{{{defaultRenderGlobals}}} node (node type: {{{renderGlobals}}})
**Image format, Renderable Objects, Per camera, what to render (Image, Mask, Depth, or renderable at all), renderable animation range, etc.
It has two sibling nodes that help it control your renders. The:
*{{{defaultRenderQuality}}} node (type: {{{renderQuality}}})
**Setting Anti-aliasing, particle sampling, motion blur stuff, etc.
*and {{{defaultResolution}}} node (type: {{{resolution}}})
**Resolution, aspect ration, field-rendering, etc.
For example, set our output render format to .jpg:
{{{
setAttr "defaultRenderGlobals.imageFormat" 8;
}}}
For Hardware rendering you have the:
*{{{hardwareRenderGlobals}}} (node type: {{{renderGlobals}}})
*and {{{defaultHardwareRenderGlobals}}} (node type: {{{hwRenderGlobals}}})
**I wonder why it's special and gets two nodes?
Other renderers would have their own nodes. But since I don't have any loaded right now, this is all I'm listing ;)
Also see:
*[[How can I get a list of the available renderers?]]
*[[How can I change the current renderer through mel?]]
*Edit the Maya script here:
{{{
C:/Program Files/Alias/MayaX.X/scripts/AETemplates/AEshaderTypeNew.mel
}}}
*And in the {{{global proc AEshaderTypeCB}}} inside that script,
*After this line:
**{{{ delete $shaderNode;}}}
*Add this:
**{{{rename $replaceNode $shaderNode;}}}
*Save, re-{{{source}}} {{{AEshaderTypeNew}}} in Maya, it should do just what you want when changing the "Type" in the Attribute editor, when a shader is selected. If you want to some thing more custom, the guts of that script will tell you how to do it. I wonder why this isn't the default option?
The {{{defaultRenderGlobals}}} node (see notes [[here|How can I change my Render Settings?]]), who's node type is {{{renderGlobals}}}, allows you to modify it to do this.
To change the file format, you modify the {{{.imageFormat}}} attr:
{{{
// set to .tga:
setAttr defaultRenderGlobals.imageFormat 19;
}}}
From the docs, these are the current choices:
<<<
GIF (0), SI (1), RLA (2), Tiff (3), Tiff16 (4), SGI (5), Alias (6) IFF (7) JPEG (8) EPS (9) ~IFF16 (10) Cineon (11) Quantel (12) ~SGI16 (13) TARGA (19) BMP (20) SGIMV (21) QT (22) AVI (23) MACPAINT (30) PHOTOSHOP (31) PNG (32) QUICKDRAW (33) QTIMAGE (34) DDS (35) DDS (36) ~IMFplugin (50) Custom (51) SWF (60) AI (61) SVG (62) SWFT (63)
<<<
Thanks to a tip from [[Seith|http://seithcg.com/wordpress/]], based on the {{{IMFplugin}}} setting (#50), you can load in formats //other// than what is listed. For example, to render out as {{{.xpm}}}:
{{{
// set to IMFplugin format:
setAttr defaultRenderGlobals.imageFormat 50;
// set the imfPluginKey to .xpm:
setAttr defaultRenderGlobals.imfPluginKey -type "string" "xpm";
}}}
Nice! If you're wondering, you can visually see the {{{imfPluginKey }}} data in the 'Extra Attributes' section of the Attribute Editor on the {{{defaultRenderGlobals}}}.
{{{
setCurrentRenderer "rendererName";
}}}
*Note that {{{setCurrentRenderer}}} is a sub-proc living in {{{.../scripts/others/supportRenderers.mel}}}
*This has the same effect as changing the render type in the Render Globals top dropdown menu. It turns out that if you open a scene that's in "Maya Software" mode, and you want to change values on the Mental Ray renderer, you need to "set" the renderer to mental ray first, because by default, the mental ray nodes don't exist in the scene. By setting the renderer to mental ray, the appropriate nodes are created.
{{{polyOptions}}}
*Example, show the UV map border:
{{{
polyOptions -dmb 1;
}}}
*Example, enable the vertex color of a poly object in shaded mode:
{{{
polyOptions -cs 1;
}}}
I personally dislike the view-cube. Just yet one more thing to clutter the UI. But maybe now I'll give it a second chance :)
Based on this post (by Shawn ~McClelland):
http://forums.cgsociety.org/showpost.php?p=6009050&postcount=216
It says:
<<<
In your install location ({{{C:\Program Files\Autodesk\Maya2009\icons\AutoCam\}}}) there is a file called {{{VCfacemap.png}}} which is basically a simple cube map that the ~ViewCube uses to texture itself.
...
Save the {{{VCfacemap.png}}} file and reload Maya, voila you now have a textured viewcube
<<<
To turn on\off:
Window > Settings/Preferences > Preferences -> Interface -> ~ViewCube
Some examples :)
http://www.flickr.com/photos/7875859@N03/3772730145
{{{setStartupMessage}}}
*Notes: You'll need to edit the {{{\MayaX.X\scripts\startup\initialLayout.mel}}} script to see this work.
{{{setToolTo}}}
*Example: Set the current tool to the "select tool"
{{{
setToolTo moverSuperContext;
}}}
{{{connectionInfo}}}
Only requires a single object.attr.
{{{
// Is the object.attr a source of a connection?
connectoinInfo -is object.tx;
}}}
{{{
// Is the object.attr the destination of a connection?
connectionInfo -id object.tx;
}}}
{{{
// List an object.attr's source connection, meaning, list the input connection.
// This will return a single object.attr.
connectionInfo -sfd object.tx
}}}
{{{
// List an object.attr's destination connection, meaning, list the all output connections:
// This could return multiple object.attrs.
connectionInfo -dfs object.tx;
}}}
Sometimes when running a {{{getAttr}}} on an float node attribute, Maya will return back some very tiny value:
{{{
# Python code:
print mc.getAttr('myNode.myFloat')
2.7912680472872789e-017
}}}
The {{{print}}} statement turns the {{{float}}} into a {{{string}}} for display to the screen. And in the process, does some shorthand so it's not an incredibly long number displayed before you.
If you see {{{e-}}} in the number (exponent), it basically means the value is zero for all intensive purposes.
But how can you really make that value zero? At least so it prints nicer?
If you're using Python (and you should be), you can use Python's {{{round()}}} function to take care of this. Just pass in how many places to the right of the decimal you want it rounded to:
{{{
val = 2.7912680472872789e-017
print round(val, 4)
0.0
}}}
Matt Estela pointed me to this posting over on tokeru.com:
http://www.tokeru.com/t/bin/view/Maya/MayaMelAndExpressions#Strip_spaces_and_punctuation_mak
Here's the source from the site (thanks Matt!):
{{{
string $comment = "this, is a line with numbers like 0.25 and: stuff!";
string $regex = "[^a-zA-Z0-9_]";
string $replace = "_";
print ($comment+"\n");
int $i = 0;
string $match;
int $totalChars = `size($comment)`;
for ($i = 0; $i < $totalChars; $i++) {
$match = `match $regex $comment`;
$comment = `substitute $regex $comment $replace`;
if ($match == "") break;
}
print $comment;
print "\n";
// returns "this__is_a_line_with_numbers_like_0_25_and__stuff_"
}}}
I thought I'd see how I could author this in Python, and posted an alternative over at my [[Python Wiki|http://pythonwiki.tiddlyspot.com/#%5B%5BHow%20can%20I%20strip%20illegal%20characters%20from%20a%20string%3F%5D%5D]]
By default, when a UI is created, Maya will remember its size. If later in your script you change it's size, those changes may not be reflected on screen.
{{{
windowPref -remove "UI_nameGoesHere";
}}}
Old example:
{{{
// compute current position:
float $posNowX = obj.translateX;
float $posNowY = obj.translateY;
float $posNowZ = obj.translateZ;
// compute previous position:
float $posThenX = `getAttr -t (frame - 1) obj.translateX`;
float $posThenY = `getAttr -t (frame - 1) obj.translateY`;
float $posThenZ = `getAttr -t (frame - 1) obj.translateZ`;
// compute distance traveled per frame:
float $distance = sqrt( pow(($posNowX - $posThenX), 2) +
pow(($posNowY - $posThenY), 2) +
pow(($posNowZ - $posThenZ), 2) );
print ($distance + "\n");
}}}
Newer example: (thanks Jamie ~McCarter)
{{{
float $t = `currentTime -q`;
vector $currentP = `getAttr object.translate`;
vector $previousP = `getAttr -t ($t-1) object.translate`;
float $dist = mag( $currentP - $previousP );
print( "distance travelled " + $dist + "\n");
}}}
Newer, with matrices! ;) Probably the most accurate way of doing it?
{{{
float $t = `currentTime -q`;
float $curM[] = `getAttr object.worldMatrix`;
float $prevM[] = `getAttr -t ($t-1) object.worldMatrix`;
vector $currentP = <<$curM[12], $curM[13],$curM[14]>>;
vector $previousP = <<$prevM[12], $prevM[13],$prevM[14]>>;
float $dist = mag( $currentP - $previousP );
print( "distance travelled " + $dist + "\n");
}}}
The {{{curveInfo}}} node lets you do this. I havn't actually tried all of this yet, but here's the idea:
Make the {{{curveInfo}}}, and connect your curve to it:
{{{
# Python code
import maya.cmds as mc
lenCalc = mc.createNode("curveInfo")
mc.connectAttr("myCurveShape.worldSpace[0]", lenCalc+".inputCurve")
}}}
The in the //Attribute Editor//, with the new {{{curveInfo}}} selected, go to the "Control Points" drop down, and "Add New Item" for each of the CV's.
Then, those attributes will be available to connect to, either through the 'Connection Editor', or via scripting.
I need to research how to do all of this in mel\Python
I've made several rigs that could for all intensive purposes be envisioned as suspension bridges: There is a chain of joints stretched over a certain distance. There is a controller at the start and end of the chain. Transforming either the start or the end controller will in-turn transform the chain, but the transformations have a falloff over the distance of the chain.
This is done by constraining all the joints in the chain to both controllers, and then modifying the weights of each constraint to give priority to the closer controller.
Presuming that "target1" is at the same location as "joint1", and "target2" is at the same location as "joint5", here are the resulting weights that would be applied to the joints (based on the below example code):
| | target1 | target 2|
| joint1 | 1.0 | 0 |
| joint2 | .75 | .25 |
| joint3 | .5 | .5 |
| joint4 | .25 | .75 |
| joint5 | 0 | 1.0 |
{{{
# Python code
import maya.cmds as mc
# define our source list of nodes, plus the two targets they'll follow
nodes = ["joint1", "joint2", "joint3", "joint4", "joint5"]
target1 = "locator1"
target2 = "locator2"
# define the "step" a weight will increase or decrease for each node:
weightStep = 1.0/(len(nodes)-1)
for i in range(len(nodes)):
# create the constraint:
pc = mc.parentConstraint(target1, target2, nodes[i], maintainOffset=True)
# query the constraint target attrs just created:
targets = mc.parentConstraint(pc, query=True, targetList=True)
# calculate the weights to be applied to the two target attrs:
w1 = abs(1 - i * weightStep)
w2 = 1 - w1
# apply weights:
mc.setAttr(pc[0]+"."+targets[0]+"W0", w1)
mc.setAttr(pc[0]+"."+targets[1]+"W1", w2)
}}}
The above example gives a linear falloff between the targets. Multiplying the weights based on a (Python) {{{math.sin()}}} function could start to give you a more ease-in \ ease-out weighting solution.
I wanted to build a rig animation controller that would change its wireframe color depending on which 'state' it was in, via a {{{.localGlobal}}} attr (going from 0-1). If it was in a "local" state, it would be green, in "global" state, red.
FYI, Maya has 32 available override colors: 0 = default color, and 1-31 are other colors. Red happens to be 13, and green 14. The {{{colorIndex}}} command will return back the RGB or HSV vals of each index.
{{{
// Define the 'state' node name (a transform) that has our switch attr (.localGlobal),
// and the shape node name of our animation controller that will change color:
string $stateNode = ("someTransform");
string $controllerShape = ("someShape");
// define our color indices.
int $green = 14;
int $red = 13;
// enable our override colors onr our shape:
setAttr ($controllerShape + ".overrideEnabled") 1;
// make our condition node, to define what the colors should be:
string $con = `createNode condition`;
// we want values less than .5 to be green, and greater than .5 to be red:
setAttr ($con + ".secondTerm") 0.5;
// set our condition to 'greater than'
setAttr ($con + ".operation") 2;
// define our condition colors:
setAttr ($con + ".colorIfTrueR") $red;
setAttr ($con + ".colorIfFalseR") $green;
// hook our switch attr into our condition:
connectAttr -f ($stateNode + ".localGlobal") ($con + ".firstTerm");
// and hook the output of the condition into our controllers color:
connectAttr -f ($con + ".outColorR") ($controllerShape + ".overrideColor");
}}}
Now, when the .localGlobal attr goes from 0-1, it will change the wireframe color from green to red.
Got pointed to the blog post [[here|http://www.185vfx.com/2003/03/convert-a-3d-point-to-2d-screen-space-in-maya/]] by Rob Bredow. Take a gander.
Some languages give you the ability to Boolean test strings: You can convert a {{{string}}} to a Boolean, and if the string has any characters, the bool = {{{True}}}. If the string is empty, the bool = {{{False}}}. How to do something similar with ''mel''?
Mel doesn't have Boolean variable types, so we have to fake it with an {{{int}}}. (Although, you can make Boolean //attribute// types. Odd you can make one but not the other.)
You could try this, but we get a warning, and an {{{int}}} of {{{0}}}:
{{{
string $s = "word";
int $i = $s;
// Warning: line 2: Converting string "word" to an int value of 0. //
// Result: 0 //
}}}
However, you could use the '{{{?}}}' operator, and get the result you're after:
{{{
string $s = "word";
int $i = size($s) ? 1 : 0;
// Result: 1 //
}}}
Show help docs on the {{{?}}} operator:
{{{
showHelp -d "General/_operator.html";
}}}
----
Similar stuff in ''Python''. Python //does// have Boolean object types, so we first convert the {{{string}}} to {{{Boolean}}}, then to {{{int}}} (or obviously leave off the {{{int}}} part if all you want is the bool). Trying to convert a {{{string}}} directly to an {{{int}}} raises a {{{ValueError}}}.
{{{
s = "word"
i = int(bool(s))
# 1
}}}
Maya comes with a script to do this, which lives here (Maya 2008):
{{{
<drive>:\Program Files\Autodesk\Maya<version>\scripts\others\clusterCurve.mel
}}}
However, it doesn't return anything, just does work. Here is a modified snippet that can provide the cluster info:
{{{
// $curve is the shape of some curve
string $clusters[];
int $numCVs = `getAttr -size ($curve + ".controlPoints")`;
int $i = 0;
for ($i; $i < $numCVs; $i++)
{
//make CV into a cluster
string $c[] = `cluster -relative ($curve + ".cv[" + $i + "]")`;
$clusters[$i] = $c[0];
}
}}}
----
Also see:
*[[How can I get a list of all the CV's on a curve?]]
I seem to end up doing this a lot. Using Python and 'list comprehensions', it's really, really easy:
In this example, we'll base it on a selection of verts:
{{{
# Python code
import maya.cmds as mc
verts = mc.ls(selection=True, flatten=True)
pos = [mc.pointPosition(v, world=True) for v in verts]
myCurve = mc.curve(degree=1, point=pos)
}}}
That's it. If you were basing it of a selection of transforms, you'd need to change your {{{pointPosition}}} arg to '{{{v+".rotatePivot}}}' to pull the proper location, fyi.
Often when making UI's you want a {{{fileBrowserDialog}}} to consistently open in the same location. Or, remember the last place it was opened from. The below example shows how to store a setting that remembers the last dir that was accessed, and repoint the browser there on later browsing expeditions.
The magical secret is the {{{workspace}}} command's {{{directory}}} arg: Need to set that sucker to your saved values. Took me a while to figure that out...
{{{
# Python code:
import os
import maya.cmds as mc
# What is called to via the fileBrowserDialog:
def fbdCommand(fileName, fileType):
# Do stuff:
# stuff, stuff, stuff....
# Then store the path:
path = os.path.split(fileName)[0]
mc.optionVar(stringValue=['testPath', path])
# Load our directory preset (if it exists) before launching the dialog:
if mc.optionVar(exists='testPath'):
savedPath = mc.optionVar(query='testPath')
mc.workspace(directory=savedPath)
# In this example, make a file-save dialog:
mc.fileBrowserDialog(mode=1, fileCommand=fbdCommand, fileType='mayaAscii',
operationMode='SaveAs', actionName="Save_as")
}}}
If you're trying to open the dialog in 'directory mode', which is mode 4, then this subject has additional notes:
[[How can I modify the current project \ workspace?]]
*Use an 'if statement' with correct variable scope:
*Example 1 (keep scope within the variable):
{{{
string $foo = "abcd";
if(1)
$foo = "dcba";
print $foo;
// result: "dcba" //
}}}
**This works for two reasons: {{{$foo}}} is defined first, outside the scope of the 'if statement'. Second, when {{{$foo}}} is updaetd inside the 'if statement', since it's being updated by '{{{$foo = ...}}}', //rather// than '{{{string $foo = ...}}}', then it keeps the original scope of {{{$foo}}}. If we had //instead// updated it with '{{{string $foo = ...}}}', then the print statement would have printed the original value, since we would have broken out of its original scope. In a nutshell, if you put the variable descriptor before variable name (like {{{string}}}), then that defines the start of a new scope for that variable name. Every time after that, if you call direcltly to the variable name without the descriptor, then you're still acting within the scope of that original variable. I really hope this makes some sense.
*Example 2 (//loose// original variable scope due to re-declaring the variable inside the 'if statement'):
{{{
string $foo = "abcd";
if(1)
string $foo = "dcba";
print $foo;
// result: "abcd" //
}}}
*Example 3: //Or//, use a 'conditional statement':
{{{
string $bob = "asdf";
string $lary = $bob == "asdf" ? "happy" : "sad";
}}}
**so, //if// $bob = asdf, then $lary is "happy". Otherwise, $lary is "sad".
**The conditional statements can be nested, to provide different return values.
{{{
camera -e -startupCamera 0 $camName;
delete $camName;
}}}
Default camera's ({{{persp}}}, {{{top}}}, {{{front}}}, {{{side}}}) have a special flag that is set to let Maya know they are "startup cameras". This keeps the cameras persistent between different Maya files when you're opening them, importing, etc. Have you even wondered why when you import multiple maya files the cameras don't start piling up?
There have been issues when external tools start to monkey with the cameras, renaming them, or evening creating duplicates, that can't be deleted. Using this method, you can now delete them. FYI, there always needs to be at least one cam in the scene listed as a "startup camera".
!!!mel:
I'm not sure what version of Maya introduced it, but //at least// by Maya 2009 the {{{sysFile}}} command has a {{{-removeEmptyDir}}} arg (thanks for the tip from Matti Grüner)
{{{
sysFile -removeEmptyDir "c:/temp";
}}}
http://download.autodesk.com/us/maya/2009help/Commands/sysFile.html
!!!Python:
Of course if you have Maya 8.5 or newer you can just use Python:
This only works if the dir is empty:
{{{
import os
os.rmdir("c:/temp")
}}}
if you want to remove a directory, and all the stuff in it:
{{{
import shutil
shutil.rmtree("c:/temp")
}}}
!!!Direct Windows system call:
The DOS {{{rmdir}}} command.
Example: Delete a directory and all files in it ({{{/s}}}), without prompt ({{{/q}}}):
{{{
system("rmdir /s /q c:/temp");
}}}
{{{deleteUI "buttonName";}}}
And then be sure to save the shelfs\prefs
Here is some code to figure out the members of what the current shelf is.
{{{
string $buttons[]; clear $buttons;
global string $gShelfTopLevel;
if (`tabLayout -exists $gShelfTopLevel`)
{
string $currentShelf = `tabLayout -query -selectTab $gShelfTopLevel`;
$buttons = `shelfLayout -q -ca $currentShelf`;
}
print $buttons;
}}}
Often times I want to cleanup a scene by deleting all leaf nodes: Nodes that don't have children. Or, hierarchies of the same kind of node that don't have any meaningful children.
{{{
# Python code
import maya.cmds as mc
def delLeafByType(types):
"""
types : list : List of given DAG node types to delete, presuming
they're leaf of a hierarchy.
"""
totalDel = 0
triedToDel = 0
leaf = mc.ls(dag=True, leaf=True)
noDel = []
while len(leaf) > 0 and len(leaf) >= len(noDel):
for i in leaf:
try:
if mc.objectType(i) in types:
mc.delete(i)
totalDel += 1
else:
noDel.append(i)
except Exception, e:
noDel.append(i)
triedToDel += 1
print str(e).strip(), i
leaf = mc.ls(dag=True, leaf=True)
if triedToDel:
print "Deleted %s leaf nodes, tried to delete %s others but couldn't. Check Script Editor."%(totalDel, triedToDel)
else:
print "Deleted %s leaf nodes"%totalDel
}}}
{{{
delLeafByType(["transform", "locator"])
}}}
{{{
Deleted 14 leaf nodes
}}}
Often, when I'm cleaning a scene for export to game, I want to make sure only nodes of a certain type live in it. Here's one way to simply remove everything from the scene that isn't part of the list of "acceptable nodes" you want.
{{{
# Python code
import maya.cmds as mc
def delAllByTypes(exceptThese, dag=False):
"""
Delete all nodes in the scene, except the ones provided by the user.
If anything is flagged for deletion and can't be, print exception results.
exceptThese : list : node types that *shouldn't* be deleted.
dag : bool : Optional. If True, will limit node types to only
DAG nodes (transforms & shapes and their associates). Otherwise,
look to ALL node types.
"""
nodes = []
if dag:
nodes = mc.ls(dag=True)
else:
nodes = mc.ls()
nodes.reverse()
removal = []
for d in nodes:
if mc.objectType(d) not in exceptThese:
removal.append(d)
for r in removal:
try:
mc.delete(r)
except Exception, e:
print str(e).strip(), r
}}}
{{{
# delete all DAG nodes in the scene that aren't of type 'good':
good = ["joint", "transform", "mesh"]
delAllByTypes(good, dag=True)
}}}
Execute the proc:
{{{
MLdeleteUnused;
}}}
Which lives in the script here: {{{../MayaX.X/scripts/others/MLdeleteUnused.mel}}}
"ML" stands for "~MultiLister" the old version of the Hypershade (which was brought into Maya from Alias).
This code is also called to when you use "Optimize Scene Size" from the File menu, or in the '~HyperShade -> Edit -> Delete Unused Nodes'.
{{{pointCurveConstraint}}}
{{{disconnectAttr}}}
''Some examples'':
''mel'':
{{{
// Disconnect only keyable attrs.
string $obj = "null1";
string $keyable[] = `listAttr -k $obj`;
for($i=0;$i<size($keyable);$i++)
{
string $incoming[] = `listConnections -s 1 -d 0 -p 1 -c 1 ($obj+"."+$keyable[$i])`;
if (size($incoming) )
disconnectAttr $incoming[1] $incoming[0];
}
}}}
{{{
// Disconnect ALL incoming connections.
string $obj = "null1";
string $incoming[] = `listConnections -s 1 -d 0 -p 1 -c 1 $obj`;
for($i=0;$i<size($incoming);$i=$i+2)
disconnectAttr $incoming[$i+1] $incoming[$i];
}}}
''Python'':
{{{
import maya.cmds as mc
string obj = "null1"
string keyable = mc.listAttr(obj, keyable=True)
for k in keyable:
incoming = mc.listConnections('%s.%s'%(obj,k), source=True,
destination=False, plugs=True, connections=True)
if incoming is not None:
mc.disconnectAttr(incoming[1], incoming[0])
}}}
In both examples we have {{{listConnections}}} return back both plugs and connections, which gives us a list of source\destination objec.attr pairs, making them easy to feed directly into {{{disconnectAttr}}}.
Best to make the border size bigger too in the process:
{{{
polyOptions -activeObjects -displayBorder true -global -sizeBorder 10;
}}}
You can also add the {{{-relative}}} flag, which will turn it into a toggle.
To turn it back off:
{{{
polyOptions -activeObjects -displayBorder false;
}}}
Around Maya 2010 (maybe earlier) they have a wiz-bang feature that displays a soothing gradient color in the background of the viewports. To turn it on, the default hotkey is {{{ALT+B}}}. Or, you can use code (after hacking apart {{{cycleBackgroundColor.mel}}}):
{{{
# Python code
import maya.cmds as mc
mc.displayPref(displayGradient=True)
mc.optionVar(intValue=["displayGradient", 1])
}}}
I always forget how to do this. So, I'm making a note:
Where {{{joints}}} is a list of joints, and {{{mesh}}} is the name of the thing being bound:
{{{
# Python code
import maya.cmds as mc
mc.skinCluster(joints, mesh, toSelectedBones=True)
}}}
I know WAY more stuff in mel than in dos. But if you operate in windows, and need to do stuff in the system, then you'll need to know how to do it in dos. Here are some links I've found that are useful for explaining things like batch scripts, redirection, variables, etc. The list will grow...
*http://www.febooti.com/products/command-line-email/batch-files/
*http://www.computerhope.com/msdos.htm
Python has no built-in vector math calls :( You can see my notes on this [[here|What mel commands are 'missing' from the Python integration?]]. In Maya however, you have a few solutions to continue to do vector math:
----
''Method A'': Write your own wrappers around the @@mel@@ vector commands:
{{{
import maya.mel as mm
def mvCross(vec1, vec2):
mVec1 = vec1.__str__().replace("[", "<<").replace("]",">>")
mVec2 = vec2.__str__().replace("[", "<<").replace("]",">>")
res = mm.eval("cross(" + mVec1 + ", " + mVec2 + ")")
return list(res)
vec1 = [0,1,0]
vec2 = [1,0,0]
cross = mvCross(vec1,vec2)
print cross
# [0.0, 0.0, -1.0]
}}}
----
''Method B'': Call to {{{maya.OpenMaya}}}, and use API goodness:
{{{
import maya.OpenMaya as om
vec1 = om.MVector(0,1,0)
vec2 = om.MVector(1,0,0)
vec3 = vec1^vec2 # calculate cross product
print vec3.x, vec3.y, vec3.z
# 0.0 0.0 -1.0
}}}
----
''Method C'': Write your own Python vector module:
I document the math behind some of the basic vector functions over at my Python Wiki
[[here|http://pythonwiki.tiddlyspot.com/#%5B%5BVector%20math%5D%5D]]
----
''Method D'': Get {{{NumPy}}}
http://numpy.scipy.org
You'll need to make sure that the lib is installed where Maya can see it, like here:
{{{
C:\Program Files\Autodesk\Maya<version>\Python\lib\site-packages
}}}
Example:
{{{
import numpy
v1 = (1,2,3)
v2 = (3,2,1)
v3 = numpy.cross (u, v)
}}}
You can use the {{{headsUpMessage}}} command to do this. It will disappear on the next screen refresh (by default). It has arguments to have it display for a fixed amount of time, or be centered on a particular object \ object selection.
You could do something similar with a {{{confirmDialog}}} or {{{promptDialog}}}, but they are modal dialog boxes that stop the user from interacting until their condition is met. {{{headsUpMessage}}} doesn't stop the user from interaction, and automatically goes away.
From the docs:
{{{
headsUpMessage "Ouch!";
headsUpMessage -object circle1 "This is Circle 1";
headsUpMessage -selection "These objects are selected";
headsUpMessage -time 5.0 "Text appears for minimum of 5 seconds.";
headsUpMessage -verticalOffset 20 "Text appears 20 pixels above point.";
headsUpMessage -horizontalOffset -20 "Text appears 20 pixels to the left of the point.";
}}}
Presuming you have a node with children, how can you duplicate it, but not its children?
You can use the {{{parentOnly}}} argument of the {{{duplicate}}} command
{{{
# Python code
import maya.cmds as mc
sel = mc.ls(selection=True, long=True)
for s in sel:
new = mc.duplicate(s, parentOnly=True)
}}}
----
Old data, incorrect concept (the execution still works, but obviously it's unneeded).
<<<
To my knowledge, you can't: The children are always created during the duplication. However, you can duplicate it, and then delete the new children:
{{{
# Python code
import maya.cmds as mc
sel = mc.ls(selection=True, long=True)
for s in sel:
new = mc.duplicate(s, renameChildren=True, returnRootsOnly=True)
kids = mc.listRelatives(new, children=True, fullPath=True)
if kids is not None:
mc.delete(kids)
}}}
<<<
If a node lives in a set, when you duplicate the node, the new duplicate well live in that set as well. But, what if you don't want this behavior?
{{{
import maya.cmds as mc
def dupeNoSet(nodesInSet):
"""
Duplicate the passed in nodes, and remove them from any sets the original
nodes were in.
Args:
nodesInSet : ["node", ...] : List of node names to duplicate.
return : ["node", ...] : List of duplicated node nams.
"""
if len(nodesInSet) == 0:
return []
newNodes = mc.duplicate(nodesInSet, renameChildren=True)
# Rename the new joints, and fill our jointMap dict:
for i in range(len(newNodes)):
outgoing = mc.listConnections(newNodes[i], source=False,
destination=True, plugs=True,
connections=True)
if outgoing is not None:
for i in range(0, len(outgoing), 2):
if "instObjGroups" in outgoing[i]:
try:
mc.disconnectAttr(outgoing[i], outgoing[i+1])
except:
# there can be dupe reports in our outgoing list, so skip
# if its already been disconnected.
pass
return newNodes
}}}
{{{
nodesInSet = mc.ls(selection=True, long=True)
dupes = dupeNoSet(nodesInSet)
}}}
One of the most important things when painting smooth skin weights, is holding and unholding influence weights. If an influence is 'unheld' (unlocked), its influence weights can be modified. If it is held (locked), its influence weights can't be modified. In general when painting weights, you want ALL the influences held except the current two you're working on.
In the 'Paint Skin Weights Tool', under the 'Influence' frame, it shows the influence list for everything effecting that geo, and a button at the bottom to 'Toggle Hold Weights on Selected'. If you have 100 joints, then you need to pick each one in the list, and hit that button. This can be, really time consuming. What if you want to simply hold all the weights at once, then unhold just the two of your choosing?
Two simple shelf buttons can be written to do this, the code is below. The key is to query all the nodes in the scene with a {{{.liw}}} attr. {{{.liw}}} stands for "Lock Influence Weights", and any node that is added as a smooth skin influence will have this attr added to it. If the value is 1, the weights are locked\held. If the value is 0, the weights are unlocked\unheld.
{{{
// 1 = lock\hold, 0 = unlocked\unheld.
//Change this for each shelf button
int $lockState = 1;
string $boundJoints[] = `ls "*.liw"`;
for($joint in $boundJoints)
setAttr $joint $lockState;
if($lockState)
print "Locked all influence weights in scene\n";
else
print "Unlocked all influence weights in scene\n";
// optional: Relaunch the 'Paint Skin Weights Tool'
//to see the effect of the code:
ArtPaintSkinWeightsToolOptions;
}}}
Also see:
*[[How is a joint's ".liw" (Lock Influence Weights) attribute connected, with regards to a skinCluster?]]
The {{{animCurveEditor}}} command lets you access a named Graph Editor.
It's confusing, since this is a UI element, you'd think in the Maya mel docs it'd be under the general "Windows" section, or maybe even "Panels \ Controls" sections. But no, it's under "Animation". Which makes sense too I suppose. But I don't like it...
{{{graphEditor1GraphEd}}} is the name of the default Graph Editor.
From the Maya docs on {{{animCurveEditor}}}:
{{{
// Check to see if the "default" graph editor has been created
animCurveEditor -exists graphEditor1GraphEd;
// Show result curves
animCurveEditor -edit -showResults on graphEditor1GraphEd;
// Decrease the sampling rate for the result curves
animCurveEditor -edit -resultSamples 5 graphEditor1GraphEd;
}}}
What I'm still trying to figure out: What kind of UI element //is// the 'graph-side' of a 'Graph Editor'? According to the {{{animCurveEditor}}} docs, it gets created by a {{{scriptedPanel}}}. The name of the panel that the default Graph Editor is associated with is {{{graphEditor1}}}:
{{{
animCurveEditor -q -pnl graphEditor1GraphEd;
// Result: graphEditor1 //
}}}
But that still dosn't answer the above question....
----
FYI, the Outliner to the left of the Graph Editor is an {{{outlinerEditor}}} called {{{graphEditor1OutlineEd}}}, and it's parent is a {{{scriptedPanel}}} called {{{graphEditor1}}}. This appears to be the same {{{scriptedPanel}}} that is associted with the default Graph Editor (from above, which would make some sense).
{{{
scriptedPanel -q -ex graphEditor1;
outlinerEditor -q -ex graphEditor1OutlineEd;
}}}
If you want to be able to figure out what you have highlighted in that {{{outlinerEditor}}}, see here:
[[How can I get a string array of the contents for a selectionConnection?]]
Also:
[[How can I highlight channels in the Graph Editor based on what's selected in the Channel Box?]]
*Every DG node in Maya has the ability to have a "note" added to it, which exposed in the bottom of the Attribute Editor. Not really a convention, but a feature they exposed in the Attribute Editor around Maya 5? How do you update that via mel?
*It would seem simple with a setAttr command. The issue is, that attr dosen't exist by default, so if you try to set it without creating it first, it won't work. When entering notes directly in the Attribute Editor, it creates the attr for you behind the scenes.
{{{
if(!`attributeQuery -n $node -ex "notes"`)
addAttr -ln notes -dt "string" $node;
setAttr ($node + ".notes") -type "string" "My notes";
}}}
{{{fileInfo}}}
*Note: This seems similar to the {{{optionVar}}} command, but stores the var's relative to the file, not wherever the optionVar's are stored.
*Example: store a "keyword" with a value in the scene:
{{{
fileInfo "secretData" "WarpCat was here...";
string $secretData[] = `fileInfo -q "secretData"`;
}}}
Based on a new install of Maya, I believe the default frame rate is set to '{{{Film (24 fps)}}}'. I work in games, where most of the time the frame rate should be set to '{{{NTSC (30 fps)}}}'. Based on the tools we use, if an animator loads a scene up, and starts animating at 24 fps, then loads it into our animation sequencer that expects things to be at 30fps,... bad things will happen.
I've isolated two locations that need to be updated to ensure that whenever a scene is created \ opened, it will be a the right frame rate. For this example, we'll be using '{{{NTSC (30 fps)}}}'.
!Location #1: Maya Preferences:
Maya has its default preferences (''Window -> Settings / Preferences -> Preferences -> Settings -> Working Units -> Time'') that set the overall time for the scene. When you change the value, Maya calls to the {{{currentUnit}}} command to update the system. What's interesting, is that it //doesn't// seem to modify any {{{optionVar}}}s to store these settings.
Look in the {{{global proc prefWndUnitsChanged()}}} in the script:
{{{C:/Program Files/Autodesk/Maya<version>/scripts/startup/createPrefWndUI.mel}}}
...to see what's really going on.
However, this value can be changed by incoming files with different values. So we need to do something else to ensue we have the correct settings when we open //any// scene...
!Location #2: New Scene Options
When you make a //new// scene (''File -> New -> Options''), there are //new scene options// where you can set the framerate. These will //override// anything set in your Maya preferences (see #1, above). This is a main gotcha I see for most people: They don't realize that these need updated as well.
Changing the 'time' in these options //does// store the change in an {{{optionVar}}}.
Look in {{{global proc setDefaultUnits()}}} in the script:
{{{C:/Program Files/Autodesk/Maya<version>/scripts/startup/performNewScene.mel}}}}
... for more details.
!The Solution:
You need these options to be set every time you access a //new// scene, or open an //existing// scene. The best way I've found to do this is add a {{{scriptJob}}} your {{{userSetup.mel}}} that executes on any file access:
{{{
// userSetup.mel
global proc setNTSC(){
// Set our 'Maya preferences':
currentUnit -t ntsc;
// Set our 'new scene options':
optionVar -sv workingUnitTimeDefault ntsc;
}
scriptJob -event SceneOpened setNTSC;
scriptJob -event NewSceneOpened setNTSC;
}}}
Done: Anytime you open access a file, you're sure to know that it's at the correct framerate. With a bit more 'detection code', you could query what the values were before modification, and alert the user if any changes were taking place.
{{{
string $format = "mayaAscii";
optionVar -sv defaultFileOpenType "mayaAscii";
optionVar -sv "defaultFileSaveType" $format;
file -type $format;
}}}
Adding this to your {{{userSetup.mel}}} file will make sure that things are saved in that format by default, and the new scene that Maya opens with is also of that format.
When doing a "Save As", this will set the "File Type" under "General Options" to the defined {{{$format}}}.
Optionally, if you really wanted to make sure your users always saved as a given format no matter what: Presuming you push a studio-wide {{{userSetup.mel}}}, in that file, you can set, via the {{{file}}} command, a {{{-preSaveScript}}} arg. That points to a script that will be ran before save. In that script, you can query for the file type, and if not the format you want, change the format, then save.
@@Note:@@ Wing can interact with Maya in three different ways. This is one of the three. See an overview here on the whole system: [[Interaction between Wing and Maya]]
----
Maya comes with its own cut of Python (see [[Python versions in Maya]]). The executable for that lives here (on Windows):
{{{
c:\Program Files\Autodesk\Maya<version>\bin\mayapy.exe
}}}
You can execute this directly to launch an interactive prompt of Python (most commonly for batching purposes). Or, if you're running a Python IDE external to Maya, but you want Maya's Python calls to exist in it, you can point your IDE To that exe. There is often an option in your IDE to allow for this (Wing allows you to do this, for example).
Once that executable has been started, you still need load and initialize Maya into that session. Per Maya's docs: "{{{These commands take a significant amount of time to execute as they are loading all of the Maya's libraries and initializing the scene.}}}" This is the code I use:
{{{
try:
import maya.standalone
maya.standalone.initialize()
print "Imported maya.standalone\n"
except:
print "Couldn't find 'maya.standalone' for import\n"
}}}
If you want that code to execute //every time// you launch Maya's version of Python (I do this in Wing), you can author the above code as a module, and point Python's {{{PYTHONSTARTUP}}} environment variable to it:
{{{
PYTHONSTARTUP = <somePath>\<somePythonStartupModule>.py
}}}
I have [[Wing|For Python: Maya 'Script Editor' style IDE]] execute this code for my 'Maya Python coding sessions'.
----
The {{{maya.cmds()}}} library is actually empty on disk (go take a look for yourself) other than the {{{__init__.py}}} module that defines that dir structure //as// a library:
{{{
C:\Program Files\Autodesk\Maya<VER>\Python\lib\site-packages\maya\cmds\
}}}
Although you can fully access all Maya commands through Python like '{{{maya.cmds.ls()}}}', the Python {{{ls()}}} command has no physical location on disk: Until you execute the above code example ({{{import maya.standalone}}} ...), Python won't see any of the commands. If you want to get a better understanding, see the subject [[How does Maya populate maya.cmds?]]. There are several downsides to this:
*You can't call Python's {{{help()}}} on any ~Python-Maya commands.
*If you have an external IDE, context sensitive highlighting won't work correctly, nor will auto-completion, or debugging (big bummer).
Really frustrating issues if you do work anywhere else other than the Script Editor.
Maya has the mel command {{{python}}}, which lets you execute Python code inside of mel. I will often author python modules outside of the defined 'Python PATH'. If you're in the middle of a mel script, and want to successfully execute Python code that isn't part of the Python path, how to do?
Below is one method I have come up with. Presuming you're trying to execute some module called {{{c:\some\path\myModule.py}}}:
{{{
// First, set the path to where the module lives, and import the
// module. Note the use of Python single-quotes inside the
// Maya double-quotes:
python("import sys;" +
"import os;" +
"sys.path.append(os.path.normpath(r'c:/some/path'));" +
"import myModule");
// Then, you can execute whatever function is in that module:
string $results[] = python("myModule.someFunction()");
}}}
In theory, you could do it all in the same {{{python}}} call. However, if you want to capture the return value from your executed module, you need to execute it in its //own// {{{python}}} command (according to the docs). As well, you could split each execution into a separate {{{python}}} call (rather than using '+' to catenate the strings), but I try to keep it as compact as possible.
Also, why am I calling {{{os.path.normpath}}}? Why not just use double-backslashes in the path name, rather than using forward-slashes, and making the string //raw// (via the little {{{r}}} in front of it)?
*Let's say your path was: {{{"c:\temp"}}}
*But, when the interpreter sees {{{\t}}}, it thinks 'tab', and inserts a tab into your path: {{{"c: emp"}}}
*To author that with double-backslashes, so it would get read in properly: {{{"c:\\temp"}}}. And in this example, it would work. BUT:
*I have found mixing double-backslashs with //other// letter combinations creates all sorts of additional problems. Plus, many Maya commands return forward-slash paths.
*So by passing a a //raw// ('{{{r}}}') string with forwardslashes, and then via {{{os.path.normpath}}} converting that to pythonic-backslashes, it circumvents this issue.
With the {{{python}}} mel command.
Example: Presuming you have a python script called {{{returnRange.py}}}, with a {{{main}}} function, and that function returns a range of numbers based on two arguments:
{{{
// via mel:
python("import returnRange");
int $rangeInt[] = python("returnRange.main(1, 4)");
print $rangeInt;
1
2
3
}}}
See the Maya docs on how data type conversions are handled.
Also see:
*[[How can I execute mel code via Python?]]
{{{commandPort}}}
*Example on a local network: First, "Open a commandPort in Maya":
**(Win2000 syntax is "computerName:portNumber")
{{{
commandPort -n "warpcat:8888";
}}}
**How do I find my IP address if need be on Windows? At the Command Prompt (windows):
{{{
>> ipConfig
}}}
**On your external machine or command prompt, open an app to talk with your other machine:
***On Unix, {{{telnet}}}.
***On Linux, {{{socat}}}.
***On Windows I HIGHLY recomend using [[Putty|http://www.chiark.greenend.org.uk/~sgtatham/putty/]], since according to some online threads I've read, W2K telnet acts "funny": It won't accept more than a single character at a time. As a result, you'll need to download a new client. And I've found that Putty works very well.
**At the prompt:
**Unix (this is the windows format, but I'm 'guessing' it's the same?):
{{{
telnet warpcat 8888;
}}}
**Linux (tip from Hal Bertram):
{{{
socat readline tcp4:localhost:8888
}}}
***From Hal: You can use socat instead of telnet to get a maya prompt that feels more like a shell with history, filename completion, and other readline goodness.
**Windows:
{{{
insert Putty example here
}}}
**Now you can start typing commands and executing them in Maya.
----
*Notes:
**I've only tested this on windows with telnet and putty, FYI. Irony that I don't have a Putty example isn't it?
The Python {{{maya}}} package has a {{{mel}}} module. In that, it has the {{{eval}}} function that can evaluate mel directly.
Example: Presume you have a mel script called {{{returnRange.mel}}}, that has a {{{global proc returnRange}}} which returns a range of numbers based on two arguments
{{{
# via python:
import maya.mel as mm
range = mm.eval("returnRange(1,10)")
print range
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}}}
See the Maya docs on how data type conversions are handled.
Also see:
*[[How can I execute Python code via mel?]]
*[[Understanding Python integration in Maya]]
See my notes on {{{lerp()}}} [[here|How can I find a value some percentage between two other values?]]. This expands on the concept, but using 3d points rather than single values.
{{{p1}}} and {{{p2}}} are the two 3d points in space. {{{amt}}} is the percentage (from 0-1) between them you want to find the new point. If {{{amt}}} is {{{.5}}}, {{{lerp3D()}}} will return a point 50% between {{{p1}}} and {{{p2}}}:
{{{
# Python code
def lerp3D(p1, p2, amt):
x = p1[0] + (p2[0] - p1[0]) * amt
y = p1[1] + (p2[1] - p1[1]) * amt
z = p1[2] + (p2[2] - p1[2]) * amt
return [x, y, z]
print lerp3d([2,2,2], [10,20,10], .25)
# [4.0, 6.5, 4.0]
}}}
Since color values are also expressed (usually) as three floats, you could use this code to {{{lerp}}} colors too:
{{{
print lerp3d([0,0,0], [255, 255, 255], .75)
# [191.25, 191.25, 191.25]
}}}
But, you'd need to convert those to {{{int}}}'s to be usable.
In other languages they have a {{{lerp()}}} command. Mel and Python don't seem to.
Here's how it works:
{{{val1}}} and {{{val2}}} are the two values. {{{amt}}} is the percentage (from 0-1) between them you want to find the new value. If {{{amt}}} is {{{.5}}}, {{{lerp()}}} will return the value 50% between {{{val1}}} and {{{val2}}}:
{{{
# Python code
def lerp(val1, val2, amt):
return val1 + (val2 - val1) * amt
print lerp(2, 16, .5)
# 9
}}}
{{{
// Mel code
global proc float lerp(float $val1, float $val2, float $amt)
{
return $val1 + ($val2 - $val1) * $amt;
}
lerp(5, 10, .5);
// Result: 7.5 //
}}}
See docs, and visual examples for {{{lerp()}}} over at [[processing.com|http://www.processing.org/reference/lerp_.html]]
----
Also see:
*[[How can I find a 3d point some percentage between two other 3d points?]]
Thanks to my bud Demetrius Leal on the tip with {{{netstat}}}:
On a //Windows// system, you can use {{{subprocess.Popen}}} to run the system {{{netstat}}} command. It returns a file-object which you can then parse. It should be noted that this doesn't need to be executed //in Maya//, it could be in an external Python shell. But Maya needs to be running of course. And you can run it in Maya if you feel like it ;)
{{{
# Python code
import subprocess
pipe = subprocess.Popen(['netstat', '-ab'], stdout=subprocess.PIPE)
data = [line.strip() for line in pipe.stdout]
for i,e in enumerate(data):
if 'maya.exe' in e and 'LISTENING' in data[i-1]:
print e, data[i-1].strip()
}}}
{{{-a}}} : Displays all connections and listening ports.
{{{-b}}} : Displays the executable involved in creating each connection or listening port.
Will print something like this (presuming you have active commandPorts in Maya...):
{{{
[maya.exe] TCP [::]:6000 userName:0 LISTENING
}}}
It removes other ports that aren't 'LISTENING', since they'd probably do you no good anyway ;)
I have a post on [[my blog|http://www.akeric.com/blog/?p=1087]] for this topic as well.
I'd tried a variety of methods using mel commands to try and trawl the scene looking for instances. This worked well on shape nodes. However, when it dawned on me you can instance transforms (or anything I suppose) just as easily as shapes, that code went out the window. The Python ~OpenMaya API came to the rescue:
{{{
# Python code
import maya.OpenMaya as om
def getInstances():
instances = []
iterDag = om.MItDag()
while not iterDag.isDone():
instanced = om.MItDag.isInstanced(iterDag)
if instanced:
instances.append(iterDag.fullPathName())
iterDag.next()
return instances
}}}
This function will return a list of the full path to all instanced nodes in the scene.
The below function will then uninstance all those nodes:
{{{
import maya.cmds as mc
def uninstance():
instances = getInstances()
while len(instances):
parent = mc.listRelatives(instances[0], parent=True)[0]
mc.duplicate(parent, renameChildren=True)
mc.delete(parent)
instances = getInstances()
}}}
Say you have a polygonal mesh, with a bunch of joints bound to it. How can you find, for a given joint, all the verts that have non-zero weights?
{{{
# Python code
import maya.cmds as mc
def getWeightedVerts(joint, geo):
geoShape = mc.listRelatives(geo, shapes=True, noIntermediate=True)
skinCluster = mc.listConnections(geoShape, source=True, destination=False,
type="skinCluster",
skipConversionNodes=True)[0]
vertCount = mc.polyEvaluate(geo, vertex=True)
nonZero = []
for i in range(vertCount):
weightVal = mc.skinPercent(skinCluster, geo+".vtx["+str(i)+"]",
transform=joint, query=True)
if weightVal > 0:
nonZero.append(geo+".vtx["+str(i)+"]")
return nonZero
}}}
Usage: (in Python)
{{{
joint = "l_knee"
geo = "legs"
print getWeightedVerts(joint, geo)
['legs.vtx[499]', 'legs.vtx[500]', 'legs.vtx[501]', etc...]
}}}
{{{ls}}}
{{{
// find all objects with an "outTime" attr in your scene:
string $attr = "outTime";
string $objs[] = `ls -o ("*." + $attr)`;
print $objs;
// time1
}}}
Notes:
*If you leave off the {{{-o}}} flag, it will return back the whole obj.attr name
*Sometimes, it will return back duplicate names for everything. Weird.
*I can't believe it's taken me 8.5 versions of Maya to learn this... shame... Thanks to Doug Brooks for showing me this :)
{{{file}}}
*Example:
{{{
string $alldependencies[] = `file -q -l`;
}}}
{{ls}}
Example A: Find all objects with the string "bob" in their name:
{{{
ls "*bob*";
}}}
Example B. Find all "transforms" with the string "larry" in their name:
{{{
ls -type transform "*larry*";
}}}
{{{file}}}
*Example:
{{{
string $allrefs[] = `file -q -r`;
}}}
{{{listSets}}}
Let's you query convenient things like object sets, or rendering sets (shadingEngines. or shaders).
Given this hierarchy:
*top
**middle1
***bottom11
***bottom12
**middle2
{{{
string $kids[] = `ls "middle1|*"`;
// Result: bottom11 bottom12
string $kids[] = `ls "top|*"`;
// Result: middle1 middle2
}}}
Basically {{{ls}}} is finding those strings in the full path of the object names since we include the pipe in the search.
Tricky!
{{{currentTime}}}
*Example:
{{{
float $frame = ‘currentTime -q‘;
}}}
*At the Command Prompt
{{{
>> lmutil lmstat -c <licence file name> -a
}}}
*Notes:
**{{{lmutil}}} is usually found here: {{{c:/aw/com/bin}}}
***But since Autodesk bought Maya, it's probably in a new dir...
**<licence file name> is usually {{{c:/flexlm/aw.dat}}}
{{{polyEditUV}}}
*Example: find a UV called object.map[21]
{{{
polyEditUV -q -uValue object.map[21];
}}}
* Example: Select a vertex, and find it's U value:
{{{
string $sel[] = `ls -fl -sl`;
string $UV[] = `polyListComponentConversion -tuv $sel[0]`;
float $uval[] = `polyEditUV -q -uValue $UV[0]`;
}}}
{{{system}}} & {{{set}}} (windows command) (and others):
{{{
string $envVar = `eval "system \"set MAYA\""`;
string $buffer[];
tokenize $envVar "=" $buffer;
print $buffer;
}}}
*The system call returns all the variables as a single string, so to make sense of them, tokenize the output by the equals "=" sign.
*To get a list from Windows Command Line, you'd simply type "set MAYA" at the prompt. What's interesting is that Windows doesn't see any environment variables defined by the Maya.env file, but if you make the call like above IN Maya, the variables are visible.
{{{getenv}}}
*Example:
{{{
string $msp = `getenv MAYA_SCRIPT_PATH`;
}}}
The documentation:
Example:
*Essentials -> Basic Features -> Setting Environment Variables -> Standard Maya environment variables
{{{translator}}}
*Example:
{{{
translator -q -do "animImport";
translator -q -do "animExport";
}}}
{{{
string $user = `getenv USER`;
string $user = `getenv USERNAME`;
}}}
Thready goodness:
http://groups.google.com/group/python_inside_maya/browse_thread/thread/1b865f8c3c0c81c2
i.e., what "kind" of node is' lambert'? This appears to function on non-DAG objects.
{{{getClassification}}}
{{{
getClassification "lambert";
// Result: shader/surface //
}}}
For example, given a poly sphere, named "bob", what type of node is "bob"? (transform). What kind of node is "bobShape"? (mesh\nurbsCurve\etc.).
{{{nodeType}}}
This is very similar to {{{objectType}}}
For example, given a sphere named "bob", what type of object is "bob"? (transform) What type of object is "bobShape"? (mesh)
{{{objectType}}}
This is very similar to {{{nodeType}}}
{{{angleBetween}}} //command//.
OR
{{{angleBetween}}} //node//.
Given a list of any number of nodes, how can I find their average position in worldspace?
First, we need to write a function that can calculate the average value of any number of passed in nodes:
{{{
# Python code
import maya.cmds as mc
def average(*args):
# find the average of any number of passed in values
val = 0
for a in args: val += a
return val / len(args)
}}}
Then we can use Python's {{{map}}} built-in function to apply our average function to our positional values. Here it is all on one line using a 'list comprehension', based on selected objects:
{{{
avg = map(average, *[mc.pointPosition(p+".rotatePivot", world=True) for p in mc.ls(selection=True)])
}}}
Simplification step #1: Move out list of selected objects to its own line and variable.
{{{
transforms = mc.ls(selection=True)
avg = map(average, *[mc.pointPosition(p+".rotatePivot", world=True) for p in transforms])
}}}
Simplification step #2: Move our list of positions to it's own line and variable.
{{{
transforms = mc.ls(selection=True)
positions = [mc.pointPosition(p+".rotatePivot", world=True) for p in transforms]
avg = map(average, *positions)
}}}
And the end result of all three would look something like this, based on what you had initially picked:
{{{
[110.11913165450096, 163.66115890443325, 40.902932167053223]
}}}
There are two keys to this:
#Our {{{average}}} function can accept any number of passed in arguments via it's {{{*arg}}} argument.
#The {{{map}}} function also accepts any number of arguments, via it's {{{*positions}}} (simplification #2) or {{{*[list comprehension]}}} (simplification #1 or first example).
I have quite a few notes on list comprehensions and the map function on my [[Python Wiki|http://pythonwiki.tiddlyspot.com/]]
This is in relation to DAG nodes.
{{{listRelatives}}}
In Maya v7 or LESS:
{{{closestPointOnMesh.mll}}} ships with Maya's "Bonus Game Tools", and will create a {{{closestPointOnMesh}}} node. You can download this through Autodesk's '[[Area|http://area.autodesk.com/index.php/downloads_plugins/plugins_list/]]'
In Maya v8+:
It now ships with the ability to create a {{{closestPointOnMesh}}} node, no plugin required! I hear it also has a {{{nearestPointOnMesh}}} node, which is "slower". I have yet to test this.
Expanding the notes on [[How can I query the color at a certain UV point on a render node?]], here's how you can find a color based on the location of a given vertex:
{{{
// define vert to query, and render node to lookup color from:
string $myVert = "myGeo.vtx[88]";
string $myRenderNode = "myFileNode";
// convert from vert to uv:
string $uv[] = `polyListComponentConversion -tuv $myVert`;
// Get the UV value of the given vert. The previous
// command can return multiple uv's if on a uv border
// edge, so we sample the first one found:
float $uvVal[] = `polyEditUV -q -uValue $uv[0]`;
// then find the color at hat UV, on the given render node (file, ramp, etc)
float $rgbCol[] = `colorAtPoint -o RGB -u $uvVal[0] -v $uvVal[1] $myRenderNode`;
// 0.482353 0.427451 0.423529 //
}}}
{{{getFileList}}}
*Example: Get a list all the ".mov" files in a specific directory:
{{{
string $fileList[] = `getFileList -fld "c:/mayaStuff/myFiles/" -fs "*.mov"`;
}}}
Some math!
Below example queries the position of two nodes in worldspace, and finds the distance between:
{{{
float $pointA[] = `xform -q -ws -rp $nodeA`;
float $pointB[] = `xform -q -ws -rp $nodeB`;
float $distance = sqrt( pow(($pointA[0] - $pointB[0]), 2) +
pow(($pointA[1] - $pointB[1]), 2) +
pow(($pointA[2] - $pointB[2]), 2) );
}}}
{{{getAttr}}} - but it's the .cp attr that holds the local position, while the .pnts attr holds the tweak value
*Example:
{{{
polyCube;
select -r pCube1.vtx[0];
getAttr pCube1.cp[0];
move -r .1 .1 .1;
//Result: 0.1 0.1 0.1 //
// need to source the .xv val separately rather than the whole compound value.
getAttr pCube1.cp[0].xv;
//Result: -0.4 // Real x position (-.5 + .1)
}}}
I seem to do this just not quite often enough to remember how to do it, so I'll make a note here.
Func returns a tuple containing the min and max frame range based on the list of nodes passed in.
{{{
# Python code
import maya.cmds as mc
def getMinMaxAnimRange(nodes):
first = 0
last = 0
curves = mc.listConnections(nodes, source=True, destination=False, type='animCurve')
if curves is not None:
first = mc.findKeyframe(curves, which='first')
last = mc.findKeyframe(curves, which='last')
return (first, last)
}}}
----
Also see:
*[[How can I find the min and max keyframe values for all objects in the scene?]]
Will return back a float array with two items, the [start, end] frames:
{{{
global proc float[] returnFrameRange(){
select -r `ls -type "animCurve"`;
return {float(`findKeyframe -which first`), float(`findKeyframe -which last`)};
}
float $frameRange[] = returnFrameRange();
// Result: 1 22 //
}}}
----
Also see:
*[[How can I set the range slider to match the keyframe range on the selected object(s)?]]
*[[How can I find the min and max keyframe range for the provided objects?]]
All of Maya's main menu's are prefixed with {{{$gMain}}}... Here's how to get a list of them all:
{{{
string $all[] = `env`;
for($i=0;$i<size($all);$i++)
{
if(`match "^\\$gMain" $all[$i]` == "$gMain")
{
global string $temp;
eval("$temp = " + $all[$i]);
print ($all[$i] + " = " + $temp + "\n");
}
}
}}}
Also see:
*[[How can I update Maya's menus with my own entry?]]
!!!Mel:
{{{
string $name = "assG:bzspsG:asdf:bob";
string $namespace = `match ".*:" $name`;
// Result: assG:bzsps:asdf: //
}}}
So basically what the match is doing is saying: 'as long as I can find some character followed by a colon, return that". Once it hits the last colon, it stops matching.
!!!Python:
{{{
import re
name = "namespace2:namespace1:objectName"
ns = ""
try:
ns = re.match('.*:', name).group(0)
except AttributeError:
pass
print ns
# namespace2:namespace1:
}}}
Looks like one of the few cases where the mel is actually less code to write than the Python ;)
{{{pointOnSurface}}}
*Example, presuming your shape node is called "nurbsSphereShape1?":
{{{
float $normal[] = `pointOnSurface -u .5 -v .5 -normal nurbsSphereShape1`;
}}}
{{{
string $sel[] = `ls -sl`;
referenceQuery -f $sel[0];
}}}
{{{getAttr}}} & the {{{file}}} node.
*Example: You can use the .outSizeX? and .outSizeY? attr on the file node to get the size in pixles.
{{{
string $xSize = `getAttr ("file1.outSizeX")`;
}}}
The below examples expects {{{$object}}} to be some transform-level name:
Method A:
{{{
// Query the object directly (transform level, presuming it HAS a bound shape)
string $cluster = `findRelatedSkinCluster $object`;
}}}
Method B:
{{{
// query the shape of $object (useful if the object has multiple shape nodes):
string $shape[] = `listRelatives -s -ni $object`;
string $cluster = `findRelatedSkinCluster $shape[0]`;
}}}
Method C:
{{{
// different approach: Use node connections to find it:
string $shape[] = `listRelatives -s -ni $object`;
string $cluster[] = `listConnections -s 1 -d 0 -type "skinCluster" -scn 1 $shape[0]`;
}}}
----
{{{findRelatedSkinCluster}}} is a mel script located here:
{{{C:\Program Files\Autodesk\Maya<VERSION>\scripts\others\findRelatedSkinCluster.mel}}}
Presuming the surface is enclosed, you can use this script that comes with Maya:
{{{
C:/Program Files/Autodesk/Maya<version>/scripts/others/computePolysetVolume.mel
}}}
Note: sometimes this likes to return negative values, so use the {{{abs}}} command to correct for it.
This could be a vertex, particle, CV, lattice point, etc. If querying a face (or other poly element), it will return the positions of each vertex associated with the face.
{{{pointPosition}}}
Here's some //Python// code that will return a list with the worldspace position for every component selected, based on using a //list comprehension//:
{{{
import maya.cmds as mc
pts = [mc.pointPosition(point, world=True) for point in mc.ls(selection=True, flatten=True)]
}}}
Given a default polygonal cube, if you selected its verts and executed the above you'd get:
{{{
print pts
[[-0.5, -0.5, 0.5],
[0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5],
[0.5, 0.5, 0.5],
[-0.5, 0.5, -0.5],
[0.5, 0.5, -0.5],
[-0.5, -0.5, -0.5],
[0.5, -0.5, -0.5]]
}}}
I ran into an instance where I had an asset with //many// shaders assigned, and I had to import in a new one and assign it. But when I imported in the new one (not knowing the name), I couldn't figure out which one it was. I wrote this code to find all //shadingEngines// (shaders) in the scene that weren't assigned to anything. //Actually// its the other way around: shadingEngines are sets, so you assign things //to them//. As well, //materials// (phong, lambert, etc) plug into shadingEngines, nothing is actually 'assigned to a material', or vice-versa:
{{{
# Python code
import maya.cmds as mc
def findUnusedShaders():
shadingEngines = mc.ls(type='shadingEngine')
unused = []
for se in shadingEngines:
items = mc.sets(se, query=True)
if items is None:
unused.append(se)
return unused
}}}
{{{
unused = findUnusedShaders()
}}}
The mel {{{linstep}}} command. This isn't available in Python.
Windows System Call:
{{{
// The number specified is in seconds. However, I have seen this vary on different systems. Weird.
system "sleep 3";
}}}
Maya command:
{{{
pause -sec 5;
}}}
Sometimes when writing code that calls to many other procedures, you can run into issues where code will start executing out of order.
*In one instance, I'd import a file, and constrain something to it. But the end result had all sorts of problems: the item constrained was in the wrong location. I tried using commands like {{{dgdirty -a}}} and runTimeCommands like {{{EvaluateAll}}} in my code to fix the issues, but neither of them worked.
*I figured out though, that by advancing the frame by 1 (and then setting it back), it absolutely makes the scene refresh. This solved the issue.
*In the case of my example, come to find out when the rig was imported into the scene, some of its plugs were dirty, thus it was in the wrong location. {{{dgdirty}}} simply made //everything dirty//, and it wouldn't "fix" itself until the next free cycle, which happened //after// I'd constrained my other object and all my code stopped running. {{{EnableAll}}} seemed to have the same effect: Nothing was evaluated until the next free cycle, which was after the code finished. Since I put the frame changing code in the //middle// of my scripts, it forced all the plugs to evaluate themselves //during// the code evaluation.
''Update #3'': I have yet to test it, but there is also the mel command {{{refresh}}} that should be investigated.
''Update #2'': I got the bottom most code working, but I found the secret was this: I needed to run it just before my scene import: Again, I'd have issues where I'd import a scene, run the below refresh code, and it wouldn't work. But if I found that I ran it just before I imported the scene, success!
''Update #1'': The bottom code block was what I //had// been doing. Then I found instances where even it wasn't working: I had proven examples of frames advancing, but the mel after it executing and finishing //before// the frame advanced had finished. Argh!
Doing more research, I change approaches: Using the {{{play}}} command, it has a flag called {{{-wait}}} that //forces// Maya to take a break while things are playing. THIS, fixed it: You set the framerange to be one more than the start frame, play that range, then set all your defaults back.
''One other thing'': I found another issue of when neither of these code bits worked: I was setting a dynamic initial state on a softbody mesh, and all parts of that dynamic system were hidden. I found that making that system //visible//, then changing the frame, then setting it back invisible again refreshed it. I thought that calling {{{dgdirty -a}}} before the frame change would cause the plugs to dirty, and force a refresh, but even that didn't do it. In this case, Maya was simply too optimized for its own good.
{{{
// define our init values:
float $startF = `playbackOptions -q -minTime`;
float $endF = `playbackOptions -q -maxTime`;
string $loopMode = `playbackOptions -q -loop`;
// start the playback
currentTime $startF;
playbackOptions -loop "once";
playbackOptions -maxTime ($startF+1);
play -wait;
// reset the default values
currentTime $startF;
playbackOptions -maxTime $endF;
playbackOptions -loop $loopMode;
}}}
Another method, still trying to figure out which method is best.
{{{
// force the scene to refresh by changing the frame:
float $time = `currentTime -q`;
currentTime ($time + 1);
currentTime $time;
}}}
Also see:
*[[How can I Enable \ Disable node evaluation?]]
The animators wanted a tool that would let them grab the faces on the bottom of a characters foot, and generate a representative curve from the border of the selection, which they could use for foot placement reference in their animation. The below code does that: Based on a selection of faces, create a nurbs curve that surrounds their border. However, for speedy coding purposes, it generates a nurbs curve //per edge segment//, and parents them all to a single transform, rather than making a //single curve// for all the points. That would take more work, since you have to deal with point ordering issues within your selection ;)
{{{
# Python code
# Select polygonal faces, and execute
import maya.cmds as mc
border = mc.polyListComponentConversion(toEdge=True, border=True)
border = mc.ls(border, flatten=True)
if len(border) == 0:
raise TypeError("Please select polygonal faces before executing")
else:
curveList = []
for edge in border:
edgePts = mc.polyListComponentConversion(edge, toVertex=True)
edgePts = mc.ls(edgePts, flatten=True)
pts = [mc.pointPosition(point, world=True) for point in edgePts]
curveList.append(mc.curve(name="faceToCurve#", degree=1, point=pts))
parent = mc.group(empty=True, name="faceToCurveNode#")
for c in curveList:
shapes = mc.listRelatives(c, shapes=True)
mc.parent(shapes, parent, shape=True, relative=True)
mc.delete(c)
mc.xform(parent, centerPivots=True)
mc.select(parent)
print "Created '" + parent + "'",
}}}
Name clashing in Maya is a real pain. Some tools will return back full paths for a node, while others only return back the leaf name. So a safe way to deal with this is to always generate nodes with unique names, based on what's currently in the scene.
''Update #2:''
'Update #1' works if you can define the names at the time of creation. But what if your code gives you a name with a number postfix on the end that could be clashing with something else in the scene? This example shows how to capture the end postfix numbers, remove them, and then add the magical 'pound\hash' symbol that does the name updating that we love:
{{{
#Python code:
import re
import maya.cmds as mc
# create an empty group
mc.group(empty=True, name="group1")
# create another empty group as its child
mc.group(empty=True, name="group2", parent="group1")
# create a third empty group, that clashes names with the second group.
# but since the second group is in a group, Maya allows this
group = mc.group(empty=True, name="group2")
# find the end number on our name:
endNum = re.findall('[0-9]+$', group)
try:
# try to rename, presuming we found a number
newName = group[:-len(endNum[0])]+"#"
mc.rename("|"+group, newName)
except IndexError:
# do nothing if no end number is found.
pass
}}}
''Update #1:''
Originally I had the below (old) code to check to see if a name already exists, and return back the unique name. But then I got tipped that if you simply use the 'pound' symbol ({{{#}}}) at the end of any object name (when created), it'll always return back a unique name:
{{{
#Python code
import maya.cmds as mc
# make first group. Is called "group1"
g1 = mc.group(empty=True, name="group#")
# make second group called "group2", which "group1" is now a child of
g2 = mc.group(name="group#")
# make third group as a new hierarchy. It is automatically named "group3"
g3 = mc.group(empty=True, name="group#")
}}}
The resulting heirarchy looks like this: (no name clashing!)
*{{{group2}}}
**{{{group1}}}
*{{{group3}}}
If you //didn't// use the '{{{#}}}' at the end of the names, the resulting hierarchy would look like this: (name clashing)
*{{{group1}}}
**{{{group}}}
*{{{group}}}
----
''Old code:''
The below goes into a loop checking for the current '{{{checkName}}}'. If it finds a node by that name, it simply prepends an increasingly higher number to the end of the name, and does the check again. When it finds a version of the name with no match, it returns the new name.
{{{
# Python code
import maya.cmds as mc
def uniqueName(name):
"""return back a unique name for a node"""
checkName = name
j=0
searching = True
while searching:
if mc.objExists(checkName):
j = j+1
checkName = name + str(j)
else:
searching = False
return checkName
# presuming you already have two nodes by the name "checkName" in the
# scene, parented to different hierarchies:
print uniqueName("checkName")
# checkName1
}}}
Two ways that I know of:
''Method #1'' : {{{userSetup.py}}}:
*Create a {{{userSetup.py}}} module here: (Windows)
{{{
<drive>:\Documents and Settings\<username>\My Documents\maya\<Version>\scripts\userSetup.py
}}}
*And put whatever Python code you want to execute at startup in there. This is basically just like the {{{userSetup.mel}}} file, but for Python.
''Method #2'' : Update {{{PYTHONSTARTUP}}}:
*If you add an environment variable with the above name, and point it to a valid python module, Python will execute the code in that module before the prompt is displayed in interactive mode.
{{{
PYTHONSTARTUP = c:/some/path/someModule.py
}}}
*Note: I haven't tested this //in Maya// yet, but I know it works with Python outside of Maya. I use method #1.
Also see:
*[[How can I update my Python Path?]]
*"Pymel makes python scripting with Maya work the way it should. Maya's command module is a direct translation of mel commands into python commands. The result is a very awkward and unpythonic syntax which does not take advantage of python's strengths -- particularly, a flexible, object-oriented design. Pymel builds on the cmds module by organizing many of its commands into a class hierarchy, and by customizing them to operate in a more succinct and intuitive way."
Google Code page:
*http://code.google.com/p/pymel/
Docs:
*http://www.luma-pictures.com/tools/pymel/docs/1.0/index.html
*http://pymel.googlecode.com/svn/docs/index.html
Updates:
*Pymel is included with Maya 2011. That's good. Time for mainstream acceptance.
*'Their latest push (v0.9) is now Open Source software, which makes me much more comfortable with it. Still need to try it out though.
My thoughts on the matter before it was included with Maya.
<<<
I haven't had a chance to use this yet. At first I was really excited, since the syntax is so clean. But after thinking more about it, and talking to others, I'm not sure of how well it would work in a production environment: It's authored by an external company, and all your code is written in their format. This makes me feel very uncomfortable if it was ever discontinued. Autodesk should just buy it, and integrate, because it looks great....
<<<
I like to have code report info to the user via the command line. But Pythons printing syntax is a bit different from mel:
For example in //mel//, if you print this:
{{{
print "foo\n";
}}}
it happily prints in the command line.
But if you do this in //Python//:
{{{
print "foo"
}}}
Nothing shows up in the command line.
To get something to show up, you need to put a comma after the string, like so:
{{{
print "foo",
}}}
//That// will now show up in the command line. The comma means "don't add a return character" (and it also adds a an extra space after the string, fyi).
Another thing to note: In either mel //or// python, if you put a leading return character in the string like: {{{"\nfoo..."}}}, //that// string won't print to the command line. So finicky.
mel has the command {{{env()}}} that returns back the name of every global variable in memory at the time. Via Python, this can be nicely wrappered in to a dictionary containing both the global var name, and its values. This really only falls down in regards to matrix (which are converted into lists of lists of floats) and vector data types (which are converted into 3-float tuples), since Python doesn't have those types (by default). But it still handles the conversion ok :)
{{{
# Python code
import maya.mel as mm
env = {}
allMelVars = mm.eval('env()')
for v in allMelVars:
# Convert mel into Python by assigning it to a dummy var the same
# name as the original var, but with an underscore added to the name.
# This lets us get around mel global scope naming conflicts if the tool
# is ran twice:
pv = mm.eval('%s_ = %s'%(v,v))
# strip off mel $ prefix:
env[v[1:]] = pv
}}}
Now you can use all of Pythons dictionary tools on the {{{env}}} dict.
I like to make a lot of subsets while working, to help organize things. While sets are {{{DG}}} nodes, and have no real hierarchy, by adding sets to one another, it sort of mirrors the child\parent {{{DAG}}} transform hierarchy, and the Outliner displays them this way. However, since they aren't in a hierarchy, you can't use commands like {{{listRelatives}}} to get a list of all of their 'children'. They have no 'children', since they aren't in a hierarchy. But you still may want to get a list of their 'pseudo-chilld sets' anyway.
This code will recursively search a parental sets for all {{{objectSet}}}s connected too it, and all sets connected to them, etc.
{{{
# Python code
import maya.cmds as mc
sets = ["DZS_TORSO"]
for s in sets:
subSets = mc.listConnections(s, source=True, destination=False, type='objectSet')
if subSets is not None:
for ss in subSets:
sets.append(ss)
print sets
}}}
''Update'': This is a perfect example of overengineering ;) I did all the below work before I realized there is a {{{listHistory}}} node, which does all this for you. Check it out...
----
Say you have a list of Maya materials, and you want to find all the place2dTexture nodes connected to them.
{{{
# Python code
import maya.cmds as mc
def allIncomingConnections(nodes, nodeType=None):
"""
Based on the nodes, find all incoming connections, and return them.
This will recursively search through all incoming connections of all inputs.
nodes : string or list : of nodes to find all incoming connections on.
nodeType : None, string, list : Optional, will filter the result by these
types of nodes.
return : list : Nodes found
"""
ret = []
# Convert our args to lists if they were passed in as strings:
if type(nodes) == type(""):
nodes = [nodes]
if nodeType is not None:
if type(nodeType) == type(""):
nodeType = [nodeType]
for n in nodes:
incoming = mc.listConnections(n, source=True, destination=False)
if incoming is not None:
for i in incoming:
if nodeType is not None:
if mc.objectType(i) in nodeType:
ret.append(i)
else:
ret.append(i)
nodes.append(i)
# remove dupes:
ret = list(set(ret))
return ret
}}}
Example:
{{{
incoming = allIncomingConnections("myMaterial", "place2dTexture")
print incoming
# [u'place2dTexture643', u'place2dTexture642', u'place2dTexture641']
}}}
{{{
ls -as;
}}}
The {{{-as}}} stands for 'assemblies'.
I always forget how to do this for some reason... so here it is. In STONE.
{{{
string $curve = "myCurve";
string $shape[] = `listRelatives -shapes $curve`;
int $numCVs = `getAttr -size ($shape[0] + ".controlPoints")`;
string $cvs[] = `ls -fl ($shape[0] + ".cv[0:" + $numCVs + "]")`;
// myCurveShape.cv[0] myCurveShape.cv[1] myCurveShape.cv[2] etc...
}}}
If you take out the {{{-fl}}} flag from {{{ls}}}, it will collapse them into a single element that still works:
{{{
myCurveShape.cv[0:16] // presuming it had 17 cv's
}}}
I pulled this by digging into the script:
{{{
C:/Program Files/Autodesk/Maya<VER>/scripts/others/selectCurveCV.mel
}}}
Many attributes in in Maya are considered "multi": They are effectively "array" attributes. They can have many children attributes, and are often dynamically generated. How to get a list of them?
Given a node called "{{{node}}}", with a muti-attr called "{{{multiAttr}}}":
{{{
string $multi[] = `listAttr -m "node.multiAttr"`;
// example values:
// multiAttr[0] multiAttr[0].filename multiAttr[0].entity[0] multiAttr[0].entity[1]
// multiAttr[1] multiAttr[1].filename multiAttr[1].entity[0] multiAttr[1].entity[1]
}}}
----
Also see:
*[[How can I add a multi attr to my node, query its values?]]
{{{
>> mayaBatch -proj c:\directory\with\file -archive filename.ma -log c:\output\directory\logFile.txt
}}}
*Notes: I have been unsuccessful getting the return value of this from within Maya using the {{{system}}} command. It will execute and generate a log file, but Maya won't capture the return value.
{{{
runTimeCommand -q -ca;
}}}
If you select a bunch of verts (or any component-level selection), and run {{{ls}}} on your selection, Maya will give you an ordered list. What if you want them in the order selected?
Based on a lot of research and talking with others, there's no built-in easy way to do this. Not that it can't be done with a lot of hoop jumping. It usually involves running scriptJobs that track what the user picks, and updates a global variable or a optionVar.
However, I did find this (free) plugin that seems to do it too:
http://www.3dhornet.eu/index.php?main_page=document_general_info&cPath=63_67&products_id=194
It would be interesting to see how this could also be scripted through the Python API.
These could be nodes other than DAG nodes.
{{{listHistory}}}
{{{
listConnections -s 1 -d 0 -p 0 -c 0 "setName";
}}}
*Note: Often times, one would think they could simply select the set, and use the {{{ls}}} command to get an array of membership. The problem is, the {{{ls}}} command will return them in a different order than the Outliner may show. Furthermore, the order seems to change from time to time (hard to confirm this). When an item is "added" to a set, what's actually happening is the set has a new attribute generated, and that attr is connecting to the object. By using the "listConnections" command, we can get a list, in order, of all the attribute connections. The order still may be different than what's shown in the Outliner, but at least it should be consistant.
*There is only one caveat: The listConnections command will return DG objects in a set before it lists DAG objects. If you don't have mixed data types in your sets, then there is no issue. However, if you have both DG and DAG objects in the set, then you still could run into future problems of shifting order in the return value.
{{{
renderer -query -namesOfAvailableRenderers;
}}}
...or for multiple objects:
{{{
# Python code
import maya.cmds as mc
meshes = ['myMesh']
}}}
This method requires the selection of objects, which isn't always desirable:
{{{
mc.select(mc.polyListComponentConversion(meshes, toVertex=True))
verts = mc.ls(selection=True, flatten=True)
}}}
Non-selection method, but a bit more code:
{{{
verts = []
for mesh in meshes:
numVerts = mc.polyEvaluate(mesh, vertex=True)
vtxList = ['%s.vtx[%s]'%(mesh, val) for val in range(numVerts)]
verts += vtxList
}}}
{{{
print verts
# [u'myMesh.vtx[0]', u'myMesh.vtx[1]', u'myMesh.vtx[2]', ... ]
}}}
I had an issue where a tool would return a list of joints, but I wanted to list them in their hierarchical order. Below is the solution I came up with: By querying the long name of each joint, and then simply sorting that list, it will return them in a nice hierarchical order. I should note that this may not be the //exact// sibling order that Maya has them in since they've been alphabetized at the sibling level: For example, based on the below print, it's very possible that spineA is the first child of pelvis, rather than hip_L. But for my usage, it worked fine.
{{{
# Python code
import maya.cmds as mc
# Define a list of joints in random order based on their actual hierarchy:
joints = ['hip_L', 'pelvis', 'hipTwist_L', 'ball_L', 'spineA', 'spineC', 'spineB']
# Sort the joints by their full path. Generate the list of full paths via a
# Python list comprehension:
sorter = sorted([mc.ls(jntA, long=True)[0] for jntA in joints])
for s in sorter:
print s
}}}
prints:
{{{
|root|pelvis
|root|pelvis|hip_L
|root|pelvis|hip_L|hipTwist_L
|root|pelvis|hip_L|knee_L|ankle_L|ball_L
|root|pelvis|spineA
|root|pelvis|spineA|spineB
|root|pelvis|spineA|spineB|spineC
}}}
Should node that bad things will happen if there are duplicate named things in the scene.
Credit Steve Kestell.
This will return a list of all the items picked in the main {{{Outliner}}}:
{{{
string $myOutliner = "outlinerPanel1";
string $selectionConnection = `outlinerEditor -q -selectionConnection $myOutliner`;
string $selectList[] = `selectionConnection -q -obj $selectionConnection`;
}}}
The {{{window}}} command has the flag {{{-rtf}}} (resize to fit), which will make it resize itself (bigger) to fit the child controls. However, if the UI is dynamic, or its child controls change to be smaller, when the UI is regenerated, it'll keep its larger size. How can you make it change size every time, to fit the child controls, even if the child controls shrink the overall UI size?
You need to add a call to 'remove the saved window size' at the top of the UI code:
{{{
if(`windowPref -exists "myUI_name"`)
windowPref -remove "myUI_name";
}}}
In this example, you can change the button size however you’d like, and the UI will always resize correctly when reopened:
{{{
global proc tempWin_UI()
{
if(`window -exists tempWin_UI`)
deleteUI tempWin_UI;
if(`windowPref -exists "tempWin_UI"`)
windowPref -remove "tempWin_UI";
window -t "Temp Window" -rtf 1 tempWin_UI;
columnLayout -adj 1 -co both 5;
button -w 200 -h 200;
showWindow;
}
tempWin_UI;
}}}
As of v8.0, Maya has no good debugger for scripting :(
The {{{trace}}} command allows you to call to it inside a procedure. It will print back values to the Output Window (Maya's standard output, {{{stdout}}}) for debugging purposes.
If you use the {{{-where}}} argument, it will //also// add the prefixed file and line number indicating where the trace call originates.
For example, save this file on disk as {{{foo.mel}}} in your Script Path, and then run {{{rehash}}} so Maya sees it as a script. Then execute {{{foo}}}.
{{{
global proc foo()
{
global int $intvar;
$intvar = rand(1,100);
trace -where ("int $intvar = " + $intvar);
}
}}}
Result in the Output Window when ran would be (executing the procedure multiple times):
{{{
file: C:/scripts/foo.mel line 5: int $intvar = 18
line 5: int $intvar = 71
line 5: int $intvar = 61
line 5: int $intvar = 11
line 5: int $intvar = 30
}}}
{{{
>> maya -h
}}}
*Notes: From initial tests, it looks like an empty scene uses about half the ram (35 megs) as the "UI" version (80 megs).
When writing strings to text files, you have to embed a 'newline' character or everything will be on the same line. For some reason, Notepad doesn't respect the standard {{{\n}}} 'newline' character: It will print evertying on the same line, with a "square" [] character replacing where the 'newlines' should be. The secret apparently, is that you need to \\also\\ embed a 'return character', and do it before the 'newline character':
{{{
// snip //
int $fid = `fopen $filepath "w"`;
for($i=0;$i<size($data);$i++)
fprint $fid ($data[$i] + "\r\n");
// snip //
}}}
You //have// to do it {{{\r\n}}}: Storing {{{\n\r}}} will provide unsatisfactory results :(
Also see:
*[[How can I write text to my own file?]]
{{{polyUVSet}}} command.
Has a variety of options including the ability to (from the docs):
*Delete an existing uv set
*Rename an existing uv set.
*Create a new empty uv set.
*Copy the values from one uv set to a another pre-existing uv set.
*Set the current uv set to a pre-existing uv set.
*Modify sharing between instances of per-instance uv sets
*Query the current uv set.
*Set the current uv set to the last uv set added to an object.
*Query the names of all uv sets
Some examples:
{{{
//Get all UV sets on a mesh:
string $allUV[] = `polyUVSet -query -allUVSets "myMesh"`;
}}}
{{{
// Query the current UV via shading engine:
string $currentUV[] = `polyUVSet -query -currentUVSet "someSG"`;
}}}
----
Also see:
*[[Understanding how UV attrs work on polygonal meshes.]]
You can set a global int that will tell Maya to //not// access a saved UI configuration when a scene is opened. You also need to update an optionVar:
{{{
$gUseScenePanelConfig = 0;
optionVar -iv useScenePanelConfig $gUseScenePanelConfig;
// run file open code
}}}
These layouts are saved (unless you set your options to NOT save these files...) in each scene as a {{{scriptNode}}} called {{{uiConfigurationScriptNode}}} (easily accessed via the Expression Editor).
OR, you could just modify the Maya Preferences through the UI, which basically does what was done above:
*Window -> Settings/Preferences -> Preferences... -> UI Elements -> “When Opening [''uncheck''] Restore Saved Layouts from File”
You can turn on "Auto Frame" in the Graph Editor, but when you restart Maya, it's off again. It doesn't seem to ever remember, which is quite bothersome. It even seems to store an optionVar, but upon restart, it seems reset.
Add this to your {{{userSetup.mel}}} file to always force it to be on:
{{{
global proc setAutoFrame()
{
animCurveEditor -edit -autoFit 1 graphEditor1GraphEd;
optionVar -intValue graphEditorAutoFit 1;
print ("userSetup.mel: ** Turned on Graph Editor 'auto fit'\n");
}
scriptJob -event SceneOpened setAutoFrame;
}}}
Whenever a scene is opened, the scriptJob will run the {{{setAutoFrame}}} proc, resetting these values.
Also see:
*[[How can I edit \ query the contents of the Graph Editor]]
{{{loadPrefObjects}}}
*Example: By simply saving a scene called loadPrefObjects.ma in your prefs directory, they will be imported every time a scene is opened.
*Maya has the default hotkeys {{{ctrl+c}}} and {{{ctrl+v}}} like most software to copy and paste data.
*//But//, the hotkeys are //context sensitive//: They do different things if the Channel Box, Graph Editor, or modeling panel (like, the perspective view) is active.
*In the Channel Box or Graph Editor, they copy and paste //keys//. In a modeling panel, they //physically copy and past scene data//, based on what's currently selected. It does this by exporting and importing data to\from a temp file on disk.
*This can wreck some major havoc when it comes to animators (most commonly) copying\pasting keys, and not realizing they //don't// have the Graph Editor\Channel Box actually active. They'll get some temp file dumped into their scene, making the file dirty. Then they yell. It gets ugly.
*IMO, what Maya should do by default is //prompt the user// with a confirmDialog //before// the paste (scene import) happens. But //not// prompt for the pasting of keyframes. The below code does that. I made a //new// 'user hotkey', and remapped it to {{{ctrl+v}}}
{{{
int $bad = 1;
// get the current panel. If it's the Graph Editor, we're good.
string $panel = `getPanel -withFocus`;
if(`match "^graphEditor" $panel` == "graphEditor")
$bad = 0;
// is anything picked in the Channel Box? If so, we're good.
string $selAttr[] = `channelBox -q -sma "mainChannelBox"`;
if(size($selAttr))
$bad = 0;
// if things are still 'bad', then fire up our confirmDialog, since we must be trying
// to paste (import) our temp scene:
if($bad)
{
string $yesNo = `confirmDialog -title "Confirm File Paste"
-message "Are you sure you want to paste (import)\nthe previously 'copied' (exported) Maya file?"
-button "Yes" -button "No" -defaultButton "Yes"
-cancelButton "No" -dismissString "No"`;
if($yesNo == "Yes")
cutCopyPaste "paste";
}
else
cutCopyPaste "paste";
}}}
There have been rare instances where I've needed to create mel procedures inside of Python modules. For example, the {{{outlinerEditor}}} command has a {{{selectCommand}}} argument that expects a //mel procedure// passed in as a string. If you're creating the {{{outlinerEditor}}} via Python (via {{{maya.cmds.outlinerEditor()}}}), and you want to access that argument, you can't call to another Python function... you //have to// call to a mel proc. :-(
It's pretty easy to author and execute mel from Python by simply making a multi-line string with the contents of the proc, and having the Python {{{maya.mel.eval()}}} function evaluate it:
{{{
# Python code:
import maya.mel as mm
mel ='''
global proc foo(){
print "foo is a go!\\n";
}
'''
mm.eval(mel)
}}}
Then to execute the newly created mel through Python:
{{{
# Python code:
mm.eval('foo()');
# foo is a go!
}}}
You can also confirm this in mel of course:
{{{
// mel code:
foo();
// foo is a go!
}}}
Continuing the example from above, your Python {{{outlinerEditor}}} code could look like this:
{{{
# Python code:
import maya.cmds as mc
mc.outlinerEditor(myOutliner, selectCommand='foo();')
}}}
@@Note:@@ Wing can interact with Maya in three different ways. This is one of the three. See an overview here on the whole system: [[Interaction between Wing and Maya]]
----
I using [[Wing IDE|For Python: Maya 'Script Editor' style IDE]] for authoring Python and mel code for Maya, and have come up with a solution (presuming you have the 'Professional' version of Wing) to have the code you @@highlight in Wing@@ //execute in Maya// (aka 'evaluate selection'). This lets me completely replace the Script Editor when authoring Python or mel, which we all know doesn't have the best Python integration... :-S
>I've added some updated code below that now allows you to publish ''mel'' code from Wing to Maya as well.
>>Note that you can also have Wing recognize {{{.mel}}} files, with //some// (not full) context sensitive highlighting, correct indentation, scope hilighting, etc: Under Wing 'Prefs -> Files -> File Types', 'insert' a new file type as {{{mel}}} and set its 'Mime Type' to be {{{C++ Source}}}.
There are a few steps involved to get this working. I should point out again this only works with the 'Professional' version of Wing, and you'll need Maya v8.5 or //newer//: when Maya started supporting Python (below this section I list some old code, for version of Maya8.5 and earlier.)
----
''1.'' In Maya's {{{userSetup.mel}}} file, add this line to open a network socket. This will later let Wing talk with Maya.
{{{
// userSetup.mel
commandPort -name ":6000" -echoOutput;
}}}
It appears, if you're running Maya on Vista \ 64bit, or maybe it's a Maya2010 thing, you need to call to {{{commandPort}}} twice to get this working, otherwise your socket will later be refused:
{{{
// userSetup.mel
commandPort -name "127.0.0.1:6000" -echoOutput;
commandPort -name ":6000" -echoOutput;
}}}
For whatever reason, having your {{{userSetup.py}}} execute these commands instead won't let the below process work. This really confuses me, since Maya claims the port is open. But Wing says the port is refused....
----
''2.'' This Wing Python module ({{{wingHotkeys.py}}}) and functions inside are authored to do a few things:
* A. Save the text selected in Wing as a temp file on disk.
* B. Ping Maya through the opened command port, and tell Maya to execute the contents of that file. Depending on the type of data sent, either Python or mel, the tool will tell Maya how to intercept it, and execute it.
Again, this code is tailored to Wing IDE's API, and saved as part of that program's own "scripts" directory. Default location (on winXP) of that dir is here:
{{{
C:\Documents and Settings\<userName>\Application Data\Wing IDE 3\scripts
}}}
*{{{send_to_maya()}}} : Function that does the heavy lifting
*{{{python_to_maya()}}} : Wrapper to send the code as Python to Maya. I have this mapped to {{{ctrl+p}}}. (Wing hotkeys don't allow args, so you need to author wrapper functions)
*{{{mel_to_maya()}}} : Wrapper to send the code as mel to Maya. I have this mapped to {{{ctrl+m}}}.
If you already have a {{{wingHotkeys.py}}} module for Wing, you can just add the below code to it:
{{{
# wingHotkeys.py
# Author: Eric Pavey - warpcat@sbcglobal.net
import wingapi
import socket
import os
def getWingText():
"""
Based on the Wing API, get the selected text, and return it
"""
editor = wingapi.gApplication.GetActiveEditor()
if editor is None:
return
doc = editor.GetDocument()
start, end = editor.GetSelection()
txt = doc.GetCharRange(start, end)
return txt
def send_to_maya(language):
"""
Send the selected code to be executed in Maya
language : string : either 'mel' or 'python'
"""
if language != "mel" and language != "python":
raise ValueError("Expecting either 'mel' or 'python'")
# Save the text to a temp file. If we're dealing with mel, make sure it
# ends with a semicolon, or Maya could become angered!
txt = getWingText()
if language == 'mel':
if not txt.endswith(';'):
txt += ';'
tempFile = os.path.join(os.environ['TMP'], 'wingData.txt')
f = open(tempFile, "w")
f.write(txt)
f.close()
# Tell Maya to execute the temp file:
maya = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Optional way of doing it, if using Vista:
#maya = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
try:
# Sometimes connect likes to complain, not sure why
maya.connect(("127.0.0.1", 6000))
# Optional way of doing it, if using Vista:
# maya.connect(("::1",6000))
if language == "mel":
maya.send('execMelCode();')
if language == "python":
maya.send('python("import execPythonCode; execPythonCode.main()")')
except:
maya.close()
maya.close()
def python_to_maya():
"""Send the selected Python code to Maya"""
send_to_maya('python')
def mel_to_maya():
"""Send the selected code to Maya as mel"""
send_to_maya('mel')
}}}
----
''3.'' This Python module {{{execPythonCode.py}}} is the one Wing's {{{send_to_maya('python')}}} function (above, step 2) triggers: In //Maya//, it uses the Python {{{exec}}} command to execute the code in the file saved on disk.
Be sure to save this in a location seen by //Maya's// Python path.
{{{
# execPythonCode.py
# Author: Eric Pavey - warpcat@sbcglobal.net
import __main__
import os
def main():
"""
exec the temp file on disk, in Maya
"""
tempFile = os.path.join(os.environ['TEMP'], 'wingData.txt')
if os.access(tempFile , os.F_OK):
# open and print the file in Maya:
f = open(tempFile , "r")
lines = f.readlines()
for line in lines:
print line.rstrip()
f.close()
print "\n",
# execute the file contents in Maya:
f = open(tempFile , "r")
exec(f, __main__.__dict__, __main__.__dict__)
f.close()
else:
print "No temp file exists: " + tempFile
}}}
The key here, is the {{{exec}}} command updating the {{{__main__.__dict__}}} dictionary with any defined variable names passed in from Wing. It is effectively adding them to the 'main', or 'builtin' scope of Python. If this //doesn't// happen, after the code is executed, you won't be able to access any variables created\changed from within Maya.
----
''4.'' This //mel script// allows for the temp file saved by Wing to be executed as ''mel'' in Maya. It to is triggered via the Wing {{{send_to_maya('mel')}}} Python function.
Be sure it is saved as part of Maya's script path.
{{{
// execMelCode.mel
// Author: Eric Pavey - warpcat@sbcglobal.net
global proc execMelCode(){
string $tempFile = (`getenv "TMP"`+"/wingData.txt");
if (`filetest -f $tempFile`){
// open and print the file in Maya:
int $fid = `fopen $tempFile "r"`;
string $line = `fgetline $fid`;
string $printer = strip($line);
if(size($printer) > 0){
print $line;
}
while ( size($line) > 0){
$line = `fgetline $fid`;
$printer = strip($line);
if(size($printer) > 0){
print $line;
}
}
fclose $fid;
print "\n";
eval("source \""+$tempFile+"\"");
}
else{
print ("No temp file exists: " + $tempFile + "\n");
}
}
}}}
We jump through some hoops to get it printed correctly, and at the end, we simply {{{source}}} the script to execute its contents.
----
''5.'' That's it: You can now highlight code in Wing (either Python or mel), and via a wing hotkey, have it execute directly in Maya. I use it __every day__...
----
''Older methods:''
*For Maya versions 8 and //earlier// (Maya 8.5 has Python embedded, yah!)
*Thanks to Hamish ~McKenzie "//macaroniKazoo//" on highend3d.com
*First, in Maya, create an INET domain on the local host at the command port:
**Win2000 syntax is "{{{computerName:portNumber}}}", I've wondered why you don't have to specify the computerName here?
{{{
commandPort -n ":5055";
}}}
*Second, in Python:
{{{
import socket
maya = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
maya.connect(("127.0.0.1", 5055))
}}}
*Then, to actually have Python talk to Maya:
{{{
maya.send('print "I am typing in Python, but I see it in Maya!\\n";')
}}}
*Of course, you have to embed your //mel commands// in the Python syntax.
*Python library docs on 'socket' [[HERE|http://docs.python.org/lib/module-socket.html]]
*What's so special about '127.0.0.1'? Check Wikipedia [[HERE|http://en.wikipedia.org/wiki/127.0.0.1]]
!Worldspace Matrix
If you're only interested in translation values, you can query a nodes {{{worldMatrix}}} attr to get this info directly from the node in question:
{{{
string $node = "pSphere1";
float $offset = 1.0;
float $t = `currentTime -q`;
float $worldMatrix[] = `getAttr -t ($t- $offset) ($node+".worldMatrix")`;
vector $offsetTranslate = <<$worldMatrix[12], $worldMatrix[13], $worldMatrix[14] >>;
print $offsetTranslate;
}}}
!decomposeMatrix node
Before I had a better understanding of nodes matrix attrs, I used to use this method:
*The {{{decomposeMatrix}}} plugin wil help do the trick (got the info for this tip in a forum by Lutz Paelike)
*I'm not a big fan of plugins in development, they can really cause a bottleneck, but since this plugin is distributed with Maya, I find it safer than usual.
*What the decomposeMatrix node does, among other things, is let you pass in a source matrix, and it will spit out the worldspace trans, rot, scale, and shear for that matrix. From there, we can use {{{getAttr -t}}} to query those values in time.
*Given this concept, regardless of your source objects hierarchical transformations, as long as your destination object lives in worldspace, it will follow along happily in time:
First, load the "decomposeMatrix.mll" plugin:
{{{
int $isLoaded = `pluginInfo -q -l "decomposeMatrix.mll"`;
if (!$isLoaded)
loadPlugin "decomposeMatrix.mll";
}}}
Next create the decomposeMatrix node:
{{{
string $dm = `createNode decomposeMatrix`;
}}}
given you have an object with it's name in ' string $obj = "myObj" ', connect:
{{{
connectAttr -f ($obj + ".worldMatrix[0]") ($dm + ".inputMatrix");
}}}
Now you can query the decomposeMatrix's output attrs to find the worldspace transformations. (or, you could connect them to another node, for a direct connection situation) And if we make an *expression* in like the link below, you can query them in time, to generate lag:
{{{
float $t = `currentTime -q`;
followObj.translateX = `getAttr -t ($t - 10) decomposeMatrix1.outputTranslateX`;
}}}
Also see:
*[[How can I compute distance traveled in an expression?]]
*[[How can I make one object "follow" another, offset in time, but without using getAttr or setAttr?]]
The {{{arrayMapper}}} node, and quite a bit of other work. The expression work below comes from the brain of Daniel Roizman: roizman@kolektiv.com
*Steps, based on a nurbs curve softbody:
**On your particle object there is a .goalPP attr. This allows the user to change the goal weights per particle in the component editor. However, it'd be a lot easier if you could use a ramp to do this instead.
**On your particle object, in the Attribute editor, under "Add Dynamic Attributes", add a "General" attr that is a float "Per Particle (array)" attr, and name it uValuePP?.
**Next make a "Runtime Before Dynamics Expression" on the new uValuePP? attr by RMB on it in the "Per Particle (Array) Attributes) section:
{{{
myparticle.uValuePP = float(myparticle.particleId) / float(myparticle.count);
}}}
**This isn't super accurate, but it gets the job done.
**RMB on the "goalPP" attr field and choose "Create Ramp (options)".
**Set the "Input V" to uValuePP?, hit OK. This creates the arrayMapper, & associated ramp.
**That's it. Animate your source curve and the default ramp will make it stick on one end, and be very floppy on the other.
*You may want to have a script return back where it physically lives on disk. You can use this for file processing, tracking of assets... whatever. How to do?
*The {{{whatIs}}} command is very handy at returning this info.
*For example, you make a mel script called whatsit here: {{{C:/whatsit.mel}}} You save it, and then highlight-enter it in the script editor to define it:
{{{
global proc string whatsit()
{
string $whatIs = `whatIs whatsit`;
if(`match "^Mel procedure found in: " $whatIs` == "Mel procedure found in: ")
$whatIs = `substitute "Mel procedure found in: " $whatIs ""`;
return $whatIs;
}
}}}
*If you then quit Maya, restarted it, and entered the command you'd get this:
{{{
whatsit;
// Result: C:/whatsit.mel //
}}}
*One tricky bit where the code can get hung up is if you’re //currently authoring// the code, and trying to get whatIs to work properly.
*Say you then highlighted the procedure, and defined it in the script editor by pressing Enter. You //then// run whatsit, and you get:
{{{
whatsit;
// Result: Mel procedure entered interactively. //
}}}
*Hmm… not the full path (but true). However, if you have it {{{rehash}}}, then {{{source}}} itself first, then it’s happy:
{{{
global proc string whatsit()
{
rehash;
source whatsit;
string $whatIs = `whatIs whatsit`;
if(`match "^Mel procedure found in: " $whatIs` == "Mel procedure found in: ")
$whatIs = `substitute "Mel procedure found in: " $whatIs ""`;
return $whatIs;
}
}}}
*Now try executing the code after ''saving'' the script and 'highlighting-Enter' declaring it in the script editor
**FYI saving it is very important: Since it's being rehashed and sourced, it will then look a the data ON DISK, not what you last interactively entered in the script editor.
{{{
whatsit;
// Result: C:/whatsit.mel //
}}}
*Again, this is only an issue if you’re in the habit of re-highlighting and re-defining the procedure //while// you’re working on it (which I am). If you restarted Maya, the first example above would work just fine.
*Be warned: If you had mel commands outside of the whatsit //proc// but inside the whatsit.mel //script//, the {{{source}}} statement would executethem, so even then it’s not a best case solution. Python anyone? ;)
----
Also see
*[[How can I query the location of a script?]]
*[[How can I add images to my UI, without hardcoding their paths?]]
Often, animators only want to work on a specific animation channels based on multiple selected objects. Say you have 10 objects picked, and only want to modify their tx and ry channels in the Graph Editor. You'd have to, by hand, scroll down through the Graph Editor Outliner, picking those channels.
The Channel Box however has this functionality: With multiple objects picked, whatever channels are selected in the Channel Box will effect each of those objects.
So the question is, how can you take what's selected in the Channel Box, and 'transfer' it to the Graph Editor?
In a nutshell:
*Find the {{{selectionConnection}}} being used by the Graph Editor Outliner.
*Get a list of all the objects picked.
*Query what attrs are picked in the Channel Box.
*For each obj+attr combo, 'update' the {{{selectionConnection}}} in the Graph Editor Outliner to highlight these channels. Also need to "un-highlight" the picked //objects//.
This will give the animator the visual filter they need to only work on those channels, in the Graph Editor.
{{{
# Python code
import maya.cmds as mc
selectionConnection = mc.outlinerEditor("graphEditor1OutlineEd",
query=True, selectionConnection=True)
selObj = mc.ls(selection=True, long=True)
selAttr = mc.channelBox("mainChannelBox", query=True,
selectedMainAttributes=True)
for so in selObj:
mc.selectionConnection(selectionConnection, edit=True, deselect=so)
for sa in selAttr:
mc.selectionConnection(selectionConnection, edit=True, select=so+"."+sa)
}}}
{{{
# Python code
import maya.cmds as mc
# Get a list of all the current references. They in turn
# may reference other things that will be exposed once
# they are imported:
allrefs = mc.file(query=True, reference=True)
for ref in allrefs:
try:
mc.file(ref, importReference=True)
except RuntimeError:
pass
# See if there are any new references to import:
newrefs = mc.file(query=True, reference=True)
if len(newrefs):
allrefs += newrefs
}}}
Presuming you have a list of nodes, each with a numbered postfix on the end, like so:
*bob_1
*bob_2
*bob_3
*etc...
Or, in a hierarchy:
*bob_1
**bob_2
***bob_3
****etc...
And you'd like to increment or decrement those numbers, how would you author code to do so?
It's actually a pretty good example, because you can run into a lot of issues:
{{{
// get our list of nodes, full paths:
string $sel[] = `ls -l -sl`;
// define the number we will add to \ subtract from the postifx
int $number = -1;
// loop throug hour list:
for($i=0;$i<size($sel);$i++)
{
// match numbers only on the end of our object name:
string $endString = `match "[0-9]*$" $sel[$i]`;
// increment, or decrement the number. Temporarily convert our string-based name to an int:
string $newString = int($endString) + $number;
// Danger: Can't rename somthing to less than zero, Maya wouldn't accept the name:
if(int($newString) >= 0)
{
// If greater than zero, generate our new name. Substitute only numbers on the end of the name
string $newname = `substitute ($endString + "$") $sel[$i] $newString`;
// Check to see if an object with this full path name already exists. If so, print warning, skip
if(`objExists $newname`)
warning ("Can't rename node '" + $sel[$i] + "' to this name: '" + $newname + "'; that name alraedy exists in scene");
// If this is a unique name for the full path:
else
{
// get just the leaf name, if it is a full path name:
string $buffer[];
tokenize $newname "|" $buffer;
string $endName = $buffer[size($buffer)-1];
// FINALLY rename our full-path'd node with the leaf name:
rename $sel[$i] $endName; // print $sel[$i]
// Important: Redefine the selection list. If you're renaming parental nodes, based on the
// full path names, the child path names would then change, and they wouldn't be found
// any more by the code. Must update the full paths:
$sel = `ls -l -sl`;
}
}
// If the name would have been less than zero, print warning, and skip
else
warning ("Can't rename node '" + $sel[$i] + "' to a lower numbered postfix, would be less than zero");
}
}}}
Our result would be:
*bob_0
*bob_1
*bob_2
*etc...
or
*bob_0
**bob_1
***bob_2
****etc...
I've had times when I'd want to frame the camera on a certain object in code. But I'm not sure which panel is currently available to view through based on the users currently layout. Maya has four viewable panels that a camera can exist in: modelPanel1, modelPanel2, modelPanel3, modelPanel4. And based on the currently layout, any one of those four could be currently maximized. The below code will find the //first// one available, and print it's name.
{{{
string $allPanels[] = `getPanel -vis`;
string $camPanel = "";
for($i=0;$i<size($allPanels);$i++)
{
if(`match "^modelPanel" $allPanels[$i]` == "modelPanel")
{
$camPanel = $allPanels[$i];
break;
}
} //
print ($camPanel + "\n");
}}}
This code could then be used to make the camera in that panel frame some object:
{{{
// we say "if" $camPanel exists, because it's possible that some other
// panel is maximized, like the Outliner:
if(size($camPanel))
{
setFocus $camPanel;
select -r "myGeometry";
FrameSelected;
fitPanel -selected;
select -cl;
}
}}}
Also see [[How can I use mel to make the current camera look at an object of choice?]]
and [[How do I query the name of the camera in the active panel?]]
*In Windows, if you write your own files to disk using Maya using commands like {{{fprint}}}, using Notepad in Windows, which is the default viewer for text documents, doesn't interpret the 'newline\return' characters correctly ({{{\n}}}, {{{\r}}}).
*Problem is, "Notepad.exe" //is// in the Windows PATH, so it's really easy to call to to view text files. ~WordPad, //isn't// in the windows path, but it //will// interpret the 'newline\return' character properly.
*So what is the best way to call to it? Below is a good example. You'd want to do error checking to make sure your $file actually exists first:
{{{
string $file = "c:/temp/myFile.txt";
string $programFiles = `getenv "ProgramFiles"`;
string $wordPad = ($programFiles + "/Windows NT/Accessories/WORDPAD.EXE");
if(`filetest -f $wordPad`)
system("start " + $wordPad + " " + $file);
}}}
Doing the same thing with Notepad is easier (since it's in the Windows PATH), but the formatting usually looks screwed up :-(
{{{
string $file = "c:/temp/myFile.txt";
system("start notepad " + $file);
}}}
{{{
# Python code
import maya.cmds as mc
def getAllNamespaces(start=':'):
namespaces = [start]
mc.namespace(setNamespace=":")
for n in namespaces:
mc.namespace( setNamespace=n)
subNs = mc.namespaceInfo(listOnlyNamespaces=True)
if subNs is not None:
for sn in subNs:
if n is not ':':
namespaces.append(':%s'%(sn))
else:
namespaces.append('%s%s'%(n,sn))
mc.namespace(setNamespace=":")
return namespaces
}}}
{{{
namespaces = getAllNamespaces()
for n in namespaces:
print n
}}}
Prints (for example):
{{{
:
:UI
:myNamespace
:myNamespace:subNamespace
:etc...
}}}
Without the qualifying reference node, it'll list all edits in the scene. Or you can pass in a reference node name, to limit it to just that reference
{{{
string $edits[] = `referenceQuery -editStrings refNamespaceRN`;
// Result: setAttr refNamespace:myNode.rotate -type "double3" -21.50129 -10.55454 -9.645982 //
}}}
I've found that when trying to use mel to __load__ a Python scripted plugin, you have to provide the //full path// to the scripted plugin, even if it's already in you 'maya plugin path'. However to __unload__ it, you only need the //name//:
{{{
# Python code
import os
import maya.cmds as mc
# Import our plugin, so we can extract the full path
# from it. Let's pretend it lives in this package:
# c:\spDev\myScriptedPlugin.py
import spDev.myScriptedPlugin
# Pull the full path from it:
fullPath = spDev.helloWorld2Cmd_sp.__file__
if fullPath.endswith('.pyc'):
fullPath = fullPath [:-1]
dirPath, plugin = os.path.split(fullPath)
# Load:
mc.loadPlugin(fullPath, quiet=True)
mc.pluginInfo(plugin, edit=True, autoload=True)
# Unload:
mc.unloadPlugin(plugin)
mc.pluginInfo(plugin, edit=True, autoload=False)
}}}
If you have the Plug-in Manager open while doing this, you won't always see the checkboxes check on and off. But if you close it and reopen it, they'll have updated correctly.
Below is a //code snippet//. By itself it won't run, but better than nothing until I get a working example up ;) But conceptually it works.
{{{
// Make blendShape:
string $blendShape[] = `blendShape $target $myMesh`;
// Turn "on" the $target blendShape (this may be optional)
setAttr($blendShape[0] + $bsattr) 1;
// fire this command to build the context we need
ArtPaintBlendShapeWeightsTool;
// read in our correct map:
select -r $myMesh;
string $correctMap = "c:/temp/someMap.jpg";
artAttrCtx -e -importfileload $correctMap artAttrBlendShapeContext;
}}}
Common operation:
{{{
# Python code
import maya.cmds as mc
plugin = "MyPlugin.mll"
mc.loadPlugin(plugin, quiet=True)
mc.pluginInfo(plugin, edit=True, autoload=True)
}}}
If the plugin you're trying to load isn't part of your {{{MAYA_PLUG_IN_PATH}}} Python will raise a {{{RuntimeError}}} [[exception|What kind of exceptions does Python raise when a mel command fails?]]:
{{{
# RuntimeError: Plug-in, "MyPlugin.mll", was not found on MAYA_PLUG_IN_PATH.
}}}
and shoot this error out to Maya:
{{{
# Error: line 1: Plug-in, "MyPlugin.mll", was not found on MAYA_PLUG_IN_PATH.
}}}
You can write a simple expression to do it. The below code presumes a few things:
*You've created a NURBS curve, that is closed. (with a min u of 0, and a max of 1)
*You've attached something too it as a motion path.
*On the motionPath itself, you've deleted the default keyframes in the {{{.uValue}}} attr
{{{
// Maya Expression:
int $startFrame = 1;
float $speed = .01;
float $curveLenthU = 1.0;
if(`currentTime -q` <= $startFrame){
motionPath1.uValue = 0;
}
else{
motionPath1.uValue = motionPath1.uValue + $speed;
if(motionPath1.uValue > $curveLenthU ){
motionPath1.uValue = motionPath1.uValue -1;
}
}
}}}
You can change {{{$curveLenthU}}} to be anything you want (if the curve isn't normalize from 0-1), and change the speed to make it faster or slower.
An easy way to do it is with the {{{textToShelf}}} script that ships with Maya. It lives here:
{{{.../MayaX.X/scripts/startup/textToShelf.mel}}}
Don't forget to save your shelves\prefs after this of course (usually don't when Maya exits, but not always).
Here's the guts of its code, for informative purposes:
{{{
global proc textToShelf (string $label, string $script)
{
global string $gShelfTopLevel;
if (`tabLayout -exists $gShelfTopLevel`)
{
string $currentShelf = `tabLayout -query -selectTab $gShelfTopLevel`;
setParent $currentShelf;
// Create the shelf button on the current shelf.
// Give it the default MEL script icon,
// and set the short label too.
shelfButton
-command $script
-label $label
-annotation $script
-imageOverlayLabel $label
-image1 "commandButton.xpm"
-style `shelfLayout -query -style $currentShelf`
-width `shelfLayout -query -cellWidth $currentShelf`
-height `shelfLayout -query -cellHeight $currentShelf`;
}
}
}}}
*The DOS attrib command. If there is a way to do it in mel then I'm missing it. Type 'help attrib' in DOS to get all the arugments
*Example: Make a file writable:
{{{
system ("attrib -r " + $nameOfMyFile);
}}}
{{{allViewFit}}}
This is nice to use when you want to frame the camera on something, but don't know which panel the user has active, or which camera they're using.
{{{
//Fit the view to all objects:
allViewFit(1);
//Fit the view to just the selected objects:
allViewFit(0);
}}}
*This is a general outline, mel to follow
*Particle way (credit Colin Penty):
**Selct the vert you want to constrain to
**"Particles -> Emit from Object [options] -> Emitter Type -> Surface -> Create
**Delete the "particle1" node. You now have "emitter1" you can parent objects to, glued to the object.
*"Hair Follicle" method:
**Found example of this method here: http://www.creativecrash.com/maya/downloads/scripts-plugins/polygon/c/djrivet-mel--3
The {{{condition}}} command.
----
Also see:
*[[How can I query if certain conditions exist in Maya?]]
*The {{{frameCache}}} node
*This is an alternative to the above "expression based" solution.
*Example: Make "cube's" .ty lag behind "ball's" .ty by 3 frames:
{{{
// this creates the frameCache node:
createNode frameCache;
// frameCache needs to have time as an input:
connectAttr -f time1.outTime frameCache1.varyTime;
// frameCache needs to have the animation as an imput:
connectAttr -f sphere.ty frameCache1.stream;
// To determine the frame offset, you connect the array attr's .past attribute with the
// frame offsetvalue as its element (like .past[3]) into the input of your other node:
connectAttr -f frameCache1.past[3] cube.ty;
}}}
*Notes: It appears that after the nodes are connected, the "frameCache" node caches (duh) the animation data. This means that changing the original animation has "strange" results on the frameCache node, with the newer animation not being applied properly to the "offset" node. Don't know the workaround yet.I've spoken with AW about this, and they confirm it's a limitation... A bug report has been submitted.
This could also be simply called "how to add things to sets".
I find it really handy to organize sets inside of sets. However, my brain can never seem to wrap itself around how to add a 'child set' to a 'parent set' via code: The syntax to me is... a bit backwards:
Given a parental set '{{{parentSet}}}', and a set you want to add as a subset called '{{{subset}}}':
@@Mel:@@
{{{
sets -addElement parentSet subSet;
}}}
@@Python:@@
{{{
import maya.cmds as mc
mc.sets("subSet", addElement="parentSet")
}}}
In both instances of the command, to me, it looks like you're trying to add {{{parentSet}}} to {{{subSet}}}, but that's just not the case.
In the Outliner this would give you the effect:
{{{
- parentSet
|
---- subSet
}}}
{{{play -w;}}}
*Example: I have a script that requires the current frame range is played through to define a dynamic simulation. But when I execute my script, the script doesn't wait for the range to finish playing. What to do? Use the -w flag, which is "wait".
{{{
//script;
play -w;
// more script
}}}
This... 'annoyance' was introduced around Maya 2008 (+- a version?):
If you had objectB.tx connected to objectA.tx, and objectB.tx was keyframed, and you opened the Graph Editor for objectA, you'd see it's 'Translate X' channel expanded, with a pointer to 'objectB.Translate X' under it:
----
(pretend this is the Outliner in the Graph Editor)
*objectA
**//Translate X//
***@@//objectB.Translate X//@@
----
@@This@@ is annoying enough on it's own, but presuming that there were many connections from B->A, A's Graph Editor could get full of all sorts of unwanted data.
The fix, to turn this "functionality" off:
{{{
# Python code
import maya.cmds as mc
# This is the name of the outlinerEditor in the Graph Editor:
graphEd = 'graphEditor1OutlineEd'
mc.outlinerEditor(graphEd, edit=True, expandConnections=True)
}}}
Languages like Processing have [[functions|http://www.processing.org/reference/map_.html]] that will take a given value from one range and map it into another. I can't seem to find this in Python, but here's the code:
{{{
# Python code
def valueRangeMap(val, origMin, origMax, newMin, newMax):
"""
Will remap val from the original range into the new range.
val : The value you are mapping.
origMin : The original minimum value that val should be greater than.
origMax : The original maximum value that val should be less than.
newMin : The new minimum value that val will be mapped into.
newMax : the new maximum value that val will be mapped into.
return : float : The newly mapped value.
"""
# Find the percentage val is between origMin and origMax:
percetVal = float(val - origMin) / float(origMax - origMin)
# Map into the new range:
mappedVal = (newMin+newMax)*percetVal
return mappedVal
print valueRangeMap(5, 0, 10, 10, 20)
# 15.0
}}}
Mel examples first, Python (way at the bottom) second.
!Mel
Say you have a line with mixed-case characters in it, and you're trying to match a string in it. However, the string you're trying to match could in no-way be in the same case as the original string. In addition, you need to get the result back in the original case.
For example:
{{{
string $src = "My Mixed Case String";
string $matcher = "mixed case";
}}}
What you want to get back is a string like this:
{{{
"Mixed Case"
}}}
There is no "easy" way I've found to do this. If you don't care about having the return value come back case sensitive, you can use the {{{match}}} command combined with the {{{tolower}}} (or {{{toupper}}}) command to format all the string ahead of time.
But if you want to maintain case sensivity as part of the return, you need to get into some regular expression voodoo.
In a nutshell, the below code will take the {{{$matcher}}} string, and for each alphabetic character, turn it into a matching pair of upper and lower-case characters. In addition to handling characters that are "special" to {{{match}}}, and need to be escaped when searching. An example of what this crazy search string looks like is given at the very bottom.
{{{
global proc string matchNonCase(string $matcher, string $line)
{
// convert our $matcher string into a string array:
string $array[];
for($i=0;$i<size($matcher);$i++)
$array[$i] = `substring $matcher ($i+1) ($i+1)`;
// define characters special to the match command that need to be escaped:
string $spcl[] = {".", "*", "+", "^", "$", "\\", "[", "]", "(", ")"};
// generate our special match pattern string:
string $matchPattern = "";
for($i=0;$i<size($array);$i++)
{
string $char = `match "[a-z]*[A-Z]*" $array[$i]`;
if(size($char))
{
string $lower = tolower($char);
string $upper = toupper($char);
$matchPattern = ($matchPattern + "[" + $lower + $upper + "]");
}
else
{
int $special = 0;
for($j=0;$j<size($spcl);$j++)
{
if($spcl[$j] == $array[$i])
{
$matchPattern = ($matchPattern + "\\"+$array[$i]);
$special = 1;
break;
}
}
if($special == 0)
$matchPattern = ($matchPattern + $array[$i]);
}
}
// print $matchPattern;
// and finally, see if we have a match:
string $result = `match $matchPattern $line`;
return $result;
}
}}}
{{{
string $line = "lineData -- \"c:/my/\\Dir$/is/MIXED/case\" -- more Data";
string $matcher = "/my/\\diR$/is/mIxed/";
string $result = matchNonCase($matcher, $line);
// Result: /my/\Dir$/is/MIXED/ //
}}}
If you're wondering, {{{$matchPattern}}} equals this given the above example:
{{{
/[mM][yY]/\\[dD][iI][rR]\$/[iI][sS]/[mM][iI][xX][eE][dD]/
}}}
!Python
As ever, doing this in Python is way easier:
{{{
import re
search = "/my/dir/is/mixed"
line = "lineData -- C:/my/Dir/is/MIXED/case --moreData"
print re.findall(search, line, re.IGNORECASE)
# ['/my/Dir/is/MIXED']
}}}
However, embedding special chars will confuse it, so the mel is a bit more robust. I'm currently checking to see if the special chars in Python can b handled as well. I presume they can, just havn't found it yet...
We query the {{{.worldMatrix}}} of our source node, then via {{{xform}}} set our destination node to that worldspace transformation:
{{{
// mel
string $source = "pCube1";
string $destination = "pCube2";
float $m[16] = `getAttr ($source+".worldMatrix")`;
xform -worldSpace -matrix $m[0] $m[1] $m[2] $m[3]
$m[4] $m[5] $m[6] $m[7]
$m[8] $m[9] $m[10] $m[11]
$m[12]$m[13] $m[14] $m[15] $destination;
}}}
{{{
# Python
source = "pCube1"
destination = "pCube2"
m = mc.getAttr(source+".worldMatrix")
mc.xform(destination, worldSpace=True, matrix=m)
}}}
Why is this cool? Because no matter what you have done to the parental hierarchy of either node, and despite the rotation orders of either node, the destination node will perfectly match the worldspace matrix of the source.
This also solves problems that other code (like the below example illustrates) will get tripped up on (which include the links at bottom) when dealing with matching orientations:
If the __rotate order__ of the destination //differs// from the source, the below code will __fail__.
{{{
# Python:
import maya.cmds as mc
worldRot = mc.xform(source, rotation=True, query=True, worldSpace=True)
# two different methods, will fail if differing rotate orders:
mc.xform(destination, rotation=worldRot, worldSpace=True)
mc.rotate(worldRot[0], worldRot[1], worldRot[2], destination, worldSpace=True)
}}}
----
Also see:
*[[How can I rotate an object that has one rotation order to the world space orientation of another object with a different rotation order?]]
*[[How can I move an object to an exact worldspace position? How can I find an exact worldspace position?]]
*[[How can I find the world\local space position of a point?]]
I often find myself needing to mirror nodes from one half of the global YZ plane to the other. This simple Python script will mirror the positions of nodes on the -x side of the YZ plane based on positions on the +x side. All the nodes are selected at once: All the +x nodes first, and the -x nodes second. And they should be selected parent first, child last.
{{{
import maya.cmds as mc
nodeList = mc.ls(selection=True)
if len(nodeList) % 2 == 1:
raise(Exception("Please pick an even number of nodes."))
half = len(nodeList)/2
counter=0
while counter < half:
sourceNode = nodeList[counter]
destNode = nodeList[half + counter]
counter = counter+1
sourcePos = mc.pointPosition((sourceNode+".rotatePivot"), world=True)
mc.move((sourcePos[0]*-1), sourcePos[1], sourcePos[2], destNode,
absolute=True, worldSpace=True, rotatePivotRelative=True)
print "Finished World XY Mirror",
}}}
The script located here:
{{{../MayaX.X/scripts/startup/generateChannelMenu.mel}}}
This script has all the UI code which builds the marking menu, so edit for your needs.
Of course, what would be safer would be to duplicate this script, rename it to something else (like say, {{{myCustomGenerateChannelMenu.mel}}}), and when Maya starts up, add this line to your {{{userSetup.mel}}} script:
{{{
source myCustomGenerateChannelMenu;
print "Custom generateChannelMenu.mel script sourced -- userSetup.mel\n";
}}}
In the UI, Maya calls the term 'project' (as in File -> Project -> Set...). But the mel behind the scenes is {{{workspace}}}.
Query the current workspace:
{{{
string $proj = `workspace -query -active`;
}}}
Set the current workspace:
{{{
string $path = "c:/myProject";
if(`filetest -d $path`){
// This upates the optionVar used by the Preferences UI:
optionVar -sv ProjectsDir $path;
// This (procedure, not command) sets the current path as the *default project*.
// It also makes directories on disk in the process, so it's not always wanted.
setProject $path;
// Almost superfluous at this point, but for completeness:
workspace -dir $path;
}
else
warning("Can't set project: Directory '" + $path + "' doesn't exist.");
}}}
This sets the '@@current //workspace// directory@@', which also happens to be the path that opens by default when launching a {{{fileBrowser}}} (or a {{{fileBrowserDialog}}} when in //directory// mode), and is also used when searching for files. This is different from the [[current working directory]] (see those notes), that is used by the {{{pwd}}} and {{{chdir}}} commands.
If you //don't// set the {{{optionVar}}}, things will still work, but if you look at the Maya Preferences UI, you could see a conflicting path. {{{setProject}}} does the real magic, and makes it persist between Maya sessions. {{{workspace -dir}}} also works, but doesn't seem to persist between Maya sessions.
It should be noted that using this technique with a {{{fileBrowserDialog}}} doesn't seem to work, unless it is in 'directory mode', which is mode '4'. For other {{{fileBrowserDialog}}} techniques, see this subject: [[How can I define a start location for my fileBrowserDialog?]]
{{{move}}} \ {{{xform}}}
*Example A: Given a transform:
{{{
float $position[] = `xform -q -ws -rp transformA`;
move -a -ws -rpr $position[0] $position[1] $position[2] transformB;
}}}
*Example B: Given a joint:
{{{
float $position[] = `xform -q -ws -rp transformA`; // (or jointA, or whatever)
move -a -ws $position[0] $position[1] $position[2] jointB.scalePivot;
move -a -ws $position[0] $position[1] $position[2] jointB.rotatePivot;
}}}
{{{autoPlace}}}
*Note: It appears the return values are always in centimeter, so depending on your working units, you may have to use some math to get the correct values.
*Note: It's supposed to work with the mouse position too, but seems very inaccurate
Thanks to 'Campbell' over on highend3d.com for this info
*Do it "C++" style (or so they say...). Here's some examples:
{{{
string $jnts[] = ls ("-type", "joint", listHistory ("-f", 1, listRelatives ("-p", "-ad", sets("-q", "some_quick_select_set"))));
}}}
{{{
string $jnts[] = ls ("-type", "joint", listHistory ("-f", 1, 'eval("listRelatives -p -ad 'sets -q \"some_quick_select_set\"'")'));
}}}
This is also called 'function syntax', see notes on [[Commands]].
{{{
system("start explorer " + toNativePath(dirname(`file -q -sn`)));
}}}
{{{file}}} \ {{{fileDialog}}}
*Example: I want to import a specific kind of file from a specific directory:
{{{
file -i `fileDialog -dm \"Z:/rotk/objects/char/rig/scenes/myFile.*\"`;
}}}
{{{
import maya.cmds as mc
def getSavedPath(ovName):
"""
Get an optionVar by the given name. Return an empty string if either no
optionVar exists, or the path it defines doesn't exist.
"""
savedPath = mc.optionVar(query=ovName)
if savedPath == 0:
savedPath = ""
elif not os.path.isdir(savedPath):
savedPath = ""
return savedPath
def getSavePath(fileType):
"""
Create a fileDialog the user can use to specify a file to save, either new,
or pre-existing. Will store the path to the file for future access.
returns the name of the file to save.
"""
try:
ovName = '%sSaveDir'%fileType
savedPath = getSavedPath(ovName)
# Open our file browser:
f = mc.fileDialog(mode=1, directoryMask="%s*.%s"%(savedPath,fileType),
title="Save .%s data"%fileType)
# Append the extension to the file if the user didn't:
if f != "":
ext = os.path.splitext(f)
f = "%s.%s"%(ext[0], fileType)
else:
raise Exception
# Save our path for later browsing
browsedPath = os.path.split(f)
mc.optionVar(stringValue=[ovName, browsedPath[0]+"/"])
if os.access(f, os.F_OK) and not os.access(f, os.W_OK):
raise Exception("'%s' is read-only, unable to save."%f)
return f
except Exception:
print "Save canceled by user:",
def getLoadPath(fileType):
"""
Create a fileDialog the user can use to specify a file to load. Will store
the path to the file for future access.
returns the name of the file to load.
"""
try:
# Define the name of our optionVar
ovName = '%sLoadDir'%fileType
# Query for saved path data:
savedPath = getSavedPath(ovName)
# Open our file browser:
f = mc.fileDialog(mode=0, directoryMask="%s*.%s"%(savedPath,fileType),
title="Load .%s data"%fileType)
# Save our path for later browsing
if f != "":
browsedPath = os.path.split(f)
mc.optionVar(stringValue=[ovName, browsedPath[0]+"/"])
else:
raise Exception
return f
except Exception:
print "Load canceled by user",
}}}
{{{
print getLoadPath("ma")
# C:/temp/file.ma
}}}
{{{
print getSavePath("ma")
# C:/temp/file.ma
}}}
There is a runTimeCommand called:
{{{
HardwareRenderBuffer
}}}
that will launch it, but it's just a wrapper for the procedure:
{{{
glRenderWin()
}}}
which lives in the script:
{{{
C:/Program Files/Autodesk/Maya<VER>/scripts/others/glRenderWin.mel
}}}
----
Also see:
*[[How can I render from the Hardware Render Buffer?]]
New way my buddy Te pointed out to me:
{{{
# Python code:
import maya.cmds as mc
mc.parent('someShapeNode', 'someTransform', shape=True, add=True)
}}}
I thought the code would //re-parent// the shape node. But it doesn't, it makes it an instance.
----
Older method:
*First, you "instance" your shape node, which will give you a new transform as well.
*Second, you parent your new instanced shape node to it's new parental transform.
*Third, remove the 'no-longer-needed' transform
{{{
string $shapeNode = "myShape";
string $newParent = "someTransform";
string $instancedShape[] = `instance $shapeNode`;
parent -s -r ($instancedShape[0] + "|" + $shapeNode) $newParent;
delete $instancedShape[0];
}}}
Also see:
[[How can I reparent the shape node of one object to another. OR, how can I have a single transform with multiple shapes?]]
If you have a polygonal asset with multiple materials assigned to multiple faces, and you pick a single face and try to find what material is assigned to it in the Hypershade, the Hypershade will list ALL the materials assigned to that mesh.
The below code will select the given material assigned to any single face:
{{{
string $sel[] = `ls -fl -sl`;
if(size($sel)!= 1)
error "Please select a single face";
string $shaders[] = `listSets -type 1 -object $sel[0]`;
string $mat[] = `listConnections -s 1 -d 0 ($shaders[0] + ".surfaceShader")`;
select -r $mat[0];
}}}
Presuming your selection is a //single face//, this code will find the associated {{{shadingEngine}}}, all items in that engine, and select them.
{{{
string $sel[] = `ls -fl -sl`;
if(size($sel)!= 1)
error "Please select a single face";
string $shaders[] = `listSets -type 1 -object $sel[0]`;
string $items[] = `sets -q $shaders[0]`;
select -r $items;
}}}
The above example will pick all faces assigned to that shader in the scene, spanning all meshes. The code can be changed slightly: The //below// example will do the same as the above, but it will only pick faces on the //same mesh// as the original selected face.
{{{
string $sel[] = `ls -fl -sl`;
if(size($sel)!= 1)
error "Please select a single face";
string $shaders[] = `listSets -type 1 -object $sel[0]`;
string $items[] = `sets -q $shaders[0]`;
string $locMesh[];
tokenize $sel[0] "." $locMesh;
select -cl;
for($i=0;$i<size($items);$i++){
if(`match $locMesh[0] $items[$i]` == $locMesh[0])
select -add $items[$i];
}
}}}
I blogged about it here:
http://www.akeric.com/blog/?p=1049
----
And notes on the Python functions:
http://docs.python.org/library/pickle.html
http://docs.python.org/library/pickle.html#module-cPickle
http://docs.python.org/library/stringio.html#module-StringIO
If you're on Windows, the easiest way is to call out to {{{Windows Media Player}}} via the command line:
{{{
string $app = "C:/Program Files/Windows Media Player/wmplayer.exe";
string $vid = "C:/temp/myMovie.avi";
system("start " + $app + " \"" + $vid + "\"");
}}}
This will of course load it in {{{Windows Media Player}}}. If you wanted to play a movie //in// a Maya UI, you'd need to know HTML programming, and tie that in with the {{{webBrowser}}} mel command.
Note: You can find a list of the {{{Windows Media Player}}} command line options here:
http://support.microsoft.com/KB/241422
The Windows {{{sndrec32}}} executable can be ran in command line mode. In UI mode, it's the little window that ships with Windows and lets you record and playback .wav files. Here are a list of it's arguments that I've found from online searching: (I haven't verified them all yet)
{{{
sndrec32 arguments:
/embedding - ???
/play - play file
/open - open file but don't play it
/new - open new file ready for recording
/close - close file
}}}
Based on that, and given a {{{.wav}}} file you'd like to play, you could script something like this (info modified from 'Joojaa' on Highend3d.com forums):
{{{
string $wav = "C:/WINDOWS/Media/Notify.wav";
$wav = toNativePath($wav);
system ("shell start /min sndrec32 /play /close " + $wav );
}}}
It //looks// like the {{{system}}} command uses both {{{shell}}} and {{{start}}} (since both shell and start are 'arguments' it can call to when calling to a cmd shell). In fact, the {{{system}}} command is //only// using '{{{shell}}}'. '{{{start}}}' is actually being called //by// the command shell once opened, and it has an argument which is {{{/min}}}, which causes the new cmd shell, and thus the sound recorder, to start in minimized mode. Check out the docs on the {{{system}}} command to learn about what {{{start}}}, {{{shell}}}, and {{{load}}} do in its context.
So here is another way to visualize how the data is being wrappered, with 'arguments' in quotes ('shell' isn't an actual argument of the {{{system}}} command, but can be considered one in this sense):
{{{system "shell" [ start "/min" [ sndrec32 "/play" "/close" [ $wav ] ] ]}}}
!!!Python:
{{{
import sys
sys.__stdout__.write("Printing to the Output Window")
}}}
!!!Mel:
The {{{trace}}} command. For example:
{{{
trace "Printing to the Output Window";
}}}
Found this awesome reference to {{{itertools}}} via the comments in this blog post:
http://tartley.com/?p=1081
http://docs.python.org/library/itertools.html#itertools.product
You need to be running Maya 2010 for {{{itertools.product}}} to work, since {{{product}}} was introduced in Python 2.6
{{{
# Python code
import maya.cmds as mc
import itertools
edge=1
verts=list(itertools.product(*([-edge/2,edge/2],)*3))
print verts
for v in verts:
mc.spaceLocator(position=v)
}}}
prints:
{{{
[(-1, -1, -1), (-1, -1, 0), (-1, 0, -1), (-1, 0, 0), (0, -1, -1), (0, -1, 0), (0, 0, -1), (0, 0, 0)]
}}}
It's often handy to have a UI that has a viewport with a custom perspective camera in it. The below example shows a really easy way of doing it. By default, it will create a window with the standard {{{persp}}} camera in it. But it allows the user to pass in their own custom camera name as well.
{{{
# Python code
import maya.cmds as mc
class App(object):
def __init__(self, camera="persp"):
self.name = "customModelPanel"
if mc.window(self.name, exists=True):
mc.deleteUI(self.name)
mc.window(self.name, resizeToFitChildren=True)
mc.paneLayout(configuration='single')
mc.modelPanel(camera=camera, menuBarVisible=True)
mc.showWindow()
}}}
To execute:
{{{
App()
}}}
I often have geometry provided to me from the animation staff placed some distance away from the origin. For rigging purposes, I need them centered about the origin, with their pivots at the origin. How to get it moved back there quickly?
{{{
string $sel[] = `ls -sl`;
for($i=0;$i<size($sel);$i++)
{
xform -cp $sel[$i];
vector $pos = `xform -q -ws -rp $sel[$i]`;
move -a -ws (($pos.x)*-1) (($pos.y)*-1) (($pos.z)*-1);
}
// After this is done, you probably want to
// freeze their transforms too:
makeIdentity -a 1 $sel;
}}}
{{{
string $filters[] = `itemFilter -q -listBuiltInFilters`;
print $filters;
}}}
{{{
DefaultGeometryFilter
DefaultNURBSObjectsFilter
DefaultPolygonObjectsFilter
DefaultSubdivObjectsFilter
DefaultCameraShapesFilter
DefaultJointsFilter
....etc...
}}}
You can also query 'user-defined' filters and 'other filters' as well.
Also see:
*[[Based on an itemFilter, how can I find all the nodes in Maya that match?]]
*[[How can I use itemFilter or itemFilterAttr?]]
{{{
skinCluster -q -inf $object;
// or leave off $object for the selected object
}}}
The easiest way is to call out to {{{dos}}} using its {{{DIR}}} command. The default {{{dos}}} syntax to get a list of all the subdirs of a dir is this:
{{{
DIR /ad /s /b
}}}
You could jump through a lot of hoops with the {{{getFileList}}} command, but I've tried, and it's exponentially slower than doing below:
And here is how to wrapper that with mel:
{{{
// good chance the incomming path has forwardslashes
string $path = "c:/temp";
// convert to backslash:
$path = toNativePath(path);
// query the system:
string $dirList = system("DIR "+ $path + " /ad /s /b");
// and convert into an array:
string $dirs[];
tokenize $dirList "\n" $dirs;
print $dirs;
// the last index will be an empty string, FYI
}}}
You'll probably want to run {{{fromNativePath}}} on each array member to put them back in "forwardslash" mode when you itter through the list.
If you want to kick it up a notch, you can also use dos to look for a particular dir IN each of the paths, at the same time the list is being generated. Say, you want to find all the dirs that END with {{{\foo}}}. The dos command would be:
{{{
DIR /ad /s /b |FINDSTR "\\foo$"
}}}
As you can see, you pipe the return of the DIR command into the FINDSTR command, which then looks for {{{\foo}}} at the end up each line using the ({{{$}}}) regular expression. You have to use double-backslashes in front of foo, since the backslash is an escape character in regular expressions.
To wrapper that in mel would look like this:
{{{
string $dirList = system("DIR " + $path + " /ad /s /b |FINDSTR \"\\\\foo$\"");
}}}
And if you wanted to find all the .ma files in that result (notice the {{{/ad}}} was removed):
{{{
string $dirList = system("DIR " + $path + " /s /b |FINDSTR \"\\\\foo$\" |FINDSTR \".ma$\" ");
}}}
And that, is a lot of escape backslashes....
The {{{control}}} command.
While each UI control usually has its own edit and query flags, you can also use the generic {{{control}}} command to do these operations. Makes it easier, if you know the name of a control, but not its 'type', to edit it.
Good for querying/editing:
control size, visibility, parent, and popup menus among others.
(Answer, from Alias:)
{{{
int $sj = `scriptJob -e RecentCommandChanged rcc`;
global proc rcc()
{
string $lastCommands[] = `repeatLast -q -cl`;
if (`gmatch $lastCommands[0] "Save*"`)
print ("scene was saved with " + $lastCommands[0] + "\n");
}
}}}
{{{
int $unloaded = `file -query -deferReference $refFilePath`;
}}}
Will return {{{1}}} if unloaded, {{{0}}} if loaded.
You can actually use the {{{objExists}}} command, like:
{{{
int $txAttrExists= `objExists persp.tx`;
// Result: 1 //
}}}
Which is easier than the syntax for {{{attributeQuery}}}, and I'm told that it actually evaluates faster too.
See:
[[Command: attributeQuery]]
*Example: Select an object and:
{{{
string $sel[] = `ls -sl`;
referenceQuery -inr $sel[0];
}}}
{{{isTrue}}} is a command that you can use to query various condition and events in Maya.
What are those events and conditions?
The {{{scriptJob}}} command can be used to find them:
{{{
help -doc scriptJob;
}}}
Or you can have the events and conditions listed:
{{{
print `scriptJob -listEvents`;
print `scriptJob -listConditions`;
}}}
So for example, if you wanted to know if Maya was currently playing back animation:
{{{
int $playback = `isTrue playingBack`;
}}}
Or is something picked:
{{{
int $selected = `isTrue SomethingSelected`;
}}}
There are many others. Handing if while scripting, you need to have your script detect for a certain condition.
{{{
global proc int isKeyed(string $obj)
{
string $connections[] = `listConnections -s 1 -d 0 $obj`;
for($i=0;$i<size($connections);$i++)
{
string $type = `objectType($connections[$i])`;
if(`match "^animCurve" $type` == "animCurve")
return 1;
}
return 0;
}
string $sel[] = `ls –l –sl`;
int $keyed = isKeyed($sel[0]);
}}}
{{{
float $keys[] = `keyframe -q -sl -vc`;
}}}
changing the "-vc" with other flags will return different values
Constraints (seemingly all of them) have a {{{-tl}}} (target list) flag that can be used to query a list of targets, and a {{{-wal}}} flag that can be used to query the coresponding list of 'weight attributes' on the constraint itself:
Presuming it's a {{{parentConstraint}}}
{{{
// mel
string $targetObjs[] = `parentConstraint -q -tl $constraintName`;
string $weightAttrs[] = `parentConstraint -q -wal $constraintName`;
}}}
Armed with this knowledge, I decided to write a Python function that would capture this data easily for the user. Since the mel command to deal with the a given constraint conveniently matches the objectType of the given node, it allows me to write one piece of code that can work on any constraint type.
{{{
# Python code:
import maya.cmds as mc
def getConstraintTargetData(constName):
"""
Based on the given constraint, will return a list of data for each constraint
weight. Specifically, each list index is a dict, with the keys "target"
(target object) and "attribute" (corresponding attribute weight name on the
constraint)
"""
constType = mc.objectType(constName)
constraintData = []
targetList = eval('mc.%s("%s", query=True, targetList=True)'%(constType, constName))
weightList = eval('mc.%s("%s", query=True, weightAliasList=True)'%(constType, constName))
for i in range(len(targetList)):
constraintData.append({"target":targetList[i], "attribute":weightList[i]})
return constraintData
}}}
To see it in action, parent constrain pSphere1 to pCube1 and pTorus1:
{{{
targetData = getConstraintTargetData("pSphere1_parentConstraint1")
print "Number of targets: %s"%len(targetData)
for i,e in enumerate(targetData):
print "Index %s: %s"%(i, e)
print "Weight attribute for index 0: %s"%targetData[0]["attribute"]
print "Target object for index 1: %s"%targetData[1]["target"]
}}}
prints:
{{{
Number of targets: 2
Index 0: {'attribute': u'pTorus1W0', 'target': u'pTorus1'}
Index 1: {'attribute': u'pCube1W1', 'target': u'pCube1'}
Weight attribute for index 0: pTorus1W0
Target object for index 1: pCube1
}}}
Pick a vertex, and run:
{{{
// get just the object name from selection:
string $selObj[] = `ls -o -sl`;
// get the vert name
string $selPt[] = `ls -sl`;
// find our skindCluster:
string $sc[] = `listConnections -s 1 -d 0 -type "skinCluster" $selObj[0]`;
// get transforms (binding joints and influence object)
string $skinInfluences[]=`skinPercent -q -t $sc[0] $selPt[0]`;
// get skin weight of selected vertex
float $sknVals[]=`skinPercent -q -v $sc[0] $selPt[0]`;
// only print values that are greater than 0. If you want to know
// influence names even if they are zero, remove the 'if' line:
for($i=0;$i<size($skinInfluences);$i++)
{
if($sknVals[$i] > 0) // print $sknVals[$i]
print ($skinTrans[$i] + "\t\t" + $sknVals[$i] + "\n");
}
}}}
*the index of $skinTrans and $skinVals is 1-1 matched
Via the '{{{Deform-> Paint Blend Shape Weights Tool}}}', you can paint the influence that a blend shape has on a mesh. The tool lets you save the weights out as a map, and read the weights back in. But where do the weights actually get stored on the object?
On the blendShape node's '{{{.inputTarget.inputTargetGroup.targetWeight}}}' att:
{{{
getAttr blendShape1.it[0].itg[0].tw;
// Result: 1 1 1 1 1 1 1 0.5 0.5 0.50.5 0.5 etc....
}}}
There is a 1:1 relationship beween these values, and the verts on the mesh.
I figured this out by looking in the .ma file itself. I couldn't find anything //in// Maya that would explain what\where these values were. You could edit them in the Component Editor, but it wouldn't explain what it was doing. And when you paint the actual weights, or load a weight file, neither does it tell you what it's doing then either.
I gave this info to someone else, and they came back with this description:
*inputTarget[0]. >> Maybe your source mesh?
*inputTargetGroup[1]. >> your target by index
*targetWeights[0] >> the actual vert index to modify
*0; > the value to set
With the 'Paint Skin Weights Tool' visible:
{{{
# Python code
import maya.cmds as mc
print mc.artAttrSkinPaintCtx('artAttrSkinContext', query=True, influence=True)
}}}
{{{
string $list[]= `listAttr -m "<name of blendshapes>.w"`;
}}}
*thanks to a post from David Coleman
{{{colorAtPoint}}}
*Example given a "ramp" node:
{{{
colorAtPoint -o RGB -u .5 -v .5 ramp1;
}}}
I was told that if your UV's are tiling, UV 0 and 1 can return the same values. Querying .99999 is a safer bet.
Should also be noted that this only samples the color for the given node, not the final combination of multiple nodes (if they exist).
Haven't yet found an //easy// way to grab the final color of a given uv point based on a complex shading network.
----
Also see:
*[[How can I find the color of a render node at a given vertex?]]
{{{
string $paneConfiguration = `paneLayout -q -configuration $gMainPane`;
string $paneKids[] = `paneLayout -q -ca $gMainPane`;
}}}
{{{about}}}
*Example, query the current date or time:
{{{
string $date = `about -cd`;
string $time = `about -ct`;
}}}
You don't want to query the //current// enumAttr value, you want to query //all// their names:
----
Tip from my buddy Te:
{{{
string $enumAttrs[] = `attributeQuery -node "myNode" -listEnum "myEnumAttr"`;
}}}
First off, you can use the {{{whatIs}}} command, and I have a tiddler specifically authored aroud that here: [[How can I have a script return where it lives on disk?]]
However, that method can run into problems if you're currently authoring the script, since Maya can think it's an interactively entered procedure possibly, and not give you the path back.
Another solution is to actually use the {{{file}}} command. Presuming you save a script in your script path, you can query its location this way:
{{{
string $path = `file -query -location "testScript.mel"`;
// Result: C:/temp/./testScript.mel //
$scriptLocation = `substitute "/./" $scriptLocation "/"`;
// Result: C:/temp/testScript.mel //
}}}
With a little formatting via the {{{substitute}}} command, you get the full path back.
When you reference a scene, Maya creates a node of type {{{reference}}} to help manage the changes. Most {{{reference}}} nodes are the defined namespace plus "RN". So if you reference someting in with the "bob" namespace, the {{{reference}}} node created would be called {{{bobRN}}}.
Presuming you know that reference node name (easily gathered by the '{{{ls -type "reference";}}}' command), how can you query the namespace that the referenced file currently resides in?
{{{
string $refNode = "bobRN";
string $file = `getAttr ($refNode +".fileNames[0]") `;
string $ns = `file -q -rpr $file`;
// bob
}}}
After a file has been referenced into a scene, it's possible that it's namespace could be different from what was specified due to namespace clashing. How to query what the namespace actually is?
{{{
# Python code
import maya.cmds as mc
namespace = mc.file('c:/path/to/my/file.ma', query=True, namespace=True)
}}}
{{{
polyNormalPerVertex -q -xyz pSphere1.vtx[41];
}}}
Note, this will shoot back multiple sets 3 floats for each "vertexNormal", one for each tri that the vert shares.
Also see: [[How can I set the normal of a vertex?]]
{{{
string $name = `setParent -q myElementName`;
}}}
Thanks to Eric Vignola ;-)
As you edit a ramp node, you can add and remove "colors" from it. You may at some point want to be able to query where on the ramp those colors are. Here is a quick way to do it:
{{{
string $ramp = "ramp1";
string $mults[] = `listAttr -m ($ramp + ".colorEntryList")`;
for($i=0;$i<size($mults);$i++)
{
if(`match "\\.position$" $mults[$i]` == ".position")
{
float $pos = `getAttr ($ramp + "." +$mults[$i])`;
print ($ramp + "." + $mults[$i] + " is: " + $pos + "\n");
}
}
// result:
ramp1.colorEntryList[0].position is: 0
ramp1.colorEntryList[1].position is: 0.5
ramp1.colorEntryList[2].position is: 1
}}}
*Maya 5:
{{{
addAttr -q -en object.attr;
}}}
*Maya 6 and later:
{{{
getAttr -as object.attr\\ (as string)
}}}
Based on [[this post|http://www.rtrowbridge.com/blog/2009/05/render-selected-meshes-region/]] by Ryan Trowbridge, I found some code I slightly changed to spit out the width and height in pixels of the active view. The rest of [[the module he wrote|http://www.rtrowbridge.com/rtRenderSelectedMeshesRegion.zip]] to render selected meshes is pretty sweet too.
{{{
import maya.OpenMaya as om
import maya.OpenMayaUI as omui
def getViewportResolution():
activeView = omui.M3dView.active3dView()
xPtrInit = om.MScriptUtil()
yPtrInit = om.MScriptUtil()
widthPtrInit = om.MScriptUtil()
heightPtrInit = om.MScriptUtil()
xPtr = xPtrInit.asUintPtr()
yPtr = yPtrInit.asUintPtr()
widthPtr = widthPtrInit.asUintPtr()
heightPtr = heightPtrInit.asUintPtr()
activeView.viewport(xPtr, yPtr, widthPtr, heightPtr)
viewWidth = widthPtrInit.getUint( widthPtr )
viewHeight = heightPtrInit.getUint( heightPtr )
return (viewWidth, viewHeight)
widthHeight = getViewportResolution()
print widthHeight
#(477, 269)
}}}
{{{
getAttr -type "persp.tx";
// doubleLinear //
getAttr -type "persp.rx";
// doubleAngle //
getAttr -type "persp.v";
// bool //
getAttr -type "persp.filmRollOrder";
// enum //
}}}
{{{
string $selAttr[] = `channelBox -q -sma "mainChannelBox"`;
}}}
*Note, you can query the inputs and outputs as well, with different arugments.
{{{file}}}
*Example: Based on some given filename $filename:
{{{
file -q -rpl $filename;
}}}
{{{
# Python code
import maya.cmds as mc
import maya.mel as mm
# cast mel variable to Python:
gPlayBackSlider = mm.eval("$g = $gPlayBackSlider")
# Is anything highlighted?
rangeHighlighted = mc.timeControl(gPlayBackSlider, query=True, rangeVisible=True)
# What is the highlight range?
highlightedRange = mc.timeControl(gPlayBackSlider, query=True, rangeArray=True)
print rangeHighlighted, highlightedRange
# 1 [9.0, 17.0]
}}}
Say the user has picked some anim curves in the Graph Editor... what are they?
{{{
string $pickedKeys[] = `keyframe -query -selected -name`;
// Result: pCube2_rotateZ
}}}
{{{getModifiers}}}
{{{
int $mods = `getModifiers`;
// note that it returns back "bit" values, so Shift: 1, CapsLock: 2, Ctrl: 4, and Alt: 8.
}}}
{{{displayRGBColor}}}
Basically, the stuff found in 'Window -> Settings/Preferences -> Color Settings'
For example, to print a list of every possible UI element and their current values:
{{{
displayRGBColor -list;
}}}
prints:
{{{
template 0.47 0.47 0.47
preSelectHilite 1 0 0
userDefined1 0.63 0.41391 0.189
userDefined2 0.62118 0.63 0.189
userDefined3 0.4095 0.63 0.189
// etc...
}}}
To set the background color to black:
{{{
displayRGBColor background 0.0 0.0 0.0;
}}}
It also lets you create new color presets as well.
The {{{upAxis}}} command.
{{{
# Python code
import maya.cmds as mc
up = mc.upAxis(query=True, axis=True)
# y
}}}
{{{MAYA_SCRIPT_PATH}}} is the environment variable in question.
Additions are //usually// stored and updated in the {{{Maya.env}}} file like:
{{{
MAYA_SCRIPT_PATH = c:\myScripts;
}}}
The {{{Maya.env}}} is usually stored per maya version:
{{{
C:\Documents and Settings\<userName>\My Documents\maya\<version>\Maya.env
}}}
The {{{Maya.env}}} file is parsed when Maya launches, so any modifications to it require Maya to be restarted to be seen.
To add a new path to the current environment variable, //after// Maya has launched:
{{{
// Define our new path to add:
string $newPath = "c:/temp";
// Get our path, and split into list:
string $msp = `getenv "MAYA_SCRIPT_PATH"`;
string $pathList[];
tokenize $msp ";" $pathList;
// For comparison purposes, make all slashes forward, and add
// a slash to the end
$newPath = fromNativePath($newPath);
if(`match "/$" $newPath` != "/")
$newPath = ($newPath + "/");
int $found = 0;
// Loop through all the paths, seeing if our path already exists:
for($i=0;$i<size($pathList);$i++)
{
// Same comparison check from above:
string $compare = fromNativePath($pathList[$i]);
if(`match "/$" $compare` != "/")
$compare = ($compare + "/");
if(tolower($newPath) == tolower($compare))
{
$found = 1;
break;
}
}
// If no new path was found, add:
if(!$found)
{
// The *whole* path needs to be re-added:
// putenv seems to replace, not append.
$pathList[size($pathList)] = $newPath;
string $fullPath = stringArrayToString($pathList, ";");
putenv "MAYA_SCRIPT_PATH" $fullPath;
print ("Added to MAYA_SCRIPT_PATH '" + $newPath + "'\n");
}
else
print ("path '" + $newPath + "' is already in the MAYA_SCRIPT_PATH\n");
}}}
To just query what dirs are currently in the path:
{{{
string $msp = `getenv "MAYA_SCRIPT_PATH"`;
string $pathList[];
tokenize $msp ";" $pathList;
print $pathList;
}}}
*Example: Make an evn var with the path to the file, then reference it in:
{{{
// here we build the var in Maya, but it could be declared in Windows first:
putenv "MAYAFILES" "c:/temp";
file -r -type "mayaAscii" -gl -namespace "TEST" -options "v=0" "$MAYAFILES/myFile.ma";
}}}
*From now on, the scene will look to $MAYAFILES as the directory for the file. If at any point you need to move the file, move it to the new location, and then update the $MAYAFILES enviroment variable. The Maya scene file will automatically look to the new location.
While this isn't mel code, it can be entirely recreated in mel:
There are some nodes in Maya, notably the lightLinker node, that can't be deleted from a scene file. It's "special". This is no real issue until, for whatever reason, they start to breed and fill your scene in the background. I've seen scenes with hundreds of these nodes taking up unnecessary space. How to get rid of them? A fellow by the name of Ted Charlton use the below concepts in a script. And they should work for any other kind of read-only node:
*Select the bad nodes, and "export them as a reference" from your scene as a new file:
{{{
File->Export Selected->Options->"Keep only a Reference" = yes.
}}}
*Then, remove that reference from your scene:File->Reference Editor...->
{{{
Select Reference File->Reference->Remove Reference
}}}
If you reference the same file into your scene multiple times, Maya will put it into a unique namespace each time (incrementing it with a #), plus put a file incrementor # at the end of its filepath in the Reference Editor. Presuming this is your file:
{{{
c:/temp/foo.ma
}}}
and you referenced it in twice, you'd end up with these namespace \ filepath \ 'reference node name' combos:
{{{
foo | c:/temp/foo.ma | fooRN
foo1 | c:/temp/foo.ma{1} | fooRN1
}}}
(that's 'foo-one', not fool, like a court jester)
To remove a reference, based on those conditions, we jump though a few hoops: The user supplies a namespace name. That's turned into a {{{reference}}} node name (which by default is name of namespace + 'RN' + incrementor), from which the file path of that reference can be queried, and then removed.
In this example, we remove the reference in namespace {{{foo1}}}:
{{{
string $namespace = "foo1"; // foo + 'one'
string $increment = `match "[0-9]*$" $namespace`;
string $refNode = substitute(($increment+"$"), $namespace, ("RN"+$increment));
string $refFile = `referenceQuery -f $refNode`;
file -rr $refFile;
}}}
Making custom HUD's (heads up display) can be cool, but Maya doesn't seem to provide an easy way to kill them in the UI. For example, you get someone else's scene file, full of HUD's you don't want to see. What to do?
{{{
// get a list of all the hud's in the scene:
string $hud[] = `headsUpDisplay -lh`;
// this is a list of all the default HUD's, retrieved from an empty scene with no custom HUD's (using the above command):
string $defaultList = "HUDObjDetBackfaces HUDObjDetSmoothness HUDObjDetInstance HUDObjDetDispLayer HUDObjDetDistFromCam HUDObjDetNumSelObjs HUDPolyCountVerts HUDPolyCountEdges HUDPolyCountFaces HUDPolyCountTriangles HUDPolyCountUVs HUDSubdLevel HUDSubdMode HUDCameraNames HUDHQCameraNames HUDFrameRate HUDIKSolverState HUDCurrentCharacter HUDPlaybackSpeed HUDViewAxis HUDFrameCount";
// convert that string to an array:
string $defaultHud[];
tokenize $defaultList " " $defaultHud;
// and for each item in the array of all HUD's in the scene, see if there is a default match. If not, kill:
for($i=0;$i<size($hud);$i++) // print $defaultHud[$i]
{
if(`stringArrayCount $hud[$i] $defaultHud` == 0)
{
headsUpDisplay -rem $hud[$i];
print ("deleted non-default HUD: " + $hud[$i] + "\n");
}
}
}}}
When you skin mesh to joints, there is an an option to 'colorize skeleton'. But what if you want to turn this //off// after the initial bind? Or if you want to change the individual colors after the fact?
In the UI, the colors are controlled by the 'Display->Wireframe Color...' UI. In that UI, you can bonk the "Default" button to reset the color.
All this really does is call to the {{{color}}} command:
{{{
color;
}}}
Which with no args sets the default color on the selected objects.
{{{strip}}}
*Example:
{{{
string $word = "asdf\r\n";
int $size = size($word) \\ result: 6
$word = strip($word);
$size = size($word); \\ result: 4
}}}
When it comes to rigging and tools, duplicate node names can be a real pain. How can you easily rename all the node names in the scene that clash?
{{{
# Python code
import re
import maya.cmds as mc
newNames = []
fail = []
# Get two lists of all names in the scene that are duplicates.
# If there are any dupes, they'll have a pipe in their name:
dupes = [name for name in mc.ls() if "|" in name]
# Get another list with just the leaf names:
dupesLeaf = [name.split("|")[-1] for name in mc.ls() if "|" in name]
for i,e in enumerate(dupes):
# refresh our list of clashing names:
leafCompare = [name.split("|")[-1] for name in mc.ls() if "|" in name]
# if our current leaf node is in our clashing list, rename:
if dupesLeaf[i] in leafCompare:
# If nodes are referenced (etc) they can't be renamed
try:
endNum = re.findall('[0-9]+$', dupes[i])
try:
name = dupesLeaf[i][:-len(endNum[0])]+"#"
except IndexError:
name = dupesLeaf[i]+"#"
newName = mc.rename(dupes[i], name)
newNames.append([dupes[i], newName])
except Exception, e:
# Tell user the the node that was tried to be renamed, and why it failed.
fail.append([dupes[i], str(e).strip()])
if len(fail):
print "Some nodes couldn't be renamed:"
for f in fail:
print "\t"+" : ".join(f)
if len(newNames):
print "Nodes renamed:"
for n in newNames:
print "\t"+" : ".join(n)
print "Rename complete: Renamed %s node(s), and had %s failures, see Script Editor for deatails." %(len(newNames), len(fail)),
}}}
To just print a list of all the dupe node names in the scene:
{{{
# Python code
import maya.cmds as mc
dupeNodes = [name for name in mc.ls() if "|" in name]
for d in dupeNodes:
print d
}}}
See the command:
{{{
glRender
}}}
For example, render the current sequence as defined by the Hardware Render Globals (presuming the Hardware Render Buffer Window is open):
{{{
glRender -renderSequence hardwareRenderView;
}}}
'{{{hardwareRenderView}}}' is the default name of the {{{glRenderEditor}}} used to display the hardware view. Even though {{{glRenderEditor}}} appears to be a type of UI 'panel\editor' (like say, {{{modelEditor}}}), in the mel docs it's categorized under 'Rendering'.
----
Also see:
*[[How can I open the Hardware Render Buffer Window?]]
*[[How can I change my Render Settings?]]
{{{parent}}}
{{{
// mel
parent -s -r "shapeNode" "transformNode";
}}}
Here's a function in Python that will do it, and clean up the leftover transform nodes:
{{{
# Python code
import maya.cmds as mc
def parentShapes():
"""
Select nodes in order: The last node picked will have all other shape nodes
parented to it.
Note: Should freeze transforms on all nodes before execution!
"""
sel = mc.ls(selection=True)
if len(sel) < 2:
raise Exception("Please select at least two objects with shape nodes")
source = sel[-1]
targets = sel[0:-1]
for t in targets:
shapes = mc.listRelatives(t, shapes=True)
if shapes is not None:
for s in shapes:
mc.parent(s, source, shape=True, relative=True)
mc.delete(t)
mc.select(source)
}}}
----
Also see:
[[How can I parent the instance of a shape node from one object to another?]]
For example, you've just created a sphere, and want to redo that action:
{{{
repeatLast;
}}}
Would then execute:
{{{
CreatePolygonSphere;
polySphere -r 1 -sx 20 -sy 20 -ax 0 1 0 -tx 2 -ch 1;
// Result: pSphere1 polySphere1 //
}}}
*Note, this appears to be an undocumented command, but can be accessed via:
{{{
help repeatLast;
}}}
Also see:
*[[How do I query what the last command entered was?]]
{{{
// this is the name of the referenced scene you're replacing:
string $refScene = "c:/temp/foo.ma";
// find the reference node for this file:
string $refNode = `file -q -rfn $refScene`;
// name of the scene to replace it with
string $newName = "c:/temp/goo.ma";
file -loadReference $refNode $newName;
}}}
If you're wondering, this was deduced by picking apart this script:
{{{
../MayaXXXX/scripts/others/referenceEditorPanel.mel
}}}
In particular, the procs {{{referenceEdRenameCB}}} and {{{findRefNodeToReplace}}}
* Maya has the built-in {{{substitute}}} command, but it only substitutes the //first// instance it encounters. Maya also has the {{{substituteAllString}}} //script//, but it never works like I'd expect. So because of that, we loop over the {{{substitute}}} command until our source no longer contains any of our matching elements.
* For example, in our source data have all matching instances of "Happy" replaced with "Sad":
{{{
string $source= "Happy Happy Joy Joy";
string $match = "Happy";
string $replace = "Sad";
while(`match $match $source` == $match)
$source = `substitute $match $source $replace`;
print ($source + "\n");
Sad Sad Joy Joy
}}}
Use Maya's {{{imconvert}}} executable outside of Maya, or from a system call:
*Example: Convert a texture to a new resolution of 800x600 at the commandline
{{{
imconvert -geometry 800x600 -filter Quadratic in.jpg out.jpg
}}}
{{{selectedNodes}}}
or
{{{ls -sl}}}
I've never actually seen anyone use {{{selectedNodes}}}
{{{rotate}}} \ {{{xform}}}
*Example: transformA has rotate order xyz, transformB has rotate order zyx:
{{{
float $rotationVals[] = `xform -q -ws -ro transformA`;
xform -p 1 -roo xyz transformB;
rotate -ws $rotationVals[0] $rotationVals[1] $rotationVals[2] transformB;
xform -p 1 -roo zyx transformB;
}}}
{{{rotate}}} \ {{{xform}}}
*Example: Find the worldspace rotation of one object, and assign it to another. Either of these objects can be children of a hierarchy:
{{{
float $rotationVals[] = `xform -q -ws -ro transformA`;
rotate -ws $rotationVals[0] $rotationVals[1] $rotationVals[2] transformB;
}}}
*This will open in a new command prompt:
{{{
>> maya -prompt
}}}
*This will run in the same command prompt as it was executed in:
{{{
>> mayaBatch -prompt
}}}
{{{system}}}
*Example, open notepad:
{{{
system ("start notepad");
}}}
*You don't have to use "start", but if you don't, as long as the system function is running, you'll be locked out of using Maya.
Had an animator approach me with a problem: He wanted a tool that would look at ALL the keyframes in the scene, and based on a user-defined start and end range, scale all the keys in the range, and offset all the keys after the range based on the scale value.
The code example does that. It will also insert keys (maintaining curve shape) on all objects at the oldRange frames, to do proper scaling
{{{
#Python code
import maya.cmds as mc
# define the old and new range:
oldRange = [5, 10]
newRange = [5, 20]
# figure out how much to offset keys after the oldRange
offset = newRange[1] - oldRange[1]
# stamp down keys based on oldRange
curves = mc.ls(type="animCurve")
mc.select(curves, replace=True)
mc.setKeyframe(insert=True, time=oldRange)
# move all keys after the end of the oldRange
mc.selectKey(time=(str(oldRange[1]+1)+':',))
mc.keyframe(edit=True, relative=True, timeChange=offset)
# now scale all keys in the oldRange to match the newRange
mc.select(curves, replace=True)
mc.selectKey(time=(str(oldRange[0])+":"+str(oldRange[1]),))
mc.scaleKey(curves,
time=(oldRange[0],oldRange[1]),
newStartTime=newRange[0],
newEndTime=newRange[1])
}}}
{{{
import maya.cmds as mc
def getInfluences(sel=None):
"""
Return influences for the selected mesh. If mesh names are passed in instead,
use them instead of selection.
"""
if not sel:
sel = mc.ls(selection=True)
else:
if type(sel) is not type ([]):
sel = [sel]
if not len(sel):
raise Exception("Please select a node, or pass in a list of nodes")
mesh = mc.listRelatives(sel, shapes=True, noIntermediate=True)
skinClusters = mc.listConnections(mesh, source=True, destination=False, type='skinCluster')
influences = []
if skinClusters:
for sc in skinClusters:
connections = mc.listConnections('%s.matrix'%sc)
if connections:
influences.extend(connections)
influences = sorted(list(set(influences)))
return influences
}}}
{{{
infs = getInfluences()
if len(infs):
for i in infs:
print i
mc.select(infs)
}}}
In Maya's prefs, under Settings -> Timeline, there are options to set the 'Playback Speed' and 'Max Playback Speed'. My maya had a habit of forgetting this stuff. I wanted it to play every frame of the animation, but lock it to 30fps.
There are two steps to this: Update {{{optionVars}}} so the settings will persist between Maya sessions, and updating the values for the current Maya session. I figured out the optionVars by hunting around my {{{userPrefs.mel}}} file.
I added the below code to my {{{userSetup.mel}}} file:
{{{
optionVar -fv "timeSliderPlaySpeed" 0;
optionVar -fv "timeSliderMaxPlaySpeed" 1;
playbackOptions -playbackSpeed 0;
playbackOptions -maxPlaybackSpeed 1;
}}}
As you can see, you're not setting the actual framerate values: The values are indexed, so you need to choose the correct index for the option that you're after. The Prefs UI drop-downs show you the options you can choose from.
{{{
windowPref -w # -h # windowName;
}}}
*Note: This should be entered after the "window" command when making the UI.
Presuming that the joints the mesh are bound to have nothing connected to their transform values, this code should work:
{{{
string $dagPose[] = `ls -type "dagPose"`;
for($i=0;$i<size($dagPose);$i++){
select -r $dagPose[$i];
dagPose -restore;
}
}}}
----
While I haven't tested it yet, I saw this post to help rest the bind pose after changes have been made to the asset:
1) Select the root joint of the skel, and save out your "bind pose":
{{{
select -hierarchy;
dagPose -save -selection -name mypose;
}}}
2) Later, if any changes have happened to the asset, you can go back to that bind pose:
{{{
dagPose -restore mypose;
}}}
----
Also see:
*[[How can I set the selected meshes to their bind pose?]]
The mel command is:
{{{
polyNormalPerVertex
}}}
However, it only works on one vert at a time. Furthermore, if you call to it a lot (if working on a thousand normals, you'll call to it a thousand times), it can really slow down Maya, and eat up all your memory (based on some tests, nearly a meg per call, that's crazy!).
Here's a way you can query the normals on the selected items, and then set them again based on the stored values. It works by directly accessing the normal attributes on the polygonal mesh, rather than using the {{{polyNormalPerVertex}}} command at all.
{{{
// pick a polygonal object, or pick some components. Then define them:
string $sel[] = `ls -sl`;
// convert everything to vertex-faces
select -r `polyListComponentConversion "-tvf" $sel`;
string $vertFaces[] = `ls -fl -sl`;
// define our list that we'll later fill with all of our normal info:
string $cmdList[]; clear $cmdList;
// for each vertex face, store a command that will reapply it's normal values later.
for($i=0;$i<size($vertFaces);$i++)
{
// find the shape node of this object:
string $buffer[];
tokenize $vertFaces[$i] ".[]" $buffer;
string $shape = $buffer[0];
// get the face and vert ID's
int $face = $buffer[size($buffer)-1];
int $vert = $buffer[size($buffer)-2];
// Get the normal value for this particualr vertex-face. We actually use polyNormalPerVertex here
// because, if we query the vert normal with getAttr (using a syntax similar to the below $cmdLis)
// it always returns back these values for all three xyz vert face normals: 100000002004087730000
// The mesh docs say that these attrs have a default value of 1e20, hmm.....
// However, once they've been set by the user using the setAttr command, you CAN use getAttr on
// them to pull the correct vals. So weird!
vector $normalVec = `polyNormalPerVertex -q -xyz $vertFaces[$i]`;
// and build the command to later re-set these values:
$cmdList[$i] = ("setAttr " + $shape
+ ".npvx.vn[" + $vert + "].vfnl[" + $face +"].fnxy "
+ ($normalVec.x) + " " + ($normalVec.y) + " " + ($normalVec.z) +";");
}
}}}
Print our findings:
{{{
print $cmdList;
}}}
Now, go to your objects, and screw up all their normals.
Then re-set all the normals to the stored values:
{{{
for($i=0;$i<size($cmdList);$i++)
eval($cmdList[$i]);
}}}
After you run this, the normals should be set back again. Minimal memory usage, and much faster than calling to {{{polyNormalPerVertex}}} mutiple times.
{{{
# Python code
import maya.cmds as mc
animCurves = mc.ls(type='animCurve')
first = mc.findKeyframe(animCurves, which='first')
last = mc.findKeyframe(animCurves, which='last')
mc.playbackOptions(min=first, max=last)
}}}
{{{
selectKey -time ":";
float $allKeys[] = sort(`keyframe -q -sl`);
playbackOptions -min $allKeys[0] -max $allKeys[(size($allKeys) -1)];
}}}
----
Also see:
*[[How can I find the min and max keyframe values for all objects in the scene?]]
*[[How can I find the min and max keyframe range for the provided objects?]]
Around Maya 2008 (maybe a version or so before?) when you smooth bound a mesh, there was a new option called "Allow Multiple bind poses". This is supposed to bring some functionality benefits, but it also breaks some of Maya's own tools: Notably, if a skeleton is bound to more than one mesh with this option, it will create a bind pose {{{(dagPose}}} node) for each mesh. But Maya's own UI command {{{Skin -> Go To Bind Pose}}} is expecting only a //single// bind pose... and it will fail.
Furthermore, Maya expects a mesh to be picked when using its tool. But often I want to pick a joint instead. The below code addresses both: It will track down the {{{dagPose}}} node(s) for each selected mesh\joint, and 'restore' them to their bind pose:
{{{
# Python code
import maya.cmds as mc
# Get a list of everything selected
# It is expecting bound mesh\joints or a combination of the two...
sel = mc.ls(selection=True)
dagPose = []
for s in sel:
if mc.objectType(s) == 'joint':
# If joints are picked:
poses = list(set(mc.listConnections(s, source=False, destination=True, type='dagPose')))
dagPose.extend(poses)
else:
# If mesh is picked, find their shape nodes:
shapes = mc.listRelatives(sel, shapes=True, noIntermediate=True)
# get all incoming skinClusters connected to the shapes
skinCluster = mc.listConnections(shapes, source=True, destination=False, type="skinCluster")
# get all incoming dagPoses connected to the skinClusters. Remove duplicates via 'set'
poses = set(mc.listConnections(skinCluster , source=True, destination=False, type="dagPose"))
dagPose.extend(poses)
# remove any dupes:
dagPose = list(set(dagPose))
# 'restore' each dagPose:
for d in dagPose:
mc.dagPose(d, restore=True)
print "Bind Pose restored on these nodes: " + ", ".join(sorted(dagPose))
}}}
There's no error detection here, so if you don't have anything picked it will error, or if there are unbound meshes it will error, but it's a start ;)
----
Also see:
*[[How can I set all meshs in the scene to their bind pose?]]
In mel, when copying or pasting all keframes, the syntax is pretty simple (replace {{{...}}} with the rest of the command code):
{{{
cutKey -time ":" ... ;
}}}
And even the Python command docs say this:
>{{{-time ":"}}} is a short form to specify all keys.
But that's not how the Python formatting actually works.
Per Maya Docs -> User Guide -> General -> Python -> Using Python -> Ranges ->
You need to specify the time settings in this format:
{{{
mc.cutKey(time=(":",), ...)
}}}
I first noticed this in Maya 2008. I'm not yet sure if the below solution fixes it for 2008, I'm told it does for 2009. If you have an Nvidia GeForce card, this may do the trick.
Set this in your {{{Maya.env}}} file
{{{
MAYA_GEFORCE_SKIP_OVERLAY = 1
}}}
Found the info on this page (lots of other maya.env stuff in there):
http://www.toxik.sk/?p=22
{{{optionVar}}}
*Example:
{{{
optionVar -iv "myVariable" 4;
// Result: 4 //
}}}
** After Maya has been restarted, you can query that {{{optionVar}}}. This is how Maya does it's bulk of preset saving.
There is the {{{stringToStringArray}}} command, but this only works if you have a 'separation character': Meaning, it works good on string like this:
{{{
string $line = "my words";
string $array[] = `stringToStringArray $line " "`;
print $array;
// my
// words
}}}
However, {{{stringToStringArray}}} //won't// work if there is no separation character. So how can you convert a string to a string array without using that command?
{{{
string $line = "asdf";
string $array[];
for($i=0;$i<size($matcher);$i++)
$array[$i] = `substring $matcher ($i+1) ($i+1)`;
print $array;
// a
// s
// d
// f
}}}
{{{ctxEditMode}}} or {{{EnterEditMode}}}
*Notes: {{{ctxEditMode}}} is the actual command. {{{EnterEditMode}}} is actually a runTimeCommand that simply calls {{{ctxEditMode}}}. They both act as toggles.
{{{setFocus}}}
*Note: Maya's four main modelPanels are called "modelPanel1" - 4.
Simple example of reading in a text file to Maya, and turning its results into a string array. There is no error checking here: You'd want to first confirm that the file actually exists on disk before you try and open it
In the process it will strip off any return chars.
{{{
global proc string[] returnFileAsArray(string $filePath){
int $fid = `fopen $filePath "r"`;
string $lines[] = {};
string $line = `fgetline $fid`;
$lines[0] = strip($line);
while ( size($line) > 0 ){
$line = `fgetline $fid`;
$lines[size($lines)] = strip($line);
}
fclose $fid;
return $lines;
}
}}}
Also see:
*[[How can I write text to my own file?]]
The {{{ikSystem}}} node
*Useful for moving joints around without the IK getting in the way during rig reproportioning.
{{{
setAttr "ikSystem.globalSolve" 0;
}}}
{{{help}}}
I always like having them on, and I find that some code actually turns them off.
{{{
help -popupMode true;
help -popupDisplayTime 10;
}}}
You can ADD tooltips to (nearly) any UI element with their {{{-annotation}}} ({{{-ann}}}) flag.
Here's an example of how you can embed this into a UI for a user, so it's obvious that they can turn them on:
{{{
window -mb 1 -mbv 1;
menu -l "Turn on tooltips";
menuItem -l "Turn On" -c "help -popupMode true; help -popupDisplayTime 10; print \"Tooltips turned on\"";
columnLayout -adj 1;
button -l "Mouse over me" -ann "This is a tooltip";
showWindow;
}}}
In more recent versions of Maya, they've given joints 'displayable labels'. Maya has a bunch of preset labels, but I tend to prefer my own naming conventions, based on the names of the actual joints. The below code will turn on the joint lables for all joints in the selected hierarchy, and set the label name to the joint name:
{{{
# python code
import maya.cmds as mc
def enableJointNames(onOff=1):
root = mc.ls(selection=True)[0]
hier = mc.listRelatives(root, allDescendents=True, type='joint')
hier.append(root)
for joint in hier:
mc.setAttr(joint+".drawLabel", onOff)
mc.setAttr(joint+".type", 18)
mc.setAttr(joint+".otherType", joint, type='string')
# to turn on labels, pick the root joint and execute:
enableJointNames()
# to turn off labels, pick the root joint and execute:
enableJointNames(0)
}}}
There is a Maya startup script located here:
{{{
C:/Program Files/Autodesk/Maya<version>/scripts/startup/initHUDScripts.mel
}}}
That is filled with procedures to do this. Check the script for the particulars, but here are some that can be used:
{{{
setObjectDetailsVisibility(int $visibility)
setPolyCountVisibility(int $visibility)
setSubdDetailsVisibility(int $visibility)
setCameraNamesVisibility(int $visibility)
setFrameRateVisibility(int $visibility)
setCurrentFrameVisibility(int $visibility)
setViewAxisVisibility(int $visibility)
// this list goes on...
}}}
Example: Embed a link to this wiki into Maya's Help menu:
{{{
menuItem
-p $gMainHelpMenu
-l "MEL Wiki" -ann "Your Maya Mel Resource on the Web "
-ecr false -c "showHelp -a \"http://mayamel.tiddlyspot.com/\"";
}}}
All of Maya's main menu's have their names defined in global string arrays.
Also see:
*[[How can I find the names of Maya's main UI menus?]]
Condensed from the Maya docs:
Method #1: ''userSetup.py''
*Create a {{{userSetup.py}}} module here: (Windows)
{{{
<drive>:\Documents and Settings\<username>\My Documents\maya\<Version>\scripts\userSetup.py
}}}
*Append to {{{sys.path}}} in your {{{userSetup.py}}}:
{{{
import sys
sys.path.append('c:/myPy/maya/scripts')
}}}
Method #2: ''PYTHONPATH''
*Set {{{PYTHONPATH}}} in your {{{Maya.env}}} file, OR as a Windows environment variable:
{{{
PYTHONPATH = c:/myPy/maya/scripts;
}}}
**I've heard that if you have it defined both in your Windows path //and// in the {{{Maya.env}}} file, the Windows one will override the {{{Maya.env}}}, so be aware.
----
Two other important things:
*Make sure your paths //don't// have a slash at the end. This seems to dissuade Python from recognizing what's //in// the path.
*Don't add any python modules to the default Maya script paths that live under {{{..\My Documents\Maya\scripts}}} or {{{..\My Documents\Maya\<version>\scripts}}}. Even though, as an exception, the ''{{{userSetup.py}}}'' module //can// live in there (see above), __no other python module can__. Maya 'just won't see it'. Very... odd...
Also, be sure to check out how Python ''packages'' work. See my notes over on my [[Python Wiki|http://pythonwiki.tiddlyspot.com/#Packages]]
{{{dagMenuProc.mel}}}
*You'll need to hand edit that file.
**In the proc dagMenuProc, {{{string $shortName}}} is the name of the object under curser, which you can then write an "if" statement for allowing you to run other procedures based on that name.
As of this authroing (I'm still on Maya2008) I've been told Maya should be switchign to {{{PyQt}}} for all its UI calls in Maya2010. But in the mean time, you can start using it in Maya2008, and Maya2009. I found this thread here on getting it going, but haven't started with it yet:
http://groups.google.com/group/python_inside_maya/web/using-pyqt-with-maya (you may have to be a member (free) of this Google Group to read it).
Python info:
http://wiki.python.org/moin/PyQt
There is a .pdf that ships with Maya that also goes over the install:
{{{
C:\Program Files\Autodesk\Maya<version>\devkit\other\PyQtScripts\Windows_QT_Installation.pdf
}}}
I pulled these notes from highend3d.com by "picksel"
*Example: Basically, you create an objectFilter node when you execute itemFilter (don't ask why their names are different).
{{{
itemFilter -byName "*lambert*";
}}}
*This creates an {{{objectFilter}}} named {{{objectFilter7}}} or something like that.
*Then you can do:
{{{
lsThroughFilter "objectFilter7";
}}}
*and it should return a long list of all nodes containing 'lambert' anywhere in their names.
*Now, there is another such thing for ATTRIBUTES which is known as an objectAttrFilter? but I still can't get it to work.. it's accessible via the itemFilterAttr? command..
Also see:
*[[Based on an itemFilter, how can I find all the nodes in Maya that match?]]
*[[How can I query all the default itemFilters in Maya?]]
{{{viewPlace}}}
!!!Example
{{{
float $pos[] = `xform -q -ws -t $objName`;
string $panelName = `getPanel -wf`;
string $camName = `modelPanel -q -camera $panelName`;
viewPlace -la $pos[0] $pos[1] $pos[2] $camName;
}}}
Also see: [[How can I know which camera panel is currently available based on the current layout?]]
It's commonly known that you can use operators inside of conditional statements. Like:
{{{
int $a;
if($b == $c)
$a = 1;
else
$a = 0;
// '==' is the operator
}}}
But you can also use them //outside// of conditional statements as well, sort of a 'short hand' version:
{{{
int $a = $b == $c; \\ If $b and $c are equal, 1, else 0
int $a = $b != $c \\ if $b and $c are NOT equal, 1 else 0
int $a = $b || $c; \\ If $b or $c is equal, 1 else 0
int $a = $b < $c; \\ if $b is less than $c, 1 ,else 0
int $a = $b > $c; \\ if $b is greater than $c 1, else 0
int $a = $b <= $c; \\ if $b is less than or equal to $c, 1 ,else 0
int $a = $b >= $c; \\ if $b is greater than or equal to $c, 1 ,else 0
}}}
----
There is also the '{{{?}}}' operator. From the Maya docs:
The {{{?}}} operator lets you write a shorthand if-else statement in one statement.
The operator has the form:
{{{condition ? exp1 : exp2;}}}
If condition is true, the operator returns the value of exp1. If the condition is false, the operator returns the value of exp2.
{{{
// If $y > 20, $x is set to 10,
// otherwise $x is set to the value of $y.
$x = ($y > 20) ? 10 : $y;
// If $x > 10, print "Greater than", otherwise
// print "Less than or equal".
print(($x > 10) ? "Greater than" : "Less than or equal");
}}}
I've ran into instances when I'll execute a command from a UI button (for the sake of example), and when I undo the action, I end up undoing a LOT of steps. I'm not sure yet what causes Maya to decide whether or not it's going to undo the last operation, or all the steps that made up the last operation. But if you run into a similar problem, you can use {{{undoInfo}}} to setup an 'undo chunk' that will store all the operations performed during its creation. After you close the chunk, if you undo, it will undo everything put in the chunk.
Easy to do, and I recommend doing it with Python, since you can wrapper it in a {{{try}}} \ {{{except}}} clause, so that if any of the code fails in the middle of the chunk, the chunk can still close itself.
{{{
# Python code
import maya.cmds as mc
mc.undoInfo(openChunk=True)
try:
doBunchOfStuff()
except Exception, e:
print e
mc.undoInfo(closeChunk=True)
}}}
{{{
objectB.translateX = `getAttr -t (frame-3) objectA.translateY`;
}}}
*This will make objectB match objectA's translateX, but three frame laters.
*DRAWBACK:This is using getAttr & setAttr on your source and destination nodes. It's not querying their absolute position in worldspace, it's the local transformations applied to the nodes themselves. If they don't have the same initial transformation values, the destination object could have an offset based on the difference of the trans, rot, an scale between the two nodes. This obviously could be a major issue. To work around it, look below to the "decomposeMatrix" section.
*I've read in several places that Maya technical guru's don't recomend this operation since the getAttr somehow breaks the dependency graph evaluation, but I personally have never had anything go haywire with it. It's basically very similar to expression code I used to write in PowerAnimator with great success.If this isn't a viable solution though, then try the below frameCache proposal.
Simple example of writing text to a file. You should make sure ahead of time that if the file exists, it's writable. You also need to check and make sure the directory exists too.
FYI, opening these files in Windows Notepad usually has bad formatting. But opening them in anything else (Word, ~WordPad, etc) has correct formatting. I don't think Notepad likes the 'newline character' {{{\n}}}.
{{{
string $filepath = "c:/temp/test.txt";
string $data[] = {"line1", "line2", "line etc..."};
int $fid = `fopen $filepath "w"`;
for($i=0;$i<size($data);$i++)
fprint $fid ($data[$i] + "\n");
fclose $fid;
filetest -f $filepath;
// Result: 1 //
}}}
Also see:
*[[How can I turn a text file into a string array?]]
*[[How can I get return characters to work in Notepad properly?]]
By default, when you launch Maya in prompt mode a dos prompt:
{{{
c:\> maya -prompt
}}}
,,,a new session of Maya will launch in a shell, ready for //mel// commands to be entered and executed. The prompt looks like this:
{{{
mel:
}}}
But what if you want this same functionality, but with //Python// commands?
With Maya 8.5 and later, presuming it's been added to you system path, you can execute (on Windows) {{{C:\Program Files\Autodesk\<version>\bin\mayapy.exe}}} from the command prompt, to launch Maya's version of Python in that shell:
{{{
c:\> mayapy
Python 2.5.1 (r251:54863, Jun 5 2007, 22:56:07) [MSC v.1400 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
}}}
Then you can import in maya.standalone (see notes [[here|How can I execute Maya's version of Python outside of Maya?]]), and start running Maya from within Python:
{{{
>>> import maya.standalone
>>> maya.standalone.initialize('python')
}}}
then you can start running Python in maya as usual:
{{{
>>> import maya.cmds as mc
>>> mc.ls()
[u'time1', u'renderPartition', u'renderGlobalsList1', u'defaultLightList1', u'defaultShaderList1'....
}}}
... and yer good to go!
An animator asked me if there was a way to change the color of a given curve in the graph editor. I realize that animCurves have both a {{{userCurveColor}}} attr (bool), and a {{{curveColor}}} attr (double3). Armed with this knowledge, I built a function that will query what curves the user has selected in the Graph Editor (both in the Graph Editor Outliner, and in the Graph Editor Panel), launch a color chooser, and color them the defined values:
{{{
def setAnimCurveColor():
"""
Based on the curves selected in the graph editor, or the obj.attrs selected
in the Graph Editor channel box, launch a color-chooser allowing the changing
of the individual curve colors.
"""
# This finds a list of the "obj.attrs" selected in the graph editor channel box:
selectionConnection = mc.outlinerEditor("graphEditor1OutlineEd",
query=True, selectionConnection=True)
outlinerSelection = mc.selectionConnection(selectionConnection, query=True, object=True)
# convert to incoming connected keyframe nodes:
outlinerAnimNodes = []
if outlinerSelection is not None:
for os in outlinerSelection:
# make sure we have an attr picked, and not a transform
if "." in os:
incoming = mc.listConnections(os, source=True, destination=False, type="animCurve")
if incoming is not None:
outlinerAnimNodes.append(incoming[0])
# This grabs the acutal keyframe nodes picked in the graph editor panel
graphEdAnimNodes = mc.keyframe(query=True, selected=True, name=True)
if graphEdAnimNodes is None:
graphEdAnimNodes = []
# combine lists, and remove dupes
curves = list(set(outlinerAnimNodes+graphEdAnimNodes))
# Launch our color chooser:
if len(curves) > 0:
mc.colorEditor()
if mc.colorEditor(query=True, result=True):
color = mc.colorEditor(query=True, rgb=True)
for c in curves:
mc.setAttr("%s.useCurveColor"%c, 1)
mc.setAttr("%s.curveColor"%c, *color)
else:
print "Cancled color chooser",
else:
print "No cuves picked in Graph Editor",
}}}
"ELF controll" {{{-bgc}}}
Most UI (ELF) controlls have options to change their colors. If they have a {{{-bgc}}} argument, then they can have color added.
*Example: Make a button with a color other than default:
{{{
button -bgc 1.0 0 0;
}}}
{{{polyListComponentConversion}}}
*Example, convert from selected faces to vertices:
{{{
select ‘polyListComponentConversion -tv‘;
}}}
{{{polySelectConstraint}}}
*Example, convert a surface to all of it's faces:
{{{
polySelectConstraint -m 3 -t 0008;
}}}
You can also use this command to pick the shell elements of a surface, which can be really handy.
Requires two object.attrs (source and destination).
{{{isConnected}}}
*Export an anim (with one or more objects selected):
{{{
file -es
-type "animExport"
-op "-cb api -fea 1 -animation objects -option keys -hierarchy none -controlPoints 0 -shape 000"
"c:/temp/foo.anim";
}}}
*Import an anim (with one or more objects selected):
{{{
file -i
-type "animImport"
-op "targetTime=4; copies=1; option=replaceCompletely; connect=0;"
"C:/temp/test.anim";
}}}
*Notes:
**You must have the "{{{animImportExport.mll}}}" plugin loaded.
**The options listed are based primarily on the copyKey and pasteKey mel commands. In the case of an export, the animation data is copied (via copyKey) into the api clipboard, and then sourced by the plugin for export. In the case of an import, the plugin copys the data from the anim file to the api clipboard, then pastes it (via pasteKey) onto the objects.
{{{whatIs}}}
*Example A:
{{{
whatIs "sphere"
// Result: command
}}}
*Example B:
{{{
int $abc[42];
whatIs "$abc";
// Result: int[] variable //
}}}
Good for figuring out which camera view you're in….
{{{getPanel}}}
*Example: get the current or last active panel:
{{{
string $panel = `getPanel -withFocus`;
}}}
*Example: get the current panel the mouse is over:
{{{
string $panel = `getPanel -underPointer`;
}}}
{{{about}}}
*Examples:
{{{
about -v; // returns a string with the version number, i.e. 4.0.2
about -p; // returns a string with the product version, i.e. Maya Unlimited 4.0.2
}}}
{{{exists}}}
*Example:
{{{
exists ls;
// result: 1
}}}
{{{catch}}}
*Example A: First define some proc "bob". Then:
{{{
catch (`bob`) // this is the start of a new procedure
}}}
**if "bob" failed execution, without the catch, the rest of the new procedure wouldn't execute, but with the catch, the rest of the proc would execute.
*Example B: define some string that does something, $string. Then:
{{{
catch (`eval($string)`);
}}}
{{{modelPanel}}} & {{{getPanel}}}
*Example: (presuming the active panel IS a camera view…)
{{{
string $currentPanel = `getPanel -withFocus`;
string $cameraName = `modelPanel -q -cam $currentPanel`;
}}}
Also see:
*[[For the current panel, how can I make a named camera be the one viewed through?]]
You can use a sneaky back-door via {{{undoInfo}}}:
{{{
// presuming you say, just moved something:
string $lastCmd = `undoInfo -q -undoName`;
// Result: move -r 0.321949 -0.212393 0 //
}}}
Since Maya's undo-que holds everything just executed (so, it can be undone), you can use this method to query what //would// be undone... which obviously would be the last commands entered.
Also see:
*[[How can I repeat the last action in Maya?]]
{{{recordAttr}}}
*Example:
*Select the object you want to record motion on.
*In the script editor, enter
{{{
recordAttr -at "translateX" -at "translateY";
// to control the tx and ty for example
}}}
*Execute that bit of code. You'll see new "record1" outputs on your object
*Next type in the script editor, but don't execute yet:
{{{
play -record;
}}}
*Now:
**select your object in the camera view,
**go back and highlight 'play -record' in the script editor,
**move your mouse back over your object (but do not pick it!),
**and hit the 'Enter' button on the NUMBER pad.
**This will execute the mel code, but leave your mouse right over your object ready to be selected and moved.
*This will start the recorder, capturing your mouse movement.Click on the object, and start moving it.Obviously, you should constrain you translation plane to the two axes you've selected before you start this process.Hit Esc when done.
*This can work for any attr you want actually, I just used trans as an example.
*Also, if you want to delete the motion and try again, you'll need to also delete the "record" nodes that are output connections to your animated node as well. You can use this code:
{{{
delete `ls -type record`;
}}}
When using Python in Maya, to access the bulk of the //mel// commands via Python, you import the {{{mel.cmds}}} package, usually into a namespace, like so:
{{{
import maya.cmds as mc
print mc.ls()
}}}
But if you browse to that package on disk, you'll find it empty, only containing an {{{__init__.py}}} file (required for package creation):
{{{
C:\Program Files\Autodesk\Maya<VERSION>\Python\Lib\site-packages\maya\cmds
}}}
So how are all the 'mel commands' added to that package for execution in Python?
There is a startup Python module living here, that works the magic of populating that package:
{{{
C:\Program Files\Autodesk\Maya<VERSION>\Python\Lib\site-packages\maya\app\commands.py
}}}
It parses a file called {{{commandList}}} (no extension, but it's text) living here:
{{{
C:\Program Files\Autodesk\Maya<VERSION>\bin\commandList
}}}
You can open that file in a text editor, and what you'll find are two columns: One with a 'mel' command name, and one with a .dll where that command 'lives'. Those .dll's live in the same dir as the {{{commandList}}} file. Here's a sample:
{{{
TanimLayer AnimSlice.dll
about Shared.dll
addAttr Shared.dll
addDynamic DynSlice.dll
addPP DynSlice.dll
affectedNet Shared.dll
affects Shared.dll
agFormatIn Translators.dll
agFormatOut Translators.dll
aimConstraint AnimSlice.dll
air DynSlice.dll
aliasAttr Shared.dll
align Shared.dll
}}}
What {{{commands.py}}} does is parse the {{{commandList}}} file and append each command (via a Python command wrapper function) to {{{maya.cmd}}}'s {{{'__dict__}}}' attribute ({{{maya.cmds.__dict__}}}), which is the lookup table users access when calling their favorite mel command via Python (like {{{maya.cmds.ls(}}}) It also passes the name of the .dll to the command wrapper function, so when the user executes the given command, it first loads the parental .dll.
Crack open commands.py for the nitty-gritty details.
----
If you want to see the end result of all this hard work, you can run this code in Maya. But it prints a *lot* of stuff:
{{{
import maya.cmds as mc
for k in sorted(mc.__dict__.keys()):
print k, mc.__dict__[k]
}}}
Since Python is now available in Maya, it makes sense to use it's power. One of it's coolest features is being able to use its 'object oriented programming' (oop) features. This is old hat in the world of programming, but not supported with mel.
Since it's been a relatively new concept for me, I've been having a hard time to find a good description of it in print. As I learn it, I update another wiki (my Python wiki) with notes specifically on it. You can find that info here: http://pythonwiki.tiddlyspot.com/#Class
Also, the below link does a pretty good job of it, for the beginner:
http://www.pasteur.fr/formation/infobio/python/ch18.html
This is pulled from the 'Programming Course for Biologists at the Pasteur Institute', so the examples are a bit... genetics related. But they have nice pictures, and get the point across. And as of //this// writing, it was updated Feb 2, 2008 (pretty recently).
*".liw" stands for "Lock Influence Weight". By default, a joint doesn't have this attribute, but when it is smooth bound to geometry, each joint receives this new attribute. It is just a custom attribute. Its output connects to the respective skinCluster's ".lockWeights[]" (.lw) imput attribute. ".lockWeights[]" is an array attribute, that can receive inputs from multiple joint.liw attrs.
*There is no reference to this attribute (.liw) in the documentation, since it is a custom attr generated by the smoothBind process. You can find documentation for the ".lw" attr in the "joint" documentation.
Also see:
*[[How can I easily hold or unhold all the influence weights on skinned geo?]]
Some good stuff from this post by Peter Shipkov :
http://groups.google.com/group/maya_he3d/browse_thread/thread/5a78a2fce5de57b4#
getattr:
{{{
getAttr .boundingBox.boundingBoxMin;
getAttr .boundingBox.boundingBoxMax;
}}}
Command:
{{{
exactWorldBoundingBox(someNode);
}}}
API:
{{{
MFnDagNode.boundingBox()
}}}
Sometimes when I orient constraint one object to another (often joints) with 'maintain offset' turned ON, the object being constrained has new rotation values (usually something like 180, -180, 180) applied. So 'maintain offset' isn't doing its job. Very frustrating, especially since it seem to happen inconsistently.
I have found the usual culprit is that the two nodes have //different// rotate orders. If I change the rotate order of node B to match node A, THEN do the orientConstraint, THEN set the rotate order back, it makes it happy, and your rotate values don't change.
A buddy of mine ran into this. He had to encode it as an uncompressed AVI with these values:
*640x480
*Millions Colors (not thousands or millions +)
*30 fps
Presume you have a rig, that is controlling a skeleton. The skeleton is bound to mesh. But not all of the joints are bound to the mesh: Some leaf joints aren't bound to anything. Before you export to the game, you delete the rig for cleanliness purposes. When you make the joints visible, any joint not bound to a mesh has snapped to identity (directly on top of its parent). Why? How to fix?
It appears that Maya is optimizing too much for its own good. It seems that if a joint has incoming connections to translate, rotate, etc, //and// it's hidden, //and// if it's not bound, if you delete the nodes that the incoming connections are coming from, Maya doesn't know what to set the joint attributes to, so it sets them to zero. This snapping them on top of their parent. Being visible, or having mesh bound to it will leave it in the correct place.
To fix in code, you can make them visible first. But even then (if this "cleaning step" is happening via code) they may not become visible until after the clean code is done. To force the visibility, you need to change the frame immediately after, to force a scene refresh.
{{{
showHidden -all;
float $time = `currentTime -q`;
currentTime ($time + 1);
currentTime $time;
}}}
Jump through flaming hoop A, and through flaming hoop B.
Tested in 7.01:
*I'm guessing this may be a display bug in Maya: If the collision object has input creation history, it appears to also show 'OUTPUTS' (thus, the geoConnector) in the channel box. However, if its history has been delted before it was made to be collision, and thus there are no inputs, after collision has been added, the 'OUTPUTS' section of the channel box just dosn't show up.
*The geoConnector node is still there, just not showing up. You can see it in the Hypergraph, and check for it with listConnections. Bothersome!
Never figured out the exact reasoning, but I've seen this from time to time: It's possible that whatever layout was saved with their scene file isn't jiving with your current version of Maya. Usually, no matter what one does, they can't get a camera to appear to view though.
To fix, disable Maya from loading the saved layouts on file open (and then reopen the file):
*Window->Settings\Preferences->Preferences:
**UI Elements
***//Uncheck//:
****{{{When Opening [] Restore Saved Layouts from File}}}
What's going on behind the scenes when you uncheck that box and save the prefs?
{{{
$gUseScenePanelConfig = false;
file -uc false;
optionVar -iv useScenePanelConfig 0;
}}}
It's setting the global int $gUseScenePanelConfig to "off", setting the file command's 'uiConfiguration' flag to off, and setting the optionVar 'useScenePanelConfig' to off.
*The actual window is called {{{toolProperties}}}
*It has a master {{{formLayout}}} called {{{toolProperties_C}}}
*There is a {{{tabLayout}}} called {{{context_T}}} which the custom tool settings go.
*Given the Paint Skin Weights tool as an example, its UI information lives in a {{{columnLayout}}} called {{{artAttrSkin}}}, and this is where you can add your custom UI's
----
Also see:
*[[How can I access \ query the Tool Settings UI?]]
{{{C:/Program Files/Alias/MayaX.X/scripts/others/showEditor.mel}}}
And these are some global vars that define some of its elements:
{{{
global string $gAECurrentTab;
global string $gAttributeEditorWindowName;
global string $gAEMenuBarLayoutName;
}}}
{{{
showEditor($myObjName);
}}}
*From Autodesk support when I queried them on this limitation:
**Apperantly Microsoft command-line interpreter {{{CMD.EXE}}} can only execute strings up to 8191 characters in length. So when you pass a mel system call to it, that is longer than 8191 characters, it craps out. For more info on it, see [[HERE|http://support.microsoft.com/default.aspx?scid=kb;en-us;830473]].
*For example:
{{{
string $cmd = "someApp someArgs some more args, more args to 8192 characters....";
system($cmd);
// but... nothing happens.... //
}}}
*Workaround: Try creating a .bat file from Maya using the fopen and fprint commands, fill it with the system commands you want, and execute it instead: {{{system("c:/mybat.bat");}}}.
Also see:
*[[Using the system command in Maya, how can I call to a .bat file and have the .bat file execute on the contents of the directory it lives in?]]
If you're not, save your file as a .ma (mayaAscii). Open the .ma in a text editor, and look for your value in there. Often you'll perform an operation via the UI, and even when "Echo All Commands" is turned on in the Script Editor, you still aren't to sure what attrs\values were affected by the result. If you know the value you're looking for, checking the saved .ma file can often reveal a lot about what is going on.
Say you have a list of object names that you try to pick stored in an array. But you get an error saying that an item in the list dosn't exist. But you //know// the object //does// exist, what's causing this?
Example:
{{{
// create two null objects:
group -em; // this is called "null1" by default
select -cl ;
group -em; // this is called "null2" by default
// make our list with an extra object name in it, "null3":
string $list[] = {"null1", "null2", "null3"};
// try and pick the list:
select -r $list;
// Error: line 1: No object matches name: null1 //
}}}
*Huh? Object {{{null1}}} most definitly exists, what's going on?
*I'm not sure if it's a bug, or as designed, but when you try to select a list of objects based on an array, and one or more of the names in the array dosn't exist as an object, it errors that the FIRST object in the array is missing. This can be //very// confusing to the user.
*Instead, add-select the objects if they exist, and print a warning if they don't:
{{{
select -cl;
for($i=0;$i<size($list);$i++)
{
if(`objExists($list[$i])`)
select -add $list[$i];
else
warning ("Object in list dosn't exist: " + $list[$i]);
}
// Warning: Object in list dosn't exist: null3 //
ls -sl;
// Result: null1 null2 //
}}}
{{{stringArrayCount}}}
*Example:
{{{
string $string[] = {"happy", "bob", "angry"};
string $strung = "happy";
int $count = stringArrayCount($strung, $string);
// Result: 1 //
}}}
{{{
\\ this will return the shape nodes of the objects based on the selected components:
ls -o -sl;
}}}
Sometimes you'll luck out when Maya crashes, and it'll save a backup of the current scene, in the current state. It usually tells you this in a dialog box just before it crashes.
If you //are// so lucky, you can find those files here (on Windows):
{{{
C:\Documents and Settings\<userName>\Local Settings\Temp
}}}
And they usually have fun names like:
{{{
<userName>.20081202.1412.ma
}}}
{{{<userName> . <year> <month> <day> . <hour> <minute> .ma}}}
*Example:
{{{
listAttr -s -cfo objectName;
}}}
*The -cfo is the important flag.
Layer Editor UI Structure:
*{{{global string $gCurrentLayerEditor;}}} -- {{{formLayout}}}.
**{{{LayerEditorTypeRadio}}} -- {{{radioButtonGrp}}}.
**{{{DisplayLayerUITabLayout}}} -- {{{tabLayout}}}
***{{{DisplayLayerTab}}} -- {{{layout}}}
****{{{formLayout38}}} -- {{{formLayout}}} -- it's name appears to be generated on the fly
***** ... and they just keep going ...
***{{{RenderLayerTab}}} -- {{{layout}}}
****{{{RenderLayerFormLayout}}}} -- {{{formLayout}}}
***** ... and they just keep going ...
Name of last layer editor button selected:
{{{
global int $gLayerEditorLastButtonSelection;
}}}
UI control type for making layer buttons:
{{{
layerButton;
}}}
Script that holds the guts of the code to build the Layer Editor:
{{{
../MayaXX/scripts/startup/layerEditor.mel
}}}
By using the Python {{{inspect}}} module, you can query which //line of a module// is currently being executed. By using a Python module's special {{{__file__}}} attribute, you can query which //module// is currently being executed. Combine the two with one of Maya's {{{confirmDialog}}}'s, and you have something useful to tell the user when something bad happens:
{{{
# linetest.py
import inspect
import maya.cmds as mc
def getLine():
return inspect.currentframe().f_back.f_lineno
def errorWin(message, module, lineNumber):
mc.confirmDialog( message="%s\n\n%s\nLine : %d"%(message, module, lineNumber), title="Error", button="Bummer..." )
print "%s\n%s\nLine : %d"%(message, module, lineNumber)
def main():
if True != False:
errorWin("You have encountered an error :-(", __file__, getLine() )
return
}}}
Other than displaying a nice {{{confirmDialog}}} for the user to click through, it will also print the coresponding info:
{{{
import linetest
linetest.main()
}}}
{{{
You have encountered an error :-(
C:\stuff\linetest.py
Line : 15
}}}
<<gradient horiz #ddddff #ffffff >>This is a [[TiddlyWiki|http://tiddlywiki.com/]]. To really get a grasp on how to navigate it, check out their [[homepage|http://tiddlywiki.com/]]. Quick directions below:
----
Conceptually, each 'subject' I post in the wiki is put in a 'tiddler' (based on how "Tiddlywiki's" work). The [[Instructions For Use]] section you're in now is a 'tiddler'. [[Welcome]] is a tiddler. I tag each tiddler with keywords relevant to the subject of said tiddler, and you can see those tags on a small box on the top right of each tiddler. Clicking on a tag will display a list of every other subject with that tag. Subjects (tiddlers) are also categorized on the left menu-bar under larger subject-headings. ALL the tags from //every// tiddler are in the 'Tags tab', which is on the right most column of the page.
Mainly, you come to the page looking for an answer, and to find it, you ''search'' for tags using keywords that may exist in your problem. When you find a tag, click on it, and see the resultant subjects... see if one matches what you're looking for.
''SEARCHING FOR DATA:''
#You can do key-word searches in the search-field in the top of the //right column//.
#Browse the "Tags" tab in the //right column// for mel-ish key-words.
**Inside the Tags tab, major ''Categories'' are all in caps, like "[[ATTRIBUTES]]".
**When picking a 'Tag' with more than one link, you can either:
###'{{{Open all}}}' the topics in that Tag (meaning, fully expand all the topics listed in the middle of that pop-up menu).
###Open a single topic by picking its heading.
###Show all the headings in the Tag by picking: {{{Open tag 'tagname}}}'.
#Use your web browsers screen-search ability ('Ctrl+F' in both Firefox & Internet Explorer) to find key-words you're after (good if 'Tags' tab is open).
#Or start browsing from the ''Categories'' section in the //left column//. This will open each of their major headings in a new tiddler.
If things get too crowded, use the "close all" from the //right column// to clean up the page. Or, you can use "close others" from an individual tiddler (This block called "[[Instructions For Use]]" is a tiddler, for example).
----
''COPYING DATA FROM WIKI, TO MAYA:''
*The way the text has been entered into this wiki, copying code from the source-boxes should work:
{{{
string $source = "source-code in a box";
}}}
*Other times it's not in a box, but is still safe for copy:
{{{string $source = "source-code not in a box, but still safe to copy";}}}
*If you copy any code outside of a box that's 'not safe', Maya //may// have a hard time with it's formatting and be angered. Weird
[[Wing|For Python: Maya 'Script Editor' style IDE]] has the ability to interact with Maya in three different ways. Each has it's own setup, independent from another. For clarity, here they are listed below:
#''Send Python & mel commands from Wing to Maya, bypassing the Script Editor''. Meaning, replace the Script Editor with Wing. See Docs [[here|How can I have Wing send Python or mel code to Maya?]]
##I use this on a daily basis, and the main reason I use Wing.
#''Remote debug Python code executed in Maya with Wing''. This lets you use Wing's debugger to step through Python code in an active Maya session. See Docs [[here|Remote Python debugging in Wing]]
##This has been extremely valuable to track down bugs in large modules.
#''Execute a standalone version of Maya inside Wing's Python shell''. See Docs [[here|How can I execute Maya's version of Python outside of Maya?]]
##Note, I've yet to have a need for this, as neat as it seems.
When you are setting these up for the first time, do each separately before moving on to the next, so as not to confuse yourself.
There is a good chance you have an Nvidia __~GeForce__ video card card. These cards aren't supported by Maya, they expect you to have a much more expensive __Quadro__ card. I've used ~GeForce cards for years though, with no ill effect. Every so often though, usually when a new version of Maya comes out, there can be dramatic slowdowns interaction. Issues I've seen:
*If joints are visible, there is a 1sec pause when picking anything.
*Having 'high quality rendering' turned on in the modeling panels makings any kind of interaction super-slow.
This happened when I switched to Maya 7: If joints were visible in scene, picking anything had a 1sec pause. I learned (took until I installed Maya 2008), that by simply updating to the latest drivers from Nvidia, the problem was solved. Sometimes it takes some fiddling with the driver options too.
http://www.nvidia.com/Download/index.aspx
I will often work on multiple projects simultaneously, that each have their own set of required preferences. In the course of a day, I may need to access multiple teams data, each having environment variables that need to be set ahead of time, version control software that lives in different depots, and differing mel\Python paths.
The easiest way to do this I've found is to author unique configuration files for each project, and then have a .bat file copy them on top of the standard location that Maya expects to find them. To explain:
*For each project, author these custom files, each having a unique purpose:
**{{{projName_userSetup.mel}}}
***Set custom Maya script paths via recursive searching procedure (to support an ever-expanding mel library, without having to ever type in custom script paths again)
***Define my own personal prefs (grid color, set linear units, set correct time units, confirm I'm running the correct version of Maya for this project, etc..)
***Load any project-required plugins.
**{{{projName_userSetup.py}}}
***Set custom Python script paths.
**{{{projName_Maya.env}}}
***Define project-specific environment variables
***//Don't// define Maya Script Path (I do that via custom code in the userSetup.mel)
***Define plug-in path
**{{{mayaLauncher_projName.bat}}}
***Copy {{{projName_userSetup.mel}}} to standard install location as {{{userSetup.mel}}}
***Copy {{{projName_userSetup.py}}} to standard install location as {{{userSetup.py}}}
***Copy {{{projName_Maya.env}}} to standard install location as {{{Maya.env}}}
***Since I use Perforce (P4) as version control software, setup the p4Port, p4Client, and p4User for this project.
***Launch Maya
I have multiple .bat files on my desktop, that when executed, copies all the appropriate code to where Maya expects to find it, sets up my version control software appropriately, and then launches Maya.
Pretty darn easy:
{{{
# Python code
import maya.cmds as mc
mc.showHelp("http://www.akeric.com", absolute=True)
}}}
Growing list as I find them:
*''python_inside_maya'' Google group. You must be a member to read and post:
**http://groups.google.com/group/python_inside_maya?lnk=li
*''Python Wiki'': This is one of my other tiddlywiki's, specifically geared to my notes while learning Python (just like this mel wiki). While it has nothing to do with mel, it's all about the basics of Python, which the mel user trying to learn Python could find valuable. Plus, I like to plug my site.
**http://pythonwiki.tiddlyspot.com/
*''~Highend3D'''s [[Maya Python Forums|http://www.highend3d.com/boards/index.php?s=282e161ce612a241c6c68355266cbdbf&showforum=723]]
<<gradient horiz #ddddff #ffffff >>
[[Welcome]]:
[[About mel wiki]]:
[[Instructions For Use]]:
[[Latest Updates|History]]:
[[Maya Links]]:
[[Other Links]]:
[[Blog]]:
[[About Author|WarpCat]]:
[[Guest Book!|GuestBook]]
----
''Subscribe:''
''[[RSS|http://mayamel.tiddlyspot.com/index.xml]]'' [img[http://farm1.static.flickr.com/197/492915948_b4a9f3233e.jpg?v=0]]
----
[[All Subjects]]
----
''Categories:''
[[ANIMATION]]
[[API]]
[[ATTRIBUTES]]
[[BATCH]]
[[CLASSIFICATION]]
[[COMPONENTS]]
[[CONTEXT]]
[[DEFORM]]
[[DYNAMICS]]
[[EXPRESSION]]
[[FILE OPERATION]]
[[FUNDAMENTALS]]
[[GENERAL]]
[[HARDWARE]]
[[INFO]]
[[NETWORKING]]
[[NURBS]]
[[POLYGON]]
[[PYTHON]]
[[REFERENCING]]
[[RENDERING]]
[[RIGGING]]
[[SCRIPTING]]
[[SELECTION]]
[[SHADING]]
[[STRING]]
[[SYSTEM]]
[[TEXTURE]]
[[TIME]]
[[TRANSFORMATION]]
[[TROUBLESHOOTING]]
[[UI]]
[[VARIABLES]]>>
When I first authored this subject I didn't use matrices very often. But I've found the more I learn about them, the more I end up using them...
Also see my other subject: [[Working with matrices with Python]]
----
Launch help for the root {{{dagNode}}} type node, which has the base matrix attrs:
{{{
showHelp -d "Nodes/dagNode.html
}}}
Launch help for the {{{transform}}} node (lots of matrix info):
{{{
showHelp -d "Nodes/transform.html";
}}}
Web:
*I made an image describing how 4x4 matrix multiplication works. You can find it on my [[blog here|http://www.akeric.com/blog/?p=693]], or on [[flickr here|http://www.flickr.com/photos/warpcat/3956108191/]]
*[[chapter 9|http://books.google.com/books?id=_ztrTBEDK28C&lpg=PA191&ots=r56b7bLPzY&dq=python%20transformation%20matrix&pg=PA181#v=onepage&q=python%20transformation%20matrix&f=false]] from the book [[Beginning Game Development with Python and PyGame: From Novice to Professional|http://www.apress.com/book/view/9781590598726]] has a really well described overview.
*http://eddieoffermann.com/blog/tag/matrix-multiplication/
*http://www.185vfx.com/2003/03/convert-a-3d-point-to-2d-screen-space-in-maya/
*Online video lectures from [[MIT|http://ocw.mit.edu/OcwWeb/Mathematics/18-06Spring-2005/VideoLectures/]]
*http://knol.google.com/k/koen-samyn/matrices-for-3d-applications/2lijysgth48w1/3#
----
''Quick 4x4 matrix review of an identity matrix'' (presuming +X is 'left', +Y is 'up', and +Z is 'forward'). To visualize the x, y, and z-axes, use your hand: Hold your right hand in front of you, pointer straight ahead, thumb up, and middle-finger left: Middle-finger = +X-axis, thumb = +Y-axis, pointer = +Z-axis.
*The fourth //column// '{{{W}}}' is for the most part unused, it is always {{{(0, 0, 0, 1)}}}: This is called the 'homogeneous row'. W = A scalar value (also known as the 'homogeneous coordinate') used for conversion from Euclidean to Cartesian coordinates.
*The first three //rows// represent the vectors that point in the x, y, and z directions of the transformation, which represent the //axis// in 3D space. Effectively the orientation/heading of the 'local axis' of a transform in Maya. They also calculate //scale//.
*The fourth //row// ({{{w}}}) represents the translation, or point in space when transformed by this matrix. This is the 'homogeneous row':
| || X | Y | Z | W || notes |
| || | | | || |
|x-axis || 1 | 0 | 0 | 0 || 'left', mag=scaleX |
|y-axis || 0 | 1 | 0 | 0 || 'up', mag=scaleY |
|z-axis || 0 | 0 | 1 | 0 || 'forward', mag=scaleZ |
|(w) translate || 0 | 0 | 0 | 1 || |
(remember, the right column is ignored)
*''translation'': Derived from the bottom row.
**translateX, translateY, translateZ = {{{(0 ,0 ,0)}}}
*''orientation'': Vectors that point in the x, y, and z directions of the transformation.
**{{{x-axis}}} : defines the current heading of the 'left' vector = {{{(1, 0, 0)}}}
**{{{y-axis}}} : defines the current heading of the 'up' vector = {{{(0, 1, 0)}}}
**{{{z-axis}}} : defines the current heading of the 'forward' vector = {{{(0, 0, 1)}}}
*''scale'' : The magnitude of each x, y, and z-axis defines the scale = {{{(1, 1, 1)}}}
**scaleX = {{{mag(x-axis)}}} = {{{mag(1, 0, 0)}}} = 1.0
**scaleY = {{{mag(y-axis)}}} = {{{mag(0, 1, 0)}}} = 1.0
**scaleZ = {{{mag(z-axis)}}} = {{{mag(0, 0, 1)}}} = 1.0
*''shear'' : still working on that one ;)
''Some examples, based on that info:''
Where individual rotation axis data is stored in the matrix:
__X-axis__:
| || X | Y | Z | W |
| || | | | |
|x-axis || 1 | 0 | 0 | 0 |
|y-axis || 0 | @@1@@ | @@0@@ | 0 |
|z-axis || 0 | @@0@@ | @@1@@ | 0 |
|(w) translate || 0 | 0 | 0 | 1 |
__Y-axis__:
| || X | Y | Z | W |
| || | | | |
|x-axis || @@1@@ | 0 | @@0@@ | 0 |
|y-axis || 0 | 1 |0 | 0 |
|z-axis || @@0@@ | 0 | @@1@@ | 0 |
|(w) translate || 0 | 0 | 0 | 1 |
__Z-axis__:
| || X | Y | Z | W |
| || | | | |
|x-axis || @@1@@ | @@0@@ | 0 | 0 |
|y-axis || @@0@@ | @@1@@ |0 | 0 |
|z-axis || 0 | 0 | 1 | 0 |
|(w) translate || 0 | 0 | 0 | 1 |
__Translate XYZ__:
| || X | Y | Z | W |
| || | | | |
|x-axis || 1 | 0 | 0 | 0 |
|y-axis || 0 | 1 |0 | 0 |
|z-axis || 0 | 0 | 1 | 0 |
|(w) translate || @@0@@ | @@0@@ | @@0@@ | 1 |
*Identity matrix rotated 45 degrees along the Z axis. Compare this to the above chart that shows where those values will be stored. Note how the z-axis remains the same {{{(0, 0, 1, 0)}}} since it hasn't changed orientation. But both the x-axis and y-axis now have a new heading? (since they were transformed by the z-axis rotation) And notice how all their magnitudes still equals {{{1.0}}}? Scale remains at {{{(1,1,1}}}).
**Visualize this using the 'left hand rule' described above: Start with your left hand at identity. Imagine there is a point on the tip of each finger corresponding to each identity vector. Now rotate your hand along the z-axis (pointer) //clockwise// 45 degrees. Your pointer is still pointing the exact same direction {{{(0, 0, 1)}}}, but the points defined by your thumb (+y) and middle finger (+x) have moved in space. But they're all still the same distance away from the middle of the transform, so their magnitudes still each equal {{{1.0}}}.
| @@.707107@@ | @@-.707107@@ | 0 | 0 |
| @@.707107@@ | @@.707107@@ | 0 | 0 |
| 0 | 0 | 1 | 0 |
| 0 | 0 | 0 | 1 |
*Identity matrix scaled to be half as wide, and twice as high:
scaleX = {{{mag(x-axis)}}} = {{{mag(.5, 0, 0)}}} = .5
scaleY = {{{mag(y-axis)}}} = {{{mag(0, 2, 0)}}} = 2
scaleZ = {{{mag(z-axis)}}} = {{{mag(0, 0, 1)}}} = 1
| .5 | 0 | 0 | 0 |
| 0 | 2 | 0 | 0 |
| 0 | 0 | 1 | 0 |
| 0 | 0 | 0 | 1 |
----
''Maya transformation matrix:''
Simple version:
* Scale Matrix * Rotate Matrix * Translate Matrix = final matrix
Long version:
{{{matrix = SP *(-1) S * SH * SP * ST * RP *(-1) RA * R * RP * RT * T}}}
{{{*}}} = matrix multiplication, (-1) = matrix inversion
*SP = scale pivot
*S = scale
*SH = shear
*ST = scale (pivot) translate
*RP = rotate pivot
*RA = rotate axis =
**AX * AY * AZ
*R = rotate =
**RX * RY * RZ (Defined by rotate order)
*RP = rotate pivot
*RT = rotate (pivot) translate
*T = translate
Matrix multiplication happens in order, from left to right. It can be thought of exactly like a transform hierarchy, with child nodes on the left, and parental nodes on the right. The matrix to the right will effect the matrix to the left. So in effect, the root of the hierarchy is '{{{T}}}' (translate), while the leaf-most child would be '{{{S}}}' (scale). This is why the translation of a node effects where it is rotated and scaled, the rotation of a node effects the orientation of scale but has no effect on translation, and the scale of a node has no effect over rotation or translation.
>It's important to note that Maya internally doesn't store its animation data as matrices. It stores them on nodes, as keyframe data (scalar values), expression data, dynamic sim, etc. The matrix is simply a state value that can be queried based on the end result of the incoming anim data for a given transform.
----
Matrices are //multiplied// together to get new matrices. For example, if you wanted to assign the position of objectC as the position of objectA + objectB:
{{{
matrixC = matrixB * matrixA
}}}
So when you think of 'adding' values, convert that to 'multiplying' matrices.
But what if you want to 'subtract' values? For example, you want the position of objectC to be the difference between objectA and objectB? In this case you add (multiply) by the //inverseMatrix//.
{{{
matrixC = matrixB * inverseMatrixA
}}}
----
{{{dagNode}}} node's matrix related attrs (all of these are available through any {{{transform}}} based node):
*__Local__ matrix data, relative to the nodes //parent//:
**{{{.matrix}}} : Local transformation matrix for the dagNode.
**{{{.inverseMatrix}}} : Inverse of matrix attribute. Multiplying by this value will place it at its parent.
*Matrix data for the nodes __//parent//__:
**{{{.parentMatrix}}} : The parentMatrix instanced attribute represents the world-space transformation matrix for the parents of this dagNode. If the dagNode is a transform node and its inheritTransform attribute is false, then the parentMatrix is identity.
**{{{.parentInverseMatrix}}} : Inverse of parentMatrix instanced attribute. Multiply the parent by this value to return it to origin.
*Matrix data relative to the __//world//__:
**{{{.worldMatrix}}} : The worldMatrix instanced attribute is the 4x4 transformation matrix that transforms the object to world-space. There is a world-space matrix for each unique path to the object. Eg. 'ball.worldMatrix[0]' identifies the world-space transformation matrix for the first instance of the object 'ball'. Each world-space transformation matrix is the result of post multiplying the 'matrix' attribute by corresponding 'parentMatrix' instanced attribute (i.e. worldMatrix[i] == matrix x parentMatrix[i]). Thus, the worldMatrix is the concatenation of the 'matrix' attribute of all the dagNodes along the path from the node up to the root of the dag hierarchy.
***Basically: {{{dagNode.worldMatrix = dagNode.matrix * dagNode.parentMatrix}}}
**{{{.worldInverseMatrix}}} : Inverse of worldMatrix instanced attribute. Multiplying by this will place it at the world origin.
{{{transform}}} node's matrix related attrs:
*{{{.xformMatrix}}} : Local transformation matrix. From the docs: "Contains the same information as the matrix attribute on dagNode but it is stored in a format that can be interpolated easily.". Personally, I'm not sure what makes it more easy to interpolate.
It should be noted that on all these attrs you //can// connect to or query their //outputs//, but they //don't// allow for input connections or modifications via {{{setAttr}}}.
----
Notes on getting Matrix attr data:
<<<
Some attributes represent transformation matrices. Although these attributes are represented internally as 4x4 matrices, unfortunately, matrix attributes are not returned as MEL 4x4 matrix types. Instead, they are returned as an array of floats where the elements of the matrix are listed in row major order. For instance, "matrix $m[4][4] = `getAttr ball.worldMatrix`;" will not work. To obtain the value of a matrix attribute, you need something like "float $m[16] = `getAttr ball.worldMatrix`;".
<<<
These float arrays (for a 4x4 matrix) are indexed like this:
| 0 | 1 | 2 | 3 |
| 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 |
----
{{{xform}}} command:
*It should be noted that when querying matrix //translation// values (index 12, 13, & 14) Maya always returns them as cm values. If your working units are something else, you'll need to convert them!
*If the node ever had 'freeze transformations' ({{{makeIdentity}}}) applied to it, it will throw off the worldspace matrix position.
{{{
// Copy the worldspace matrix from pCube1 to pCube2:
// Using 'getAttr' on the .worldspaceMatrix, it calculates all
// scale operations based on all parental nodes as well.
// This can cause the target .shear attrs to be effected.
float $m[16] = `getAttr pCube1.worldMatrix`;
xform -matrix $m[0] $m[1] $m[2] $m[3]
$m[4] $m[5] $m[6] $m[7]
$m[8] $m[9] $m[10] $m[11]
$m[12]$m[13] $m[14] $m[15] pCube2;
// you could optionally use this to get the matrix, via xform:
float $m[16] = `xform -query -matrix pCube1`;
// Which queries the .xformMatrix attr (see above), but this only
// tracks the local scale values, not any extra parental scale
// values: .shear attrs are not effected.
}}}
*Sets/returns the composite transformation matrix. *Note* the matrix is represented by 16 double arguments that are specified in row order.
----
''Matrix Nodes''
*{{{decomposeMatrix}}} (standard Maya plugin):
**See Mel Wiki notes [[here|How can I have a node return the worldspace position of my object, offset in time?]]
*{{{addMatrix}}}
**Add a list of matrices together.
*{{{fourByFourMatrix}}}
**This node outputs a 4x4 matrix based on 16 input values. This output matrix attribute can be connected to any attribute that is type "matrix".
*{{{holdMatrix}}}
**Cache a matrix.
*{{{multMatrix}}}
**Multiply a list of matrices together.
*{{{passMatrix}}}
**Multiply a matrix by a constant without caching anything
*{{{pointMatrixMult}}}
**The dependency graph node to multiply a point by a matrix.
*{{{wtAddMatrix}}}
**Add a weighted list of matrices together.
''Matrix Commands''
*{{{pointMatrixMult}}} (mel)
**This script returns the multiplication of a point and a matrix as an array of 3 doubles. It is a wrapper around the {{{pointMatrixMult}}} node, which the script actually creates, modifies, gathers the return value, and then deletes.
**Located: {{{C:/Program Files/Autodesk/Maya<version>>/scripts/others/pointMatrixMult.mel}}}
*For //Python// related matrix commands see: [[Working with matrices with Python]]
----
Syntax for authoring an identity transformation matrix:
{{{
matrix $m[4][4] = <<1, 0, 0, 0;
0, 1, 0, 0;
0, 0, 1, 0;
0, 0, 0, 1>>;
}}}
----
Printing 4x4 matrix data:
{{{
float $goo[] = `getAttr myNode.worldMatrix`;
for($i=1;$i<size($goo)+1;$i++)
{
print ($goo[$i-1] + " ");
if($i%4==0)
print("\n");
}
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
}}}
Using {{{getAttr}}} on a matrix attr will return back a float array. But when you want to do matrix math, you have to use the {{{matrix}}} variable type. These funtions will convert back and forth between the two:
{{{
global proc matrix floatArrayToMatrix(float $m[]){
if(size($m) != 16)
error("Please pass in a float array with exactly 16 elements");
matrix $mat[4][4] = <<$m[0], $m[1], $m[2], $m[3];
$m[4], $m[5], $m[6], $m[7];
$m[8], $m[9], $m[10], $m[11];
$m[12], $m[13], $m[14], $m[15]>>;
return $mat;
}
}}}
{{{
global proc float[] matrixToFloatArray(matrix $m){
float $return[] = {};
for($row=0; $row<4; $row++){
for($column=0; $column<4; $column++)
$return[size($return)] = $m[$row][$column];
}
return $return;
}
}}}
----
Match the worldspaceMatrix of objectB to objectA:
{{{
string $source = "pCube1";
string $destination = "pCube2";
float $m[16] = `getAttr ($source+".worldMatrix")`;
xform -worldSpace -matrix $m[0] $m[1] $m[2] $m[3]
$m[4] $m[5] $m[6] $m[7]
$m[8] $m[9] $m[10] $m[11]
$m[12]$m[13] $m[14] $m[15] $destination;
}}}
This will work, no matter what any parental groups have done to the nodes. Sweet.
----
Another snippet:
Have a destination node match the position of a hierarchy of source nodes. In this scene, "pCube1" is parented to "group1". The below code will match the worldspace matrix of "pCube2" to the postion of "pCube1" by multiplying the local matrices of "group1" and "pCube1" together.
{{{
string $sourceParent = "group1";
string $sourceChild= "pCube1";
string $destination = "pCube2";
// query the local matrix of the parent and child
matrix $matrixSP[4][4] = floatArrayToMatrix(`getAttr ($sourceParent+".matrix")`);
matrix $matrixSC[4][4] = floatArrayToMatrix(`getAttr ($sourceChild+".matrix")`);
// Generate our new destination matrix, order is important!
matrix $dPos[4][4] = $matrixSC * $matrixSP;
float $m[16] = matrixToFloatArray($dPos);
xform -worldSpace -matrix $m[0] $m[1] $m[2] $m[3]
$m[4] $m[5] $m[6] $m[7]
$m[8] $m[9] $m[10] $m[11]
$m[12]$m[13] $m[14] $m[15] $destination;
// it worked. Trust me.
}}}
Quality links you should know about, having to do with Maya, and mel:
----
'Official':
*[[Autodesk's|http://usa.autodesk.com/adsk/servlet/home?siteID=123112&id=129446]] site. (So long Alias...)
*[[Maya Documentation|http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=8782084]]: Online, for multiple versions.
*[[Current release notes|http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=13710407&linkID=10809894]]
*[[Autodesk Subscription|http://subscription.autodesk.com/sp/servlet/home/index?siteID=11564774&id=11595437]] - Need to log in here first
**[[Autodesk Maya e-Learning|http://subscription.autodesk.com/sp/servlet/elearning/course?catID=11484791&cfID=Autodesk+Maya&siteID=11564774]] - Can find podcasts buried in here.
**[[Ask Autodesk|http://forums.ask.autodesk.com/category.jspa?categoryID=1010]] forums.
<<<
These sites appear to be down, or lost to the Autodesk takeover.
*[[Autodesk Support & Service|http://pointa.autodesk.com/local/enu/portal/signin.jsp?po=enu]] (need to log in here first to access the next two items)
**The [[Maya Annex|http://www.alias.com/glb/eng/support/maya.jsp]]
**[[Maya Podcasts|http://www.alias.com/glb/eng/support/maya/podcasts/index.jsp]] (in the Annex)
<<<
*The [[AREA|http://area.autodesk.com/]]
*Maya's '[[Bonus Tools|http://area.autodesk.com/index.php/downloads_plugins/plugins_list/]]'
*[[Maya Station|http://mayastation.typepad.com/]] blog. (I *think* this can be considered official)
----
'Unofficial':
*[[Highend3d.com|http://www.highend3d.com/maya/]]'s Maya forums.
*[[CG Society|http://forums.cgsociety.org/]]'s (~CGTalk) Maya forums.
*[[Maya Wiki|http://www.tokeru.com/t]] over at tokeru.com. And they have a pile more links.
*[[Brian Ewerts|http://ewertb.soundlinker.com/maya.htm]] mel page (''great'' stuff!). I leared a lot from this page when I was just getting started with mel.
*[[python_inside_maya|http://groups.google.com/group/python_inside_maya?hl=en]] Google Group.
*List of [[Script Editor Replacements]].
----
'Books'
*Scripting\Programming:
**[[Complete Maya Programming|http://www.amazon.com/Complete-Maya-Programming-Extensive-Kaufmann/dp/1558608354/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1204144080&sr=1-1]]
***I've read this, recommended.
**[[Complete Maya Programming Volume 2|http://www.amazon.com/Complete-Maya-Programming-Vol-Depth/dp/0120884828/ref=pd_bbs_sr_3?ie=UTF8&s=books&qid=1202404517&sr=8-3]]
**[[MEL Scripting for Maya Animators|http://www.amazon.com/Scripting-Animators-Kaufmann-Computer-Graphics/dp/0120887932/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1202404517&sr=8-1]]
***I've read this, recommended.
**[[Processing: A Programming Handbook for Visual Designers and Artists|http://www.amazon.com/Processing-Programming-Handbook-Designers-Artists/dp/0262182629/]]
***I've read this, recommended. It focuses on the [[Processing|2008 02 18]] programming language (wrapper around Java), but it has direct application to mel, and is a great overview to the world of programming and scripting.
*Rigging
**[[Inspired 3D Advanced Rigging and Deformations|http://www.amazon.com/Inspired-3D-Advanced-Rigging-Deformations/dp/1592001165/ref=pd_bbs_11?ie=UTF8&s=books&qid=1202404700&sr=8-11]]
**[[Inspired 3D Character Setup|http://www.amazon.com/Inspired-Character-Setup-Michael-Ford/dp/1931841519/ref=pd_bxgy_b_img_b]]
***The 'Inspired' series of books always seem to have some good fundamentals in them. Good place to start for character setup \ rigging.
----
Got any other suggestions? Let [[me|WarpCat]] know...
In this example, we have an Python object (made via [[OOP|How does Object Oriented Programming work in Python?]]), but we want to access the attributes of that object //in Maya//, via ''mel''. How to do so? But also, //why// would you want to?
The why: For me, I have a lot of older mel scripts doing work. But now a-days I like to do all my scripting in Python. So what I end up with are new tools in Python, but being wrappered and executed with older mel procedures that still chug along. Plus, a Python 'object' is a much more complex data structure than a mel procedure: A mel procedure can only return back a single type of variable (string, int, float array, etc). But with a Python object you can query many different types of data, which is extremely handy. We are also limited again by mel's variable types: It has nothing of a greater complexity like Python's dictionaries, or 'lists (arrays) within lists within lists...'. So if you're using mel to query a more complex data structure in Python, you //first// need to prepare that data //in Python// to be received happily by mel. For example, mel has no idea what a Python dictionary is, so you'd first, in Python, need to split the dictionary apart into variable containers that mel could understand.
Here is a simple Python object, as defined by a class. Presume that this file is called {{{MayaTestObj.py}}} and it is saved in Maya's Python path. It has some float, string, and list variables ready to be queried, all mel consumable. From the above discussion, these values //could// have been derived from a more complex Python dictionary, and then made ready for mel consumption.
{{{
# Python code
# MayaTestObj.py
class MObject(object):
def __init__(self):
self.floatVar = 23.23
self.stringVar = "a string"
self.listVar = ["A", "list", "of", "strings"]
def __str__(self):
return "A Python object trying to live in Maya..."
def getFloat(self):
return self.floatVar
def getString(self):
return self.stringVar
def getList(self):
return self.listVar
}}}
Now, for interacting with it in mel. We have to use the mel {{{python}}} command to pull in the data we're after:
{{{
// mel code
global proc getPyObjData()
{
// First import the module:
python("import MayaTestObj");
// Create our object:
python("obj = MayaTestObj.MObject()");
// See what our object has to say for itself:
python("print obj");
// Now lets grab our variables:
float $floatVar = python("obj.getFloat()");
string $stringVar = python("obj.getString()");
string $listVar[] = python("obj.getList()");
// and print the results:
print ($floatVar + "\n");
print ($stringVar +"\n");
print $listVar;
}
getPyObjData();
}}}
{{{
A Python object trying to live in Maya...
23.23
a string
A
list
of
strings
}}}
It should be noted we're calling to the object's 'get' methods to access the attribute data. You //could// call directly to the attributes themselves if no 'get' methods existed, but this is slightly frowned upon, and the get methods should be provided:
{{{
float $floatVar = python("obj.floatVar");
// Result: 23.23 //
}}}
I installed ~UltraEdit. I like ~UltraEdit. In the process it asked me if it should be the 'default application' for HTML files. Sure, why not? Later in Maya, I try to access the docs either through the F1 help, or through the mel:
{{{
help -doc ls;
}}}
And I get an error window:
>Windows cannot find '~UltraEdit.htm\shell\open\command'. Make sure you typed the name correctly, and then try again.
This looks like a registry issue, and sure enough it was. Long story short, I needed to update (in Windows 7, via regedit.exe) this:
*Computer
**~HKEY_CLASSES_ROOT
***.htm
****(Default) : Data : Change to -> "~FirefoxHTML"
Since Firefox is my default browser. I've also seen "~ChromeHTML" for Google Chrome, and "htmlfile" for Internet Explorer.
Python has some shortcuts you can implement when passing arguments to mel commands (or other Python functions).
Take for example a sphere (named {{{pSphere1}}}) that you want to scale. Here is the basic mel to do so:
{{{
float $scaleVals[] = {1.0, 2.0, .5};
// optionA:
setAttr pSphere1.scale -type double3 $scaleVals[0] $scaleVals[1] $scaleVals[2];
// optionB also works:
setAttr pSphere1.scale $scaleVals[0] $scaleVals[1] $scaleVals[2];
}}}
Likewise in Python, you can do something very similar:
{{{
import maya.cmds as mc
scaleVals = [1, 2, .5]
# optionA:
mc.setAttr("pSphere1.scale", scaleVals[0], scaleVals[1], scaleVals[2], type="double3")
# optionB also works:
mc.setAttr("pSphere1.scale", scaleVals[0], scaleVals[1], scaleVals[2])
}}}
However, in Python you can pass all the args in as a list\tuple variable by prefixing the variable name with an asterisk '*'. The function (mel command) will unpack the variable as individual positional arguments. It's important that the variable have the same number of items as the command expects argument values, or a Python {{{RuntimeError}}} exception will be raised:
{{{
# option C, much cleaner!:
mc.setAttr("pSphere1.scale", *scaleVals)
}}}
You can see more about how this argument system works over on my [[Python Wiki|http://pythonwiki.tiddlyspot.com/#argument]].
I keep forgetting this, so I'll make a note:
Outliner -> Display -> Container Contents :
*Under Container
*Under Parent
*Both
*None
----
To modify to dispay 'under container':
{{{
outlinerEditor -e -showContainerContents 1 outlinerPanel1;
outlinerEditor -e -showContainedOnly 1 outlinerPanel1;
}}}
To modify to display 'under parent' (which I prefer...):
{{{
outlinerEditor -e -showContainerContents 0 outlinerPanel1;
outlinerEditor -e -showContainedOnly 0 outlinerPanel1;
}}}
From my bud Te:
----
When you get windows 7 and you use your pen (for weighting or whatever) you might enjoy disabling all the crap that 7 puts on the pen:
<<<
For those of you who use a wacom here is a link to help remove all of the annoyances dealing with windows 7.
http://forum.wacom.eu/viewtopic.php?f=3&t=2146
first .. start > (then search for) services.msc > scroll down until you find Tablet PC Input Serv...
(right click) : stop > (then right click) properties > startup type : disable.
<<<
{{{aimConstraint}}}s are great, much more stable for handling rotations than {{{orientConstraint}}}s or {{{parentConstraint}}}s (in my experience). But they too have their bugs.
One of the options you can set during creation is a '{{{worldUpType}}}', which can be set to a variety of options, one of which is '{{{object}}}'. This allows you to then set the {{{worldUpObject}}} to be the name of a given node. The result makes the local {{{upVector}}} of your constrained object aim at this '{{{worldUpObject}}}' //position//.
However, sometimes.... it doesn't aim correctly at all. Or, even worse, the //rotation// values of your '{{{worldUpObject}}}' effect the orientation of your aimed object, (poorly imitating the effect of having the {{{worldUpType}}} set to '{{{objectrotation}}}'). Why?
I've (finally) tracked this down: If you ''freeze transforms'' ({{{makeIdentity}}} command) on your '{{{worldUpObject}}}', it... screws up... the aim constraint system. Or, even more nefarious: If you're authoring 'locators' via code ({{{spaceLocator}}} command) to use as your {{{worldUpObject}}}, you have the option to give them a default '{{{position}}}'. This has the same effect as creating them at the origin, moving them to that position, and freezing their transforms there. {{{worldUpObject}}} = fail. You need to make them at the origin, and then translate them to their required position, and //not// freeze their transforms.
I haven't looked into the technicalities of the reasons behind this. But I'm happy enough just to know: ''don't freeze transforms on your '{{{worldUpObject}}}''' ;)
When using parentConstraints or orientConstraints, you run into the problem of the constraints 'flipping' during blended rotational transitions.
This usually occurs when the difference between the set of corresponding target rotational axes becomes greater than 180 degrees.
Meaning:
*If you have objectA constrained to targetA, you can rotate targetA all you want, way past +-180 degrees and all will be well.
*However, if you have objectA constrained to targetA and targetB, and a rotation axis on targetB is +-180 different from the corresponding axis on targetA, as you blend your constraint weights, rotational value flipping will ensue will ensue on objectA.
Why is this happening? My guess is that internally, the constraints are comparing and blending between the [[matrices|Matrix info]] of the two nodes. Since matrices store orientation data as vectors, those vectors have no concept of a '720 degree' (for example) rotation. They simply point in that direction, and blend between the shortest distance.
----
Solutions:
WIP :)
*Many constraints have the option of changing their {{{.interpType}}} attr to '{{{cache}}}'. I have see this fix the problem, but unfortunately animators don't like it, since it tends to screw up the 'scrubbing' of the animation: It behaves like dynamics, since the cached data is based on the frames before: if you scrub, the cache becomes confused and introduces other glitches.
I've heard of this both happening in a Maya session, and in API calls.
I've been told that if an expression doesn't 'reference an attribute', then it can cause it to not always execute. And by 'reference an attribute' I mean:
{{{
foo.tx = 5;
}}}
In that expression, there is a clear reference to an attribute.
However, you could run into trouble here:
{{{
move 0 5 0 foo.tx;
}}}
Sine you're now calling to the move command to do the work, and not referencing an attribute directly.
A hack I'm told that works is to simply have the expression update some dummy attr on a node. You could create a dummy attr specifically for this, or set the visibility on a node:
{{{
move 0 5 0 foo.tx;
foo.dummy = 1;
}}}
(from Doug Brooks)
On Windows XP, this can happen. The common fix is this:
*Find your maya.exe file:
{{{
C:\Program Files\...\MayaX.X\bin\maya.exe
}}}
*'Right Mouse Button' on it -> Properties -> Compability
*Check "Run this program in compability mode for:"
*And choose "Windows 2000"
Sometimes when you try to load a plugin, it may toss some esoteric error\warning and fail. Maya plugins (.mll's) are really just .dll's (Dynamic Link Libraries). The Maya .mll may be dependent on other dll's on your disk, that you don't have yet installed (the same way that scripts can point to other scripts\procs). But knowing what your mll is missing can be hard.
''Until now:''
*http://www.dependencywalker.com/
This gives you the (free) "depends.exe" application that will break down what a mll (or dll) is looking for, so you can figure out if you're missing it or not.
Thanks to Doug Brooks for tip.
Your last line may be missing a semicolon: {{{;}}}
*Example code, that would have a problem:
{{{
// this is a script called foobar.mel
proc foo(){
print "bar\n";}
foo // <--- missing the semicolon
}}}
{{{
source foo;
// Error: foo //
// Error: "C:/scripts/foo.mel" line 4.3: Syntax error //
}}}
*The last line, "foo" has no semicolon after it. This will cause Maya to error when either the script is sourced, (or, if 'proc foo()' was instead 'global proc foo()', then it would error when executed). Add the semicolon, it'll be happy:
{{{
// this is a script called foobar.mel
proc foo(){
print "bar\n";}
foo; // <--- has the semicolon
}}}
{{{
source foo;
bar
}}}
Below is a bunch of notes I took while listening to a presentation done by ''Andrea Maiolo'' & ''Tim Naylor'' on their //Bi-directional constraint// rigging system. Stuck them here mainly just to put them //somewhere// where I wouldn't loose them. Really, these notes have nothing to do with that constraint system, and more of just a big brain-dump of technical Maya know-how.
<<<
* Two passes in the DG:
** The Push Update Request: 'dirty propogation' pass,
** Pull Latest Computed Data
* Coordinate Spaces:
** Euclidean - Infinite planes.
** Cartesian - 3d scene with origin. Ordered valued system in the Euclidean system. Maya viewport is a Euclidean to Cartesian conversion.
**Polar
**Cylindrical
** Homogenous
* Handedness : Cartesian to Euclidean conversion: Maya is 'right handed':
** Right hand in front of you: Thumb is up, pointer is forward, middle finger is left:
** Pointer is +X, thumb is +Y, middle is -Z. An 'orthagonal' coordinate system.
** Positive rotation values go counter-clockwise.
* Vectors: a direction with a magnitude.
** Dot Product : Compare directions of two vectors.
*** Normalize vecs before comparing.
*** Returns value between -1 and 1: -1 = parallel but opposite directions.
*** 0 = not parallel. 1 = parallel.
** Cross Product: Perpendicular vector of two input vecs
*** Normal vecs first
*** Useful for building orthogonal transforms from vectors.
MEL - sees things as vectors. API distinguishes between vectors & points.
* Homogenous Coordinates : Used to descrive a projection onto an infinite plane. Find a point on an infinitly plane, and you need to convert it to cartesian space, you use the homogenouse coordinate to convert it.
** Allows for euclidean to cartesian conversion.
** From infinite space (euclidean) to finite space (cartesian) requires 4 dimensions
** Positions in space represented as X, Y, Z, W
*** W = Scalar for convertsion from euclidean to cartesian.
** Vector : direction with arbitrary origin, inifitely long
** Point : position from a given origin
** Homogenous vector = << X, Y, Z, 0>>
** Homogenous point = X, Y, Z, 1
* The base of all rotations in Maya are vectors.
* Each Unit vector has it's own XYZ components. Angle of rotations is calculated from these vectors.
**A matrix is a structure for holding multiple elements that can be recorded or extracted easily and represents useful information to the end user.
* The fourth (right) column in a 4x4 matrix holds the homogenous scalar values.
* Each matrix has its own identity, transpose (flip diagonally), and inverse matrix (swapping rows and colums).
* Can be multipled but are not associateve: B*A != A*B
* Multiply matrices in Maya from row to column
* Transformation matrices are post-multiplied in Maya
Build a point constraint between cube and pyramid:
* Make VectorProduct node
** has matrix input
* Take worldmatrix attr from your cube and feed into matrix input of vectorProduct node
* Change vectorProduct node operation to 'point matrix product': Will extract the translation of the worldmatrix.
* The output of the vectorProduct node is the worldspace translation values of the cube.
* Make another vectorProduct node, and connect pyramid's parent inversive matrix into it:
**This tells me where the parent of the pyramid is in worldspace. The path the parentmust take to get back to the origin.
* Connect the first vectorProduct nodes output (cube's worldspace translation) into the second vectorProduct nodes Input1. Now in the second vectorProduct node you'll see the worldspace translation values in it's input1 section in the attr editor.
* Set second vector prodcut node 'operation' to 'Point Matrix Product'. Which will set it's output to basically be the local space of the pyramid.
* Connect the output from the second vector product and connect it to the translation of the pyramid.
* Pyramid now follows cube!
When you use getAttr to query a matrix in Maya, it causes the DG to eval to give
you the most current data. How do you query this without making the DG eval? the .matrix attr of a node is output only, so it is only updaed (causing the DG to update)
when it is queried.
* Invert the parent invert matrix gives you the parent matrix.
**The parent matrix * node matrix = where the node is in worldspace.
* To apply this to a 'driven node', you multiply the driven nodes inverseParentMatrix times the world-matrix previously generated to get its new local position.
<<<
If you use Maya, and mel, then your brain may identify with these links. Pulled from my personal collection, enjoy.
*http://www.makezine.com/
*http://www.ted.com/
*http://www.instructables.com/
*http://www.processing.org/
*http://www.notcot.org/
*http://www.ponoko.com/
*http://graphics.pixar.com/
*http://www.buglabs.net/
*http://www.openmoko.com/
*http://www.arduino.cc/
*http://www.solarbotics.com/
*http://www.chumby.com/
*http://www.python.org/
*http://www.motionmountain.net/
*http://ocw.mit.edu/OcwWeb/
*http://www.metacritic.com/
If you use the version control software [[Perforce|http://www.perforce.com]] (P4), and you're reading this, you probably want a way to have Maya talk with it. In the past I've written a large mel script to call out to the system and manage P4. But since the release of Maya2008 which has Python on-board, we might as well ride on the shoulders of giants.
Perforce offers up tools to let Python talk with it. I've documented this on my Python Wiki here:
{{{http://pythonwiki.tiddlyspot.com/#[[Perforce%20access%20via%20Python]]}}}
(due to formatting reasons, I couldn't get the link to work. Please copy-paste)
However, to get this working in Maya takes an extra step. Since Maya runs it's own custom cut of Python, it has those executables saved in a different location from the normal Python install. When you run the P4\Python installer executable, it'll put the resources in a location that Maya doesn't see it. ~Maya-Python looks here for 'site-packages' in Python:
{{{
C:\Program Files\Autodesk\Maya<version>\Python\lib\site-packages\
}}}
While 'raw' Python looks here:
{{{
C:\Python<version>\Lib\site-packages\
}}}
When you install P4\Python integration, they go into the //Python// site-packages, not the //~Maya-Python// site-packages. Copying over the files (listed in my Python Wiki link) seems to work a-ok.
//Or//, you could make another script dir in your ~Maya-Python path, and put the modules there of course.
Note: I have an older subject found [[here|Executing external functions via UI's authord in Python]] that also sheds light on this issue.
<<<
''Update'', bit of an addendum to the '{{{lambda}}}' code below: The Maya 2010 docs describe using the Python module [[functools|http://docs.python.org/library/functools.html]] under '[[Custom UI using a Python class|http://download.autodesk.com/us/maya/2010help/index.html?url=WS73099cc142f48755f2fc9df120970276f7-2158.htm,topicNumber=d0e183276]]'. Specifically using {{{functools.partial}}}
Here's a code snippet:
{{{
import functools
# inside your UI:
mc.button(command=functools.partial(self.myMethod, someVal))
# presuming myMethod() was expecting a single arg
}}}
I've found that this will work in rare situations when {{{lambda}}} won't.
<<<
When creating UI controls, one of the several ways of accessing the data is via "positional arguments". This is data the control can pass back to callback functions when executed (via say, their "changeCommand", or "command" argument). For example, if you make a {{{floatFieldGrp}}} with three fields, you can use 'positional arguments' to directly intercept those values when modified. But how can you actually do this?
----
The Maya docs loosely cover this here:
''Maya Help -> Using Maya -> General -> Python -> Python -> Using Python -> Positional arguments'' (at the bottom).
Here is a link to the [[online docs|http://download.autodesk.com/us/maya/2008help/General/Using_Python.html]] reference above. Read that section to get an understanding if the two different methods available.
----
In a nutshell:
*Method #1: Embedding the callback in a string, using Python string formatting to grab the results:
{{{
mc.floatSlider( cc="print '%(1)s'" )
}}}
**The Python '{{{%(1)s}}}' syntax says capture the first {{{(1)}}} positional arg, and convert it to a string {{{%s}}} for printing. The UI control returns its positional args as a Python {{{dictionary}}}, that are then passed into the string formatting.
**Given the above example, and presuming that the {{{floatSlider}}} was dragged and produced a value of 3.65, this would be a representation of how the command was being formatted behind the scenes, just before execution:
{{{
print '%(1)s' % {'1': 3.65}
# In the dictionary {}, the '1' represents the first positional arg,
# and 3.65 is the value assigned to it.
}}}
**See Python docs [[online|http://docs.python.org/library/stdtypes.html#string-formatting-operations]]
*Method #2: Uses a //compiled function//, where Maya passes the control's positional arguments to the function. It's important that your callback function can handle any number of args (usually via {{{*args}}}) since depending on the control type, it can return back a different number of args.
{{{
def genericCallback( *args ):
print( "args: " + str ( args ) )
mc.button( command=genericCallback )
}}}
It should be noted that using method #2, I've found two different approaches: using the compiled function technique (above), //or// wrappering the compiled function in the Python {{{lambda}}} expression. What is {{{lambda}}} you may ask? See notes on my [[Python Wiki|http://pythonwiki.tiddlyspot.com/#%5B%5BUnderstanding%20lambda%5D%5D]]. Easier than describing it all here :)
----
Here's a practical example in a UI, using all three methods on a random selection of UI controls, with notes afterwords:
{{{
import maya.cmds as mc
class PyTempWindow(object):
def __init__(self):
self.showWindow()
def genericCallback(self, *args):
print "Callback args: " + str(args)
def showWindow(self):
if mc.window("PyTempWindow", exists=True):
mc.deleteUI("PyTempWindow")
mc.window("PyTempWindow", title="Positional arg callback UI", resizeToFitChildren=True)
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
mc.text(label="Printed Positional Args:", align="left")
mc.floatSlider(changeCommand="print 'Positional args: %(1)s'")
mc.floatFieldGrp(numberOfFields=3, changeCommand="print 'Positional args: %(1)s %(2)s %(3)s'")
mc.textField(changeCommand="print 'Positional args: %(1)s'")
mc.button(command="print 'Positional args: %(1)s'", label="Positional")
mc.textFieldButtonGrp(changeCommand="print 'Positional args: %(1)s'", buttonCommand="print 'Positional args: %(1)s'")
mc.separator(height=20)
mc.text(label="Callback Function:", align="left")
mc.floatSlider(changeCommand=self.genericCallback)
mc.floatFieldGrp(numberOfFields=3, changeCommand=self.genericCallback)
mc.textField(changeCommand=self.genericCallback)
mc.button(command=self.genericCallback, label="Callback")
mc.textFieldButtonGrp(changeCommand=self.genericCallback, buttonCommand=self.genericCallback)
mc.separator(height=20)
mc.text(label="Lambda Callback Function:", align="left")
mc.floatSlider(changeCommand=lambda *args:self.genericCallback(args, "Custom Lambda Arg"))
mc.floatFieldGrp(numberOfFields=3, changeCommand=lambda *args:self.genericCallback(args, "Custom Lambda Arg"))
mc.textField(changeCommand=lambda *args:self.genericCallback(args, "Custom Lambda Arg"))
mc.button(command=lambda *args:self.genericCallback(args, "Custom Lambda Arg"), label="Callback")
mc.textFieldButtonGrp(changeCommand=lambda *args:self.genericCallback(args, "Custom Lambda Arg"), buttonCommand=lambda *args:self.genericCallback(args, "Custom Lambda Arg"))
mc.showWindow()
win = PyTempWindow()
}}}
So what's going on the the above UI?
*In the first "positional arg" section, the commands are embedded directly into the UI itself, using positional arguments and Pythons string formatting notation.
*In the middle section, the commands are calling to a callback function. Note in the syntax that the callback function isn't being executed in the control command (there is no parenthesis at the end of it: '{{{changeCommand=self.genericCallback}}}'), there is just a pointer back to the callback function. This is because a UI control command can only execute strings (the first example) and functions //without arguments// (this example).
*In the bottom section, commands are calling to the callback function, but that's being wrappered in a {{{lambda}}} expression. Things of note:
**{{{lambda}}}'s return 'anonymous functions': A function object that can then be executed.
**'{{{lambda *args}}} : this lets the {{{lambda}}} intercept all incoming arguments, and then pass them on to their returned function.
**Since {{{lambda}}}'s //are// 'anonymous functions', this lets then both accept //and// 'pass on' arguments to their containing functions, circumventing the issue of UI's 'only being able to execute functions without args'. Nice. In the above examples '{{{lambda *args:self.genericCallback(args, "Custom Lambda Arg")}}}', you can see that the {{{lambda}}} both passes on any //positional args// provided by the UI control when executed, and its own arg: {{{"Custom Lambda Arg"}}}.
----
''What's the result of all of this?''
----
Print result from ''Printed Positional Args'' section, with random UI fiddling (with UI type listed before each):
{{{
# floatSlider:
Positional args: 5.28
# floatFieldGrp:
Positional args: 0.0 3 0.0
# textField:
Positional args: foo
# button:
Positional args:
# textFieldButtonGrp (textField)
Positional args: asdf:
# textFieldButtonGrp (button)
Positional args: %(1)s
}}}
Notes:
*The {{{button}}} returns an empty positional arg.
*The {{{textFieldButtonGrp}}} button returned back a literal string of the string formatting instead of an actual positional arg value. Odd. Maybe this is because it actually returns back no positional arguments at all (illustrated below).
----
Print result from ''Callback Function'' section, with random UI fiddling (with UI type listed before each):
{{{
# floatSlider:
Callback args: (u'6.6',)
# floatSliderGrp:
Callback args: (u'0.0', u'3', u'0.0')
# textField:
Callback args: (u'asdf')
# button:
Callback args: (u'')
# textFieldButtonGrp (textField):
Callback args: (u'asdf')
# textFieldButtonGrp (button)
Callback args: ()
}}}
Notes:
*The return value is captured as a Python {{{tuple}}}. A {{{tuple}}} is an immutable (unchangeable) list. Based on our {{{genericCallback()}}}, each item in the {{{tuple}}} is turned into a string for printing purposes.
*Like above, the {{{button}}} returned an empty positional arg.
*Here, it can be plainly visualized that the {{{textFieldButtonGrp}}} 'button' returned back //no positional args// to be captured.
----
Print result from ''Lambda Callback Function'' section, with random UI fiddling (with UI type listed before each):
{{{
# floatSlider
Callback args: ((u'11.222',), 'Custom Lambda Arg')
# floatSliderGrp
Callback args: ((u'0.0', u'3', u'0.0'), 'Custom Lambda Arg')
# textField
Callback args: ((u'asdf',), 'Custom Lambda Arg')
# button
Callback args: ((u'',), 'Custom Lambda Arg')
# textFieldButtonGrp (textField):
Callback args: ((u'asdf',), 'Custom Lambda Arg')
# textFieldButtonGrp (button)
Callback args: ((), 'Custom Lambda Arg')
}}}
Notes:
*The return is very similar to the 'Callback Function' section. But note that rather than printing a list filled with multiple items, the 1st position is another {{{tuple}}}, which has specifically captured the positional arguments from the UI command.
*Since {{{lambda}}} lets us pass our own arguments into the callback, in addition to capturing the //positional arguments// of the control itself, our custom {{{"Custom Lambda Arg"}}} string is listed last.
*This gives another clear indication that the {{{button}}} returns back an empty positional arg, while the {{{textFieldButtonGrp}}} button command returns back no positional args at all.
''Procedures'' (WIP)
*[[procedures|Procedures]]
**__Can contain__ (when defined):
***[[commands|Commands]]
***//executed// [[procedures|Procedures]] (no nested procedure definitions)
**__Can accept__ (when executed, depending on the procedure):
***procedure [[arguments|Arguments]]
**__Can provide__ (when executed, depending on the procedure):
***[[return values|Return Values]] (as arguments to other procedures)
----
*Procedures are also known as 'functions' in other languages.
*Procedures can be both defined, and executed. When defined, they can be //local// to the [[script|Scripts]] they were defined in, or //global//, meaning they're able to be seen by the outside world. A script needs to be first sourced before any of the 'global procs' defined inside of it can be seen by external code. However, "local" procedures are never seen outisde of the script they're defined in.
** Why would you want to use local procs? : To help remove possible name clashes: Any global proc can be seen by any other proc or command. But sometimes you may just want a little "helper" bit of code in a script, and it's not important that can't be seen by anything else outside of the scope of the script.
*A procedure definition can be filled with mel commands, or calls to other pre-defined procedures. Procedures can be called in other procedures, but you can't 'nest' procedure definitions.
*Once a procedure is defined, it behaves just like a command.
*For example, pretend we have a script called foo.mel, and inside of it we'll make a global procedure definition, that contains one command ({{{print}}}):
{{{
// this is the script foo.mel
// this is a local proc, that is only seen inside of this script:
proc goo(){
print "local goo!\n";}
// this is the global proc, that has the same name as the script,
// so when the script is called to, this proc is executed:
global proc foo()
{
// call to our local proc.
goo;
print "foo!\n";
}
}}}
*After this script is saved on disk (and presuming it's been saved in Mayas [[script path|Script Paths]]), you need to make Maya aware of its existance. You can either use the source command like so:
{{{
source foo.mel;
}}}
*Or highlight the block of code in the Script Editor, and define it using the //number pad// Enter button.
*This won't actually //execute// the procedure, it will simply let Maya know it exists, and later //be// executed.
*Once define, you can execute your {{{foo()}}} procedure (which lives in the {{{foo.mel}}} script) by typing it at the command line:
{{{
> foo;
local goo!
foo!
}}}
* If you typed {{{goo}}} at the command line, you'd get an error:
{{{
> goo;
// Error: line 1: Cannot find procedure "goo". //
}}}
*Because {{{goo}}} is a local proc in script {{{foo.mel}}}.
**This COULD bring up some confusion though: If you're editing this code in the script editor, highlight it, and execute that block, irregarless of wheter you specify a script to be local or global, it will become global during that Maya session. However, restarting Maya will make the local scripts be local, and you can see this behavior.
You can run Python threads in Maya. The key is via Maya's {{{maya.utils.executeInMainThreadWithResult}}} function, which is illustrated below. This allows you to run code that won't 'hang' Maya while its executing.
The below example came from the "Practical Applications of Maya Python in Game Production" GDC 2010 Masterclass by Adam Mechtley & Ryan Trowbridge.
*http://area.autodesk.com/gdc/class2 {{{<--}}} Masterclass video
*http://209.216.55.247/~www/gdc10/python/Python_downloadable_materials_March1.zip {{{<--}}} Source Files
*http://docs.python.org/library/thread.html {{{<--}}} Python {{{thread}}} docs.
*[[Maya threading docs|http://download.autodesk.com/us/maya/2010help/index.html?url=Python_Python_and_threading.htm,topicNumber=d0e182779]]
I've actually modified it quite a bit so it makes more sense to my brain:
{{{
import thread
import maya.utils as utils
from functools import partial
import os
def myFunc(path):
"""
Some function to execute.
"""
# Get a list of files to pas to myFunc():
fileList = os.listdir( path )
for file in fileList:
print file
def myFuncThreadWrapper(path):
"""
Define a function that wrappers myFunc() so it can
be ran in a Python thread.
"""
# Wrapper the myFunc() function in functools.partial(),
# which allows it to be passed to Maya's threadding function:
myfunc = partial(myFunc, path)
# Execute our wrapped function in Maya's main thread:
utils.executeInMainThreadWithResult(myfunc)
path = "C:/Program Files/Autodesk/Maya2010"
# Run the function in the thread. Args are
#('function', ('tuple of', 'function args') )
thread.start_new_thread(myFuncThreadWrapper, (path,))
}}}
----
Notes from their presentation:
*Maya does not allow for multiple Python thread. So there is no advantage of running multiple simultaneous threads, since Maya will execute them in order.
*Executing any Maya commands \ API call is not allowed in a Python thread. If you need to, use the {{{maya.utils}}} function {{{executeInMainThreadWithResults}}}.
*{{{executeInMainThreadWithResults}}} calls a function that is executed in the main Maya thread.
*Calling the function {{{executeInMainThreadWithResults}}} more than one at a time will crash Maya.
*Use a Python {{{functools.partial}}} or {{{lambda}}} function to pass the callable function with arguments to {{{executeInMainThreadWithResults}}}.
| Maya | Python | Qt |
| 8.5 | 2.4.3 | |
| 2008 | 2.5.1 | |
| 2009 | 2.5.1 | |
| 2010 | 2.6.1 | |
| 2011 | 2.6.4 | 4.5.3 |
Starting in Maya 2011, Autodesk changed the UI system to [[Qt|http://qt.nokia.com/]] (made by Nokia). I'm not 2011 yet... but I found this snazzy video blog discussing some of the high level features:
*http://area.autodesk.com/blogs/stevenr/maya_2011_highlight_qt_user_interface
The {{{sceneEditor}}} command makes an editor that shows the references in the scene. It's the bottom part of the 'Reference Editor' UI.
The name of Maya's {{{sceneEditor}}} is stored in:
{{{
global string $gReferenceEditorPanel;
}}}
From the docs, make a new one:
{{{
# Python code
import maya.cmds as mc
window = mc.window()
mc.paneLayout()
mc.sceneEditor()
mc.showWindow(window)
}}}
New to Maya //2010//, you can see the docs [[here|http://download.autodesk.com/us/maya/2010help/index.html?url=WS73099cc142f48755f2fc9df120970276f7-2158.htm,topicNumber=d0e183276]].
This is done via the Python {{{maya.mel.createMelWrapper}}} function.
Why? For passing in Python functions to parts of Maya that only accept mel procedures (like marking menus, or in the container node, the RMB Command attribute under the Context Properties section only accepts MEL procedures).
@@Note:@@ Wing can interact with Maya in three different ways. This is one of the three. See an overview here on the whole system: [[Interaction between Wing and Maya]]
----
I currently do all my Python authoring in [[Wing|For Python: Maya 'Script Editor' style IDE]] (professional). As explained in other subjects (see note above) I have it setup so I can execute commands in Wing, and send them to Maya, which allows me to prettymuch bypass the script editor. More or less.
There are some instances where I need to leave the code in wing, but have it act on a physical Maya file in Maya. To do this, Wing lets you setup remote debugging: What does this give you? You set break-points in you code in Wing. You execute Python code in Maya. When the break-point is hit, Maya halts, and you can then start debugging in the Wing window, but physically querying the data from the open Maya session.
>Since I've authored this, I found that Maya's official documentation has been updated as well to show this process, [[here|http://download.autodesk.com/us/maya/2010help/index.html?url=WS73099cc142f48755f2fc9df120970276f7-2158.htm,topicNumber=d0e183276]] (under 'Using Wing IDE with Maya ')
Here's how to set it up:
----
*Copy these two files to the directory that the module you want to debug lives in:
{{{wingdebugw}}} (no extension) from Wing's [[user settings directory|http://www.wingware.com/doc/install/user-settings-dir]]
{{{
XP:
C:\Documents and Settings\<userName>\Application Data\Wing IDE 3\wingdebugpw
Vista:
C:\Users\<userName>\AppData\Roaming\Wing IDE 3\wingdebugpw
}}}
{{{wingdbstub.py}}} from the Wing IDE installation directory
{{{
XP:
C:\Program Files\Wing IDE X.X\wingdbstub.py
Vista:
C:\Program Files (x86)\Wing IDE X.X\wingdbstub.py
}}}
*It is important that in {{{wingdbstub.py}}} on around line 90, you set '{{{kEmbedded = 1}}}'. Elsewise, things just may not work so well....
*In the module you want to debug, you need to import {{{wingdbstub.py}}}, since this tells Wing where to start debugging:
{{{
# myModule.py
import wingdbstub
def main():
# code...
}}}
*In your Wing preferences, under 'Debugger: External/Remote', you need to turn ''on'':
**'Enable Passive Listen'
**'Kill Externally Launched'
*You may also need to also turn ''on'' 'Enable Passive Listen' in the Wing 'Debugger' menu, which is the "bug-looking button" at the very bottom-left of the UI (better safe than sorry)
*In your Wing Project Properties UI, make sure that you have the Python Executable set to Maya's version of Python. For Maya 2008, it lives here:
{{{
c:\Program Files\Autodesk\Maya2008\bin\mayapy.exe
}}}
To start the debugging, set some breakpoints in your module in Wing.
In Maya, then import and execute your code:
{{{
import myModule
myModule.main()
}}}
*Note: Do //not// {{{import wingdbstub}}} in this code.
At which point Maya should 'hang' at each breakpoint Wing hits, and you can start checking the Stack Data (etc) in Wing while running the debugger.
That's more or less it. The connection between the two apps seems a bit twitchy though, so it often times requires a restart of Maya to make the systems reconnect again.
Notes:
*If you "Stop Debugging" in Wing, it will shut down Maya (based on the above set prefs). If debugging finishes successfully, Maya will stay open (in theory).
*Really bad errors can break the connection to Maya, at which point you'll need to restart Maya, to get the connection back.
This tip comes from over at the [[Maya Station|http://mayastation.typepad.com/maya-station/2010/01/remove-reference-edits.html]] blog:
<<<
If you have made changes to your reference file which has then caused incorrect results, For example making changes to an expression then saving the reference file causes you to lose your expression information; simply reloading the reference file will not be enough to get the original data back.
You will need to remove the wrong edits. In order to do this you need to unload your reference from the Reference Editor, and then go to Reference Editor ->List Reference Edits. In the window that pops up select the wrong edits and click on Remove Selected Edits. Once this is done you can Reload you reference, through the reference editor.
<<<
Listed in no particular order...
----
''JEDIT''
jEdit is a free (and full featured) text editor, which has tools authored to let it interface directly with Maya (via the Maya's {{{commandPort}}}), //irregardless// of Maya version. That's pretty nice. As of this date, I've started using it with some success.
*Download jEdit: http://www.jedit.org/
*Get mel and 'python mel' context sensitive highlighting for it at [[Highend3d.com|http://highend3d.com/maya/downloads/tools/syntax_scripting/2464.html]]
*Get the jEdit plugin that lets it talk to Maya at [[Highend3d.com|http://highend3d.com/maya/downloads/tools/syntax_scripting/jEdit-Maya-Editor-Plugin-3732.html]]
*You'll need the jEdit [[InfoViewer plugin|http://plugins.jedit.org/plugins/?InfoViewer]] too.
*And a tutorial on how to get it setup on [[Highend3d.com|http://highend3d.com/maya/tutorials/using_tools_scripts/Configuring-jEdit-with-Maya-319.html]] (you //really// need to read and follow this)
----
''MAPY''
Mappy isn't a text editor, but rather a free set of tools that let 'any' (so it states) text editor interface with Maya. Similar to what jEdit is doing above. I have yet to test this.
*Official mappy homepage: http://rodmena.com/mapy/ (was down last I checked)
*Download mappy here: http://www.highend3d.com/f/mapy
----
''MEL STUDIO PRO''
I've been using [[Mel Studio Pro|http://www.maxliani.com/technology/MelStudioPro_MainEn.html]] for years. Purchase and download it [[here|http://www.pluginz.com/product/11765]]. It is in my opinion the best and easiest option for scripting in Maya. It doesn't have all the debugging features of a full blown text editor, but it's convenience can't be beat.
It is a plugin in Maya (which costs $$$), that provides a Script Editor replacement, that has many features of a full-blown text editor, but lives in Maya. Check the features from the link. I can't work without it now...
[img[http://www.maxliani.com/technology/images/MelStudioProUI1.jpg]]
----
''Maxya Scripter''
http://www.tarzworkshop.com
I had this one recommended, but I haven't had a chance to test it yet. Looks like another standalone IDE with hooks into Maya, for both mel and Python. There is both a free, and $$$ version.
[img[http://www.tarzworkshop.com/images/maxya/editor_billboard3.gif]]
----
''Also see'':
*[[For Python: Maya 'Script Editor' style IDE]]
''Scripts'': (WIP)
*[[scripts|Scripts]]
** __Can contain:__
***[[commands|Commands]]
***//executed// [[procedures|Procedures]] (//global// procedures defined in //other// scripts, or //local// procedures defined //previously// in the //current// script)
***[[procedure|Procedures]] //defintions//
*A 'script' is an actual file on disk, that ends in {{{.mel}}}. Like {{{foo.mel}}}. Or it could be a button on the shelf.
*Scripts can hold these things:
**commands ({{{xform}}}, {{{ls}}}, {{{move}}}, etc...)
**procedure //definitions// (whether 'local' or 'global'). Often times people can mistake commands for procedures, and vice vrsa.
****If there is a //global// procedure inside of a script, with the same name as the script name, when the script is executed, so will the procedure. This is a very common way of working.
**//executed// procedures
Ran across this recently, had to make a note:
!!!Servo Tools for Maya
>"Servo Tools For Maya is a Python Plugin that sends rotational values over USB to the Arduino Micro Controller. These values are then converted into Pulse Width Modulation which is used to control multiple Hobby RC Servo Motors."
>"Applications for the plugin are only limited to your imagination. Some popular examples could be to drive complex animatronic puppetry or kinetic sculpture art installations."
Homepage:
http://danthompsonsblog.blogspot.com/search/label/Servo%20Tools%20For%20Maya
Download:
http://www.creativecrash.com/maya/downloads/scripts-plugins/utility-external/export/c/servo-tools-for-maya
Also requires:
http://pyserial.sourceforge.net/
I like Maya to always have the same grid settings, irregardless of scenes I open (sometimes opening a scene will change my grid). I usually wrapper the below code in a {{{scriptJob}}} to be executed by my {{{userSetup.py}}}. The details of all of this can be found in this mel script:
{{{
C:\Program Files\Autodesk\MayaXXXX\scripts\startup\performGridOptions.mel
}}}
{{{
# Python code
import maya.cmds as mc
size = 1000.0
spacing = 100.0
divisions = 10
axesColor = 1 # black
gridLineColor = 2 # dark gray
divisionLineColor = 3 # mid gray
mc.optionVar(floatValue=['gridSize', size])
mc.optionVar(floatValue=['gridSpacing', spacing])
mc.optionVar(intValue=['gridDivisions', divisions])
mc.displayColor('gridAxis', axesColor )
mc.displayColor('gridHighlight', gridLineColor)
mc.displayColor('grid', divisionLineColor)
# Update the grid command with the value set above to see
# an immediate change to the scene:
# mc.grid('lots of stuff...')
}}}
<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal 'YYYY 0MM 0DD '>><<saveChanges>><<tiddler TspotSidebar>><<slider chkSliderOptionsPanel OptionsPanel 'options »' 'Change TiddlyWiki advanced options'>>
<<tabs txtMainTab Tags 'All tags' TabTags >>
<<tabs txtMainTab Dummy Dummy TabDummy Timeline Timeline TabTimeline All 'All tiddlers' TabAll More 'More lists' TabMore>>
- Your Maya mel resource on the web -
[img[mel wiki|http://farm1.static.flickr.com/177/480356457_cb8f44a0f3.jpg?v=0][Welcome]] mel wiki
@@NOTE:@@ Wing has been updating their own tutorials on this, take a look at them here:
http://www.wingware.com/doc/howtos/maya
A some of //their// tutorial came from my troubleshooting with their support team (which is great by the way), and actually links back to this wiki :-)
The tutorial below were steps I was taking to get this working. But don't use the below data... use what's on Wing's site.
----
Tutorial done on Windows Vista.
This will get 'source analysis' of the Maya API working in Wing. Will allow for such nice things as auto-completion. Here are some links I found on the subject, but none of them got me exactly to where I needed to be. But they all helped ;)
*http://wingide.com/doc/edit/helping-wing-analyze-code
*http://wingware.com/pipermail/wingide-users/2008-May/005398.html
*http://groups.google.com/group/python_inside_maya/browse_thread/thread/8f0923d15747aa8e?pli=1
!Get the API ~OpenMaya calls working:
*Make 'pi file' dir here:
{{{
C:\Users\epavey\AppData\Roaming\Wing IDE 3\pi-files\maya
}}}
*Copy the Wing-made .pi files to that dir from here:
{{{
C:\Users\epavey\AppData\Local\Wing IDE 3\pi-cache\2.6\C\Program Files\Autodesk\Maya2010\Python\Lib\site-packages\maya
}}}
*Remove all the underscores from the front of the .pi files:
**{{{_OpenMaya.pi}}} rename to {{{OpenMaya.pi}}}
*In Wing's prefs, go : Source Analysis -> Advanced -> Interface File Path -> Insert.
**And add the directory you made above.
*Restart Wing
*Now, when you enter the below code, Wing should start to auto-populate the command list:
{{{
import maya.OpenMaya as om
om. # <-- Wing should now populate that attribute list.
}}}
Note, it seems like the below //won't// work. You need to use the above example:
{{{
import maya.OpenMaya
maya.OpenMaya. # <-- no auto-complete :(
}}}
!Get the mel.cmds calls working:
At the time of this authoring, it appears that it's a 'bug' in Wing that doesn't let it see the maya.cmds pacakge. However, after working with Wing they made a patch, that should be available in future versions. So the below data really doesn't have any effect... yet...
*You'll need to make your own .pi file for the {{{maya.cmds}}} package. To do that, first you'll have to get Maya running //inside// of wing. See this wiki post: [[How can I execute Maya's version of Python outside of Maya?]]
*Use this code to auto-generate the .pi file. You can see my blog post [[here|http://www.akeric.com/blog/?p=828]] on more of the specifics.
{{{
import maya.cmds as mc
filepath = 'c:/temp/cmds.pi'
f = open(filepath, mode='w')
for k in sorted(mc.__dict__.keys()):
f.write('def %s():\n'%k)
f.write(' pass\n')
f.write('\n')
f.close()
}}}
*Copy the {{{cmds.pi}}} file to the directory you made above under 'Get the API ~OpenMaya calls working'.
*And then.... (currently not working.... )
String formatting has been available in //Python// for a while. Maya (as of 2010, maybe earlier?) has it as well via the {{{format}}} command.
Example time:
!!!Mel:
*Docs on Maya ''mel'' [[format command|http://download.autodesk.com/us/maya/2010help/Commands/format.html]]
{{{
string $myStr = "Formatting with position ^1s and ^2s vals.";
string $updateStr = `format -stringArg "one" -stringArg "two" $myStr `;
print $updateStr ;
}}}
{{{
Formatting with position one and two vals.
}}}
!!!Python:
*Docs on Maya ''Python'' [[format command|*http://download.autodesk.com/us/maya/2010help/CommandsPython/format.html]]
Using Maya's {{{format}}} command:
{{{
import maya.cmds as mc
myStr = "Formatting with position ^1s and ^2s vals."
updateStr = mc.format(myStr, stringArg=('one', 'two') )
print updateStr
}}}
{{{
Formatting with position one and two vals.
}}}
----
But, if you're going to be using the {{{format}}} //concept// in Python, you might as well use it's own system, since it's much more powerful once you get into it.
*Docs to Pythons own [[string formatting|http://docs.python.org/library/stdtypes.html#string-formatting]]
{{{
myStr = "Formatting with position %s and %s vals." %("one", "two")
print myStr
}}}
{{{
Formatting with position one and two vals.
}}}
The {{{rebuildCurve}}} command is great for turing one type of curve into another
For example, you can use this process to turn a deg1 curve into a deg3 curve, that exactly matches the deg1 curve's cv's:
*Draw deg1 curve -> {{{fitBspline}}} = b-spline curve -> {{{rebuildCurve}}} = deg3 curve.
However, when the deg1 curve is at certain extreme shapes, can cause the rebuilt deg3 curve to "freak out". Weird sine-waves can form in it, and it can really look horrible.
To fix, when using the {{{rebuildCurve}}} command, be sure to set {{{-fitRebuild 0}}}. This seems to just "fix" the problem:
{{{
rebuildCurve -fitRebuild 0 "myCurve"; // plus all the other args you'd call to...
}}}
I jumped from __Maya 2008 \ ~WinXP \ 32bit__ to __Maya 2010 \ Vista \ 64bit__.
Suddenly, code I was using to communicate to Maya from an [[external Python session|How can I have Wing send Python or mel code to Maya?]] quit working.
Based on [[this post|http://area.autodesk.com/forum/autodesk-maya/python/commandport-doesnt-work-in-vista-32/]], I figured out how to address.
When in the past, I could open a socket like this in Maya:
{{{
commandPort -name ":6000" -echoOutput;
}}}
I now had to call to {{{commandPort}}} twice, with slightly different syntax:
{{{
commandPort -name "127.0.0.1:6000" -echoOutput;
commandPort -name ":6000" -echoOutput;
}}}
Now in Python in my [[external application|How can I have Wing send Python or mel code to Maya?]], I could reconnect to the socket and start sending commands again:
{{{
import socket
maya = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
maya.connect(("127.0.0.1", 6000)) # this is where it would fail
maya.send('python("print \'foo\'")')
}}}
It should be noted I still can't //open// the {{{commandPort}}} via Python in Maya: I still have to do that via mel. If I do it in Python, I get no errors, I can query its existence, but I can't send commands to it... :(
----
That post has an update about setting a Maya environment variable like so, for correcting ~IPv6 & ~IPv4 issues (for which I have no concept)
{{{
MAYA_IP_TYPE = ipv4
}}}
Testing new syndication to my blog. Please ignore.
/***
Required by Tiddlyspot
***/
//{{{
config.options.chkHttpReadOnly = false; // make it so you can by default see edit controls via http
if (window.location.protocol != "file:")
config.options.chkGTDLazyAutoSave = false; // disable autosave in d3
config.tiddlyspotSiteId = 'mayamel';
// probably will need to redo this for TW 2.2
with (config.shadowTiddlers) {
SiteUrl = 'http://'+config.tiddlyspotSiteId+'.tiddlyspot.com';
SideBarOptions = SideBarOptions.replace(/(<<saveChanges>>)/,"$1<<tiddler TspotSidebar>>");
OptionsPanel = OptionsPanel.replace(/^/,"<<tiddler TspotOptions>>");
DefaultTiddlers = DefaultTiddlers.replace(/^/,"[[Welcome to Tiddlyspot]] ");
MainMenu = MainMenu.replace(/^/,"[[Welcome to Tiddlyspot]] ");
}
merge(config.shadowTiddlers,{
'Welcome to Tiddlyspot':[
"This document is a ~TiddlyWiki from tiddlyspot.com. A ~TiddlyWiki is an electronic notebook that is great for managing todo lists, personal information, and all sorts of things.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //What now?// @@ Before you can save any changes, you need to enter your password in the form below. Then configure privacy and other site settings at your [[control panel|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/controlpanel]] (your control panel username is //" + config.tiddlyspotSiteId + "//).",
"<<tiddler TspotControls>>",
"See also GettingStarted.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Working online// @@ You can edit this ~TiddlyWiki right now, and save your changes using the \"save to web\" button in the column on the right.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Working offline// @@ A fully functioning copy of this ~TiddlyWiki can be saved onto your hard drive or USB stick. You can make changes and save them locally without being connected to the Internet. When you're ready to sync up again, just click \"upload\" and your ~TiddlyWiki will be saved back to tiddlyspot.com.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Help!// @@ Find out more about ~TiddlyWiki at [[TiddlyWiki.com|http://tiddlywiki.com]]. Also visit [[TiddlyWiki Guides|http://tiddlywikiguides.org]] for documentation on learning and using ~TiddlyWiki. New users are especially welcome on the [[TiddlyWiki mailing list|http://groups.google.com/group/TiddlyWiki]], which is an excellent place to ask questions and get help. If you have a tiddlyspot related problem email [[tiddlyspot support|mailto:support@tiddlyspot.com]].",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Enjoy :)// @@ We hope you like using your tiddlyspot.com site. Please email [[feedback@tiddlyspot.com|mailto:feedback@tiddlyspot.com]] with any comments or suggestions."
].join("\n"),
'TspotControls':[
"| tiddlyspot password:|<<option pasUploadPassword>>|",
"| site management:|<<upload http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/store.cgi index.html . . " + config.tiddlyspotSiteId + ">>//(requires tiddlyspot password)//<<br>>[[control panel|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/controlpanel]], [[download (go offline)|http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/download]]|",
"| links:|[[tiddlyspot.com|http://tiddlyspot.com/]], [[FAQs|http://faq.tiddlyspot.com/]], [[announcements|http://announce.tiddlyspot.com/]], [[blog|http://tiddlyspot.com/blog/]], email [[support|mailto:support@tiddlyspot.com]] & [[feedback|mailto:feedback@tiddlyspot.com]], [[donate|http://tiddlyspot.com/?page=donate]]|"
].join("\n"),
'TspotSidebar':[
"<<upload http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/store.cgi index.html . . " + config.tiddlyspotSiteId + ">><html><a href='http://" + config.tiddlyspotSiteId + ".tiddlyspot.com/download' class='button'>download</a></html>"
].join("\n"),
'TspotOptions':[
"tiddlyspot password:",
"<<option pasUploadPassword>>",
""
].join("\n")
});
//}}}
If you want to comment, please see the post over my my [[main blog|http://www.akeric.com/blog/?p=304]].
Python has the ability to create generator functions. See Python docs here:
http://docs.python.org/tutorial/classes.html#generators
http://docs.python.org/reference/expressions.html#generator-expressions
http://docs.python.org/reference/expressions.html#yieldexpr
http://docs.python.org/reference/simple_stmts.html#yield
What are they? From the Python docs (above):
<<<
"Generators are a simple and powerful tool for creating //iterators//. They are written like regular functions but use the //yield// statement whenever they want to return data. Each time //next()// is called, the generator resumes where it left-off (it remembers all the data values and which statement was last executed)..."
<<<
You can imagine that they are like a normal function looping over a list of items, or doing some work on each of the items in the list. The difference is, rather than looping over the //whole list// when the function is executed, the function pauses after each loop, waiting for you to tell it to continue.
This allows you the ability to make tools that can execute over a list of items, only executing on the next item when you tell it too.
Example below. In this example, the user writes a generator function that will create a locator placed at each vert passed into the function. But the locators are created one by one, only when we call to the generator function:
{{{
# Python code
import maya.cmds as mc
// Define our generator function
def generateLocs(verts):
for v in verts:
pos = mc.pointPosition(v, world=True)
loc = mc.spaceLocator(position=pos)
// Use 'yield' instead of 'return':
yield loc[0]
}}}
Now put the code to use:
{{{
# First, select a polygonal object. Then convert to verts:
verts = mc.ls(mc.polyListComponentConversion(toVertex=True), flatten=True)
// create our generator object called "loc":
loc = generateLocs(verts)
// each time we call to loc.next(), a new locator is created, base on
// the next vert in the list:
print loc.next()
# locator1
print loc.next()
# locator2
print loc.next()
# locator3
# etc...
}}}
Starting in Maya 8.5, Maya has Python integration via a 'maya Python site-package'. How exactly is that accessed?
In the Script Editor, in a //Python// tab, the way to access the data at the topmost level is:
{{{
import maya
}}}
The {{{maya}}} //site-package// actually points to a lot of other data, and you'd rarely access it that way. To understand more about how packages work, see my Python wiki [[here|http://pythonwiki.tiddlyspot.com/#Packages]]
The {{{maya}}} site-package is located here (Vista, Maya 2009):
{{{
C:\Program Files\Autodesk\Maya2009\Python\Lib\site-packages\maya
}}}
The most common usage of the mel scripter would be to import the {{{cmds}}} sub-package, and put it in a different namespace in the process (to something shorter, since you'll be typing it a lot):
{{{
import maya.cmds as mc
# other common imports:
import maya.mel as mm
import maya.OpenMaya as om
# etc...
}}}
In the maya site-package, there are other sub-packages and modules. What do they do?
{{{
help(maya) # generated data for the below list (or browse do the dir shown above).
}}}
*/{{{app}}} : Sub-package. In addition to having modules, it has its own sub-packages.Researching. Seems to have to do with UI, Mental Ray, startup code, and a few other odds and ends.
*/{{{cmds}}} : Sub-package. Gives the user full access to all the mel //commands//, through Python. __Probably the most common one used for the mel scripter.__ However, if you look at this packaged on disk, you'll notice there are no sub-modules are packages: To understand how this works, please see this subject: [[How does Maya populate maya.cmds?]]
*/{{{mel}}} : Sub-package. Provides the {{{eval}}} function, allows the user to evaluate mel //code// inside of Python;
*{{{standalone}}} : Module: Allows the usage of Maya's Python in external IDE's. Allows the execution of "Maya Python" outside of Maya. However, if you look at this packaged on disk, you'll notice there are no sub-modules are packages: Maya works some voodoo to (my guess) generate the function calls on the fly when Maya starts.
*{{{utils}}} : Module: 'General utility functions that are not specific to the Maya Command or the ~OpenMaya API'. Currently, maya.utils contains three routines relevant to threading. This module will likely expand in future versions (writing as of version 2008)
*{{{OpenMaya}}} : Module: Provides Maya API access, via Python. Contains fundamental classes for defining nodes and commands and for assembling them into a plug-in.
*{{{OpenMayaAnim}}} : Module: Contains classes for animation, including deformers and inverse kinematics.
*{{{OpenMayaCloth}}} : Module: Presumably contains classes for Cloth
*{{{OpenMayaFX}}} : Module: Contains classes for Dynamics.
*{{{OpenMayaMPx}}} : Module: Used for authoring 'scripted plugins'
*{{{OpenMayaRender}}} : Module: Contains classes for performing rendering functions.
*{{{OpenMayaUI}}} : Module: Contains classes necessary for creating new user interface elements such as manipulators, contexts, and locators.
Once a package\sub-package\module is loaded, you can access the functions via dot notation:
{{{
import maya.cmds as mc
everything = mc.ls()
}}}
The custom cut of Python that Maya runs is located here:
{{{
C:\Program Files\Autodesk\Maya<version>\bin\mayapy.exe
}}}
----
Also see:
*[[How can I execute Maya's version of Python outside of Maya?]]
(WIP)
----
*[[Scripts]]
**[[Procedures]] (defined)
***[[Commands]]
***[[Arguments]]
***[[Return Values]]
***[[Procedures]] (executed)
**[[Commands]]
***[[Flags]]
****[[Arguments]]
***[[Arguments]]
***[[Return Values]]
[[Script Paths]]
[[Scope]]
*UV's are assigned to "UV sets" on a mesh. The terminology is misleading though. They aren't assigned to a DG node "set", but are actually attributes on the mesh itself. The attributes themselves are derived from the "controlPoint" DG node that is one of many that defines a polygonal mesh. So, if you want to write a script dealing with UV's, then you need to understand how the attributes relate.
*All meshes have a UV set by default. It is expressed by an attribute called ".uvSet[0]". Since there can be multiple UV sets on a mesh, the ".uvSet[]" attr is an array attribute, allowing multiple connections. Below, I'll illustrate the various components of this multi-attribute:
*object.uvSet[0] : This is the base multi-attribute, won't return anything specifically, you must query the below attrs:
*object.uvSet[0].uvSetName : This is a string that defines the name of the ".uvSet[0]" attribute.
*object.uvSet[0].uvSetPoints[0] : A float array attribute that returns the two floating point UV values of the ".uvSetPoints?[]" value specified.
*object.uvSet[0].uvSetPoints[0].uvSetPointsU : A float attribute that returns the U value of the current ".uvSetPointsp?[]" attr.
*object.uvSet[0].uvSetPoints[0].uvSetPointsV : A float attribute that returns the V value of the current ".uvSetPointsp?[]" attr.
*object.uvSet[0].uvSetTweakLocation : can't figure this one out
*object.currentUVSet : A string attribute, that returns the name of the "current" UV set.
Mel commands relating to UV's organization:
*getAttr : for getting the above values
*polyUVSet
*polyEditUV
*polyClipboard
*polyTransfer
----
Also see:
*[[How can I handle uv sets?]]
It seems on a more regular occurrence than it should be, polygonal mesh normals get "screwed up". Black splotches at certain camera angles, white splotches, look funny when exported to the game engine, etc.
*Maya has an internal concept of "locking\freezing" \ "unlocking\unfreezing" normals. The UI calls it {{{Lock Normals}}} \ {{{Unlock Normals}}}, but the actual mel command {{{polyNormalPerVertex}}} calls it {{{-freezeNormal}}} and {{{-unfreezeNormal}}}.
*To the best of my ability, it appears that when normals are "locked\frozen", and something happens to change the polygonal mesh, you can //possibly// end up with "bad normals", since they no longer are oriented correctly to the surface's topology.
*Usually when this happens, simply unlocking\unfreezing the normals fixes the issue.
*In addition, these concepts are decoupled from the ideas of "smooth" and "hard" normals, which will also be discussed in a bit.
Some peculiarities:
*If you unlock\unfreeze the normals, Maya does some mojo in the mayaAscii file: For each normal "unlocked\unfrozen", it sets it's .n (.normal) attr to an obscure float of {{{1e+020}}}. See some example code:
{{{
setAttr ".n[0:165]" -type "float3" 1e+020 1e+020 1e+020 1e+020 1e+020 1e+020 etc...
}}}
*Apparently this tells Maya to calculate the normal on the fly (thus they are "unlocked\unfrozen").
*If the user applies any modifications to the normals, then these absolute values are stored directly in the .ma, and the 1e+020's are removed from those normals . But at any point, if the user "unlocks" the normal values, they will change back to a 1e+020 looking state again.
It gets stranger:
*However, "smooth\hard" looking normal can be the result of two different modifications:
**Softening\Hardening the polygonal //edges// (via the {{{polySoftEdge}}} mel command). This modifies the edge values, but //not// the normal values.
**Directly modifying the normals themselves like {{{polySetToFaceNormal}}} command. However this command sets both the edges //and// the normals.
*What's confusing about this, is that one is an //edge// operation, and one is a //normal// operation //and// an edge operation. The end result always looks like locked\unlocked normals, but that's really not the case. To add even more confusion, the Maya UI sticks the {{{polySoftEdge}}} command in the 'Edit Polygons -> Normals -> Soften/Harden' UI. So even the //user// thinks this is a normal operation. But if you closely inspect the .ma file, you'll see that in fact, it's modifing the edge attributes (see below):
** To make sense of the .ed (.edge) attribute values, they go in three-number sequences:
**First number = polygon first point of edge. Second number = polygon second point of edge. Third number = polygon the hard or smooth information of edge.
After executing this code (via the above-mentioned 'Soften/Harden'UI'), to make our "normals soft":
{{{
polySoftEdge -a 180;
}}}
Our mayaAscii looks like this (sample line):
{{{
// notice every 3rd number is 1:
setAttr ".ed[0:165]" 0 1 1 1 2 1 2 3 1 etc...
}}}
After executing this code, to make our "normals hard:"
{{{
polySoftEdge -a 0;
}}}
Our mayaAscii looks like this (sample line):
{{{
// notice ever 3rd number is 0:
setAttr ".ed[0:165]" 0 1 0 1 2 0 2 3 0 etc...
}}}
*As you can tell, the .ed attr is being modified, and //not// the .n (.normal) attribute (take my word for it, will save on how much I have to paste in here).
Things to consider:
*{{{unlocking}}}\{{{unfreezing}}} normals always sets them back to their "Maya interpreted" version, which is usually a "smooth" looking version.
*If after unlocking the normals, some still look "hard", then it's probably the edge values themselves that are "hard", and they need to be set as well.
It should first be said that {{{source}}} isn't a //mel command//. From the docs:
>"It is a directive that tells the interpreter what to compile and execute."
So there you go: {{{source}}} is a //directive//, not a //command//.
And while there are docs on {{{source}}}, you can only find them by browsing to them through the help UI (By Category -> Language -> Scripting). Executing either of these won't work:
{{{
help -doc source;
// Error: help source; //
// Error: Line 1.12: Syntax error //
help -doc "source";
// Result: "source" is a keyword in the MEL language.
Please see the manual "Using Maya: MEL" for more information //
}}}
----
''All about {{{source}}}'':
More info from the docs:
<<<
If a script has "source" directives in it, then all sourced files will be compiled at the same time as the script. This will happen regardless of where the source directive appears in the file. And, if the script with the "source" directives is re-run, the sourced files will not be recompiled. It is possible to override this behavior by enclosing the source directive inside of an "eval" statement.
<<<
You '{{{source}}}' a script. This has several telling benefits:
*If you've modified a script since Maya has opened, this exposes the updates to Maya.
*If there are any {{{global procs}}} in the script, those global procs are now visible outside of the script ({{{local procs}}} are never seen outside of a script).
*The contents of a script that //aren't// in any procedures is executed.
----
''Sourcing, and relative sourcing'':
When you run {{{source}}}, it's expecting the script to be part of the script path. Pretend his has been added to the {{{MAYA_SCRIPT_PATH}}}:
{{{
c:/scripts/mel
}}}
When you {{{source}}}, it will look in that dir (and every other dir in the path) for the script in question.
However, you can use relative paths when sourcing. Say you make this subdir, but it //is not// part of the path:
{{{
c:/scripts/mel/test
}}}
And in that new dir, you make a script called {{{tester.mel}}}. You can still source that script like so:
{{{
source "test/tester.mel"
}}}
----
''Some more practical examples.'' Presume they all live in Maya's script path:
----
Here is a script called {{{foo.mel}}} that has no procedure declarations, but it does call to an external proc called {{{shoe()}}}.
{{{
// C:/scripts/foo.mel
print "I'm foo.mel\n";
shoe();
}}}
Here is a script called {{{goo.mel}}} that has a local proc called {{{myLoc()}}}, and two {{{global procs}}}: {{{shoe()}}} and {{{goo()}}}.
{{{
// C:/scripts/goo.mel
proc myLoc()
{
print "I'm local\n";
}
global proc shoe()
{
print "shoe!\n";
}
global proc goo()
{
print "goo!\n";
}
}}}
Since there is no {{{global proc foo()}}} in {{{foo.mel}}}, it means you can't execute {{{foo();}}} to get any result. You need to source the script instead:
{{{
source foo;
// I'm foo.mel
// Error: file: C:/scripts/foo.mel line 3: Cannot find procedure "shoe". //
}}}
As you can see, source executes all the commands in {{{foo.mel}}}. But {{{shoe()}}} fails even though it's a {{{global proc}}} in {{{goo.mel}}}, why? As mentioned above, until a script is {{{source}}}d, any global procs inside of it won't be seen. Try:
{{{
source goo;
// nothing seems to happen...
}}}
Nothing seems to happen because there are no //commands//, or //executed procedures// in the root of {{{goo.mel}}}. However, {{{source}}} has now declared all the //defined global procedures// (not local procedures) in {{{goo.mel}}}, so they can be seen outside of {{{goo.mel}}}. So now we can re-try {{{foo.mel}}}:
{{{
source foo;
// I'm foo.mel
// shoe!
}}}
Sourcing {{{foo.mel}}} executes the print command, and it also executes the procedure call to {{{shoe()}}}.
----
''{{{source}}} Gotcha's'':
When you {{{source}}} a script, it triggers a "chain of sourcing" persay. Every {{{source}}} statement in every script that is called to is executed. For example, presume you have 4 scripts, and each script has a {{{source}}} directive calling to the next:
{{{
source script1;
}}}
This then sources {{{script2}}}, which sources {{{script3}}}, which sources {{{script4}}}.
However, at any point if any of those scripts tosses an error, it'll bring the whole system to a halt.
On one hand, it's good because it identifies broken code. On the other hand, in a shared codebase, it can introduce some nasty consequences:
Say you have a user interface script, that is updated by multiple people. One of the people puts {{{source}}} in the UI script to look to some external code. If that external code ever breaks, the whole UI script will fail to execute until the issue is resolved.
Commands I've ran into that have no online documentation. You can still run the {{{help}}} command on them though.
{{{
repeatLast
}}}
{{{
superCtx
}}}
| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |
| 13/08/2010 09:44:02 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 17/08/2010 09:17:33 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 18/08/2010 10:23:40 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 18/08/2010 11:11:52 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 20/08/2010 10:46:53 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . | ok |
| 20/08/2010 10:47:24 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 20/08/2010 15:23:59 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . | ok |
| 20/08/2010 15:24:13 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . | ok |
| 20/08/2010 15:26:37 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 01/09/2010 11:24:04 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
/***
|''Name:''|PasswordOptionPlugin|
|''Description:''|Extends TiddlyWiki options with non encrypted password option.|
|''Version:''|1.0.2|
|''Date:''|Apr 19, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#PasswordOptionPlugin|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (Beta 5)|
***/
//{{{
version.extensions.PasswordOptionPlugin = {
major: 1, minor: 0, revision: 2,
date: new Date("Apr 19, 2007"),
source: 'http://tiddlywiki.bidix.info/#PasswordOptionPlugin',
author: 'BidiX (BidiX (at) bidix (dot) info',
license: '[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D]]',
coreVersion: '2.2.0 (Beta 5)'
};
config.macros.option.passwordCheckboxLabel = "Save this password on this computer";
config.macros.option.passwordInputType = "password"; // password | text
setStylesheet(".pasOptionInput {width: 11em;}\n","passwordInputTypeStyle");
merge(config.macros.option.types, {
'pas': {
elementType: "input",
valueField: "value",
eventName: "onkeyup",
className: "pasOptionInput",
typeValue: config.macros.option.passwordInputType,
create: function(place,type,opt,className,desc) {
// password field
config.macros.option.genericCreate(place,'pas',opt,className,desc);
// checkbox linked with this password "save this password on this computer"
config.macros.option.genericCreate(place,'chk','chk'+opt,className,desc);
// text savePasswordCheckboxLabel
place.appendChild(document.createTextNode(config.macros.option.passwordCheckboxLabel));
},
onChange: config.macros.option.genericOnChange
}
});
merge(config.optionHandlers['chk'], {
get: function(name) {
// is there an option linked with this chk ?
var opt = name.substr(3);
if (config.options[opt])
saveOptionCookie(opt);
return config.options[name] ? "true" : "false";
}
});
merge(config.optionHandlers, {
'pas': {
get: function(name) {
if (config.options["chk"+name]) {
return encodeCookie(config.options[name].toString());
} else {
return "";
}
},
set: function(name,value) {config.options[name] = decodeCookie(value);}
}
});
// need to reload options to load passwordOptions
loadOptionsCookie();
/*
if (!config.options['pasPassword'])
config.options['pasPassword'] = '';
merge(config.optionsDesc,{
pasPassword: "Test password"
});
*/
//}}}
/***
|''Name:''|UploadPlugin|
|''Description:''|Save to web a TiddlyWiki|
|''Version:''|4.1.0|
|''Date:''|May 5, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#UploadPlugin|
|''Documentation:''|http://tiddlywiki.bidix.info/#UploadPluginDoc|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (#3125)|
|''Requires:''|PasswordOptionPlugin|
***/
//{{{
version.extensions.UploadPlugin = {
major: 4, minor: 1, revision: 0,
date: new Date("May 5, 2007"),
source: 'http://tiddlywiki.bidix.info/#UploadPlugin',
author: 'BidiX (BidiX (at) bidix (dot) info',
coreVersion: '2.2.0 (#3125)'
};
//
// Environment
//
if (!window.bidix) window.bidix = {}; // bidix namespace
bidix.debugMode = false; // true to activate both in Plugin and UploadService
//
// Upload Macro
//
config.macros.upload = {
// default values
defaultBackupDir: '', //no backup
defaultStoreScript: "store.php",
defaultToFilename: "index.html",
defaultUploadDir: ".",
authenticateUser: true // UploadService Authenticate User
};
config.macros.upload.label = {
promptOption: "Save and Upload this TiddlyWiki with UploadOptions",
promptParamMacro: "Save and Upload this TiddlyWiki in %0",
saveLabel: "save to web",
saveToDisk: "save to disk",
uploadLabel: "upload"
};
config.macros.upload.messages = {
noStoreUrl: "No store URL in parmeters or options",
usernameOrPasswordMissing: "Username or password missing"
};
config.macros.upload.handler = function(place,macroName,params) {
if (readOnly)
return;
var label;
if (document.location.toString().substr(0,4) == "http")
label = this.label.saveLabel;
else
label = this.label.uploadLabel;
var prompt;
if (params[0]) {
prompt = this.label.promptParamMacro.toString().format([this.destFile(params[0],
(params[1] ? params[1]:bidix.basename(window.location.toString())), params[3])]);
} else {
prompt = this.label.promptOption;
}
createTiddlyButton(place, label, prompt, function() {config.macros.upload.action(params);}, null, null, this.accessKey);
};
config.macros.upload.action = function(params)
{
// for missing macro parameter set value from options
var storeUrl = params[0] ? params[0] : config.options.txtUploadStoreUrl;
var toFilename = params[1] ? params[1] : config.options.txtUploadFilename;
var backupDir = params[2] ? params[2] : config.options.txtUploadBackupDir;
var uploadDir = params[3] ? params[3] : config.options.txtUploadDir;
var username = params[4] ? params[4] : config.options.txtUploadUserName;
var password = config.options.pasUploadPassword; // for security reason no password as macro parameter
// for still missing parameter set default value
if ((!storeUrl) && (document.location.toString().substr(0,4) == "http"))
storeUrl = bidix.dirname(document.location.toString())+'/'+config.macros.upload.defaultStoreScript;
if (storeUrl.substr(0,4) != "http")
storeUrl = bidix.dirname(document.location.toString()) +'/'+ storeUrl;
if (!toFilename)
toFilename = bidix.basename(window.location.toString());
if (!toFilename)
toFilename = config.macros.upload.defaultToFilename;
if (!uploadDir)
uploadDir = config.macros.upload.defaultUploadDir;
if (!backupDir)
backupDir = config.macros.upload.defaultBackupDir;
// report error if still missing
if (!storeUrl) {
alert(config.macros.upload.messages.noStoreUrl);
clearMessage();
return false;
}
if (config.macros.upload.authenticateUser && (!username || !password)) {
alert(config.macros.upload.messages.usernameOrPasswordMissing);
clearMessage();
return false;
}
bidix.upload.uploadChanges(false,null,storeUrl, toFilename, uploadDir, backupDir, username, password);
return false;
};
config.macros.upload.destFile = function(storeUrl, toFilename, uploadDir)
{
if (!storeUrl)
return null;
var dest = bidix.dirname(storeUrl);
if (uploadDir && uploadDir != '.')
dest = dest + '/' + uploadDir;
dest = dest + '/' + toFilename;
return dest;
};
//
// uploadOptions Macro
//
config.macros.uploadOptions = {
handler: function(place,macroName,params) {
var wizard = new Wizard();
wizard.createWizard(place,this.wizardTitle);
wizard.addStep(this.step1Title,this.step1Html);
var markList = wizard.getElement("markList");
var listWrapper = document.createElement("div");
markList.parentNode.insertBefore(listWrapper,markList);
wizard.setValue("listWrapper",listWrapper);
this.refreshOptions(listWrapper,false);
var uploadCaption;
if (document.location.toString().substr(0,4) == "http")
uploadCaption = config.macros.upload.label.saveLabel;
else
uploadCaption = config.macros.upload.label.uploadLabel;
wizard.setButtons([
{caption: uploadCaption, tooltip: config.macros.upload.label.promptOption,
onClick: config.macros.upload.action},
{caption: this.cancelButton, tooltip: this.cancelButtonPrompt, onClick: this.onCancel}
]);
},
refreshOptions: function(listWrapper) {
var uploadOpts = [
"txtUploadUserName",
"pasUploadPassword",
"txtUploadStoreUrl",
"txtUploadDir",
"txtUploadFilename",
"txtUploadBackupDir",
"chkUploadLog",
"txtUploadLogMaxLine",
]
var opts = [];
for(i=0; i<uploadOpts.length; i++) {
var opt = {};
opts.push()
opt.option = "";
n = uploadOpts[i];
opt.name = n;
opt.lowlight = !config.optionsDesc[n];
opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
opts.push(opt);
}
var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
for(n=0; n<opts.length; n++) {
var type = opts[n].name.substr(0,3);
var h = config.macros.option.types[type];
if (h && h.create) {
h.create(opts[n].colElements['option'],type,opts[n].name,opts[n].name,"no");
}
}
},
onCancel: function(e)
{
backstage.switchTab(null);
return false;
},
wizardTitle: "Upload with options",
step1Title: "These options are saved in cookies in your browser",
step1Html: "<input type='hidden' name='markList'></input><br>",
cancelButton: "Cancel",
cancelButtonPrompt: "Cancel prompt",
listViewTemplate: {
columns: [
{name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
{name: 'Option', field: 'option', title: "Option", type: 'String'},
{name: 'Name', field: 'name', title: "Name", type: 'String'}
],
rowClasses: [
{className: 'lowlight', field: 'lowlight'}
]}
}
//
// upload functions
//
if (!bidix.upload) bidix.upload = {};
if (!bidix.upload.messages) bidix.upload.messages = {
//from saving
invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
backupSaved: "Backup saved",
backupFailed: "Failed to upload backup file",
rssSaved: "RSS feed uploaded",
rssFailed: "Failed to upload RSS feed file",
emptySaved: "Empty template uploaded",
emptyFailed: "Failed to upload empty template file",
mainSaved: "Main TiddlyWiki file uploaded",
mainFailed: "Failed to upload main TiddlyWiki file. Your changes have not been saved",
//specific upload
loadOriginalHttpPostError: "Can't get original file",
aboutToSaveOnHttpPost: 'About to upload on %0 ...',
storePhpNotFound: "The store script '%0' was not found."
};
bidix.upload.uploadChanges = function(onlyIfDirty,tiddlers,storeUrl,toFilename,uploadDir,backupDir,username,password)
{
var callback = function(status,uploadParams,original,url,xhr) {
if (!status) {
displayMessage(bidix.upload.messages.loadOriginalHttpPostError);
return;
}
if (bidix.debugMode)
alert(original.substr(0,500)+"\n...");
// Locate the storeArea div's
var posDiv = locateStoreArea(original);
if((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
bidix.upload.uploadRss(uploadParams,original,posDiv);
};
if(onlyIfDirty && !store.isDirty())
return;
clearMessage();
// save on localdisk ?
if (document.location.toString().substr(0,4) == "file") {
var path = document.location.toString();
var localPath = getLocalPath(path);
saveChanges();
}
// get original
var uploadParams = Array(storeUrl,toFilename,uploadDir,backupDir,username,password);
var originalPath = document.location.toString();
// If url is a directory : add index.html
if (originalPath.charAt(originalPath.length-1) == "/")
originalPath = originalPath + "index.html";
var dest = config.macros.upload.destFile(storeUrl,toFilename,uploadDir);
var log = new bidix.UploadLog();
log.startUpload(storeUrl, dest, uploadDir, backupDir);
displayMessage(bidix.upload.messages.aboutToSaveOnHttpPost.format([dest]));
if (bidix.debugMode)
alert("about to execute Http - GET on "+originalPath);
var r = doHttp("GET",originalPath,null,null,null,null,callback,uploadParams,null);
if (typeof r == "string")
displayMessage(r);
return r;
};
bidix.upload.uploadRss = function(uploadParams,original,posDiv)
{
var callback = function(status,params,responseText,url,xhr) {
if(status) {
var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
displayMessage(bidix.upload.messages.rssSaved,bidix.dirname(url)+'/'+destfile);
bidix.upload.uploadMain(params[0],params[1],params[2]);
} else {
displayMessage(bidix.upload.messages.rssFailed);
}
};
// do uploadRss
if(config.options.chkGenerateAnRssFeed) {
var rssPath = uploadParams[1].substr(0,uploadParams[1].lastIndexOf(".")) + ".xml";
var rssUploadParams = Array(uploadParams[0],rssPath,uploadParams[2],'',uploadParams[4],uploadParams[5]);
bidix.upload.httpUpload(rssUploadParams,convertUnicodeToUTF8(generateRss()),callback,Array(uploadParams,original,posDiv));
} else {
bidix.upload.uploadMain(uploadParams,original,posDiv);
}
};
bidix.upload.uploadMain = function(uploadParams,original,posDiv)
{
var callback = function(status,params,responseText,url,xhr) {
var log = new bidix.UploadLog();
if(status) {
// if backupDir specified
if ((params[3]) && (responseText.indexOf("backupfile:") > -1)) {
var backupfile = responseText.substring(responseText.indexOf("backupfile:")+11,responseText.indexOf("\n", responseText.indexOf("backupfile:")));
displayMessage(bidix.upload.messages.backupSaved,bidix.dirname(url)+'/'+backupfile);
}
var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
displayMessage(bidix.upload.messages.mainSaved,bidix.dirname(url)+'/'+destfile);
store.setDirty(false);
log.endUpload("ok");
} else {
alert(bidix.upload.messages.mainFailed);
displayMessage(bidix.upload.messages.mainFailed);
log.endUpload("failed");
}
};
// do uploadMain
var revised = bidix.upload.updateOriginal(original,posDiv);
bidix.upload.httpUpload(uploadParams,revised,callback,uploadParams);
};
bidix.upload.httpUpload = function(uploadParams,data,callback,params)
{
var localCallback = function(status,params,responseText,url,xhr) {
url = (url.indexOf("nocache=") < 0 ? url : url.substring(0,url.indexOf("nocache=")-1));
if (xhr.status == httpStatus.NotFound)
alert(bidix.upload.messages.storePhpNotFound.format([url]));
if ((bidix.debugMode) || (responseText.indexOf("Debug mode") >= 0 )) {
alert(responseText);
if (responseText.indexOf("Debug mode") >= 0 )
responseText = responseText.substring(responseText.indexOf("\n\n")+2);
} else if (responseText.charAt(0) != '0')
alert(responseText);
if (responseText.charAt(0) != '0')
status = null;
callback(status,params,responseText,url,xhr);
};
// do httpUpload
var boundary = "---------------------------"+"AaB03x";
var uploadFormName = "UploadPlugin";
// compose headers data
var sheader = "";
sheader += "--" + boundary + "\r\nContent-disposition: form-data; name=\"";
sheader += uploadFormName +"\"\r\n\r\n";
sheader += "backupDir="+uploadParams[3] +
";user=" + uploadParams[4] +
";password=" + uploadParams[5] +
";uploaddir=" + uploadParams[2];
if (bidix.debugMode)
sheader += ";debug=1";
sheader += ";;\r\n";
sheader += "\r\n" + "--" + boundary + "\r\n";
sheader += "Content-disposition: form-data; name=\"userfile\"; filename=\""+uploadParams[1]+"\"\r\n";
sheader += "Content-Type: text/html;charset=UTF-8" + "\r\n";
sheader += "Content-Length: " + data.length + "\r\n\r\n";
// compose trailer data
var strailer = new String();
strailer = "\r\n--" + boundary + "--\r\n";
data = sheader + data + strailer;
if (bidix.debugMode) alert("about to execute Http - POST on "+uploadParams[0]+"\n with \n"+data.substr(0,500)+ " ... ");
var r = doHttp("POST",uploadParams[0],data,"multipart/form-data; boundary="+boundary,uploadParams[4],uploadParams[5],localCallback,params,null);
if (typeof r == "string")
displayMessage(r);
return r;
};
// same as Saving's updateOriginal but without convertUnicodeToUTF8 calls
bidix.upload.updateOriginal = function(original, posDiv)
{
if (!posDiv)
posDiv = locateStoreArea(original);
if((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
store.allTiddlersAsHtml() + "\n" +
original.substr(posDiv[1]);
var newSiteTitle = getPageTitle().htmlEncode();
revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
return revised;
};
//
// UploadLog
//
// config.options.chkUploadLog :
// false : no logging
// true : logging
// config.options.txtUploadLogMaxLine :
// -1 : no limit
// 0 : no Log lines but UploadLog is still in place
// n : the last n lines are only kept
// NaN : no limit (-1)
bidix.UploadLog = function() {
if (!config.options.chkUploadLog)
return; // this.tiddler = null
this.tiddler = store.getTiddler("UploadLog");
if (!this.tiddler) {
this.tiddler = new Tiddler();
this.tiddler.title = "UploadLog";
this.tiddler.text = "| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |";
this.tiddler.created = new Date();
this.tiddler.modifier = config.options.txtUserName;
this.tiddler.modified = new Date();
store.addTiddler(this.tiddler);
}
return this;
};
bidix.UploadLog.prototype.addText = function(text) {
if (!this.tiddler)
return;
// retrieve maxLine when we need it
var maxLine = parseInt(config.options.txtUploadLogMaxLine,10);
if (isNaN(maxLine))
maxLine = -1;
// add text
if (maxLine != 0)
this.tiddler.text = this.tiddler.text + text;
// Trunck to maxLine
if (maxLine >= 0) {
var textArray = this.tiddler.text.split('\n');
if (textArray.length > maxLine + 1)
textArray.splice(1,textArray.length-1-maxLine);
this.tiddler.text = textArray.join('\n');
}
// update tiddler fields
this.tiddler.modifier = config.options.txtUserName;
this.tiddler.modified = new Date();
store.addTiddler(this.tiddler);
// refresh and notifiy for immediate update
story.refreshTiddler(this.tiddler.title);
store.notify(this.tiddler.title, true);
};
bidix.UploadLog.prototype.startUpload = function(storeUrl, toFilename, uploadDir, backupDir) {
if (!this.tiddler)
return;
var now = new Date();
var text = "\n| ";
var filename = bidix.basename(document.location.toString());
if (!filename) filename = '/';
text += now.formatString("0DD/0MM/YYYY 0hh:0mm:0ss") +" | ";
text += config.options.txtUserName + " | ";
text += "[["+filename+"|"+location + "]] |";
text += " [[" + bidix.basename(storeUrl) + "|" + storeUrl + "]] | ";
text += uploadDir + " | ";
text += "[[" + bidix.basename(toFilename) + " | " +toFilename + "]] | ";
text += backupDir + " |";
this.addText(text);
};
bidix.UploadLog.prototype.endUpload = function(status) {
if (!this.tiddler)
return;
this.addText(" "+status+" |");
};
//
// Utilities
//
bidix.checkPlugin = function(plugin, major, minor, revision) {
var ext = version.extensions[plugin];
if (!
(ext &&
((ext.major > major) ||
((ext.major == major) && (ext.minor > minor)) ||
((ext.major == major) && (ext.minor == minor) && (ext.revision >= revision))))) {
// write error in PluginManager
if (pluginInfo)
pluginInfo.log.push("Requires " + plugin + " " + major + "." + minor + "." + revision);
eval(plugin); // generate an error : "Error: ReferenceError: xxxx is not defined"
}
};
bidix.dirname = function(filePath) {
if (!filePath)
return;
var lastpos;
if ((lastpos = filePath.lastIndexOf("/")) != -1) {
return filePath.substring(0, lastpos);
} else {
return filePath.substring(0, filePath.lastIndexOf("\\"));
}
};
bidix.basename = function(filePath) {
if (!filePath)
return;
var lastpos;
if ((lastpos = filePath.lastIndexOf("#")) != -1)
filePath = filePath.substring(0, lastpos);
if ((lastpos = filePath.lastIndexOf("/")) != -1) {
return filePath.substring(lastpos + 1);
} else
return filePath.substring(filePath.lastIndexOf("\\")+1);
};
bidix.initOption = function(name,value) {
if (!config.options[name])
config.options[name] = value;
};
//
// Initializations
//
// require PasswordOptionPlugin 1.0.1 or better
bidix.checkPlugin("PasswordOptionPlugin", 1, 0, 1);
// styleSheet
setStylesheet('.txtUploadStoreUrl, .txtUploadBackupDir, .txtUploadDir {width: 22em;}',"uploadPluginStyles");
//optionsDesc
merge(config.optionsDesc,{
txtUploadStoreUrl: "Url of the UploadService script (default: store.php)",
txtUploadFilename: "Filename of the uploaded file (default: in index.html)",
txtUploadDir: "Relative Directory where to store the file (default: . (downloadService directory))",
txtUploadBackupDir: "Relative Directory where to backup the file. If empty no backup. (default: ''(empty))",
txtUploadUserName: "Upload Username",
pasUploadPassword: "Upload Password",
chkUploadLog: "do Logging in UploadLog (default: true)",
txtUploadLogMaxLine: "Maximum of lines in UploadLog (default: 10)"
});
// Options Initializations
bidix.initOption('txtUploadStoreUrl','');
bidix.initOption('txtUploadFilename','');
bidix.initOption('txtUploadDir','');
bidix.initOption('txtUploadBackupDir','');
bidix.initOption('txtUploadUserName','');
bidix.initOption('pasUploadPassword','');
bidix.initOption('chkUploadLog',true);
bidix.initOption('txtUploadLogMaxLine','10');
/* don't want this for tiddlyspot sites
// Backstage
merge(config.tasks,{
uploadOptions: {text: "upload", tooltip: "Change UploadOptions and Upload", content: '<<uploadOptions>>'}
});
config.backstageTasks.push("uploadOptions");
*/
//}}}
*If you run a system call from Maya, that call is executed in what Maya thinks is the "[[current working directory]]" (see notes).
*You need to change this path to the path that your .bat file lives in, so when it is executed, it operates on the correct files. Presuming you have this file: {{{c:/temp/myBat.bat}}}, and that .bat executes operations on the contents of {{{c:/temp}}}, you'd need to tell Maya that {{{c:/temp}}} is the [[current working directory]] with the {{{chdir}}} command:
{{{
chdir "c:/temp/";
string $result = system("myBat.bat");
}}}
*//If// it is important that the original "[[current working directory]]" isn't changed, query its value before executing the above code (using the {{{pwd}}} command), then using {{{chdir}}} set it back to it's original value after code execution.
*Also realize that if you use "{{{start}}}" in front of your .bat execution like this: '{{{system("start myBat.bat");}}}', this will cause the .bat file to be executed //external// to Maya. Meaning, you won't be able to capture the return value (if there is one), nor will Maya wait for the bat file to finish before advancing to the next line of mel.
Also see:
*[[I'm passing a very long command to Windows via the mel system command, but nothing is happening, why?]]
Vectors variables are nice way to package up triplets of floats. You can also make arrays out of them, which is about the only way in mel you can get an "array of arrays". They are accessed a bit differently from other variable types though:
{{{
vector $spam = <<0,1,2>>;
print ($spam.x);
// 0 // success
vector $foo[] = {<<0,1,2>>, <<3,4,5>>};
print ($foo[0].x);
// fail
vector $boo = $foo[0];
print ($boo.x);
// 0 // success
}}}
----
More from the Maya docs:
Suppose you have a vector initialized as follows:
{{{
vector $myvector = <<1,2,3>>;
}}}
To replace the right component of {{{$myvector}}}, 3, with a new value such as 7, use this technique to preserve the other two components:
{{{
$myvector = <<$myvector.x,$myvector.y,7>>;
}}}
This statement is //incorrect//:
{{{
$myvector.z = 3;
}}}
When making UI's in Maya, it's nice to be able to //see// what you want to make, //before// you make it.
Below, is a visual style guide. The name of the ELF (extended layer framework) //command// required to make the specified //control// is in the window's titlebar. Listed in a pseudo-categorized-alphabetical order.
The code I used to make these (for the most part) is actually at the bottom of each command's help page, in the [[Maya Docs|http://download.autodesk.com/us/maya/2008help/wwhelp/wwhimpl/js/html/wwhelp.htm]] (cut, paste, execute, screengrab, upload). I've also listed the name of each control above the images, for page text-searching purposes. Some of the command names I've linked to other tiddlers, when I have more detailed info on that subject.
I'll be updating this over time, in alphabetical order.
----
attrColorSliderGrp
[img[http://farm3.static.flickr.com/2004/2179176830_688e16a8ae.jpg?v=0]]
attrControlGrp
[img[http://farm3.static.flickr.com/2011/2178386185_db74568327.jpg?v=0]]
attrFieldGrp
[img[http://farm3.static.flickr.com/2199/2179176868_9fecfc694c.jpg?v=0]]
attrFieldSliderGrp
[img[http://farm3.static.flickr.com/2120/2178386227_3baecbea24.jpg?v=0]]
attrNavigationControlGrp
[img[http://farm3.static.flickr.com/2079/2179176896_9639174130.jpg?v=0]]
----
button
[img[http://farm3.static.flickr.com/2250/2178692575_d583ef2e87.jpg?v=0]]
----
canvas
[img[http://farm3.static.flickr.com/2361/2179482410_4c72a3a903.jpg?v=0]]
----
channelBox
[img[http://farm3.static.flickr.com/2252/2179482426_fd22b3dc1f.jpg?v=0]]
----
checkBox
[img[http://farm3.static.flickr.com/2147/2178692637_323a58fee8.jpg?v=0]]
checkBoxGrp
[img[http://farm3.static.flickr.com/2131/2178692649_1de57f7df6.jpg?v=0]]
----
cmdShell
[img[http://farm3.static.flickr.com/2366/2193178370_7d6274877a.jpg?v=0]]
commandLine
[img[http://farm3.static.flickr.com/2098/2193178442_0254a07798.jpg?v=0]]
----
colorIndexSliderGrp
[img[http://farm3.static.flickr.com/2419/2192390061_273a5f3b91.jpg?v=0]]
colorSliderButtonGrp
[img[http://farm3.static.flickr.com/2218/2192390075_9a25049691.jpg?v=0]]
colorSliderGrp
[img[http://farm3.static.flickr.com/2402/2193178426_6cc2856be1.jpg?v=0]]
----
floatField
[img[http://farm3.static.flickr.com/2318/2196649760_cc3e5faa0e.jpg?v=0]]
floatFieldGrp
[img[http://farm3.static.flickr.com/2093/2195860611_c3f06546c0.jpg?v=0]]
floatScrollBar
[img[http://farm3.static.flickr.com/2212/2196649798_3758e59521.jpg?v=0]]
floatSlider
[img[http://farm3.static.flickr.com/2366/2195860637_e76d00ef1f.jpg?v=0]]
floatSlider2
[img[http://farm3.static.flickr.com/2407/2196649832_9a8f7073d5.jpg?v=0]]
floatSliderButtonGrp
[img[http://farm3.static.flickr.com/2140/2197257833_8e1a6777f3.jpg?v=0]]
floatSliderGrp
[img[http://farm3.static.flickr.com/2358/2197257845_0a5ecdd556.jpg?v=0]]
----
[[gradientControl|How can I author a 'gradientControl' into my UI?]]
[img[http://farm3.static.flickr.com/2067/2197352149_10f912b166.jpg?v=0]]
[[gradientControlNoAttr|How can I author a 'gradientControlNoAttr' into my UI?]]
[img[http://farm3.static.flickr.com/2270/2198047046_1b6111924d.jpg?v=0]]
----
helpLine
[img[http://farm3.static.flickr.com/2268/2197257875_b91d4e71b0.jpg?v=0]]
----
hudButton
[img[http://farm3.static.flickr.com/2179/2197257895_51b76918d4.jpg?v=0]]
hudSlider
[img[http://farm3.static.flickr.com/2360/2198047104_8739cbe494.jpg?v=0]]
hudSliderButton
[img[http://farm3.static.flickr.com/2413/2197257913_d1f585865e.jpg?v=0]]
----
iconTextButton
[img[http://farm3.static.flickr.com/2299/2197923567_0da721f624.jpg?v=0]]
iconTextCheckBox
[img[http://farm3.static.flickr.com/2282/2197923619_7f84042e6a.jpg?v=0]]
iconTextRadioButton & iconTextRadioCollection
[img[http://farm3.static.flickr.com/2008/2198710702_6252c0e3c0.jpg?v=0]]
iconTextStaticLabel
[img[http://farm3.static.flickr.com/2155/2198710716_8e70edb0d8.jpg?v=0]]
----
image
[img[http://farm3.static.flickr.com/2341/2197923601_3f9cf70dc0.jpg?v=0]]
----
intField
[img[http://farm3.static.flickr.com/2039/2228702575_c792f0207d.jpg?v=0]]
intFieldGrp
[img[http://farm3.static.flickr.com/2198/2229496534_019d69c52a.jpg?v=0]]
intScrollBar
[img[http://farm3.static.flickr.com/2412/2229496550_0d2defdc43.jpg?v=0]]
intSlider
[img[http://farm3.static.flickr.com/2115/2229496562_2121818c40.jpg?v=0]]
intSliderGrp
[img[http://farm3.static.flickr.com/2159/2229496588_50b98c74bc.jpg?v=0]]
----
layerButton
[img[http://farm3.static.flickr.com/2369/2228702639_ba2439f1c4.jpg?v=0]]
----
messageLine
[img[http://farm3.static.flickr.com/2133/2229496616_9f7475b945.jpg?v=0]]
----
nameField
[img[http://farm3.static.flickr.com/2045/2229496640_8d3e831b64.jpg?v=0]]
----
//Many// more to come....
<<gradient horiz #ffffff #ddddff #8888ff>>
[img[warpcat|http://farm3.static.flickr.com/2017/2118148943_75636dd96c.jpg?v=0]] Yes, that is me playing [[Rock Band|http://www.rockband.com/]].
''Eric Pavey''
*Email: - warpcat {{{(at)}}} sbcglobal {{{(dot)}}} net -
*[[Blog|http://www.akeric.com/blog/]] - [[LinkedIn|http://www.linkedin.com/in/pavey]] - [[Flickr|http://www.flickr.com/photos/8064698@N03/collections/]] - [[Youtube|http://www.youtube.com/profile?user=warpcat]] - [[MobyGames|http://www.mobygames.com/developer/sheet/view/developerId,76979/]] - [[RSWarrior|http://rswarrior.com/photos/Warpcat/default.aspx]]
>>
My other Tiddlywiki's:
*[[CG OpenSource wiki|http://cgoswiki.tiddlyspot.com/]]
*[[Python Wiki|http://pythonwiki.tiddlyspot.com/]]
*[[PyGame Wiki|http://pygamewiki.tiddlyspot.com/]]
*[[Processing Wiki|http://processingwiki.tiddlyspot.com/]]
<<gradient horiz #ddddff #8888ff >>''mel'' is, the ''[[Maya Embedded Language|http://en.wikipedia.org/wiki/Maya_Embedded_Language]]''>>
<<gradient horiz #ffffff #ddddff #8888ff >>[[About mel wiki]] (go here first)
[[Instructions For Use]] (go here second)
[[Check out the latest updates|History]] (go here third. //Hit ''F5'' to refresh your cache to see the latest stuff)//
<<gradient horiz #ddddff #8888ff >>''Browse \ Search using:''
----
{{{<---}}} Major ''//Categories//'' (or 'All Subjects') in the Left column
Key-word ''//Tags//'' tab in the Right column {{{--->}}}
The ''//Search//'' box in the Right column (top) This can slow down the wiki quite a bit though. {{{--->}}}
----
>>
Best viewed in ''[[Firefox|http://www.mozilla.com]]'' (so I've found)
Running [[tiddlywiki|http://www.tiddlywiki.com]] v<<version>>
If you find this wiki useful, let the [[author|WarpCat]] know, and sign the [[Guest Book|GuestBook]]!!!
As of Maya 2008, as I find them....
*[[Tkinter|http://docs.python.org/library/tkinter.html]] : Isn't available. I guess they really want you to uses Maya's ELF (Extended Layer Format) system.
*{{{raw_input}}} function : While it does exist in Maya, Maya has it's own gui wrapper around it and you can't provide the 'prompt argument' to it. Better to just use a {{{maya.cmds.promptDialog}}}.
----
Also see:
*[[What mel commands are 'missing' from the Python integration?]]
Here is a list (as of Maya 7) of all the "shape" nodes in Maya. FYI, I'm using Maya Complete, and some of these when created come up as {{{unknownDag}}} nodes, presumably because they exist in Unlimited.
"Shape" nodes are DAG nodes that exist as children of transform objects. They cannot exist in any other form (to my knowledge); they //must// be a child of a transform.
I found this by going into Maya's help -> 'node' documentation, sorting my hierarchy (rather than alphabetically), and grabbing a copy of everything under the "shape" node type. I took that full list into Maya as an array (after some format editing in a text editor), and looped through the list: Caught the evaluation of creating each type of node. If they failed creation(since some types are parental types, and not actual nodes you make), they weren't added to the final list below:
{{{
string $allShapes[] =
"baseLattice",
"camera",
"clusterHandle",
"deformBend",
"deformFlare",
"deformSine",
"deformSquash",
"deformTwist",
"deformWave",
"angleDimension",
"annotationShape",
"distanceDimShape",
"arcLengthDimension",
"paramDimension",
"dynHolder",
"flexorShape",
"clusterFlexo1rShape",
"follicle1",
"geoConnectable",
"nurbsCurve",
"lattice",
"fluidShape",
"fluidTexture2D",
"fluidTexture3D",
"heightField",
"mesh",
"nurbsSurface",
"subdiv",
"particle",
"directedDisc",
"environmentFog",
"implicitBox",
"renderBox",
"implicitCone",
"renderCone",
"implicitSphere",
"renderSphere",
"locator",
"dropoffLocator",
"hikFloorContactMarker",
"positionMarker",
"orientationMarker",
"sketchPlane",
"renderRect",
"snapshotShape",
"hairConstraint",
"hairSystem",
"ambientLight",
"areaLight",
"directionalLight",
"pointLight",
"volumeLight",
"spotLight",
"lineModifier",
"pfxGeometry",
"pfxHair",
"pfxToon",
"stroke",
"polyToolFeedbackShape",
"rigidBody",
"softModHandle",
"spring"};
}}}
*Change the Font size:
**ctrl-shift {{{<}}} or {{{>}}}.
*Suppress warnings, errors, etc?
**{{{scriptEditorInfo}}}
*Clear the upper and lower fields"
**{{{scriptEditorInfo -ch -input;}}}
*Change the line spacing:
**ctrl + 1, 2, or 5
*Add "numbering\bullets":
**ctrl-shift + l (that's an L)
*Add an indent:
**tab, or ctrl+i... don't know if there's a difference.
*Allign left, or right:
**ctrl+l (that's an L), ctrl+r
**ctrl+e is "center" but also seems to execute the script, odd.
{{{
global string $gshowManip; = "ShowManips"
global string $gSelect; = "selectSuperContext"
global string $gLasso; = "lassoSelectContext"
global string $gMove; = "moveSuperContext"
global string $gRotate; = "RotateSuperContext" - note the capital "R"
global string $gScale; = "scaleSuperContext"
global string $gCurrentSacredTool; = whatever context is currently active
global string $gNonSacredToolWidget; = "toolButton1" - actually, a much longer path to the Toolbox UI
global string $gNonSacredTool; = "superCtx1"
global string $gToolBox; = "gridLayout2" - actually, a much longer path to the Toolbox UI
}}}
*ALL nodes in Maya are DG nodes. DG stands for Dependency Graph. All nodes live in the dependency graph, and can connect to one another via the attributes. Picking a node, opening the Hypergraph, and pressing the "Input and Output Connections" button will show you the dependency graph for that node.
*A subset of DG nodes are DAG nodes. DAG stands for [[Directed Acyclic Graph|http://en.wikipedia.org/wiki/Directed_acyclic_graph]]. In English it really means 'parent\child relationships'. While all nodes can connect via attrs in the Dependency Graph, only certain types (transforms, shapes) can have parent\child relationships. Also see: '[[What are ALL the shape nodes in Maya?]]'
*In the Outliner, by default you see only DAG objects. There is a filter that allows you to //turn off// "Show Dag Objects Only". At which point, the Outliner fills with all the DG nodes in the scene (asside from some that are still hidden, by default, another option will display them too).
I update this as I run across them ;) By no means an exhaustive list!
For the official docs on Python's built-in exception types, go [[here:|http://docs.python.org/library/exceptions.html]].
For any of the below examples to work in Maya, you'd first need to:
{{{
import maya.cmds as mc
}}}
!{{{RuntimeError}}}
For when a command fails, but not due to a syntax problem.
http://docs.python.org/library/exceptions.html#exceptions.RuntimeError
''Example:'' (just one of many...)
{{{string}}} attrs need to be added with the {{{dataType}}} argument, not the {{{attributeType}}} argument:
{{{
mc.addAttr("myObject", longName = "stringAttr", attributeType = "string")
# Error: Type specified for new attribute is unknown.
# Traceback (most recent call last):
# File "<maya console>", line 1, in <module>
# RuntimeError: Type specified for new attribute is unknown.
# #
}}}
{{{
mc.addAttr("myObject", longName = "stringAttr", dataType = "string")
# success!
}}}
So a nice workaround would be (presuming you were procedrually adding attrs, and didn't know what type to expect:
{{{
try:
mc.addAttr("myObject", longName = "stringAttr", attributeType = "string")
except RuntimeError:
mc.addAttr("myObject", longName = "stringAttr", dataType = "string")
}}}
Using {{{except RuntimeError:}}} is better than just using {{{except:}}}, since it will only catch {{{RuntimeError}}}s, and still report any other problems. Like, our next example:
!{{{SyntaxError}}}
For when you make a syntactical goof:
http://docs.python.org/library/exceptions.html#exceptions.SyntaxError
''Example:''
I add "{{{oops!}}}" to the middle of my command:
{{{
mc.addAttr("myObject", oops!, longName = "stringAttr", dataType = "string")
# Error: ('invalid syntax', ('<maya console>', 2, 28, 'mc.addAttr("myObject", oops!, longName = "stringAttr", dataType = "string")\n'))
# File "<maya console>", line 1
# mc.addAttr("myObject", oops!, longName = "stringAttr", dataType = "string")
# ^
# SyntaxError: invalid syntax #
}}}
And look, it's even nice enough to put a little carrot ({{{^}}}) under the part that has the syntactic mistake.
!{{{TypeError}}}
When you pass the wrong data type into a command.
http://docs.python.org/library/exceptions.html#exceptions.TypeError
''Example:''
Try to pass in a {{{string}}} default value to the new attribute. It's expecting a float.
{{{
mc.addAttr("null1", longName="test", defaultValue="val")
# Error: Invalid arguments for flag 'defaultValue'. Expected float, got str
# Traceback (most recent call last):
# File "<maya console>", line 1, in <module>
# TypeError: Invalid arguments for flag 'defaultValue'. Expected float, got str #
}}}
!{{{AttributeError}}}
When you try to call to a ~Maya-Python command that doesn't exist
http://docs.python.org/library/exceptions.html#exceptions.AttributeError
''Example'':
Try to find the type of an object:
{{{
print mc.objType("null1")
# Error: 'module' object has no attribute 'objType'
# Traceback (most recent call last):
# File "<maya console>", line 1, in <module>
# AttributeError: 'module' object has no attribute 'objType' #
}}}
There is no 'attribute' of the {{{maya.cmds}}} ({{{mc}}}) module called {{{.objType()}}}.
But there //is// an attribute called {{{.objectType()}}}:
{{{
print mc.objectType("null1")
# transform
}}}
!{{{IOError}}}
Usually having to do with file operations, like trying to write to a read-only file.
http://docs.python.org/library/exceptions.html#exceptions.IOError
''Example:''
Forthcoming...
As of Maya 2008, as I find them...
*//Most// {{{string}}} commands\scripts, (like {{{match}}}, {{{size}}}, {{{sort}}}, etc) don't exist. That's ok of course, since Python has such better [[string handling services|http://docs.python.org/library/string.html]] and [[string methods|http://docs.python.org/library/stdtypes.html#string-methods]].
*//All// of the {{{math}}} commands\scripts, (like {{{abs}}}, {{{fmod}}}, {{{pow}}}, {{{seed}}}, etc). But again, Python gives you its [[math module|http://docs.python.org/library/math.html]], so no bad there.
**However, the Python math module has no commands for processing //vectors// or //matrices// like mel does, and it has no built-in types that represent them at all, which... honestly, sucks.
***For Vectors: It looks like the external libraries [[VPython|http://vpython.org/]] and [[NumPy|http://numpy.scipy.org/]] //do// have vector calls. See notes on my Python Wiki [[here|http://pythonwiki.tiddlyspot.com/#%5B%5BAre%20there%20vector%20math%20libraries%20in%20Python%3F%5D%5D]]
***There are some vector solutions [[in Maya|How can I do vector math via Python in Maya?]] as well.
***For Matrices: See my notes [[here|Working with matrices with Python]]
*The {{{catch}}} 'keyword' is gone. But it's been replaced by Python's [[exception handling|http://docs.python.org/library/exceptions.html]], which is much better (see Mel Wiki notes [[here|What kind of exceptions does Python raise when a mel command fails?]]). To understand how they work, see my [[Python Wiki|http://pythonwiki.tiddlyspot.com/#%5B%5BHow%20do%20I%20catch%20%5C%20handle%20exceptions%3F%5D%5D]]
*{{{eval}}} is gone, but Python has it's own [[eval|http://docs.python.org/library/functions.html#eval]] function for doing the same thing with //Python// expressions. And ~Maya-Python has {{{maya.mel.eval}}} for evaluating //mel// expressions.
*{{{evalDeferred}}}, gone. But, you can use {{{maya.utils.executeDeferred}}} in Python.
*{{{scriptJob}}} : Possibly gone in 8.5, but appears to be back in 2008 and newer.
*{{{trace}}} : Gone. Replaced by Python's [[traceback|http://docs.python.org/library/traceback.html]]. Also see Python's [[sys.exc_info|http://docs.python.org/library/sys.html#sys.exc_info]] to help with the traceback.
*{{{env}}} : Gone. Replaced with Python's [[locals|http://docs.python.org/library/functions.html#locals]] and [[globals|http://docs.python.org/library/functions.html#globals]] functions. However, you can actually gather this info in Python, see my post [[here|How can I get a Python dictionary of every mel global variable?]].
*{{{whatIs}}} : Gone. You have to wrapper the mel with {{{maya.mel.eval}}}. Or in Python, you can just use {{{type()}}} or {{{inspect.getfile()}}}
*{{{error}}} : Gone. You should use Python's [[exception handling|http://docs.python.org/library/exceptions.html]] instead. Serves the same purpose, only better.
*{{{warning}}} : Gone. You can use Python's [[warnings|http://docs.python.org/library/warnings.html]] instead. However, this doesn't give Maya //color feedback//. If you really need that 'purple' command-line warning color, you can use this:
{{{
import maya.mel as mm
mm.eval('warning "This is a warning";')
}}}
* {{{rehash}}} : Gone, but you can {{{eval}}} through Python this way:
{{{
import maya.mel as mm
mm.eval('rehash;')
}}}
----
Also see:
*[[What Python modules are missing from Maya's implementation?]]
{{{curveInfo}}}
{{{
# Python code
import maya.cmds as mc
lenCalc = mc.createNode("curveInfo")
mc.connectAttr("myCurveShape.worldSpace[0]", lenCalc+".inputCurve")
}}}
You can then query, or connect to the {{{curveInfo}}} node's "{{{.arcLength}}}" attribute
{{{#1}}} (#2, #3, etc)
*Example:
{{{
floatFieldGrp -nf 1 -cc "myProc(#1)";
}}}
*In this example, some proc "myProc" is executed, using the value from the floatFieldGrp as its argument. The number "#" represents the field to grab the data from, so if there was more than one field, you could use #2, #3, etc.
This doesn't work on all kinds of UI controls.
Based on the Maya 2008 docs, you can find the documentation for it here:
**Using Maya -> General -> Mel and Expressions -> Creating Interfaces -> Attaching commands to UI elements
Recently ran into several files that when I'd open them, I'd get these errors:
{{{
// Error: line 1: No object matches name: |someNamespace:someNode_parentConstraint1 //
// Error: line 0: Error reading file. //
}}}
The biggest side effect of which was Maya not logging what the scene name was. See notes on that subject [[here|Why can't I query the scene name?]].
But what was causing this? There are probably more than one reason, but this is what I ran into:
*In the file, there were referenced nodes.
*Certain referenced nodes had been constrained to other non-referenced nodes.
*The created constraint nodes were parented to the referenced nodes being constrained (as expected).
The problem however, as you can see from the error line, is that the 'missing constraint' was being queried from the //root of the scene// (based on the pipe {{{|}}} before its name) when in fact it was parented to a referenced node. So that query path is invalid.
The fix was actually quite easy: I unparented all the constraint nodes so they were children of the root, re-saved the scene. When I reopened the scene, not only had the errors gone away, but all the constraints had re-parented themselves back to their previous referenced parents. Wacky, but true.
This deals with Maya's interactive display, not prerendered situations:
*If you have a non-planar n-sided faces (quad or greater) being deformed on a mesh, Maya will "on the fly" change the triangulation to best match the non-planar shape. If there are textures on the object, this is made visible by the texture "flickering" (as the UV's are subtly changed, based on the new triangulation I presume). While there is no harm done in this, it is visually bothersome to some people.....
*To disable this "flickering", //enable// the mesh's (shape node) {{{.reuseTriangles}}} attr:
{{{
setAttr ($myMesh + ".reuseTriangles") 1;
}}}
From the docs:
*"Should the triangles be reused. If set to ON, the triangulation that existed before the tweaking, or deformation will be re-used. Setting this to true helps interactive tweaking and deforming of objects. This setting helps objects with N-sided faces. This setting is meant for display modes that require triangles, as in the case of shaded mode display, or when the displayTriangles attribute is turned on. Note that the displayed triangles may not be accurate, since the pre-deformed, pre-tweaked triangles are re-used."
Thanks to Eric Vignola for finding this ;)
I've ran into (quite frustrating) instances where after I make a UI, all the elements in the UI are either hidden, or stacked on top of each other, and they don't 'appear' until I resize the UI: Upon resize, the 'spring' into place. I've jumped through a lot of hoops writing callbacks to try to automatically resize the UI, but I didn't get any luck that way. Finally tracked it down:
To display a window after creation, you call to the {{{showWindow()}}} command. I've learned that if you call to this command //before// the window has been completely made, it won't display the window elements properly until the window is resized (which must make call to the windows geometry manager and clean things up). Here are two examples illustrating the issue:
!!!Good Example:
This example displays successfully: {{{showWindow()}}} is execute after the window is created. The two {{{frameLayout}}}s and their {{{button}}}s are properly placed in the window:
{{{
# Python code
import maya.cmds as mc
if mc.window("testWinA", exists=True):
mc.deleteUI("testWinA")
mc.window("testWinA", resizeToFitChildren=True)
topCol = mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5), rowSpacing=5)
mc.frameLayout(label="test frame1", collapse=False, collapsable=True, borderStyle='out')
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
mc.button(label='but1')
mc.setParent(topCol)
mc.frameLayout(label="test frame2", collapse=False, collapsable=True, borderStyle='out')
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
mc.button(label='but2')
# call to showWindow at the end, display is correct:
mc.showWindow()
}}}
!!!Bad Example:
However, the next example fails: The window is shown immediately after it is created, then the UI elements are added to it. The result has the two {{{frameLayout}}}s stacking on top of one another, hiding the second {{{button}}}. If you resize the UI, the layout will 'spring' into place:
{{{
# Python code
import maya.cmds as mc
if mc.window("testWinB", exists=True):
mc.deleteUI("testWinB")
mc.window("testWinB", resizeToFitChildren=True)
# call to showWindow at the beginning, final display is incorrect:
mc.showWindow()
topCol = mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5), rowSpacing=5)
mc.frameLayout(label="test frame1", collapse=False, collapsable=True, borderStyle='out')
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
mc.button(label='but1')
mc.setParent(topCol)
mc.frameLayout(label="test frame2", collapse=False, collapsable=True, borderStyle='out')
mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5))
mc.button(label='but2')
}}}
It's common to query the Maya scene name via:
{{{
string $name = `file -q -sn`;
}}}
And //normally//, it'll print the name in the process. But somtimes it won't, and just return an empty string. Why?
A couple reasons I've ran into:
#__The scene has been imported, or 'dragged' into Maya__: If Maya has a new unsaved scene, in which you import a new scene into, Maya still won't know the name of the active scene, since it hasn't been saved. Save the scene, and how it has a name.
#__There are Errors on file open__: I've ran into occurrences when the Script Editor reports Errors on open, it will fail to recognize the scene name. It also won't add the file to the 'Recent Files List'. If you resave the scene file after open, it will 'set' the name, and the {{{ls}}} command will start working again.
##In one instance, I found the reason for the file-open errors: [[When I open a scene I get 'No object matches name:" errors, how to fix?]]
Sometimes when filling variables, either in Maya, or when looking at a mayaAscii file, you'll see "non-standard" characters \ symbols like:
{{{
€ // what is that, the Euro symbol?
# // pound symbol
1.#QNAN // found this when a camera's positions corrupted
-1.#IND // same camera problem
1.#INF // same camera problem
1.#QO // showed up in the camera's tras channels when framing corrupt geo...
others? (see 'e-' * 'e+' below)
}}}
Why does this happen? My best guess is that it has something to do with 'order of operation' and writing to memory: Sometimes mel commands take longer to execute than maya expects, so it will keep running code before the past command has finished. When it tries to capture the data, or fill new data while a command is still executing, problems can occur, and these weird symbols can pop up. I've see them in skin weights and path names most commonly.
How to fix? Some ideas:
*Try breaking your procedure into multiple smaller procedures, each that has a return value. Call to them in order capturing the return value, which //should// make Maya stop and smell the roses.
*Use {{{evalDeferred -lp "command..."}}} to make Maya wait until the processor becomes free to execute the command. However, this starts a slippery slope of making everything run in {{{evalDeferred}}}, so use sparingly.
----
Notes on {{{€}}}:
*I had a script that was filling each element of a vector in a vector array with the return value from some code that maintains float precision. It was all happening on one line.
*On days 1-2, code worked fine. On day 3, it stopped working:
*When I would interactively select-execute the code in the Script Editor, I would get errors in the history: On each of the vector declaration lines, at either 125 or 126 characters, it would truncate the line, and insert a {{{€}}} character at the end (shown in the history of the Script Editor).
*The only solution I came up with (restarting Maya, and my machine didn't help) was to put each call to the precision code on a single line, filling a single variable, //then// passing that to the vector array.
*The kicker is the code that was failing was duplicated in another proc in the script, the only difference was the data was being passed to a string, rather than a vector. :-S
*I've had this show up in other places, and in those cases I found the problem: I was evaluating a string, and escaping some variable names I'd passed inside. I had one too many escape characters '{{{\}}}', and for some reason the extra escape char was causing this symbol to appear.
*Another example: I was sourcing a script, and in that script was a procedure being called to with arguments. The arguments were concatenated strings, but in on case, I forgot to add the end parenthesis, and got this as part of Maya's error. As you can see, there is a missing parenthesis in there, causing the next argument to really get confused...
**The source line being eval'd looked like this:
{{{
...rigDir, (assetDir+"/build/", "S:/assets/rigs/shared/")});
}}}
**Notice, the mistake with the parenthesis '{{{)}}}' missing after {{{/build/}}} and an extra '{{{)}}}' after {{{/shared/}}}?
**This is the error code that Maya spit out:
{{{
...$rigDir, ($assetDir+"/build/", "S:/a€ //
}}}
----
Here's an example of bad data, from a script I was writing:
{{{
file -f -chn 1 -con 0 -exp 1 -ch 1 -typ "mayaAscii" -es c:/temp/asse€
}}}
The end asset path was a variable I had passed into this command. But for whatever reason due to previous evaluation issues, it wasn't able to store the full path value, so it failed.
It should be said that sometimes you'll see numbers like this:
{{{
8.881784197e-018
8.881784197e+018
}}}
The {{{e-}}} isn't a "bug" it's saying that it's 18 decimal places over: A very very small number!:
If I detect {{{e-}}} in my number, I usually just set it to zero.
{{{e+}}} is the same thing, but it's saying it's a HUGE number.
Maya gives you the ability to right-mouse-button on a set in the Outliner, and select the members inside the set. However, on multiple occasions (reported to me), that functionality has simply quit working. After doing research, it has come down to this:
The {{{userPrefs.mel}}} somehow got corrupted:
(on Windows)
{{{
C:\Documents and Settings\<user>\My Documents\maya\<version>\prefs\userPrefs.mel
}}}
When the user deleted (or renamed) this file, it started working correctly again. I havn't spent the time figuring out //what// in that file is causing the issue.
I've tried xpm, and jpg, but nothing works? I seems that the {{{iconTextButton}}} really wants a ''bmp'' file format: Pass in the absolute path to the image, and it shows right up.
{{{
# Python code:
mc.iconTextButton(style='iconOnly', image="c:/full/path/to/image.bmp")
}}}
Notes on editing wiki:
*[[Add custom graphics to the header|http://www.tiddlywiki.org/wiki/How_To/Header_Macro/Plugin_%28for_Custom_Graphic_Header%29]]
*[[text formatting]]
*[[list of macros|http://www.tiddlywiki.org/wiki/What_macros_are_built_in_to_TiddlyWiki%3F]]
*[[DateFormatString arguments|http://www.tiddlywiki.com/#DateFormatString]]
Also see my other subject [[Matrix info]] for non-Python related matrix stuff, and an overview of how matrices in Maya work.
----
By default Python has no "matrix" data object. But //mel does// ({{{matrix[][]}}}), and through mel you can add, multiply (etc) matrices. One way I've found to do this in Python is through the {{{OpenMaya}}} api calls (reference I pulled a bunch of this info from [[here|http://www.rtrowbridge.com/blog/2008/11/05/vectors-and-matrices-in-python/]]).
----
To make things easier, first define some helper functions that will process matrices based on how Maya deals with them. Specifically, if you query a matrix attr on a node, it returns back a list of 16 floats. And, using the {{{xform}}} command, if you try to apply a matrix back to a node, it also expects a list of 16 floats. But to do any kind of matrix //math// between matrices (using operators like {{{+}}}, {{{*}}},{{{ -}}}, etc) you need to turn those float lists into actual API {{{MMatrix}}} objects that support the given operators.
*Docs for the API [[MMatrix|http://download.autodesk.com/us/maya/2010help/API/class_m_matrix.html]] class.
{{{
# Python code
# matrixutils.py
import maya.cmds as mc
import maya.OpenMaya as om
def listToMMatrix(mList):
"""
Convert a list of 16 floats into a MMatrix object
Mainly a helper for getMMatrix()
"""
if len(mList) != 16:
raise Exception("Argument 'mList' needs to have 16 float elements")
m = om.MMatrix()
om.MScriptUtil.createMatrixFromList(mList, m)
return m
def mMatrixToList(matrix):
"""
Convert a MMatrix object into a list of 16 floats.
Mainly a helper for matrixXform()
"""
return [matrix(i,j) for i in range(4) for j in range(4)]
def getMMatrix(node, matrixType):
"""
Get matrix data from a node, and return as MMatrix object.
"""
good = ["matrix", "inverseMatrix", "worldMatrix", "worldInverseMatrix",
"parentMatrix", "parentInverseMatrix", "xformMatrix"]
if matrixType not in good:
raise Exception("Argument 'matrixType' is an invalid matrix attr type."+
" Please choose from: " + ' '.join(good))
return listToMMatrix(mc.getAttr(node+"."+matrixType))
def matrixXform(node, matrix, spaceType):
"""
Transform a Maya DAG (transform, joint) object based on a given matrix value,
via the mel 'xform' command.
matrix : either a maya.OpenMaya.MMatrix object, or a list with 16 entries.
spaceType : either 'worldSpace' or 'objectSpace'
"""
mList = None
if type(matrix) is type(om.MMatrix()):
mList = mMatrixToList(matrix)
elif type(matrix) is type(list()) and len(matrix) == 16:
mList = matrix
else:
raise TypeError("Arg 'matrix' either needs to be a maya.OpenMaya.MMatrix object, or list with 16 entries")
if(spaceType == "worldSpace"):
mc.xform(node, worldSpace=True, matrix=mList)
elif(spaceType == "objectSpace"):
mc.xform(node, objectSpace=True, matrix=mList)
else:
raise ValueError("spaceType arg is either 'worldSpace' or 'objectSpace', passed value is '%s'"%spaceType)
}}}
And some example usages:
*[[Finding and applying the difference between two matrices]]
Create two objects in Maya, objectA, and objectB. Transform both of them in space away from each other. Then, grab their worldMatrix info:
{{{
matrixA = getMatrix("objectA", "worldMatrix")
matrixB = getMatrix("objectB", "worldMatrix")
}}}
Matrices are applied in the order multiplied. Order is very important! The order could be considered a transform hierarchy, with parents on the left, and children descending to the right. In the below example, we create a new matrix that could be thought of as being the same position if you had parented objectB to objectA (matrixA (the 'parent') is on the left, matrixB (the 'child') is on the right) maintaining objectB's current offset from the origin. When it is applied to objectB, it will jump to that new location as defined by the 'parental' transformations on objectA:
{{{
offsetMatrix = matrixA * matrixB
matrixXform("objectB", offsetMatrix, "worldSpace")
}}}
To query an item in a {{{MMatrix}}} object requires some hoop-jumping. This example will query the second row ({{{[1]}}}), and the first item in that row ({{{0}}}):
{{{
a = om.MScriptUtil.getDoubleArrayItem(matrixA[1], 0)
}}}
----
The book [[Beginning Game Development with Python and PyGame: From Novice to Professional|http://www.apress.com/book/view/9781590598726]] describes a module, and a specific class for dealing with matrices:
*It has a {{{matrix44}}} class in its [[gameobjects|http://code.google.com/p/gameobjects/]] module. [[gameobjects documentation|http://www.willmcgugan.com/gameobjects/docs/index.html]]. [[matrix44 documentation|http://www.willmcgugan.com/gameobjects/docs/gameobjects.matrix44.Matrix44-class.html]]. It stores its translation values along the bottom row, which is the same way Maya does it.
Here's an example using it, and some of the above code, to transform object "test2" to match positions with object "test":
{{{
import maya.cmds as mc
# matrixXform() is defined in matrixutils.py in the above example.
from matrixutils import matrixXform
from gameobjects import Matrix44
# Get our matrix as a list of 16 items:
mtxList = mc.getAttr("test.matrix")
# Make a Matrix44 object from that list:
mtx44 = Matrix44.from_iter(mtxList)
# Transform object 'test2' by object 'test's matrix:
matrixXform("test2", mtx44._m, 'worldSpace')
}}}
----
I've also found a Python module called '~PyEuclid' that cas a matrix class, but I have yet to do any research into it. However, it does seem to store its matrix data differently from Maya: It appears to store its transforms down the right column, rather than the bottom row (Maya).
*Main Page: http://partiallydisassembled.net/euclid.html
*Documentation: http://partiallydisassembled.net/euclid/
*Source: http://code.google.com/p/pyeuclid/
There are two terms that sound a lot a like, but are in fact different:
*''current //workspace// directory'' : Where the Maya 'project' defaults too, and is modified by the {{{workspace}}} command. We're not dealing with this concept in this example, but check notes on it [[here|How can I modify the current project \ workspace?]].
*''current //working// directory'' : From the Maya docs: "This starts out as the directory where Maya was launched, but will change in response to {{{chdir}}} commands as well as some file browsing operations. This directory is where system-level commands such as {{{system}}}, {{{fopen}}}, and {{{popen}}}. The working directory is not related to the [[workspace directories|How can I modify the current project \ workspace?]] where scene data is stored, and should not be used for accessing or creating scene- or project-specific files."
The @@{{{pwd}}}@@ command simply prints the 'current working directory', while the @@{{{chdir}}}@@ command sets it.
The {{{.message}}} attribute of a node is a connectible pointer to its name. It's handy for connecting to other nodes, to keep track of dependencies. In fact, this is exactly how sets work: The message attribute of a node connects to a multi-attr in the set, and that's how the set tracks its memberships.
You can add your own attributes of type 'message' to any node, then connect the message attributes of other nodes into them (below example has some presumed transforms named '{{{messageMachine}}}' and '{{{caller}}}' already in the scene):
{{{
# Python code
import maya.cmds as mc
# Add our message attr to messageMachine:
mc.addAttr('messageMachine', longName='incomingMessage', attributeType='message')
# Connect caller to messageMachine:
mc.connectAttr('caller.message', 'messageMachine.incomingMessage')
}}}
If you opened {{{messageMachine}}} in the Attribute Editor, under Extra Attributes you'd see the new {{{.incomingMessage}}} attr, with its connection to {{{caller}}}.
What's interesting though, is that Maya states the message attr has "no data" (under the {{{addAttr}}} docs). Well, we know it has //some// data value, but how to we find out what it is?
For example, we want to know the value of {{{messageMachine.incomingMessage}}}:
{{{
whosCalling = mc.getAttr('messageMachine.incomingMessage')
# Error: line 1: Message attributes have no data values.
}}}
Well, now what?
{{{
whosCalling = mc.listConnections('messageMachine.incomingMessage')
print whosCalling
# [u'caller']
}}}
The {{{listAttr}}} command tells us exactly what the 'value' of the message attr is since the 'value' of the message attribute is the name of the connected node.
----
Also see:
*[[How can I add a multi attr to my node, query its values?]]
Like most wikis, TiddlyWiki supports a range of simplified character formatting:
| !To get | !Type this |h
| ''Bold'' | {{{''Bold''}}} |
| --Strikethrough-- | {{{--Strikethrough--}}} |
| __Underline__ | {{{__Underline__}}} (that's two underline characters) |
| //Italic// | {{{//Italic//}}} |
| Superscript: 2^^3^^=8 | {{{2^^3^^=8}}} |
| Subscript: a~~ij~~ = -a~~ji~~ | {{{a~~ij~~ = -a~~ji~~}}} |
| @@highlight@@ | {{{@@highlight@@}}} |
<<<
The highlight can also accept CSS syntax to directly style the text:
@@color:green;green coloured@@
@@background-color:#ff0000;color:#ffffff;red coloured@@
@@text-shadow:black 3px 3px 8px;font-size:18pt;display:block;margin:1em 1em 1em 1em;border:1px solid black;Access any CSS style@@
<<<
//For backwards compatibility, the following highlight syntax is also accepted://
{{{
@@bgcolor(#ff0000):color(#ffffff):red coloured@@
}}}
@@bgcolor(#ff0000):color(#ffffff):red coloured@@
*Beginners Guide:
**http://www.giffmex.org/twfortherestofus.html
*TW Help
**http://tiddlyspot.com/twhelp/
*GoogleGroup: TiddlyWiki
**http://groups.google.com/group/TiddlyWiki
*GoogleGroup: TiddlyWikiDev
**http://groups.google.com/group/TiddlyWikiDev
*TiddlyWiki Development Community:
**http://www.tiddlywiki.org/