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.
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/main.html
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
----
''Differences between {{{c++}}} and {{{Python}}}''
*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}}}).
''{{{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...
''{{{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...
''{{{OpenMayaAnim}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_anim.html]]) Animation related classes
*Notes forthcoming...
''{{{OpenMayaUI}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_u_i.html]]) Clases for manipulating the UI
*Notes forthcoming...
''{{{OpenMayaFX}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_f_x.html]]) API modules for FX.
*Notes forthcoming...
''{{{OpenMayaRender}}}''([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_render.html]]) API module for rendering.
*Notes forthcoming...
''{{{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...).
----
{{{
# Python code
import maya.OpenMaya as om
selList = om.MSelectionList()
om.MGlobal.getActiveSelectionList(selList)
sel = []
selList.getSelectionStrings(sel)
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/]]>>
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.
*[[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]] - TROUBLESHOOTING
*[[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]] catagory, update wiki UI formatting.
*[[2007 04 27]] - mel wiki is born
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]]
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.
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.
I've 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 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 one:
http://www.wingware.com/
[img[https://wingware.com/images/wingide2-personal-screenshot-qtr.png]]
It has three versions:
*Wing IDE 101 (free, but //doesn't// have 'Evaluate Selection')
*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
More Wing Goodness:
*[[How can I use Python (Wing) to talk with Maya?]]
*[[Remote Python debugging in Wing]]
----
''Also'', as of v1.7 of the 'jEdit Maya Editor Plugin' (see: [[Script Editor Replacements]]), this 'evaluate selection' functionality has been implemented (as informed by its author).
----
Also see:
*[[How can I execute Maya's version of Python outside of Maya?]]
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: 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.
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.
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 break. A convention I've made 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?]]
{{{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:
*Example:
**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....
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;
}}}
{{{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;
}}}
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";
}}}
{{{
// 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");
}}}
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.
*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".
*The DOS rmdir command. If there is a way to do it in mel then I'm missing it.
*Example: Delete a directory and all files in it (/s), without prompt (/q):
{{{
system("rmdir /s /q " + $myPath);
}}}
{{{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;
}}}
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'':
{{{
// 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];
}}}
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}}}.
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)
}}}
<<<
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?]]
*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.
Maya comes with its own cut of Python (as of Maya2008 this is Python v 2.5.1). 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 does this, see notes [[here|For Python: Maya 'Script Editor' style IDE]]).
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 my external IDE), 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. This appears to be some kind of voo-doo that Maya has cooked up. 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?]]
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?
{{{
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`;
}}}
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)
}}}
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?]]
{{{
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.
{{{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]`;
}}}
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]]
}}}
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?]]
Download:
http://www.highend3d.com/maya/downloads/tools/syntax_scripting/Pymel-Python-Module-4844.html
Docs:
http://pymel.googlecode.com/svn/docs/index.html
Google Code page:
http://code.google.com/p/pymel/
Bug Tracker:
http://code.google.com/p/pymel/issues/list
pymel + ipython demo video:
http://www.youtube.com/watch?v=EaIT8czZrlo
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....
''Update'': 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.
Info from their site below. Find more info from both the links.
*"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."
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.
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
}}}
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]
}}}
{{{
>> 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;
}}}
Credit Steve Kestell.
{{{
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";
}}}
*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:
{{{
followObj.translateX = `getAttr -t (frame - 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?]]
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);
}}}
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 //
}}}
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:
**Told works, need to define, comes with dynamics for free.
*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
}}}
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 sets the current path as the *default project*:
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}}}, 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.
{{{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 direcoty:
{{{
file -i `fileDialog -dm \"Z:/rotk/objects/char/rig/scenes/myFile.*\"`;
}}}
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?]]
*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];
}
}}}
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 ] ] ]}}}
{{{trace}}}
For example:
{{{
trace "Printing to the Output Window";
}}}
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 have a {{{-tl}}} (target list) flag that can be used to query:
Presuming it's a {{{parentConstraint}}}
{{{
string $targets[] = `parentConstraint -q -tl $constraintName`;
}}}
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
{{{
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.
----
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`;
}}}
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
}}}
{{{
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)
}}}
{{{
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;
}}}
Say the user has picked some anim curves in the Graph Editor... what are they?
{{{
string $pickedKeys[] = `keyframe -q -n`;
// 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.
{{{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");
}
}
}}}
{{{strip}}}
*Example:
{{{
string $word = "asdf\r\n";
int $size = size($word) \\ result: 6
$word = strip($word);
$size = size($word); \\ result: 4
}}}
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}}}
{{{
parent -s -r "shapeNode" "transformNode";
}}}
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])
}}}
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.
{{{
selectKey -time ":";
float $allKeys[] = sort(`keyframe -q -sl`);
playbackOptions -min $allKeys[0] -max $allKeys[(size($allKeys) -1)];
}}}
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.
The below code will track down the {{{dagPose}}} nodes for each selected mesh, 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...
sel = mc.ls(selection=True)
# 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'
dagPose = set(mc.listConnections(skinCluster , source=True, destination=False, type="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?]]
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
{{{
string $filePath = "c:/temp/test.txt";
int $fileId = `fopen $filePath "r"`;
string $fileText[];
// Find the initial line
string $nextLine = `fgetline $fileId`; // print $nextLine
// Clean the line, removing any "return" or "newline" characer at the end.
// If we don't do this, each array entry will have an emply line after it (not
// very clean). We could use the 'strip' command, but this would also
// remove any tabbing or spaces at the front of the line, which we may
// want to keep.
string $fixed = `substitute "\\\n$" $nextLine ""`;
$fixed = `substitute "\\\r$" $fixed ""`;
$fileText[0] = $fixed;
// And for each line, assign it ot an array:
for($i=1;size($nextLine)>0;$i++)
{
$nextLine = `fgetline $fileId`;
// Again, clean the line
string $fixed = `substitute "\\\n$" $nextLine ""`;
$fixed = `substitute "\\\r$" $fixed ""`;
$fileText[$i] = $fixed;
}
fclose($fileId);
print $fileText;
}}}
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 using [[Wing IDE|For Python: Maya 'Script Editor' style IDE]] for authoring Python code for Maya, and have come up with a solution (presuming you have the 'Professional' version of Wing) to have the commands you @@highlight in Wing@@ //execute in Maya//. This lets me completely replace the Script Editor when authoring Python, which we all know doesn't have the best Python integration... :-S
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 -n ":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.'' In Maya's [[userSetup.py|How can I get Python code to execute during Maya startup?]] file, add this line to import the {{{execPythonCode.py}}} module (authored in step 4 below) before Maya starts:
{{{
# userSetup.py
import execPythonCode
}}}
----
''3.'' This next Python module ({{{wingHotkeys.py}}}) and function ({{{send_to_maya()}}}) is authored to do two things:
* A. Save the text selected in Wing as a file on disk.
* B. Ping Maya through the opened command port, and tell Maya to execute the contents of that file.
Again, this code is tailored to Wing IDE's API, and saved as part of that program's own "scripts" directory. Default location of that dir is here:
{{{
C:\Documents and Settings\<userName>\Application Data\Wing IDE 3\scripts
}}}
I have it mapped to a Wing hotkey so that when I select text and hit {{{ctrl+m}}}, it executes.
If you already have a {{{wingHotkeys.py}}} module for Wing, you can just add the below code to it:
{{{
# wingHotkeys.py
import wingapi
import socket
def send_to_maya():
"""Send the selected text to Maya"""
editor = wingapi.gApplication.GetActiveEditor()
if editor is None:
return
# get the selected text from our IDE:
doc = editor.GetDocument()
start, end = editor.GetSelection()
txt = doc.GetCharRange(start, end)
# Save the text to a temp file:
tempFile = "c:/temp/pythonToMaya.py"
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)
maya.connect(("127.0.0.1", 6000))
maya.send('python("execPythonCode.main()")')
}}}
----
''4.'' This final Python module {{{execPythonCode.py}}} is the one {{{send_to_maya()}}} (above, step 3) executes: 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
import __main__
import os
def main():
"""
exec the temp file on disk, in Maya
"""
tempFile = "c:/temp/pythonToMaya.py"
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.
----
''5.'' That's it: You can now highlight code in Wing, and via the 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]]
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");
}}}
{{{
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!
"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`;
}}}
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?]]
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.
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
{{{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
}}}
<<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
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.
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]]
[[NETWORKING]]
[[NURBS]]
[[POLYGON]]
[[PYTHON]]
[[REFERENCING]]
[[RENDERING]]
[[RIGGING]]
[[SCRIPTING]]
[[SELECTION]]
[[SHADING]]
[[STRING]]
[[SYSTEM]]
[[TEXTURE]]
[[TIME]]
[[TRANSFORMATION]]
[[TROUBLESHOOTING]]
[[UI]]
[[VARIABLES]]>>
I rarely ever deal with matrix data. So this is sort of a whiteboard as I learn it.
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:
*http://eddieoffermann.com/blog/tag/matrix-multiplication/
*http://www.185vfx.com/2003/03/convert-a-3d-point-to-2d-screen-space-in-maya/
All the below attr info pulled directly from the above doc links:
----
Maya transformation matrix:
{{{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\angle(?) =
**AX * AY * AZ
*R = rotate =
**RX * RY * RZ (Defined by rotate order)
*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.
----
{{{dagNode}}} node's matrix related attrs (all of these are available through any {{{transform}}} based node):
*{{{.matrix}}} : Local transformation matrix for the dagNode.
*{{{.inverseMatrix}}} : Inverse of matrix attribute.
*{{{.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.
*{{{.worldInverseMatrix}}} : Inverse of worldMatrix instanced attribute.
*{{{.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.
{{{transform}}} node's matrix related attrs:
*{{{.xformMatrix}}} : Local transformation matrix. Contains the same information as the matrix attribute on dagNode but it is stored in a format that can be interpolated easily.
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 matrixes. 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`;".
<<<
----
{{{xform}}} command:
{{{
// 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 -q -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.
*[[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 //
}}}
{{{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}}}''' ;)
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
}}}
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.
----
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.
I currently do all my Python authoring in [[Wing|For Python: Maya 'Script Editor' style IDE]] (professional). As explained in [[other subjects|How can I use Python (Wing) to talk with Maya?]] 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.
Here's how to set it up:
----
*Copy these two files to the directory that the module you want to debug lives in:
{{{
C:\Documents and Settings\<userName>\Application Data\Wing IDE 3\wingdebugpw
C:\Program Files\Wing IDE 3.1\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}}}:
{{{
# myModule.py
import wingdbstub
# 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
}}}
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.
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
<<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
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...
}}}
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 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}}} //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 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 package, there are other sub-packages, and modules. What do they do?
{{{
help(maya) # generated data for the below list
}}}
*{{{app}}} : Sub-package. 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.__
*{{{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.
*{{{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.
This package, as of Maya2008, is located here:
{{{
C:\Program Files\Autodesk\Maya<version>\Python\lib\site-packages\maya
}}}
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.
Some 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.
| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |
| 10/06/2009 09:50:03 | WarpCat | [[/|http://mayamel.tiddlyspot.com/#%5B%5BScript%20Editor%20Replacements%5D%5D]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 10/06/2009 10:05:02 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 17/06/2009 13:09:14 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 17/06/2009 14:06:08 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . | ok |
| 17/06/2009 14:44:58 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 17/06/2009 17:35:39 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 26/06/2009 16:57:59 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . | ok |
| 26/06/2009 17:01:28 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . | ok |
| 26/06/2009 17:02:38 | WarpCat | [[/|http://mayamel.tiddlyspot.com/]] | [[store.cgi|http://mayamel.tiddlyspot.com/store.cgi]] | . | [[index.html | http://mayamel.tiddlyspot.com/index.html]] | . |
| 27/06/2009 15:41:42 | WarpCat | [[/|http://mayamel.tiddlyspot.com/#%5B%5BHow%20can%20I%20set%20the%20selected%20meshes%20to%20their%20bind%20pose%3F%5D%5D]] | [[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 -
*Technical Character Director / Character TD / Technical Artist / Whatever your company calls it
*At: [[Electronic Arts|http://www.ea.com]], in [[Redwood Shores, California|http://maps.google.com/maps?f=q&hl=en&q=250+shoreline+drive,+redwood+city,+ca&sll=37.0625,-95.677068&sspn=60.50566,91.845703&layer=&ie=UTF8&z=19&ll=37.523797,-122.255964&spn=0.00188,0.003683&t=h&om=1]]
*[[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/]]
*[[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) {{{--->}}}
----
>>
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.
----
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// like mel does (mag, angleBetween, cross, etc), which... honestly, sucks.
***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.
*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}}} : Just plain gone.
*{{{trace}}} : Gone. Replacement?
*{{{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.
*{{{whatIs}}} : Gone. You have to wrapper the mel with {{{maya.mel.eval}}}
----
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
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 ;)
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.
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)
----
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 matrix objects that support the given operators.
{{{
# Python code
import maya.cmds as mc
import maya.OpenMaya as om
def listToMatrix(mList):
# Convert a list of 16 floats into a MMatrix object
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 matrixToList(matrix):
# Convert a MMatrix object into a list of 16 floats
return [matrix(i,j) for i in range(4) for j in range(4)]
def getMatrix(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 listToMatrix(mc.getAttr(node+"."+matrixType))
def matrixXform(node, matrix, spaceType):
# Transform an object based on a given MMatrix
mList = matrixToList(matrix)
if(spaceType == "worldSpace"):
mc.xform(node, worldSpace=True, matrix=mList)
if(spaceType == "objectSpace"):
mc.xform(node, objectSpace=True, matrix=mList)
}}}
And some example usages. 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")
}}}
----
I've also found a Python module called '~PyEuclid' that cas a matrix class, but I have yet to do any research into it.
*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.