<!--{{{-->
<!--}}}-->
Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}

h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

.tabSelected{color:[[ColorPalette::PrimaryDark]];
background:[[ColorPalette::TertiaryPale]];
border-left:1px solid [[ColorPalette::TertiaryLight]];
border-top:1px solid [[ColorPalette::TertiaryLight]];
border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}

#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}

#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

.tiddler .defaultCommand {font-weight:bold;}

.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}

.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}

.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}

.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}

.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}

.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}

.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}

#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
/*}}}*/
/*{{{*/
* html .tiddler {height:1%;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

a {text-decoration:none;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}

.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}

.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0em 14em;}

.toolbar {text-align:right; font-size:.9em;}

.missing .viewer,.missing .title {font-style:italic;}

.title {font-size:1.6em; font-weight:bold;}

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}

.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}

.sparkline {line-height:1em;}
.sparktick {outline:0;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/
/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
noscript {display:none;}
}
/*}}}*/
<!--{{{-->
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='sidebar'>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='editor' macro='edit title'></div>
<div macro='annotations'></div>
<div class='editor' macro='edit text'></div>
<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser'></span></div>
<!--}}}-->
To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
* SiteTitle & SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: <<option txtUserName>>
These InterfaceOptions for customising TiddlyWiki are saved in your browser

<<option chkSaveBackups>> SaveBackups
<<option chkAutoSave>> AutoSave
<<option chkRegExpSearch>> RegExpSearch
<<option chkCaseSensitiveSearch>> CaseSensitiveSearch
<<option chkAnimate>> EnableAnimations

----
Also see AdvancedOptions
<<importTiddlers>>
''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.
*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)
*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:
----
[[wiki help]]
While reading a book on the [[Processing|http://www.processing.org/]] language, they had a different take on the standard Maya {{{for}}} loop.  A normal {{{for}}} loop has this format:
{{{
for (initialization; condition; change of condition){
statement;
statement;
...
}
}}}
So a simple example:
{{{
for($i=0;$i<5;$i++){ print($i + " ");
};
0 1 2 3 4
}}}
I learned that you can actually have //multiple// __initialization__ parameters, along with multiple __conditions__, and multiple __change of conditions__:
{{{
for($i=0,$j=5; $i<$j, $j<10;$i++, $j++){ print($i + " " + $j + "\n"); } 0 5 1 6 2 7 3 8 4 9 }}} I'm really not too sure what I'm going to do with this info yet, but... I think it's good to know! Talking with other programmers, their reply was "oh yeh, //many// other languages do that". So, I guess I've been left out of the.... //loop//. (so bad, but so good). Retrospective blog entry. mel wiki is created! ''Happy New Year!'' I added this new blog section. I figured out how to move the "mel wiki" icon to the SiteTitle (very exciting). I also added my first bit of Python tagging, and setup a main [[PYTHON]] category. Still stuck on Maya 7 though... Finally, made the 'Tags' tab always be visible in the right hand column, which should make it easier for users to navigate the site by default. Man, there is a pile of rain coming down here in the San Francisco area! Tiddlyspot.com is down for some reason, so all the latest updates are being made offline... Tiddlywiki is back up again... I wonder what was going on... Robots! Not directly related to mel, but I made my first couple of solar powered robots: The Symet [img[http://farm3.static.flickr.com/2232/2170294205_36506d0c98_m.jpg][http://www.flickr.com/photos/8064698@N03/sets/72157603647631805/]] The Hexpummer [img[http://farm3.static.flickr.com/2129/2174047884_2b66e455ea_m.jpg][http://www.flickr.com/photos/8064698@N03/sets/72157603656270159/]] Added a new helpful section, the [[Visual Guide of UI Controls]]. Will be updating it slowly over time, in alphabetical order. Lets the user see the type of UI control before they build it... so you can make your UI's much faster! Added the new [[All Subjects]] tiddler in the main menu (on the left) of the screen: It will show ALL the subjects (tiddlers) in the wiki. Now you can search through the categories, or just __one big page__ of subjects. I recently got turned onto ''Processing'' through the ''Make'' blogs: [img[http://processing.org/reference/environment/images/ide.gif]] *Processing: http://www.processing.org/ *Make: http://www.makezine.com/ (which you should read all the time, FYI) It's a cool (free) language that sort of 'wrappers up' Java. From their site: <<< "Processing is an open source programming language and environment for people who want to program images, animation, and interactions. It is used by students, artists, designers, researchers, and hobbyists for learning, prototyping, and production. It is created to teach fundamentals of computer programming within a visual context and to serve as a software sketchbook and professional production tool. Processing is developed by artists and designers as an alternative to proprietary software tools in the same domain." <<< ---- Here's some fantastic examples by [[Jared Tarbell|http://www.complexification.net/programmer.html]]. One of the coolest things: He also gives the ''Processing'' source code away for each of them. That is awesome. Share the knowledge! *http://www.complexification.net/gallery/ [img[http://www.complexification.net/timeline/WTsubstrate.jpg]] [img[http://www.complexification.net/timeline/WThappyPlaceWideB.jpg]] [img[http://www.complexification.net/timeline/WTintersectionMomentary.jpg]] [img[http://www.complexification.net/timeline/WTboxFittingImg.jpg]] (many more in the above link) I added a new category (on the left) called [[TROUBLESHOOTING]]. It will take some time to organize other stuff into it, but it's a start. Added a new section called [[Other Links]]. Thought I'd share some of the other web sites that I enjoy, that have some correlation to brains that enjoy Maya and mel. [[A month or so ago|2008 02 18]] I talked about [[Processing|http://www.processing.org]]. Since then I've started to learn it, and have been posting some of my first sketches on [[Flickr|http://www.flickr.com/photos/8064698@N03/collections/72157604136742471/]]. Enjoy. I've FINALLY gotten around to re-learning Python. In lite of that, I've made a new Python Wiki: http://pythonwiki.tiddlyspot.com/ While there is still Maya-centric Python stuff on this page, non-Maya related Python stuff I"ll start posting over there. I'm still a Python noob, so there's nothing too amazing over there yet. It's like how this wiki started yeaaaars ago.  Finally got around to updating to tiddlywiki v2.4. No one will really care but me. But it's the new hottness. I've finally made a blog to tie together my various wiki's and projects. It can be found here: http://warpcat.blogspot.com/ A buddy of mine has started his own mel tiddlywiki as he learns the languange. Everyone should! ;) http://brac.tiddlyspot.com/ I thought I'd try and experiment, and create a [[Guest Book|GuestBook]]. I really have no idea how many people use my wiki, how often it is viewed, etc. I do get an occasional email telling me how someone has used it, which gives me the warm-fuzzies. If you'd like to 'sign' the [[Guest Book|GuestBook]], go to that link, and follow the directions. Since his wiki isn't open for public editing (one of the reasons I set it up that way: I used to get spammed all the time when anyone could edit it), you'll need to //email [[me|WarpCat]] your guest book comment. That can be a departure from regular website guest book signing, so I could see a reluctance on the part of some users. As I said, this is an //experiment// ;-) I've done some retagging when it comes to Python subjects: I already have an upper-case '[[PYTHON]]' category on the left-column. Before, //all// subjects that were either about Python specifically, or others that had Python code in their examples received that tag. I've now made a 'lowercase' '[[python]]' tag as well to help differentiate: *Subjects that are //specifically related to Python features// will get the upper-case //category// tag. *Subjects that have example Python code in them (or call to the {{{python}}} //mel// command), but aren't 'about' Python specifically, will get the 'lowercase tag'. I think this will help make searching for //Python-specific// issues easier, and not clutter it with code simply written //in// Python. Since I'm forcing myself to learn the API (very slowly), I added an [[API]] category. I'm mainly learning the API via Python, since I don't know much of c++ at all. Added the [[INFO]] category. Haven't yet fully populated it, but it was missing: A place on subjects about querying information about things. Added the [[HARDWARE]] category. Currently only has one item. But it's a cool item. I'm not sure how I'd use it, but I think it deserves its own category ;) Below are simple {{{lambda}}} wrappers for converting sequences (like API/~PyMel {{{Points}}} or {{{Vectors}}}) from Maya's 'internal units' (cm) to the current 'ui units' (could be cm, could be... something else) and back again. {{{ # Using API 2.0, but works just as well with maya.OpenMaya from maya.api.OpenMaya import MDistance internalToUi = lambda x:[MDistance.internalToUI(item) for item in x] uiToInternal = lambda x:[MDistance.uiToInternal(item) for item in x] }}} For example: {{{ import pymel.core as pm nodeA = pm.PyNode("coneA") nodeB = pm.PyNode("coneB") # This returns internal units: pt_nodeA_rotPiv = nodeA.getRotatePivot(worldSpace=True).cartesian() v_nodeA_rotPiv = pm.dt.Vector(pt_nodeA_rotPiv) # This expects a vector (or list of 3 vals) of *ui units*, so we convert: nodeB.setTranslation(internalToUi(v_nodeA_rotPiv), space='world') }}}  Good blog post here: http://around-the-corner.typepad.com/adn/2012/09/custom-shapes-in-the-maya-api-1.html Maya has a whole bunch of both c++ & Python API plugin \ scripted plugin example stuff online [[here|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/index.html?url=cpp_ref/examples.html,topicNumber=cpp_ref_examples_html]] (Maya 2013) There are edge-cases where certain API calls will want a c++ [[struct|http://www.cplusplus.com/doc/tutorial/structures/]] passed in. For example, the [[OpenMayaRender.MRenderView|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/index.html?url=cpp_ref/class_m_render_view.html]] class's {{{updatePixels}}} method expects a pointer to a [[RV_PIXELS|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/struct_r_v___p_i_x_e_l.html]] struct. How can you do this via the Python API? As it turns out (in this case at least) they've wrappered that struct for you: {{{ import maya.OpenMayaRender as omr rvp = omr.RV_PIXEL() }}} I'd be interested to know how many more there are? This post by Kristine Middlemiss breaks it down very nicely: *http://around-the-corner.typepad.com/adn/2012/07/setting-a-user-event-within-maya.html Transcribed from that post for prosperity: {{{ # Here's how you would register a new user event type called 'myEvent': import maya.OpenMaya as om om.MUserEventMessage.registerUserEvent('myEvent') # To have a function called 'myFunc' execute whenever the event occurs: def myFunc(data): print('Got a myEvent event!') callbackId = om.MUserEventMessage.addUserEventCallback('myEvent', myFunc) #To send a 'myEvent' event: om.MUserEventMessage.postUserEvent('myEvent') #To remove the callback function when done: om.MUserEventMessage.removeCallback(callbackId) }}} ---- Also see: *[[API: How can I author callbacks for Maya events?]] # http://help.autodesk.com/view/MAYAUL/2017/ENU/?guid=__py_ref_class_open_maya_1_1_m_fn_mesh_html ~MFnMesh.closestIntersection Note, I'e been unable to get this to work via maya.~OpenMaya, but I have got it to work via maya.api.~OpenMaya Also note the points it reruns are always in internal units (cm) so you may need to convert them to your UI units. Often times I'll have an MObject, and I want to know what it is exactly. In normal commands you can use a call to {{{objectType}}}. But how about in the API. Below is a round about way to do it. In a nutshell, you check to see if a given MObject has a given function set. The list of available function sets is here: http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_fn_html But it's often easier to just query which function sets are available for the MObject itself: The {{{getFunctionSets}}} function below returns a list of them. Armed with that info, you can then query them. Also see [[these notes|API: How can I query what function sets are available for an MObject?]]. {{{ import maya.OpenMaya as om def getMObjectByStr(strName): selList = om.MSelectionList() selList.add(strName) mObject = om.MObject() selList.getDependNode(0, mObject) return mObject def getFunctionSets(mObject): funcSets = [] om.MGlobal.getFunctionSetList(mObject, funcSets) return funcSets def isApiType(mObject, apiType): if mObject.hasFn(apiType): return True else: return False }}} So to use it: {{{ strName = "myAwesomeMeshShape" mObject = getMObjectByStr(strName) print getFunctionSets(mObject) # [u'kBase', u'kNamedObject', u'kDependencyNode', u'kDagNode', u'kShape', u'kGeometric', u'kSurface', u'kMesh'] # AH, so I can use 'kMesh' : apiType = om.MFn.kMesh print isApiType(mObject, apiType) # True }}} When making custom node types via scripted-plugins (or regular 'ol compiled plugins), they need a [[MTypeId|http://download.autodesk.com/us/maya/2011help/api/class_m_type_id.html]] set to a specific value, so that custom node types won't clash: {{{ import maya.OpenMaya as om kPluginNodeId = om.MTypeId(0x00113) }}} Autodesk divvies-out ranges of addresses to developers, but how do you get these addresses assigned? Via this web site: * http://mayaid.autodesk.io ---- This blog post does a great job covering all aspects of these id's, how they're used, and how you can register for them: http://around-the-corner.typepad.com/adn/2012/09/maya-and-node-types.html  {{{ import maya.OpenMaya as om import maya.OpenMayaUI as omui activeView = omui.M3dView.active3dView() om.MGlobal.selectFromScreen(0, 0, activeView.portWidth(), activeView.portHeight(), om.MGlobal.kReplaceList) }}} From this post here: https://groups.google.com/d/msg/python_inside_maya/FxnZzCntpp0/F7Sb4udwLK8J All of the api (~OpenMaya) work I do is via the Python bindings. And plugins authored that way don't need to be 'compiled', since they're considered 'scripted plugins'. Sometimes however I'll run into legacy plugins that need recompiled to the latest version of Maya, and Maya itself comes with many examples of c++ authored plugins that //do// need compiled (living here: {{{C:\Program Files\Autodesk\Maya20XX\devkit\plug-ins}}}). Also see: [[Compiling Python Libraries for Maya]] This is a short overview: #Install [[Visual Studio|http://www.microsoft.com/visualstudio/en-us]]. ##The version of Visual Studio is very important, based on which Maya you are going to compile for. Check out this subject to understand which version you need installed: [[Maya compiler versions]] & [[Python & Qt versions in Maya]] ##When you first launch Visual Studio, configure it to 'C++'. #Launch Visual Studio and: File -> Open {{{C:\path\to\my\plugin\myPlugin.sln}}}. ##{{{.sln}}} are Visual Studio 'solution' files. #Select the 'Release' build configuration, from the drop-down in the 'standard toolbar'. #Press F7 to build the plug-in (Build -> Build Solution): Watch the magic happen in the Output window. #The newly compiled plugin is output to {{{C:\path\to\my\plugin\myPlugin.mll}}} And that's really all there is to it. ---- Questions: *Can you use [[Visual Studio Express|http://www.microsoft.com/express]]? **Looks like there is evidence [[on the web|http://www.creativecrash.com/tutorials/build-plugins-with-visual-c-express-edition]] that [[this is possible|http://www.daisukemaki.com/projects_maya_api_cplusplus.php]]. *Can you use other compliers other than Visual Studio? **I've read of Codeblocks, Xcode, and possibly even Eclipse working. ---- ''OR'' Here's a video tutorial by Chad Vernon on how to do it using [[CMake|http://www.cmake.org/]], for Mac & Linux support. http://www.chadvernon.com/blog/maya/compiling-maya-plug-ins-with-cmake/ ---- Also see: *[[Remote debugging with Visual Studio]]  Starting with Maya 2012 Hotfix 1, enhancements were made to Python scripting with the new 'Maya Python API 2.0'. [[Maya 2015 Docs|http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=__py_ref_index_html]] The new api modules are found in the {{{maya.api}}} package. For example, here is the 'old way' to import {{{OpenMaya}}}: {{{ import maya.OpenMaya as om print type(om.MDagPath()) # <class 'maya.OpenMaya.MDagPath'> }}} And the new 'api' way: {{{ import maya.api.OpenMaya as om2 print type(om2.MDagPath()) # <type 'OpenMaya.MDagPath'> }}} As you can see, the 'old way' typed instances as objects, but the new way types them as physical new types. I've also noticed that it looks like the api objects are no longer being wrappered via ''swig'' objects: Calls to {{{someSwigObject.disown()}}} that worked in 2010 will fail on 2012 raising a nice exception: {{{ # AttributeError: 'PyCObject' object has no attribute 'disown' # }}} This isn't necessarily a result of API 2.0, but it does have a coincidence in timing.... It should be noted that in 2012 the whole API hadn't been ported over, but is much more robust in 2015. Here are the list of advantages they post: *Array types are full Python sequences, including slice support. *Methods which take Maya array parameters will usually also take native Python sequences, such as arrays and tuples. *Exceptions are made in some case for performance reasons. *The outputs of methods are usually returned in their return values, not through their parameter lists. Exceptions are made in some cases for performance reasons. *Methods which return multiple values (e.g. ~MFnFluid.getResolution) return them as a tuple or list, eliminating the need for ~MScriptUtil. *Object attributes are preferred over rather than set/get methods. For example you can now write {{{array.sizeIncrement=64}}}. *There are more types of exceptions used when methods fail. Not everything is a ~RuntimeError, as was the case in the old API. *The new API is generally faster than the old. Up to three times faster in some cases. Nice informative overview: http://discourse.techart.online/t/maya-api-file-translators-openmaya-vs-c-speeds/10293  I recently ran into the problem of trying to query a 'double array' attr via the API. In the below example, I create a "doubleArray" attr via commands on a node, then try to query it via the API. {{{ import maya.cmds as mc import maya.OpenMaya as om node = 'pSphere1' attr = "doubleArrayTest" #--------------------------- # Via cmds, add the attr to our node: mc.addAttr(node, longName=attr, dataType='doubleArray') mc.setAttr('%s.%s'%(node,attr), (2, 3.14159, 2.782), type="doubleArray") print mc.getAttr('%s.%s'%(node,attr)) # [2.0, 3.14159, 2.782] print mc.getAttr("%s.%s"%(node,attr), type=True) # doubleArray #----------------------- # Now, in the API: selList = om.MSelectionList() selList.add(node) mObject = om.MObject() selList.getDependNode(0, mObject) plug = om.MFnDependencyNode(mObject).findPlug(attr) print plug.name() # pSphere1.doubleArrayTest # So, the API is getting confused: print plug.isArray() # False print plug.numElements() # // Error: Encountered exception: (kFailure): Data type is not valid here // indexArray = om.MIntArray() plug.getExistingArrayAttributeIndices(indexArray) # // Error: Encountered exception: (kFailure): Object does not exist // # Let's try something else: # Get val as an MObject (say whaaa?!) plugValObj = plug.asMObject() # Create a function to act on that object: fnDoubleArray = om.MFnDoubleArrayData(plugValObj) # Get just the array data: doubleArray = fnDoubleArray.array() print doubleArray #[2.0, 3.14159, 2.782] # Finally! }}} ---- Docs: *[[MFnDoubleArrayData|http://docs.autodesk.com/MAYAUL/2014/ENU/Maya-API-Documentation/index.html?url=cpp_ref/class_m_fn_double_array_data.html,topicNumber=cpp_ref_class_m_fn_double_array_data_html]] *[[MFnDependencyNode|http://docs.autodesk.com/MAYAUL/2014/ENU/Maya-API-Documentation/index.html?url=cpp_ref/class_m_fn_dependency_node.html,topicNumber=cpp_ref_class_m_fn_dependency_node_html]] *[[MPlug|http://docs.autodesk.com/MAYAUL/2014/ENU/Maya-API-Documentation/index.html?url=cpp_ref/class_m_plug.html,topicNumber=cpp_ref_class_m_plug_html]] ---- Also see: *[[API: Attribute Creation & Usage]] *[[API: How can I query and set an attribute?]] If you want to loop over items in the API, the {{{MIt}}} classes help you do that. High level notes below. These are all part of the {{{OpenMaya}}} module, unless otherwise noted. EXAMPLES BELOW !!!~MSelectionLists (lists of ~MObjects): * [[MItSelectionList|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_selection_list_html]] : Iterate over the items in the selection list. !!!All Nodes: * [[MItDependencyNodes|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_dependency_nodes_html]] : Use the dependency node iterator to traverse all the nodes in Maya's Dependency Graph. !!!Node Hierarchies: *[[MItDag|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_dag_html]] : Use the DAG iterator to traverse the DAG (parent/child relationships) !!!Node Connections: *[[MItDependencyGraph|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_dependency_graph_html]] : Iterate over Dependency Graph (DG) Nodes or Plugs starting at a specified root Node or Plug. !!!Animation: * ~OpenMayaAnim.[[MItKeyframe|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_keyframe_html]] : Iterate over the keyframes of a particular Anim Curve Node, and query and edit the keyframe to which the iterator points. !!!Components: *[[MItGeometry|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_geometry_html]] : This class is the iterator class for geometry data, and can be used to loop over the ''~CVs'' of NURBS, the ''points'' of subds & lattices, and the ''vertices'' of polygonal meshes. Generic/higher level iteration over components. ** These aren't necessarily subclasses, but they go into finer detail over components. Left out all the '~MItSubd' classes, since I really never use them. ** NURBS: *** [[MItCurveCV|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_curve_c_v_html]] : Iterator class for NURBS curve control vertices (~CVs). The iteration can be for a given curve or for a group of ~CVs. *** [[MItSurfaceCV|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_surface_c_v_html]] : NURBS surface CV iterator. ** Mesh: *** [[MItMeshEdge|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_mesh_edge_html]] : Edge iterator for polygonal surfaces. *** [[MItMeshFaceVertex|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_mesh_face_vertex_html]] : The iterator for face vertices on polygonal surfaces *** [[MItMeshPolygon|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_mesh_polygon_html]] : This class is the iterator for polygonal surfaces (meshes). It iterates over their //faces//. *** [[MItMeshVertex|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_mesh_vertex_html]] : Class iterator for polygonal vertices. !!!References/Assemblies: * [[MItEdits|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_edits_html]] : Use the edits iterator to traverse all the edits on a reference or assembly. !!!Particles: *[[MItInstancer|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_it_instancer_html]] : This class provides methods for iterating over all the dag paths to the shapes created in the scene by the replacement of particles by dag nodes. !!!Utilities: * [[MIteratorType|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_iterator_type_html]] : Thisclass is used on iterators where more than one type of filters can be specified. It also provides functionalities to set and get the filter list or individual types of filter. This class should be used in conjunction with DAG/DG/~DependencyNodes iterators for using filter list (list of {{{MFn::Type}}} objects) on them, thus enabling faster traversal throgh iterators. ---- Examples Iterate over the selected components on some node. In this example, I'll pick some verts on a couple different mesh. Based on the selected components. we'll iterate over their parental mesh nodes via a {{{MItSelectionList}}}, and over the components via a {{{MItGeometry}}}. Note that even though you only pick components (and not the mesh nodes directly), the {{{MItSelectionList}}} auto sorts them into 'categories' by their parental mesh. Also see: * [[API : Understanding component level looping, and MObjects]] {{{ import maya.OpenMaya as om # Select some components (verts, cvs) on one or more mesh: selList = om.MSelectionList() # MSelectionList is a list of MObjects om.MGlobal.getActiveSelectionList(selList) iterSelList = om.MItSelectionList(selList) while not iterSelList.isDone(): # The node for the selected components: nodeDagPath = om.MDagPath() # The selected components, if any: componentsObject = om.MObject() # Define the path to our object and components. iterSelList.getDagPath(nodeDagPath, componentsObject) print nodeDagPath.fullPathName() if not componentsObject.isNull(): # Make an iterator for the components: iterGeo = om.MItGeometry(nodeDagPath, componentsObject) while not iterGeo.isDone(): # Make a point object for the current loop: componentPos = iterGeo.position(om.MSpace.kWorld) # And print its info: print "\tcomponent -index: %s -pos: %s %s %s"%(iterGeo.index(), componentPos.x, componentPos.y, componentPos.z) iterGeo.next() iterSelList.next() }}} Prints: {{{ |pSphere1|pSphereShape1 component -index: 261 -pos: 1.66245281696 1.20784258842 1.49297451973 component -index: 262 -pos: 1.20784258842 1.66245269775 1.49297451973 component -index: 280 -pos: 1.70814728737 0.555010676384 1.7960511446 |pCube1|pCubeShape1 component -index: 0 -pos: 2.99528660484 -1.26999998093 1.26999998093 component -index: 1 -pos: 5.53528656669 -1.26999998093 1.26999998093 component -index: 2 -pos: 2.99528660484 1.26999998093 1.26999998093 component -index: 3 -pos: 5.53528656669 1.26999998093 1.26999998093 }}} Initially pulled a bunch of data from this informative post: http://nccastaff.bournemouth.ac.uk/jmacey/RobTheBloke/www/maya/MSelectionList2.html ''VERY INTERESTING'' : If you have multiple components (verts, edges, cvs, etc) selected on multiple nodes (mesh, NURBS), the api calls automatically lump them into 'vert mesh lists'. Meaning, the first {{{MItSelectionList}}} will loop over each //node// you have picked (via selecting the components), and then the second {{{MItGeometry}}} loops over the selected components on said node. This is different than say, picking a bunch of verts and listing them via the {{{ls}}} command. {{{ # Python code import maya.OpenMaya as om # First, select a bunch of components on one or more nodes. # Make a MSelectionList container object: These are "lists of MObject's" objectList = om.MSelectionList() # Fill with selection, as MObjects, enabling 'ordered selection' (if that is important): om.MGlobal.getActiveSelectionList(objectList, True) # Create an iterator to loop over our selection: iterObjectList = om.MItSelectionList(objectList) while not iterObjectList.isDone(): # The selected node: nodeDagPath = om.MDagPath() # The selected components, if any: componentsObject = om.MObject() # Define the path to our object and components. iterObjectList.getDagPath(nodeDagPath, componentsObject) print nodeDagPath.fullPathName() if not componentsObject.isNull(): # Make an iterator for them: iterGeo = om.MItGeometry(nodeDagPath, componentsObject) while not iterGeo.isDone(): # Make a point object for the current loop: componentPos = iterGeo.position(om.MSpace.kWorld) # And print its info: print "\tcomponent -index: %s -pos: %s %s %s"%(iterGeo.index(), componentPos.x, componentPos.y, componentPos.z) iterGeo.next() iterObjectList.next() }}} Example print. Regardless what order we picked the components in across multiple objects, the always get lumped together: {{{ |myNodeA|myNodeAShape component -index: 3380 -pos: 37.918586731 28.5343780518 132.385604858 component -index: 3394 -pos: 35.4213676453 27.519777298 135.702865601 |myNodeB|myNodeBShape component -index: 308 -pos: 52.6544418335 -2.37073945999 103.18737793 component -index: 309 -pos: 52.5206260681 1.12697529793 103.657699585 }}} ---- Also see: * [[API : Understanding MIterators]] {{{ # Python code import maya.OpenMaya as om import maya.OpenMayaAnim as oma def getSkinCluster(shape): """ shape : string : name of shape node to query skincluster on return : MFnSkinCluster : MFnSkinCluster node assigned to the mesh, or None. """ # Create an MDagPath for our shape node: selList = om.MSelectionList() selList.add(shape) mDagPath = om.MDagPath() selList.getDagPath(0, mDagPath) # Make a dependency graph iterator, passing in our MDagPath as the root of the # system. Set the Direction to 'From source to destination' and Level to be # 'Visit each Plug at most once' mItDependencyGraph = om.MItDependencyGraph(mDagPath.node(), om.MItDependencyGraph.kDownstream, om.MItDependencyGraph.kPlugLevel) # Start walking through our shape node's dependency graph: while not mItDependencyGraph.isDone(): # Get an MObject for the current item in the graph: mObject = mItDependencyGraph.currentItem() # If the MObject can have a MFnSkinCluster function applied, then it must # be a skincluster: if mObject.hasFn(om.MFn.kSkinClusterFilter): # return the MFnSkinCluster object for our MObject: return oma.MFnSkinCluster(mObject) mItDependencyGraph.next() }}} {{{ shape = 'someNodesShape' sCluster = getSkinCluster(shape) # Print the name of the skincluster: print sCluster.name() # skinCluster14 }}} The above example is based on info I found in this thread: http://groups.google.com/group/python_inside_maya/browse_thread/thread/1b865f8c3c0c81c2 And did a re-write on.  ''~OpenMaya Python'': {{{ import maya.OpenMaya as om # Make a MSelectionList container object: selList = om.MSelectionList() # Fill our container object with the active selection: om.MGlobal.getActiveSelectionList(selList) # Make a list to gather the results: sel = [] # Fill the result list with strings of the selection: selList.getSelectionStrings(sel) print sel }}} Man, that is clunky! :-P Compared to: ''Python'': {{{ import maya.cmds as mc sel = mc.ls(selection=True) print sel }}} ''MEL'': {{{ string$sel[] = ls - selection;
print $sel; }}} When dealing with the API, all linear '//internal units//' are held in cm (unless overridden, but I've never seen this happen). But if you're passing that data to mel/Python commands, they expect the values to be in whatever '//ui unit//' has been set. By default, the ui unit is cm as well, but many studios will change this value to something else. In my case, it's currently inches... If these two values line up you won't notice any problems in your code. But if you author code that expects the ui units to be cm and they're not, chaos will ensue. In the below example, we grab the center-point of the bounding-box of a node, then convert that position into the current ui-units: {{{ import maya.OpenMaya as om node = 'pSphere1' # Get the MDagPath for a node: selList = om.MSelectionList() # MSelectionList selList.add(node) mDagPath = om.MDagPath() # MDagPath selList.getDagPath(0, mDagPath) # Find the centerpoint based on bounding box, this will be in cm: dagNodeFunc = om.MFnDagNode(mDagPath) # MFnDagNode boundingBox = dagNodeFunc.boundingBox() # MBoundingBox centerPoint = boundingBox.center() # MPoint #----------------------- # Now that we have some data, convert it from internal to ui units: # Convert from cm to current units: center = [] unitType = om.MDistance.uiUnit() # Get the current UI unit: for i in range(3): distance = om.MDistance(centerPoint[i]) # MDistance, as cm converted = distance.asUnits(unitType) # double, converted center.append(converted) print center }}} ---- Also see: *[[API: How can I find the working units?]] *[[API : Converting from internal units to ui (and back again)]] The below classes compare with the mel {{{currentUnit}}} command. The below classes have the ability to query both the //internal// units, and the //ui// units (except {{{MTime}}}). The internal units, while they can be changed, usually shouldn't be. Defaults for internal units are: *Linear : Centimeters *Angular : ? Mine comes up as {{{kInvalid}}}, which is odd. I'd expect it to be Radians though. *Time : Has no differentiation between internal\ui: There is only ui units. I think when Maya installs this is 24fps (film). UI units are the ones you can change via Maya's prefs, or via mel. !!!Linear: [[OpenMaya.MDistance|http://download.autodesk.com/us/maya/2010help/api/class_m_distance.html]] {{{ import maya.OpenMaya as om uiLinearUnit = om.MDistance.uiUnit() }}} This returns an int value that corresponds to the {{{Unit}}} enum on the {{{MDistance}}} class. Which corresponds to these constants: | 1 | kInches | | 2 | kFeet | | 3 | kYards | | 4 | kMiles | | 5 | kMillimeters | | 6 | kCentimeters | | 7 | kKilometers | | 8 | kMeters | !!!Angular: [[OpenMaya.MAngle|http://download.autodesk.com/us/maya/2010help/API/class_m_angle.html]] {{{ import maya.OpenMaya as om uiAngularUnit = om.MAngle.uiUnit() }}} This returns an int value that corresponds to the {{{Unit}}} enum on the {{{MAngle}}} class. Which corresponds to these constants: | 1 | kInvalid | | 2 | kRadians | | 3 | kDegrees | | 4 | kAngMinutes | | 5 | kAngSeconds | | 6 | kLast | !!!Time: [[OpenMaya.MTime|http://download.autodesk.com/us/maya/2010help/api/class_m_time.html]] {{{ import maya.OpenMaya as om uiTimeUnit = om.MTime.uiUnit() }}} This returns an int value that corresponds to the {{{Unit}}} enum on the {{{MTime}}} class. There are a lot of constant values, check the [[docs|http://download.autodesk.com/us/maya/2010help/api/class_m_time.html#ffadecde942c8f44b9ec1ce62aa9da51]] for the specifics. ---- Also see: *[[API : Converting from internal units to ui (and back again)]] *[[API: How can I convert from linear internal units to ui units?]]  (Referenced from 'Complete Maya Programming') Waaaay exciting! It should be noted that any time you use API calls outside of a plugin to modify the DG, you can't undo the operation. {{{ # 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}}}  Newer docs here: *[[PySide : Access Qt .ui widget data in Maya]] *[[PySide : Convert a Maya control into a widget]] ---- Starting with Maya 2011 they introduced a new API class [[MQtUtil|http://download.autodesk.com/global/docs/mayasdk2012/en_us/cpp_ref/class_m_qt_util.html]]. Notes from the docs: <<< The safest way to use the Qt API from within Maya is to create your own Qt window and populate it with your own controls. While it is possible to use the Qt API to modify existing Maya UI elements, such as those created using Maya commands from MEL or Python, such modifications are not supported by Autodesk and could lead to Maya becoming unstable or inoperable. In practice, you will likely find a number of modifications which appear safe, such as changing a control's text or reorganizing the items on a menu. However, Autodesk provides no guarantees that the underlying implementations of those UI elements won't change from one release of Maya to another, potentially in ways that may make formerly safe usage become unsafe. So if you choose to incorporate such actions into your plug-in be aware that you may have to modify your code in future versions of Maya. <<< Here is a very simple example using it: {{{ import maya.cmds as mc import maya.OpenMayaUI as omui # Create the window: class App(object): def __init__(self): self.name = "tempWin" if mc.window(self.name, exists=True): mc.deleteUI(self.name) self.window = mc.window(self.name, resizeToFitChildren=True) self.rootLayout = mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5)) self.button = mc.button("awesomeButton", label="this is an awesome button") mc.showWindow() # Display window, capture an instance: app = App() butQtClasses = mc.objectTypeUI(app.button, superClasses=True) qwidget = omui.MQtUtil.findControl(app.button) print app.button, butQtClasses print qwidget, type(qwidget) }}} prints: {{{ tempWin|columnLayout6|awesomeButton [u'QPushButton', u'QAbstractButton', u'QWidget', u'QObject'] _b056e53200000000_p_QWidget <type 'PySwigObject'> }}} From here, you can see that the Qt widget has been wrapped in a Python 'swig' object. Notes, notes notes... window data via the API. ---- from {{{maya.OpenMayaUI}}}: *{{{M3dView}}} : Main class used to access window info. *{{{MDrawInfo}}} : used in the draw methods of {{{MPxSurfaceShapeUI}}} **{{{MSelectInfo}}} : used in {{{MPxSurfaceShapeUI::select}}} ---- from {{{maya.OpenMayaMPx}}} *{{{MPx3dModelView}}} : Creates {{{modelEditor}}}s. *{{{MPxModelEditorCommand}}} : Creates commands for {{{modelEditors}}}. *{{{MPxControlCommand}}} *{{{MPxUIControl}}} **{{{MPxUITableControl}}} *{{{MPxSurfaceShapeU}}} *{{{MPxGlBuffer}}} ---- Query the current camera for the active view, top left corner of viewport, and viewport width\height. Similar stuff to what you can do with the window mel command. {{{ import maya.OpenMayaUI as omui import maya.OpenMaya as om camPath = om.MDagPath() activeView = omui.M3dView.active3dView() activeView.getCamera(camPath) camName = camPath.fullPathName() # c++ pointer hoop-jumping: xUtil = om.MScriptUtil() xUtil.createFromInt(0) xPtr = xUtil.asIntPtr() yUtil = om.MScriptUtil() yUtil.createFromInt(0) yPtr = yUtil.asIntPtr() activeView.getScreenPosition(xPtr, yPtr) x = om.MScriptUtil.getInt(xPtr) y = om.MScriptUtil.getInt(yPtr) pw = activeView.portWidth() ph = activeView.portHeight() print camName, "- Top Left:", x, y," - width/height:", pw, ph # |persp|perspShape - Top Left: 405 45 - width/height: 701 1060 }}} Mel has the {{{polyColorPerVertex}}} command, that will change the color of one vertex at a time. This works fine you're doing it on less than 100 verts at a time. But on more than 1000, it really starts to slow down. Enter the API. The {{{MFnMesh}}} class has a {{{setVertexColors}}} method which allows you to set multiple colors at once. And it is //much// faster. Pseudo code to use it: {{{ import maya.OpenMaya as om # Build an empty vertex ID array: idArray = om.MIntArray() # Now fill it with the vert ID's on the object to color. # Could be some, or all of them. # Build an empty color array: colorArray = om.MColorArray() # Now fill it with the colors for each of the vert ID's # defined above. # Get a MDagPath for the object we're coloring: selList = om.MSelectionList() selList.add(myObject) mDagPath = om.MDagPath() selList.getDagPath(0, mDagPath) # Get a MFnMesh for our object: mFnMesh = om.MFnMesh(mDagPath) # Apply the colors: mFnMesh.setVertexColors(colorArray, idArray) }}} It should be noted that since you're using the API to modify the scene graph, there is no undo available. To get undo, you'd need to author this as a scripted plugin command. Several subjects below, here's what we have: #Overview of API ~Attribute-Related Concepts #Attribute Property Defaults #Overview of API attribute creation classes #Creation #Example Usage #Differences between 'compound' and 'array' #Understanding Array Indices (physical & logical) #Networked and non-networked plugs #Additional examples ---- ''Important note'': In Maya, the term 'attribute' can mean two different things based on where it's used: either the 'user side' or the 'programmer side': *On the user side, they interact with attributes on nodes, querying\setting their values, keyframing them, etc. The only point of entry is "the attribute". The attribute 'has a value', for example. The value and the attribute go hand in hand. *On the programmer side, an 'attribute' is: **Quoted from 'Complete Maya Programming': "...a template or blueprint for how a piece of data in the node should be //created//. The important distinction from the user's perspective is that the attribute doesn't actually hold any data. It simply provides a specification for the data. Given this specification, the actual data is created.". **Basically, it's a description for the type of data that can be stored, but that's only part of the equation (one of four main parts). The next section describes the four areas that are required for data storage\retrieval on a node in the API. !!!Overview of API ~Attribute-Related Concepts: Sort of in order of data-flow (presuming you're doing value assignment, rather than query): *''Plugs'': **A plug is an instance of an attribute on a node. **They allow you to get and set data, make connections, lock attributes, etc. **Following the house analogy from above, a plug is like a gate-keeper: It can lock the gate, pass data from outside the gate to inside the gate (and vice-versa), and even connect gates between two different houses together. **They are represented as [[MPlug|http://download.autodesk.com/us/maya/2010help/API/class_m_plug.html]] and [[MPlugArray|http://download.autodesk.com/us/maya/2010help/API/class_m_plug_array.html]] objects. **They can be created & queried via [[MFnDependencyNode|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_dependency_node.html]] (& its descendants), queried via [[MAnimUtil|http://download.autodesk.com/us/maya/2010help/API/class_m_anim_util.html]], are passed as an argument to {{{MPxNode.compute()}}}, and additionally manipulated via [[MDgModifier|http://download.autodesk.com/us/maya/2010help/API/class_m_d_g_modifier.html]]. *''Attributes'': **Attributes in the API define the name and type of data a node can store. **Attributes themselves don't store data, they define what type of data can be stored. **Analogies help: Picture a house as a representation of a node. Each attribute is represented as a gate around the house: Only if a package can 'fit through a certain gate' can it be allowed into the house. Each gate defines access to one type of 'package', but it itself doesn't store the package. **API attributes differ from user-interaction in Maya on a node. When in Maya you execute "{{{setAttr sphere02.translsateX 3;}}}" it feels like you've set the {{{translateX}}} attribute to the value of {{{3}}}. But behind the scenes, it's not being stored that way: The {{{translateX}}} attribute is simply the gate through which you've squeezed your numeric package. Where that value actually lives is discussed below. **They are represented as [[MObject|http://download.autodesk.com/us/maya/2010help/API/class_m_object.html]] objects, and created\manipulated (at time of creation) via [[MFnAttribute|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_attribute.html]] and descendant classes. *''Data Handles'': **When a node is computing data (in its {{{compute()}}} method) and needs to access data in its data block, it gains that access through a 'data handle'. **Data handles allow you to query and set data living in a data block, as defined by a given attribute. **Data handles can't make connections between attributes or lock them (for example), they only query\set data. But because of this they can operate much faster than plugs. In addition, they can query a 'dirty' attribute without forcing a recompute of the DG which can allow for even greater optimization. **Following the analogy, a data handle is like a butler who can put data in storage (received from the plug), and take data out of storage, passing it to the plug. **They are represented as [[MDataHandle|http://download.autodesk.com/us/maya/2010help/API/class_m_data_handle.html]] and [[MArrayDataHandle|http://download.autodesk.com/us/maya/2010help/API/class_m_array_data_handle.html]] objects. They are created via [[MDataBlock|http://download.autodesk.com/us/maya/2010help/API/class_m_data_block.html]]s. *''Data Blocks'': **The data block is the location in the node where the attribute values are stored. **Whenever a new instance of a node is created, a new data block is created for it. **Using the house analogy, the data block is the store-room of the house where all the packages are stored. The butler can put stuff in and take it out. **They are represented as [[MDataBlock|http://download.autodesk.com/us/maya/2010help/API/class_m_data_block.html]] objects, and passed to the {{{MPxNode.compute()}}} method as an argument: That's the only way (I'm aware of) you can access them. Another way to visualize the analogy: | Plug | Attribute | Data Handle | Data Block | | Gate Keeper | The Gate | Butler | Storeroom | *Imagine that a node is a house. *The gate keeper (plug) receives packages (data), can lock\unlock the gate, let the packages pass through the gate (if they fit) to the butler, magically connect this gate to the gate on another house (so that they share packages), or get packages from the butler. *The gate itself (attribute) limits what type of packages are allowed into the house (or what type of package is retrieved from it). If the package doesn't fit, an alarm (error) goes off. *Presuming the package (data) fits through the gate (attribute), if the gatekeeper wants the package put in the house (or wants to get a packaged stored in the house), he needs to have the butler (data handle) do it for him. *All packages are kept in the storeroom of the home (data block), which the butler knows a quick path to. *Finally, the gate keeper can have direct access to the storeroom if he's tricky (but he's slower than the butler), and if you slip the butler a$20 (and you sneak in via {{{compute()}}}) you can access the storeroom directly.
So you could say:
*A package (data) is given to the gatekeeper (plug).  Since it fits through the gate (attribute), the gatekeeper passes it to the butler (data handle).  The butler than puts it in the storeroom (data block).
----
----
----
!!!Attribute Property Defaults
When a new attribute is created, these are the default states of its various properties.  They are modifiable via [[MFnAttribute|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_attribute.html]] (attribute superclass).
*Writable.
*Connectable.
*Storable.
*Cached.
*Not arrays.
*Have indices that matter.
*Do not use an array builder.
*Not keyable.
*Not hidden.
*Not used as colors.
*Not indeterminant.
*Set to disconnect behavior kNothing.
!!!Overview of API attribute creation classes:
*[[MFnAttribute|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_attribute.html]] : Used for changing the attribute defaults listed above (among others).
**[[MFnCompoundAttribute|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_compound_attribute.html]] : For the creation of 'compound' attributes:  Attributes that can store multiple different data types.
**[[MFnNumricAttribute|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_numeric_attribute.html]]  :  For all 'numric' attribute types as defined by [[MFnNumricData|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_numeric_data.html]].  These include boolean, byte, character, short 2short, 3short, int, 2int, 3int, long, 2long, 3long, float, 2float, 3float, double, 2double, 3double, 4double, and finally 'color' and 'point'.
**[[MFnTypedAttribute|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_typed_attribute.html]]  :  Creation of attrs that can accept specific data types as defined by [[MFnData|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_data.html]].  These include: numeric (based on [[MFnNumricData|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_numeric_data.html]]), plugin, pluginGeometry, string, matrix, stringArray, doubleArray, intArray, pointArray, vectorArray, componentList, mesh, lattice, nurbsCurve, nurbsSurface, sphere, dynArrayAttrs, dynSweptGeometry, subdSurface, nObject.
**[[MFnUnitAttribute|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_unit_attribute.html]]  :  For creation of attributes that store angle, distance, and time.
!!!Creation:
Placeholders for the node's attributes are initially added as a class-attribute for easy access later.  At this point, then can be filled with {{{MObject}}}s (since that's what they'll be ultimately), or just be filled with {{{None}}} since at this point they're not used.
{{{
import maya.OpenMaya as ompx
class Foo(ompx.MPxNode):
attr_someInput = om.MObject() # or None
attr_someOutput = om.MObject() # or None
}}}
They are actually created and added to the node inside the plugin's 'initializer' classmethod (or external function.  We'll use classmethod below).  For each attr, they go through one or more of these steps:
#Creation : An attribute is created.  You can imagine it just floating in space...
#Modification :  After creation, you configure how it should behave.
#Attach to node  :  Take it from floating in space, and actually attach it to a specific node.
#Defining affects relationship  :  Set if it affects any other attribute.  ''Special note'':  This step must occur after any affected attributes have already been attached to the node (via {{{MPxNode.addAttribute()}}}).
{{{
@classmethod
def nodeInitializer(cls):
# Create the function object that will create the attributes:
mfnNumericAttribute = om.MFnNumericAttribute()

# 1. Creation:
cls.attr_someInput = mfnNumericAttribute.create("someInput", "si", om.MFnNumericData.kFloat)
# 2. Modification:
mfnNumericAttribute.setChannelBox(True) # default is False
mfnNumericAttribute.setKeyable(True)  # default is False
# 3. Attach to node:

# 1. Creation:
cls.attr_someOutput = mfnNumericAttribute.create("someOutput", "so", om.MFnNumericData.kFloat)
# 2. Modification:
mfnNumericAttribute.setWritable(False) # Default is True.  This is an output, can't write to it.
mfnNumericAttribute.setStorable(False) # Default is True.  Do not save value with scene, since it's an output.
# 3. Attach to node:

# 4. Affects relationships for "someInput" attr.  This needs to be executed after
#     the "someOutput" attr was added to the node.
cls.attributeAffects(cls.attr_someInput, cls.attr_someOutput)
# 4. Affects relationships for "someOutput":
# None, this is an output
}}}
----
Another attribute type to make is a numric type with children.  A transforms {{{.translate}}} attribute illustrates this:  There is a root 'translate' attribute you can set, connect to, etc.  But in addition, you can set\connect to its individual children as well:
{{{
translate : float 3
--> translateX : float
--> translateY : float
--> translateZ : float
}}}
How can you set this up in the API?  You first create each of the child attributes, and then, when creating the parent, you pass them as a creation argument.  Here's a simple example snippet:
{{{
@classmethod
def nodeInitializer(cls):
mfnNumericAttribute = om.MFnNumericAttribute()

# Create the children attrs:
cls.attr_transX = mfnNumericAttribute.create("translateX", "tx", om.MFnNumericData.kFloat)
cls.attr_transY = mfnNumericAttribute.create("translateY", "ty", om.MFnNumericData.kFloat)
cls.attr_transZ = mfnNumericAttribute.create("translateZ", "tz", om.MFnNumericData.kFloat)
# Now create the parent attr by passing in the children:
cls.attr_trans = mfnNumericAttribute.create("translate", "t", cls.attr_transX, cls.attr_transY, cls.attr_transZ)
mfnNumericAttribute.setChannelBox(True)
mfnNumericAttribute.setKeyable(True)
}}}
Some things to note:
*The {{{MFnAttribute}}} docs say "This form of the create method allows the creation of compound attributes out of numeric attributes."    So, even though we build using a {{{MFnAttribute}}}, it ends up being {{{MFnCompoundAttribute}}}? (I've yet to figure out what's going on here).
**When making compound attrs, their children all need to be added to to the class via {{{cls.addAttribute()}}}. However, in the examples I've seen, when making 'child array attrs', they //don't// need to be added that way: only their parent does (like in the above example).
*You modify the parents parameters ({{{setChannelBox}}}, etc) not the children, since the children inherit the parameters.
*By passing in three {{{kFloat}}} children, it implies that the {{{someInput}}} attr is now a {{{k3float}}}.
If you didn't care about the child attributes, you could simply create the attribute this way:
{{{
@classmethod
def nodeInitializer(cls):
mfnNumericAttribute = om.MFnNumericAttribute()

cls.attr_trans = mfnNumericAttribute.create("translate", "t", om.MFnNumericData.k3Float)
mfnNumericAttribute.setChannelBox(True)
mfnNumericAttribute.setKeyable(True)
}}}
So you can still manipulate {{{translate}}} as you're used to, but you no longer gain gain access to the children, like {{{translateX}}}.
!!!Example Usage:
The attrs are accessed inside the {{{compute()}}} method.  Take note of a few things:
#First there is a query if the given input plug is an instance of the attribute that needs computing.
#Next, a data handle based on that instanced attribute is generated pointing inside of the data block.
#Work is done on the data.
#Finally, another data handle is created pointing to the data defined by the instanced output attribute, the output data is updated, and the input plug is set to clean.
{{{
def compute(self, plug, dataBlock):
"""
This is an overridden method of the MPxNode class.  Does the "work" the node is to perform.

Parameters:
"""
# Check that the requested recompute is our output value
if (plug == Foo.attr_someOutput):
# Read the input values, returns a MDataHandle, which is  a smart pointer
# back into the MDataBlock (dataBlock)
dataHandle = dataBlock.inputValue(Foo.attr_someInput)

# Compute the output values.  In this case, it is a simple mult operation:
outVal = dataHandle.asFloat() * 10

# Get a handle to the output value and store the new value.
handle = dataBlock.outputValue(Foo.attr_someOutput)
handle.setFloat(outVal)

# From the docs:  Tells the dependency graph that the given attribute
# has been updated and is now clean. This should be called after the
# data in the plug has been recalculated from the inputs of the node.
dataBlock.setClean(plug)
else:
# c++ requires this return, it seems that the Python API doesn't
# based on my experiments.  Doesn't hurt to leave it though.
return om.MStatus.kUnknownParameter
}}}
!!!Differences between 'compound' and 'array'
*''compound'' is an *attribute type*, like float, string, double etc.  You can't have a "compound float" attribute in the same way you can't have a "string double".  But you can have a compound attr that has child string, double, and float attributes.  Compound attributes themselves store no values:  They are simply parents of other attribute types.
**Compound attributes are considered 'parents', while they contain 'child' attributes.
**The child of a compound attribute can be any other attribute type, including another compound attribute, which has children (which could be compound attributes, etc).  Examples of this are the mesh node's {{{colorPerVertex}}} compound attribute.  It has a child compound attribute {{{vertexColor}}}, which has a child compound attribute {{{vertexFaceColor}}}.
*''array'' (also known as 'multi' when being created via mel, I think...) is a *property of an attribute*, like being readable, writable, keyable, etc. In theory any attribute type can be made array, *including compound attrs*.
**An example of this is a mesh node's {{{uvSet}}} attr: It is a compound attr, but is //also an array//, which gets confusing fast when trying to access it and its children.
**Array attributes contain multiple 'element attributes', one for each index in the array.  These in turn can be any type, including compound, which in turn could also be array, etc.
**Array elements are not 'children attributes', since only compound attrs have official 'children'.  Array attributes are a single attr, that holds multiple values in a list.
!!!Understanding Array Indices (physical & logical)
Given an attribute that is also array, you can reference its elements two different ways, via the "physical indices" and the "logical indices".
*''Physical index'' : Range from 0 -> {{{numElements()}}}-1.  Given four plugs for elements A, B, C, and D, A is index 0, B1, C2, D3.  If plug B was deleted, now: A0, C1, D2:  As you can see, the physical index for a given plug can change based on creation and deletion.
*''Logical index'' : For a given element, this is an assigned\fixed\absolute index that will never change regardless of creation or deletion of other indices.  You can use the plugs {{{getExistingArrayAttributeIndices()}}} method to populate a {{{MIntArray}}} with the existing indices.
**Referring to an array element in MEL uses the logical index.  MEL can't get the physical index.
**Connections between attributes are based on their *logical indices*.  This is needed since the logical indices don't change (and you don't want connections to change after they've been made...).
You can query these indices via {{{MPlug}}}.  Below is some example code and results showing the differences between them.  When calling to {{{elementByLogicalIndex()}}}, if the given index doesn't yet exist (it is a 'sparse array'), it is created and populated with a default value for that attribute.
{{{
plg = myPlg.elementByLogicalIndex(2)
plg.setDouble(11)
plg = myPlg.elementByLogicalIndex(10)
plg.setDouble(27)
plg = myPlg.elementByLogicalIndex(0)
plg.setDouble(100)
}}}
This would be the result of the physical \ logical indices:
{{{
Physical 0 \ Logical 0  : 100
Physical 1 \ Logical 2  : 11
Physical 2 \ Logical 10 : 27
}}}
!!!Networked and non-networked plugs
I'll be honest, I really don't understand the ins and outs of these yet.  But some notes:
*''non-networked plug'' : user created plugs used to establish new connections to an attr, get or set a value on an attr.  When one of these is used, a networked version of the plug is created and added to the dependency node network. A non-networked plug contains an array of array indices that plot the path from the root plug to this plug.
*''networked plug'' : Dependency node plugs, can't be explicitely created: only referenced by users.  They also describe the 'tree' of plugs indicating connections made to the attributes of the node.
*[[API: Simple scripted plugin node]] : A working overview of a bunch of what the above covers.
*[[API: Find all child attributes under a compound attribute]]
*[[API: Find all attributes & plugs on a node]]
Just some early notes...  See a working example here:
[[API: Simple scripted plugin argument passing]]

Docs:
----
A major issue I've ran into is the (apparent) lack of ability to pass array data into an argument.  A thread here discusses it pretty well:
http://forums.cgsociety.org/archive/index.php/t-701733.html
It appears that the main hack is to pass in a single string you can then later split into the array items of your choice, which is //terrible//...  From that form post: ''"MEL commands //don't// have the ability to accept a string array as a single flag argument."'', which seems to be true based on experimentation.
----
When authoring a scripted plugin command to accept arguments, you do this by by authoring a 'syntax' function that is passed to the {{{OpenMayaMPx.MFnPlugin.registerCommand()}}} method inside the {{{initializePlugin()}}} function.  The syntax function can look something like this:
{{{
import maya.OpenMaya as om
def newSyntax():
syntax = om.MSyntax()
# Start adding flags and whatnot here to pass your arguments to.
}}}
And the snippet for registering the command, which includes passing of the syntax ({{{cmdName}}} & {{{cmdCreator}}} being authored elsewhere):
{{{
import maya.OpenMayaMPx as ompx
def initializePlugin(mobject):
mfnPlugin = ompx.MFnPlugin(mobject)
mfnPlugin.registerCommand(cmdName, cmdCreator, newSyntax)
}}}
You can then capture the argument data in the {{{doIt()}}} method of the scripted plugin:
{{{
import maya.OpenMayaMPx as ompx
class MyCmd(ompx.MPxCommand):
def __init__(self):
ompx.MPxCommand.__init__(self)

def doIt(self, argList):
argData = om.MArgDatabase(newSyntax(), argList)
}}}
The {{{argList}}} parameter recieves an {{{MArgList}}} argument.  You can then pass this arg into a {{{MArgDatabase}}} object, which is a subclass of {{{MArgParser}}}, or start accessing data in the {{{MArgList}}} directly.
Scratch-pad as I become more familiar with the various API classes.  Also see:
[[API: class organization]]
----
Starting with Maya 2013, they offered a [[API Class Taxonomy|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/index.html?url=cpp_ref/_maya.html,topicNumber=cpp_ref__maya_html]] online, which is all classes arranged according to subject/concept.
----
This is mainly a place where I can note classes I've used, there are obviously more than what is listed here.
!M
Notes:
*Maya's base utility classes.
"provides methods for working with 3D model views. 3D views are based on OpenGL drawing areas"
"Control over animation playback and values"
"...a static class which provides methods which determine if an object is being animated, which attributes are animated for a given object and which animation curves are used to animate a given attribute."
*Methods include {{{isAnimated}}}, {{{findAnimatedPlugs}}}, {{{findAnimation}}}
"Implementation of a 3D bounding box"
"... used to store values of color attribute."
"Provides methods for obtaining one or all Paths to a specified DAG Node..."
*{{{node()}}} : returns an {{{MObject}}}
*{{{fullPathName()}}} : Returns string of the full path to the node.
*{{{isInstanced()}}} : Returns True\False.
See:
*[[API: MObject and MDagPath]]
"...is used to change the structure of the //dependency graph// (DG). This includes adding nodes, making new connections, and removing existing connections."
*Only usable in scripted plugins.  If you call to API commands and want to be able to undo them, you need execute those operations through this class.  See this example [[API: undoing commands]].
*Used for node creation, deletion, attribute connection, disconnection, attribute addition, attribute removal, node renaming, mel execution, etc.
>Inherits from {{{MDGModifier}}}
>"...used to change the structure of the DAG (subset of the DG, focusing on transforms\shapes). This includes adding nodes, making new connections, and removing existing connections.
"... provides a fundamental type for the Maya API to hold and manipulate linear data. All API methods that require or return distance information do so through variables of this type."
*Great for converting from ui units to system units and vice-versa.
"This class provides methods for working with euler angle rotations."
"Methods for opening, saving, importing, exporting, and referencing files."
Also gives query methods for many data items as well (like the scene name, for example).
*Closely mirrors functionality found in the mel {{{file}}} command.
"...is a utility class which provides wrappers for the basic functions in the OpenGL API"
"Provide methods for selection, 3D-views, time, model manipulation and MEL commands."
Many, many usable methods in here.
*{{{deleteNode(MObject)}}} : Delete the given node.
*{{{executeCommand(...)}}} :  Has a variety of parameter signatures, executes a mel command.
*{{{getActiveSelectionList(MSelectionList)}}} : Fill a {{{MSelectionList}}} with what is currently selected ({{{MObject}}}s).
*{{{getFunctionSetList	(MObject, MStringArray)}}} : Get a list of strings representing the function sets that will accept this object
*{{{select(MObject)}}} : Put the given object on the active selection list.
*{{{select(MDagPath, MObject) : Put the given object ({{{MDagPath}}}) and components ({{{MObject}}}) on the active selection list.
*{{{selectByName(string)}}} : Puts objects that match the give name on the active selection list, can use regular expressions.
*{{{setOptionVarValue(string, int)}}} : Set an optionVar with an int value.  More similar commands for setting other data types.
*{{{sourceFile(string)}}} :  Sources a mel script.
*{{{viewFrame(double)}}} : Sets the current frame.
"...used to describe the characteristics of an image file, such as dimensions, channel count, and pixel format."
"The generic class for accessing all Maya internal modelling, animation and rendering Objects, collectively referred to as Model Objects, through the API. This includes all Dependency Graph (DG) Nodes, of which Directed Acyclic Graph (DAG) Nodes are a subset."
*{{{isNull()}}} : Returns True\False on whether it is a valid {{{MObject}}} or not.
*{{{hasFn(MFn.kSomeConstant)}}} : Query to determine if the {{{MObject}}} is comaptible with a given Function Set.function.
See:
*[[API: MObject and MDagPath]]
"A matrix math class for 4x4 matrices of doubles."
"A plug is a point on a dependency node where a particular attribute can be connected."
*Similar to the mel commands {{{connectionInfo}}}, {{{isConnected}}}, {{{getAttr}}}, {{{setAttr}}}
"This class provides an implementation of a point. Numerous convienence operators are provided to help with the manipulation of points. This includes operators that work with the {{{MVector}}} and {{{MMatrix}}} classes."
"...manages a window containing a status message, a graphical progress gauge, and optionally a "Hit ESC to Cancel" label for interruptable operations."
*This is like the mel {{{progressWindow}}} command.
"...provides methods for working with Quaternions"
"Utility class for working with pointers to basic types such as int, float and arrays of these types."
*Used heavily by Python scripting in the API to make it jive with c++ syntax.
"A list of {{{MObject}}}s."
*{{{add()}}} :  Accepts strings (using wildcards), {{{MObject}}}, {{{MDagPath}}}, and {{{MPlug}}}.
*{{{clear()}}} : Empty the selection list.
*{{{getDagPath(int, MDagPath, MObject)}}} :  For the given index, get the dag path {{{MDagPath}}} and component {{{MObject}}} (if present).
*{{{getDependNode(int, MObject)}}} : Get the {{{MObject}}} at the given index.
*{{{getPlug	(int, MPlug)}}} : Get the plug (attribute) at the given index).
*{{{getSelectionStrings(MStringArray)}}} : Gets the string representations of the items in the selection list.
*{{{getSelectionStrings(int, MStringArray)}}} :  Gets the string representations of the items in the selection list at the given index.
*{{{length()}}} Returns the length of the list.
"Set and retrieve animation time values in various unit systems."
"...allows the manipulation of the individual transformation components (eg scale, rotation, shear, etc) of a four by four transformation matrix."
"A vector math class for vectors of doubles."

!~MFn
Notes:
*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.
*All {{{MFn}}} classes (except for {{{MFn}}} itself) inherit from [[MFnBase|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_base.html]].  The below hierarchy illustrates the class inheritance tree.
*The //mel// command {{{nodeType}}}, via it's {{{apiType}}} flag can be used to query the {{{MFn}}} type of a DAG node, which can then be used to find what type of function set to use.
*Bit of craziness:  Since Maya stores the Python API in different packages (see [[API: class organization]]), it's possible that this inheritance can span multiple packages.  For example, this is the inheritance tree for {{{MFnSkinCluster}}}, which as you'll see inherits from two different packages; {{{OpenMaya}}} and {{{OpenMayaAnim}}}:
**{{{OpenMaya.MFnBase}}}  (superclass)
***{{{OpenMaya.MFnDependencyNode}}}
****{{{OpenMayaAnim.MFnGeometryFilter}}}
*****{{{OpenMayaAnim.MFnSkinCluster}}} (subclass)
"{{{MFn}}} encapsulates all API Function Set type identifiers used for RTTI in the API."
Meaning, the {{{MFn}}} class holds the constants ({{{MFn.k*}}}) that can be tested against to determine what type function objects can be attached to an {{{MObject}}}.  Used with {{{MObject.hasFn()}}}
It has two useful methods:
*{{{setObject(MObject)}}} that allows for an {{{MObject}}} to be assigned to the function set after it was created.
*{{{object()}}}, which returns the {{{MObject}}} assigned to the function set.
----
>"Dependency node attribute function set."
----
>"This is the base class for all function sets which deal with component objects.  Components are [[MObjects|API: MObject and MDagPath]] which hold index information for shapes.   Examples of these types are mesh vertices (single indexed), nurbs surface CVs (double indexed), and lattice points (triple indexed)."
----
----
>"...allows the creation and manipulation of dependency graph nodes ({{{MObject}}}). Traversal of the dependency graph is possible using the getConnections method."
>* {{{MFnDependencyNode()}}} : Creation, no passed in object.
>* {{{MFnDependencyNode(MObject)}}} :  Creation, passing in an {{{MObject}}}.
>* {{{create(string)}}} : (has other signatures, but this one is easiest) : ''Creates a new dependency node with the given type''. The new node is placed into the dependency graph.
>* Many methods for node creation, getting connections, name setting, plug (attr) finding, attribute addition and removal, locking & unlocking, query if referenced, query parent namepace, query if name is unique, query if made via plug-in, and more...
----
>>"Create, query and edit Anim Curve Nodes and the keys internal to those Nodes."
>>*Many, many methods for interacting with animCurves.
----
>>"...used to create, edit, and query expression nodes"
----
>>"...the function set for geometry filters, the node that is the base class for deformers. Geometry filter nodes include ''clusters, ffds, nonlinears, user-defined deformers, sculpts, wires and blendShapes''. The purpose of the geometry filter is to connect to the geometry that is deformed. The geometry filter is independent of any algorithm that calculates the deformation."
----
>>>"...the function set for skinClusters"
----
>>"Provides methods for attaching Function Sets to, querying, and adding children to DAG Nodes ({{{MDagPath}}}). Particularly useful when used in conjunction with the DAG Iterator class ({{{MItDag}}})."
>>* {{{MFnDagNode()}}} : Creation with no dag path.
>>* {{{MFnDagNode(MDagPath)}}} : Creation based on a dag path.
>>*Many methods for querying parents, querying children, duplication, node creation, parenting, unparenting, etc.
----
----
>>>* Creation, transformation, value query, etc.
----
>>>>"This is the function set for inverse kinematics (IK) handles. An ik handle specifies the joints in a skeleton that are effected by an attached ik solver."
----
>>>>"...function set for joints."
----
>* "... is the parent class for all dependency graph data function sets. Conceptually, data objects are what flow through the connections in the dependency graph.  Each node in the dependency graph has a data block associated with it. The data block holds the data objects for all of the node's attributes (see {{{MDataBlock}}}). The data block is only available during the compute method of a node. A data handle ({{{MDataHandle}}}) can be created to access a particular attribute's data inside of the data block."
----
>>*"...allows the creation and manipulation of {{{MMatrix}}} data objects for use in the dependency graph."
>>*This can be used //within user-created// nodes to access matrix data.

!~MIt
Notes:
*Used for iterating though data (for looping through things). 'It' = 'Iterator'. They wrapper "iteration". These classes are iterators and work on {{{MObject}}}s similar to the way a function set ({{{MFn}}} classes) does.
"Use the DAG iterator to traverse the DAG either depth first or breadth first, visiting each node and, if desired, retrieving the node (as an {{{MObject}}})."
See:
*[[API: OpenMaya.MItDag]]
"Iterate over Dependency Graph (DG) Nodes or Plugs starting at a specified root Node or Plug."
See:
*[[API: OpenMaya.MItDependencyGraph]]
"Use the dependency node iterator to traverse all the nodes in Maya's Dependency Graph."
See:
*[[API: OpenMaya.MItDependencyNodes]]
"... is the iterator class for geometry data, and can be used to loop over the ~CVs of NURBS, the points of subds & lattices, and the vertices of polygonal meshes."
*{{{MItGeometry(MDagPath, MObject)}}} : Create a new object, passing in the path to the active node ({{{MDagPath}}}) and an {{{MObject}}} of the components to iterate over.
*{{{index()}}} : Get the index of the current component.
*{{{position(OpenMaya.MSpace.kWorld)}}} : Get the position of the current component as a {{{MPoint}}}.
*{{{setPosition(MPoing)}}} : Set the position of the current point.
"...is the edge iterator for polygonal surfaces"
"...is the iterator for face vertices on polygonal surfaces."
"... is the iterator for polygonal surfaces (meshes)."
*Good for querying or setting all verts on a mesh at once (for example)
"...is the iterator for polygonal vertices."
"Iterate over the items in the selection list."
*{{{MItSelectionList(MSelectionList)}}} :  Creation, based on a pre-existing {{{MSelectionList}}}
*{{{getDagPath(MDagPath, MObject)}}} :  Get the path to the current object ({{{MDagPath}}}) and a {{{MObject}}} representing any selected components.
"Iterate over the keyframes of a particular Anim Curve Node..."

!~MPx
Notes:
*Used for creating custom plugins. 'Px' = 'Proxy'. They are API classes designed for you to derive from and create your own object types.
"This is the proxy class for creating MEL commands through the API."
"Base class for user defined dependency nodes."
Nice simple example copied from here:
https://forums.autodesk.com/t5/maya-programming/create-mesh-from-list/td-p/7575371
{{{
# using Python API 2.0 for Maya
import maya.api.OpenMaya as om2

meshFn = om2.MFnMesh()

# list of vertex points
vertices = [om2.MPoint(-0.5, -0.5, 0.5),
om2.MPoint(0.5, -0.5, 0.5),
om2.MPoint(0.5, 0.5, 0.5),
om2.MPoint(-0.5, 0.5, 0.5),
om2.MPoint(-0.5, 0.5, -0.5),
om2.MPoint(0.5, 0.5, -0.5),
om2.MPoint(0.5, -0.5, -0.5),
om2.MPoint(-0.5, -0.5, -0.5)]

# list of number of vertices per polygon
# A cube has 6 faces of 4 vertices each
polygonFaces = [4] * 6

# list of vertex indices that make the
# the polygons in our mesh
polygonConnects = [0,1,2,3,1,6,5,2,7,6,5,4,3,2,5,4,3,0,7,4,0,1,6,7]

# create the mesh
meshFn.create(vertices, polygonFaces, polygonConnects )
}}}
If you've authored a custom scripted-plugin [[MPxNode|http://download.autodesk.com/us/maya/2010help/API/class_m_px_node.html]] (or a derived class, like [[MPxLocatorNode|http://download.autodesk.com/us/maya/2010help/API/class_m_px_locator_node.html]]), via ~OpenGL you can draw text to the screen that follows it.  Below is a code snippet showing parts of the class that you can use to implement this functionality.

I should note that I've encountered a bug where the text seems to translate 1.5x greater than that of the node... and I have yet to find a solution :(
{{{
import maya.OpenMaya as om
import maya.OpenMayaUI as omui
import maya.OpenMayaMPx as ompx

class Foo(ompx.MPxLocatorNode):

def __init__(self):
"""
Superclass override.
"""
ompx.MPxLocatorNode.__init__(self)
# Save an empty MDagPath that will later be the path to our node.
self.mDagPath = om.MDagPath()

def draw(self, view, path, style, status):
"""
Superclass override, where the text drawing happens.
"""
view.beginGL()  # M3dView
view.drawText("TestText!", self.nodeTranslation(), omui.M3dView.kLeft)
view.endGL()

def nodeTranslation(self):
"""
Custom method that returns a MPoint object based on the worldspace
position of our locator node.
"""
# Get the dag path to this object:
om.MDagPath.getAPathTo(self.thisMObject(), self.mDagPath)
# Pop from the shape to the transform
self.mDagPath.pop()
# Find the translation of the node in worldspace:
transformFn = om.MFnTransform(self.mDagPath) # MFnTransform
vec = transformFn.getTranslation(om.MSpace.kWorld) # MVector
# Convert from internal units (cm) to whatever the UI is using:
return om.MPoint(om.MDistance.internalToUI(vec.x),
om.MDistance.internalToUI(vec.y),
om.MDistance.internalToUI(vec.z))
}}}
Docs:
*[[MPoint|http://download.autodesk.com/us/maya/2011help/API/class_m_point.html]]
The below code is something I came up with to list all the attributes on a node via the API, the values stored in their plugs, and the API attribute type.  It seems //really// clunky, and maybe in six months I'll find some function that does this in two lines.  But it seems like the API makes you test against which function set the attribute belongs to, and then have to query for a very specific type of data.  After I authored this I cam across Bryan Ewerts post [[here|http://ewertb.soundlinker.com/api/api.029.php]], which uses a very similar system, so I'm guessing I'm on the right track.
Some notes:
*I had to wrap every query in a try\except clause, since some data would return None even though it was defined as a given type.
*I have yet to figure out how to query string data.  It just fails :(
*As noted below, the code ''fails to trace array attribute element plug data''.  It shows a weird {{{[-1]}}} for the index, and won't dig any deeper.  However, a link to a solution is provided at the end of this topic.
Some helper functions:
{{{
import maya.OpenMaya as om

def nameToMObject(name):
"""
return an MObject based on the provided string node name.
"""
selectionList = om.MSelectionList()
node = om.MObject()
selectionList.getDependNode( 0, node )
return node

def getAllPlugs(mObject):
"""
return a list of all the plugs for all the attributes on the given MObject.
"""
ret = []
depNodeFn = om.MFnDependencyNode(mObject)
attrCount = depNodeFn.attributeCount()
for i in range(attrCount):
attrObject = depNodeFn.attribute(i)
mPlug = depNodeFn.findPlug(attrObject)
ret.append(mPlug)
return ret
}}}
Main code:
{{{
objectName = 'pPlaneShape1'
mObject = nameToMObject(objectName)
plugs = getAllPlugs(mObject)

# Query\print values:
for plug in plugs:
attr = plug.attribute() # MObject for the coresponding attribute
val = []
#--------------------------------------------------------
# Now test to see what type of attr it is!

# Is it numeric?
if attr.hasFn(om.MFn.kNumericAttribute):
fnAttr = om.MFnNumericAttribute(attr)
unitType = fnAttr.unitType()
if unitType == om.MFnNumericData.kBoolean:
try:
val = plug.asBool()
except:
pass
elif unitType == om.MFnNumericData.kChar:
try:
val = plug.asChar()
except:
pass
elif unitType == om.MFnNumericData.kShort:
try:
val = plug.asShort()
except:
pass
elif unitType == om.MFnNumericData.k2Short:
for i in range(2):
try:
val.append(plug.child(i).asShort())
except:
pass
elif unitType == om.MFnNumericData.k3Short:
for i in range(3):
try:
val.append(plug.child(i).asShort())
except:
pass
elif unitType == om.MFnNumericData.kLong or unitType == om.MFnNumericData.kInt:
try:
val = plug.asInt()
except:
pass
elif unitType == om.MFnNumericData.k2Long or unitType == om.MFnNumericData.k2Int:
for i in range(2):
try:
val.append(plug.child(i).asInt())
except:
pass
elif unitType == om.MFnNumericData.k3Long or unitType == om.MFnNumericData.k3Int:
for i in range(3):
try:
val.append(plug.child(i).asInt())
except:
pass
elif unitType == om.MFnNumericData.kFloat:
try:
val = plug.asFloat()
except:
pass
elif unitType == om.MFnNumericData.k2Float:
for i in range(2):
try:
val.append(plug.child(i).asFloat())
except:
pass
elif unitType == om.MFnNumericData.k3Float:
for i in range(3):
try:
val.append(plug.child(i).asFloat())
except:
pass
elif unitType == om.MFnNumericData.kDouble:
try:
val = plug.asDouble()
except:
pass
elif unitType == om.MFnNumericData.k2Double:
for i in range(2):
try:
val.append(plug.child(i).asDouble())
except:
pass
elif unitType == om.MFnNumericData.k3Double:
for i in range(3):
try:
val.append(plug.child(i).asDouble())
except:
pass

# Is it 'typed'?
elif attr.hasFn(om.MFn.kTypedAttribute):
# These can be string attrs
attrType = om.MFnTypedAttribute(attr).attrType()
if attrType == om.MFnData.kString:
# NEITHER SOLUTION HERE IS WORKING!
#stringData = om.MFnStringData(plug.asMObject())
#val = stringData.string()
#val = plug.asString()  # This cause it to break! :(
val = "<string: can't query>"
elif attrType == om.MFnData.kMatrix:
try:
matrixData = om.MFnMatrixData(plug.asMObject())
matrix = matrixData.matrix()
val = [matrix(i,j) for i in range(4) for j in range(4)]
except:
pass

# Is it  angle, time, unit (distance), or enum?
elif attr.hasFn(om.MFn.kDoubleAngleAttribute):
mAngle = plug.asMAngle()
val = mAngle.value()
elif attr.hasFn(om.MFn.kTimeAttribute):
mTime = plug.asMTime()
val = mTime.value()
elif attr.hasFn(om.MFn.kUnitAttribute):
mDist = plug.asMDistance()
val = mDist.value()
elif attr.hasFn(om.MFn.kEnumAttribute):
val = plug.asInt()

# Could add more code for Generic, Light, & Message.  Compound is a type as
# well, but only holds other attrs (has no 'value'), and the previous code
# extracts all the child plugs from any compounds that exist.

if isinstance(val, list):
if not len(val):
val = None
print plug.name(), val, attr.apiTypeStr()
}}}
Prints a lot of stuff:
{{{
pPlaneShape1.message None kMessageAttribute
pPlaneShape1.caching False kNumericAttribute
pPlaneShape1.isHistoricallyInteresting None kNumericAttribute
pPlaneShape1.nodeState 0 kEnumAttribute
etc...
}}}
Furthermore, it won't print out the values for any //array attrs//.  For example, presuming you have a poly mesh and you've modified the UV's, you'd have multiple UV attrs being tweaked on.  But the above code will only print this:
{{{
pPlaneShape1.uvSet None kCompoundAttribute
pPlaneShape1.uvSet[-1].uvSetName <string: can't query> kTypedAttribute
pPlaneShape1.uvSet[-1].uvSetPoints [0.0, 0.0] kAttribute2Float
pPlaneShape1.uvSet[-1].uvSetPoints[-1].uvSetPointsU 0.0 kNumericAttribute
pPlaneShape1.uvSet[-1].uvSetPoints[-1].uvSetPointsV 0.0 kNumericAttribute
pPlaneShape1.uvSet[-1].uvSetTweakLocation None kTypedAttribute
}}}
Which I guess tells you that these are array attributes, but doesn't tell you how many of each there are, or the element plug values (other than the default vals listed).
I have a solution for compound\array attrs here: [[API: Find all child attributes under a compound attribute]]
After much trial and error, I've whipped up the below code to query all the child attributes under a given compound attribute, and print their names \ types.

This was complicated by a variety of factors:
#It took me a while to realize that compound attrs can also be array.  I hadn't quite grasped the differences between them:  Compound is a type, like float or string.  Array is a property of an attribute, like keyable, locked, hidden, etc.
#I was able to query compound children, but I wasn't understanding how to query an array of compound attrs children.  This was further messed up by the fact the {{{MPlug.getExistingArrayAttributeIndices()}}} method will return negative index values, which cause all sorts of problems.  Not sure why it does that, but skipping over them solves the problem.
#Understanding the differences between logical and physical index arrays.  In a nutshell, you want to use logical most of the time...

In our example we use a polygonal plane with four verts, called {{{pPlane1}}} (but make reference to its shape node, {{{pPlaneShape1}}}).
{{{
import maya.OpenMaya as om

# The node name, and attribute name, to query:
node = "pPlaneShape1"
attr = "uvSet"

# Get an MObject by string name:
selList = om.MSelectionList()
mObject = om.MObject()
selList.getDependNode(0, mObject)

# Attach a function set to the shape and get the plug to start querying:
mFnDependencyNode = om.MFnDependencyNode(mObject)
rootPlug = mFnDependencyNode.findPlug(attr)

# Get all child plugs for our root, presuming it is a compound attr, since they're
# the only type that have 'children':
plugs = [rootPlug]
for plug in plugs:
# If the type is compound, and it is also set to array:
if plug.isCompound() and plug.isArray():
# Find the logical indices of the array:
logicalIndices = om.MIntArray()
plug.getExistingArrayAttributeIndices(logicalIndices)
for i in range(logicalIndices.length()):
# getExistingArrayAttributeIndices() can return negative index values
# for some reason, so we need to be sure to *not* deal with those,
# since obviously bad things would happen....
if logicalIndices[i] >= 0:
# Now find the element plug of that index, which is a compound
# type attribute:
elementPlug = plug.elementByLogicalIndex(logicalIndices[i])
# And query the children of that compound attr:
for j in range(elementPlug.numChildren()):
plugs.append(elementPlug.child(j))
# If it is compound, but not array:
elif plug.isCompound():
# Just get the children of that compound attr:
for i in range(plug.numChildren()):
plugs.append(plug.child(i))

for plug in plugs:
attrObj = plug.attribute() # MObject
print plug.name(), attrObj.apiTypeStr()
}}}
Gives us this result:
{{{
pPlaneShape1.uvSet kCompoundAttribute
pPlaneShape1.uvSet[0].uvSetName kTypedAttribute
pPlaneShape1.uvSet[0].uvSetPoints kAttribute2Float
pPlaneShape1.uvSet[0].uvSetTweakLocation kTypedAttribute
pPlaneShape1.uvSet[0].uvSetPoints[0].uvSetPointsU kNumericAttribute
pPlaneShape1.uvSet[0].uvSetPoints[0].uvSetPointsV kNumericAttribute
pPlaneShape1.uvSet[0].uvSetPoints[1].uvSetPointsU kNumericAttribute
pPlaneShape1.uvSet[0].uvSetPoints[1].uvSetPointsV kNumericAttribute
pPlaneShape1.uvSet[0].uvSetPoints[2].uvSetPointsU kNumericAttribute
pPlaneShape1.uvSet[0].uvSetPoints[2].uvSetPointsV kNumericAttribute
pPlaneShape1.uvSet[0].uvSetPoints[3].uvSetPointsU kNumericAttribute
pPlaneShape1.uvSet[0].uvSetPoints[3].uvSetPointsV kNumericAttribute
}}}
It should be noted that not all attrs are so verbose.  For example, if you set this and re-run it:
{{{
attr = "pnts"
}}}
All it prints is:
{{{
pPlaneShape1.pnts kAttribute3Float
}}}
Even though we know there is more than one point in the plane.  If you grab all the verts and component level, manually move them with the translate manip, and re-run it, it prints:
{{{
pPlaneShape1.pnts kAttribute3Float
pPlaneShape1.pnts[0].pntx kFloatLinearAttribute
pPlaneShape1.pnts[0].pnty kFloatLinearAttribute
pPlaneShape1.pnts[0].pntz kFloatLinearAttribute
pPlaneShape1.pnts[1].pntx kFloatLinearAttribute
pPlaneShape1.pnts[1].pnty kFloatLinearAttribute
pPlaneShape1.pnts[1].pntz kFloatLinearAttribute
pPlaneShape1.pnts[2].pntx kFloatLinearAttribute
pPlaneShape1.pnts[2].pnty kFloatLinearAttribute
pPlaneShape1.pnts[2].pntz kFloatLinearAttribute
pPlaneShape1.pnts[3].pntx kFloatLinearAttribute
pPlaneShape1.pnts[3].pnty kFloatLinearAttribute
pPlaneShape1.pnts[3].pntz kFloatLinearAttribute
}}}
Which tells me that in some cases, the plugs don't exist until some type of user interaction.  Something even crazier I've found:  If instead of grabbing all the verts by hand and deleting them, if I instead run the command:
{{{
delete -constructionHistory;
}}}
On the mesh, the attrs will appear as well.  It must be triggering some kind of massive "you are dirty" flag on all the plugs so they suddenly show up.... or something....

I have yet to find a more elegant solution for this :(
----
Also see:
*[[API: Find all attributes & plugs on a node]]
*[[How can I get a list of all the child multi-attrs, of a given attr name?]] (using the command engine, rather than the API)
* [[Find child attributes]]
''API Code Snippet''

In this example, I set all variable names to be the type of object they are, to make it easier to understand how objects are returned and used.  I don't recommend doing this in regular code, just using it as an explanation tool
__Highlights:__
*Create {{{MItDag}}} 'DAG iterator object' to loop over all mesh.
*Create {{{MDagPath}}} 'DAG path object' for each mesh, giving access to it's path/name.
*Create {{{MFnMesh}}} 'polygonal surface function set' object, allowing for actions to be performed on the current {{{MDagPath}}} object.
{{{
# Print the name of all the mesh in the scene & their vert counts.

import maya.OpenMaya as om

# Make a MItDag iterator object, for mesh only:
MItDag = om.MItDag(om.MItDag.kDepthFirst, om.MFn.kMesh)

# Start looping over each mesh:
while not MItDag.isDone():

# Make a MDagPath object that will store the path of
# the mesh:
MDagPath = om.MDagPath()

# Set the MDagPath object to the path of the current mesh
# as defined by the current loop of the iterator:
MItDag.getPath(MDagPath)

# Create a new MFnMesh object, set to the current dag path.
# We can now do operations on that mesh via that object.
MFnMesh = om.MFnMesh(MDagPath)

# As an example, find the number of verts on the mesh
# from the MFnMesh object:
numVerts = MFnMesh.numVertices()

# get the name of our node from the MDagPath object:
name = MDagPath.fullPathName()

# Print results:
print name, numVerts

# Advance to the next mesh in our iterator:
MItDag.next()
}}}

Example via the ~OpenMaya Python API of finding the closest vertex on a polygonal mesh to a given transform.
I've not tried it, but it may be worth checking out a solution using [[MMeshIntersector|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_mesh_intersector.html]].  There is an example using it online [[here|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/index.html?url=cpp_ref/closest_point_cmd_8cpp-example.html,topicNumber=cpp_ref_closest_point_cmd_8cpp_example_html,hash=_a33]]
For fun, I made two different implementations.  They both do the same thing, just go about it different ways.
{{{
# This first code chunk is required for both examples:

# Python code
from operator import itemgetter
import maya.cmds as mc
import maya.OpenMaya as om

def getMDagPath(nodeName):
"""
Convenience function that returns a MDagPath for a given Maya DAG node.
"""
selList = om.MSelectionList()
mDagPath = om.MDagPath()
selList.getDagPath(0, mDagPath)
return mDagPath
}}}
!!!Example A:
In this example, we fill a {{{MPointArray}}} with all the vert positions at once, then do our distance comparisons over that array, filling our final sorted dictionary with the distance results.
{{{
def closestVertToPoint_A(mesh, target):
"""
Parameters:
mesh : string : Polygonal mesh to query vert positions on.
target : string : Any transform to find the closest vertex to.

return : string : Name of the vertex closest to target.
"""
# Maya node definitions:-------------
# Get a MDagPath for our mesh:
node_mDagPath = getMDagPath(mesh)
# Get a MDagPath for our target:
target_mDagPath = getMDagPath(target)

# Target Stuff:-------------
# Get a MFnTransform for our target:
mFnTransform = om.MFnTransform(target_mDagPath)
# Get a MPoint for it's worldspace position:
transform_mPoint = mFnTransform.rotatePivot(om.MSpace.kWorld)

# Mesh stuff:-------------
# Get the MFnMesh:
mFnMesh = om.MFnMesh(node_mDagPath)
# Build a point array to hold our points:
mPointArray = om.MPointArray()
# Fill the array with points:
mFnMesh.getPoints(mPointArray)

# Find closest vert ID: -------------------------
# This is a dictionary, each key:value pair will be vertID:distance
distances = {}
for i in range(mPointArray.length()):
distances[i] = transform_mPoint.distanceTo(mPointArray[i])
# Sort our dictionary by values (the distances), not keys (the vert ID's).
# This turns the dictionary into a list, fyi.
closest = sorted(distances.items(), key=itemgetter(1))[0]

# Return it: ----------------------
# Create the vertex name from the ID:
vertName = '%s.vtx[%s]'%(mesh, closest[0])
return vertName
}}}
!!!Example B:
In this example, we iterate over each vertex querying it's position and doing the distance computation before adding the result to the final sorted dictionary.
{{{
def closestVertToPoint_B(mesh, target):
"""
Parameters:
mesh : string : Polygonal mesh to query vert positions on.
target : string : Any transform to find the closest vertex to.

return : string : Name of the vertex closest to target.
"""
# Maya node definitions:-------------
# Get a MDagPath for our mesh:
node_mDagPath = getMDagPath(mesh)
# Get a MDagPath for our target:
target_mDagPath = getMDagPath(target)

# Target Stuff:-------------
# Get a MFnTransform for our target:
mFnTransform = om.MFnTransform(target_mDagPath)
# Get a MPoint for it's worldspace position:
transform_mPoint = mFnTransform.rotatePivot(om.MSpace.kWorld)

# Mesh stuff:-------------
# This is a dictionary, each key:value pair will be vertID:distance
distances = {}
# Get the MItMeshVertex:
mItMeshVertex = om.MItMeshVertex(node_mDagPath)
while not mItMeshVertex.isDone():
# For the current vert, get it's position:
mPoint = mItMeshVertex.position(om.MSpace.kWorld)
# Find distance:
distance = transform_mPoint.distanceTo(mPoint)
# Get the current vert id:
vertId = mItMeshVertex.index()
distances[vertId] = distance
mItMeshVertex.next()

# Find closest vert ID: -------------------------
for i in range(mPointArray.length()):
distances[i] = transform_mPoint.distanceTo(mPointArray[i])
# Sort our dictionary by values (the distances), not keys (the vert ID's).
# This turns the dictionary into a list, fyi.
closest = sorted(distances.items(), key=itemgetter(1))[0]

# Return it: ----------------------
# Create the vertex name from the ID:
vertName = '%s.vtx[%s]'%(mesh, closest[0])
return vertName
}}}
!!!The results
Do it.  Run a timer to see which one is faster:
{{{
import time
# Polygonal mesh with verts to query:
mesh = 'pSphere1'
# Target we want to find the closest vert to:
target = 'locator1'

# Time A:
start_A = time.time()
closestPoint_A = closestVertToPoint_A(mesh, target)
end_A = time.time()
total_A = end_A - start_A

# Time B:
start_B = time.time()
closestPoint_B = closestVertToPoint_B(mesh, target)
end_B = time.time()
total_B = end_B - start_B

print "Total time for example A:", total_A
print "Total time for example B:", total_B
print "Closest vert:", closestPoint_A
mc.select(closestPoint_A)
}}}
I made a sphere with 100 subdivision (9902 verts), and these were the results:
{{{
Total time for example A: 0.031
Total time for example B: 0.047
Closest vert: pSphere1.vtx[4298]
}}}
So it looks like A is about 1.5x  faster than B.
{{{
import maya.OpenMaya as om

node = "pPlaneShape1"
attr = "uvSetPoints"

# Get an MObject by string name:
selList = om.MSelectionList()
mObject = om.MObject()
selList.getDependNode(0, mObject)

# Get a plug based on the attribute string name:
mFnDependencyNode = om.MFnDependencyNode(mObject)
plug = mFnDependencyNode.findPlug(attr)
print plug.name()
}}}
{{{
# pPlaneShape1.uvSet[-1].uvSetPoints
}}}
*http://download.autodesk.com/us/maya/2010help/API/class_m_fn_dependency_node.html
Docs for [[MNodeMessage|http://download.autodesk.com/us/maya/2011help/API/class_m_node_message.html]]
There is the {{{scriptJob}}}'s {{{attributeChange}}} argument of course.  But if you wanted to do this via the api, the below code functions.
{{{
import maya.OpenMaya as om

# Get an MObject for our node:
node = 'pSphere1'
selList = om.MSelectionList()
mObject = om.MObject()
selList.getDependNode(0, mObject)

# Callback code:
def callback(msg, plug, otherPlug, *args):
if msg != 2056:
# Magic number for 'setAttr'!
return
if 'translateX' not in plug.name():
return
print "Message : ", msg
val = om.MDistance.internalToUI(plug.asDouble())
print "\tplug:", plug.name(), val
print "\totherPlug:", otherPlug.name()
print "\tClient Data:", args

# Create the callback:

#----
# Delete the callback later:
om.MMessage.removeCallback(cid)
}}}
What's crazy is, running the above code without the msg filter on the first line prints:
{{{
Message : 2052
}}}
And, 2052 isn't any of the ~AttributeMessage enums (below).
Furthermore the value for 'setAttr' is 2056, //also// not in the below enums.
So very confused.
{{{
kAttributeArrayRemoved 8192
kAttributeEval 4
kAttributeKeyable 512
kAttributeLocked 16
kAttributeRemoved 128
kAttributeRenamed 256
kAttributeSet 8
kAttributeUnkeyable 1024
kAttributeUnlocked 32
kConnectionBroken 2
kDefaultAction 0
kDoAction 2
kDoNotDoAction 1
kIncomingDirection 2048
kKeyChangeInvalid 0
kKeyChangeLast 3
kLast 32768
kMakeKeyable 1
kMakeUnkeyable 2
kOtherPlugSet 16384
}}}
----
Also see:
*[[API: How can I author callbacks for Maya events?]]
The most common way to do this is via mel [[scriptJobs|http://download.autodesk.com/us/maya/2011help/Commands/scriptJob.html]] or the [[callbacks|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/callbacks.html]] command.  But via the {{{OpenMaya}}} API, you can access many 'Message' classes that provide a lot more functionality to areas that {{{scriptJob}}}'s don't provide.

It should be noted that if you keep executing the same callback creation code, they'll start to pile up.  You'll need to develop a system to track if they've been made, so as to not have a lot of extra duplicate callbacks running in the background.

In the below example, we add a callback function that will be executed just before a new scene is opened via a {{{OpenMaya.MSceneMessage}}} object.  As a simple hack, we track its existence with a global mel variable.
{{{
# Python code
import maya.mel as mm
import maya.OpenMaya as om

def callback(*args):
"""
Put your callback execution code in here.
"""
print "CALLBACK!"

def makeCallback():
"""
Creates the callback, doesn't allow dupes:
"""
# Pass mel variable to Python:
mm.eval("global int $gMyCallbackExists;") callbackExists = int(mm.eval("int$callbackExists = $gMyCallbackExists;")) if not callbackExists: om.MSceneMessage.addCallback(om.MSceneMessage.kBeforeNew, callback) mm.eval("global int$gMyCallbackExists = 1")
}}}
To create the callback:
{{{
makeCallback()
}}}
And when making a new scene from the main menu:
{{{
file -f -new;
CALLBACK!
// Result: untitled //
}}}
----
''@@It should be noted@@'' that the above system is a bit of an example-hack.  In practice, I've ran into problems with associating callbacks with a windows existence:  A memory leak can (possibly) form from closing a window that a callback is associated with, and not also unregistering the callback.  A common warning I'd get in the Output Window is:
{{{
swig/python detected a memory leak of type 'MCallbackId *', no destructor found.
}}}
Here's some pseudo-code that will assist in the proper creation, and removal, of a callback:
{{{
# Below are methods\attributes on some instance of a class:

def makeCallback(self):
"""
Method that makes the callback.
"""
# self.callbackId is now of type MCallbackId, which appears to be a swig wrapper of a pointer
# to an unsigned integer.

def destroyCallback(self):
"""
Method called when callback should be destroyed.
"""
# Properly remove the callback:
om.MMessage.removeCallback(self.callbackId)
# Call a method on the swig object to 'disown' it, and remove the warning statement:
self.callbackId.disown()
}}}
----
Here is a general list I've come up with for the various classes, and what they can set callbacks for:
**Listed first, this is the base class for all message callbacks (below).  Mainly used to remove a message callback, but has other helper methods.
**Callbacks for animation messages:  animCuve edited, keyframe edited
*[[MCameraSetMessage|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-ocumentation/cpp_ref/class_m_camera_set_message.html]]
**Callbacks for {{{cameraSet}}} sepcific envent types:  Adding\removing camera layers, camera name changes.
**Callbacks triggering on //mel command// execution.
**Callbacks for changes to specific //conditions//, same conditions found in the [[scriptJob|http://download.autodesk.com/us/maya/2010help/CommandsPython/scriptJob.html]] documentation.
**Callbacks that inform about changes to published attributes on container node types.
**Callbacks for dependency graph messages:  Time changed, node added, node removed, connection made or broken.
**Callbacks that control how Maya handles locks:  On object and attributes.
**Callbacks for model related messages:  Before and after node duplicate, before dag node added (created), during dag node removed (deleted).
**Callbacks for dependency node messages of specific dependency nodes:  Attribute changed, added, or removed.  Plug dirty.  Before node deleted.  Attr keyable state change.
**Callbacks for object set messages received by specific sets.  Tracks if set members are modified.
*[[MPaintMessage|http://help.autodesk.com/view/MAYAUL/2016/ENU/?guid=__cpp_ref_class_m_paint_message_html]]
** Callbacks for vertex color paint
**Callbacks for poly component id modification messages:  Vertex, edge, or face component id's modified.
**Callbacks for scene related messages:
**__Before & after__:  new file, file import, file open, file export, file save, file save-as, file reference, remove-reference, import reference, export reference, unload reference, software render, each software render frame. plugin loaded, plugin unloaded.
**After any operation that changes which files are loaded, when an interactive render is interrupted by the user, on interactive or batch startup after initialization, just before Maya exits.
**It should be noted in Maya 2017, they updated the API to expose the new methods the end in ...{{{Check}}}, that allow internal logic to define if Maya should halt execution after or not. I've been unable to get these to work in API1.0, but have had success in API 2.0: The calling funcs need to return a bool based on success.
**Callbacks that are based on a fixed time interval.
**Callbacks to track the deletion of UI objects.  Window deletion, camera change, 3d view destroyed, 3d view about to render contents, 3d view about to display contents.
**Register user-defined event types, register callbacks with the user-defined event types, and to post user-defined messages.
**See example usage here: [[API : How can I author a user event?]]

----
There are a few other classes independent from the Message ones:
**Callbacks to gain access to Maya's Hardware Rendering device status. You can be notified of device creation, lost reset and deletion.
**Callbacks to gain access to Maya's rendering information during software rendering. You can modify Maya's shadow maps, RGB pixmap, and depth map to composite your own rendering effects into Maya's rendering.
**An array of {{{MCallbackId}}}.  These can be passed to {{{MMessage}}} instances.

----
And via {{{OpenMayaUI}}}:
**Used for querying system events such as mouse presses:  Mouse button press, release, hold, drag.  Delete/backspace key event. Complete key event.  Querying\setting event location.


Docs: [[MFnDependencyNode|http://download.autodesk.com/us/maya/2011help/API/class_m_fn_dependency_node.html]]
{{{
import maya.OpenMaya as om

depNodeFunc = om.MFnDependencyNode()
node = depNodeFunc.create("transform") # MObject
}}}
Or if you don't care about the actual {{{MFnDependencyNode}}} class object, you can skip its storage:
{{{
node = MFnDependencyNode().create("transform")
}}}
As you can see from the example, the {{{MFnDependencyNode}}} class can easily create a physical Maya node (in this case called 'transform1' in the Outliner) via its {{{create()}}} method, which is returned as a {{{MObject}}}.  Furthermore, {{{MFnDependencyNode}}} is the superclass of many other {{{MFn}}} classes, so the {{{create()}}} method can be called from them as well.  For example, this is the class inheritance tree for the {{{MFnSkinCluster}}} class:
*{{{OpenMaya.MFnBase}}} (superclass)
**{{{OpenMaya.MFnDependencyNode}}}
***{{{OpenMayaAnim.MFnGeometryFilter}}}
****{{{OpenMayaAnim.MFnSkinCluster}}} (subclass)
So let's build a {{{skinCluster}}} Maya node:
{{{
import maya.OpenMayaAnim as oma

skinClusterFn = oma.MFnSkinCluster()
# Call the create() method from the MFnDependencyNode superclass.  Could
# just as easily make any other node type with this call, not just a skinCluster:
skinCluster = skinClusterFn.create("skinCluster") # MObject
}}}
----
It should be noted that creating nodes this way bypasses the undo queue.  If you want to have this be undoable, you'll have to manage it through a scripted plugin, see: [[API: undoing commands]].  This makes use of [[OpenMaya.MDagModifier|http://download.autodesk.com/us/maya/2010help/API/class_m_dag_modifier.html]] and its superclass [[OpenMaya.MDGModifier|http://download.autodesk.com/us/maya/2010help/API/class_m_d_g_modifier.html]].
----
In addition to above, {{{OpenMaya.MGlobal.addToModel(MObject)}}} can also be used.  However, I'm not sure (yet) how to make an {{{MObject}}} representing a node that doesn't actually already exist in the DG.
When dealing with node names, Maya commands like {{{ls}}} return back the //string// representation of the nodes.  Things can get complicated really quick when there are duplicate names in the scene, or when nodes get renamed:  You can no longer accurately reference them by their string name.

{{{MDagPath}}} objects instead store a pointer to where the node lives in memory:  Other than deleting the node, this location won't change, even if the name does.  Because of this, duplicate node names and node renaming issues suddenly go away.

The below functions will convert a list of string names to {{{MDagPath}}} objects, and convert a list of {{{MDagPath}}} objects back to strings.
{{{
import maya.cmds as mc
import maya.OpenMaya as om

def MDP(names):
"""
Will filter out any non-DAG objects.

Parameters:
names : list : List of string names to convert to MDagPath objects.

Return : list : List of MDagpPath objects.
"""
dag = mc.ls(names, dagObjects=True)
mdp = []
selList = om.MSelectionList()
for name in dag:
selList.clear()
mDagPath = om.MDagPath()
selList.getDagPath(0, mDagPath)
mdp.append(mDagPath)
return mdp

def STR(mdp):
"""
Parameters :
mdp : list : List of MDagPath objects.

Return : list : List of the coresponding full path string names.
"""
return [item.fullPathName() for item in mdp]
}}}
Picking the {{{persp}}} camera and it's shape {{{perspShape}}}:
{{{
dpaths = MDP(mc.ls(selection=True))
print dpaths
strs = STR(dpaths)
print strs

[<maya.OpenMaya.MDagPath; proxy of <Swig Object of type 'MDagPath *' at 0x0000000023318510> >, <maya.OpenMaya.MDagPath; proxy of <Swig Object of type 'MDagPath *' at 0x0000000023318570> >]
[u'|persp', u'|persp|perspShape']
}}}
Presuming you need to call to //mel// someplace in your scripted plugin (rather than the Python version of the command), the {{{maya.OpenMaya.MGlobal.executeCommand()}}} method will do the trick:
{{{
# Python code:
import maya.OpenMaya as om
om.MGlobal.executeCommand("cylinder")
}}}
That's the most simple way to do it.  There are also solutions for capturing the results:
{{{
import maya.OpenMaya as om

cmdResult = om.MCommandResult()
theResult = []

melCmd = 'string $b[] = polySphere;' display, undo = True, True om.MGlobal.executeCommand(melCmd, cmdResult, display, undo) cmdResult.getResult(theResult) print theResult }}} When executed, this is printed in the Script Editor: {{{ string$b[] = polySphere;
// Result: pSphere1 polySphere1 //
[u'pSphere1', u'polySphere1']
}}}

I wanted to find the centerpoint of a node using the API.  Below is one way to do it.  The only real wrinkle is that the API, as usual, returns the results as cm, and in my case, I'm working in inches (yuck).  So you have to do that conversion.

In a nutshell, I find the bounding box for the node, find the bounding box's center point, then convert that location into the proper linear units.
{{{
import maya.cmds as mc
import maya.OpenMaya as om

# Name of the node to query:
node = 'myFancyMesh'

# Get a MDagPath for our node:
selList = om.MSelectionList() # MSelectionList
mDagPath = om.MDagPath() # MDagPath
selList.getDagPath(0, mDagPath)

# Find the centerpoint:
dagNodeFunc = om.MFnDagNode(mDagPath) # MfnDagNode
boundingBox = dagNodeFunc.boundingBox() # MBoundingBox
centerPoint = boundingBox.center() # MPoint

# Convert from cm to current units:
center = []
unitType = om.MDistance.uiUnit()
for i in range(3):
distance = om.MDistance(centerPoint[i]) # MDistance, as cm
converted = distance.asUnits(unitType) # double, converted
center.append(converted)

print center
}}}
Little more work than the mel {{{listRelatives}}} command ;)
{{{
import maya.OpenMaya as om

# Presume dagPath is a MDagPath object
dagPathFunc = om.MFnDagNode(dagPath)
kids = []
for i in range(dagPathFunc.childCount()):
kids.append(dagPathFunc.child(i))

# kids is a list of MObjects, one for each child node.
}}}
Wow, so easy:
{{{
# Python code
import maya.OpenMaya as om
import maya.OpenMayaUI as omui

fileType = 'jpg'
imageFile = 'c:/temp/mayaScreen.%s'%fileType
mimage = om.MImage()
view = omui.M3dView.active3dView()
mimage.writeToFile(imageFile, fileType)
}}}
From the {{{MImage}}} docs, these image formats are supported for output:
*iff, sgi, pic, tif, als, gif, rla, jpg
Docs:
*[[MImage|http://download.autodesk.com/us/maya/2011help/API/class_m_image.html]]
API to the rescue.  Amazingly easy.

Example module that will execute a function ({{{elapsedTimeFunc()}}}) based on a set amount of time in seconds in Maya.  Modify the contents of {{{elapsedTimeFunc()}}} (but not it's parameter signature) however you'd like.  The argument to {{{clientData}}} can be anything you want:  It could even be another function that is executed inside of {{{elapsedTimeFunc()}}}.
{{{
# timeCallback.py
import maya.OpenMaya as om

callbackId = None

def elapsedTimeFunc(elapsedTime, lastTime, clientData):
print "\nElaspsed time:", elapsedTime
print "Last Time:", lastTime
print "Client Data:", clientData

def makeTimeCallback(seconds, clientData):
global callbackId

def removeTimerCallback():
global callbackId
if not callbackId:
return
om.MMessage.removeCallback(callbackId)
callbackId.disown()
callbackId = None
}}}
To execute it in another module:
{{{
import timeCallback
timeCallback.makeTimeCallback(5, "some data")
}}}
Then, every five seconds, it will print:
{{{
Elaspsed time: 0.00076920655556
Last Time: 4.1576094601e+21
Client Data: some data

Elaspsed time: 5.00000333786
Last Time: 0.00532985245809
Client Data: some data

Elaspsed time: 5.00000476837
Last Time: 0.00644026743248
Client Data: some data

etc...
}}}
Finally, to remove the callback cleanly:
{{{
import timeCallback
timeCallback.removeTimerCallback()
}}}
You could very easily make a auto-save tool based on a certain time interval.  Or do some really annoying stuff to your coworkers ;)
Example:  Create a physical Maya {{{skinCluster}}} node (returned as {{{MObject}}} in script), find it's {{{maxInfluence}}} attribute as an {{{MPlug}}}, and then set the plug value
{{{
import maya.OpenMaya as om
import maya.OpenMayaAnim as oma

influenceCount = 4
skinClusterFn = oma.MFnSkinCluster() # MFnSkinCluster
skinCluster = skinClusterFn.create("skinCluster") # MObject
plug = om.MFnDependencyNode(skinCluster).findPlug('maxInfluences') # MPlug
plug.setInt(influenceCount)
}}}
Docs:
----
Also see:
*[[API : Querying Array Attributes]]
Based on the current selection, print a list of which function sets are available to those ~MObjects.

In this example, I set all variable names to be the type of object they are, to make it easier to understand how objects are returned and used. I don't recommend doing this in regular code, just using it as an explanation tool
{{{
# Python code
import maya.OpenMaya as om

# Make a MSelectionList container object:
MSelectionList = om.MSelectionList()
# Fill with selection, as MObjects
om.MGlobal.getActiveSelectionList(MSelectionList)
# Create an iterator to loop over our selection:
MItSelectionList = om.MItSelectionList(MSelectionList)

# Start looping over each node:
while not MItSelectionList.isDone():
# get the name of our node from the MItSelectionList object:
MDagPath = om.MDagPath()
MItSelectionList.getDagPath(MDagPath)
name = MDagPath.fullPathName()

# Define the current MObject being worked on:
MObject = om.MObject()
MItSelectionList.getDependNode(MObject)
# get Function Sets:
funcSets = []
om.MGlobal.getFunctionSetList(MObject, funcSets)

# Print results:
print "--Name:", name
print "\tfuncSets:", funcSets

# Advance to the next node in our iterator:
MItSelectionList.next()
}}}
Prints:
{{{
--Name: |side|sideShape
funcSets: [u'kBase', u'kNamedObject', u'kDependencyNode', u'kDagNode', u'kShape', u'kCamera']
--Name: |pSphere1|pSphereShape1
funcSets: [u'kBase', u'kNamedObject', u'kDependencyNode', u'kDagNode', u'kShape', u'kGeometric', u'kSurface', u'kMesh']
}}}

When you interactively execute //mel// code in the Script Editor, you'll see a result line print what you've done.  For example, if you execute this //mel// code in the Script Editor:
{{{
float $aa = 23; }}} You'll see it print this above: {{{ float$aa = 23;
// Result: 23 //
}}}
And if you execute this //Python// code in the script editor:
{{{
aa = 23
}}}
All you'll see above is the same line repeated, no {{{// Result:}}}.
This same issue is present when evaluating mel code via Python via {{{maya.mel.eval}}}, you //still// won't see the {{{// Result:}}} print line.

But what if you //really// wanted to?

While doing some random API code browsing I found a way to do it. And after looking at it some more, I realized it could be done a lot more simplistically, so now there are two versions below, 'new' and 'old'.  New just prints the results.  The old method tries to return the results as well.
!!!New Solution
{{{
import maya.OpenMaya as om

melCmd = 'string $stuffx = "asdf"' display, undo = True, True om.MGlobal.executeCommand(melCmd, display, undo) }}} When executed prints in the Script Editor: {{{ // Result: asdf // }}} ---- If you wanted to go one step further and capture the return in the process: {{{ import maya.OpenMaya as om cmdResult = om.MCommandResult() theResult = [] melCmd = 'string$b[] = polySphere;'
display, undo = True, True
om.MGlobal.executeCommand(melCmd, cmdResult, display, undo)
cmdResult.getResult(theResult)
print theResult
}}}
{{{
// Result: pSphere1 polySphere1 //
[u'pSphere1', u'polySphere1']
}}}
!!!Old Solution
It's a bit lacking in some areas:  Won't print\return results from string, string arrays, matrix, and matrix arrays, but otherwise it works.  It will evaluate the passed in mel command, and then provide the standard {{{// Result:}}} line when complete, plus return (where available) the result.
{{{
import maya.OpenMaya as om

def executePrintMelResult(melCmd):
result = om.MCommandResult()
om.MGlobal.executeCommand(melCmd, result)
rType = result.resultType()

val = None

if rType == 1:
# kInt
util = om.MScriptUtil()
util.createFromInt(0)
pointer = util.asIntPtr()
result.getResult(pointer)
val = util.getInt(pointer)
print "// Result: %s //"%val

if rType == 2:
# kIntArray
intArray = om.MIntArray()
result.getResult(intArray)
val = []
for i in range(intArray.length()):
val.append(intArray[i])
print "// Result: %s //"%val

if rType == 3:
# kDouble
util = om.MScriptUtil()
util.createFromDouble(0.0)
pointer = util.asDoublePtr()
result.getResult(pointer)
val = util.getDouble(pointer)
print "// Result: %s //"%val

if rType == 4:
# kDoubleArray
doubleArray = om.MDoubleArray()
result.getResult(doubleArray)
val = []
for i in range(doubleArray.length()):
val.append(doubleArray[i])
print "// Result: %s //"%val

if rType == 5 or rType == 6:
# kString, kStringArray
# Not supported in Python :(
pass

if rType == 7:
# kVector
vector = om.MVector()
result.getResult(vector)
val = vector
print "// Result: <<%s %s %s>> //"%(val.x, val.y, val.z)

if rType == 8:
# kVectorArray
vectorA = om.MVectorArray()
result.getResult(vectorA)
val = vectorA
# Maya only prints the first item in the array:
print "// Result: <<%s %s %s>> //"%(val[0].x, val[0].y, val[0].z)

if rType == 9 or rType == 10:
# kMatrix or kMatrixArray
# Result gathring not supported by MCommandResult()
pass

return val
}}}
If you executed this:
{{{
melCmd = 'float $myVal = 5.5' result = executePrintMelResult(melCmd) }}} In the Script Editor you'll see it print: {{{ // Result: 5.5 // }}} YES! :-P Inside the {{{compute()}}} method of a {{{MPxNode}}} in a scripted plugin, I wanted a way to track down the {{{MObject}}} for the node at the other end of an incoming connection. Maybe there is a better way to do this, but the solution I came up with below. The code says : If the output attr needs to be computed, then track down the node feeding some (unrelated, in this case) input attr. {{{ import maya.OpenMaya as om import maya.OpenMayaMPx as ompx class MyNode(ompx.MPxNode): someOutAttr = om.MObject() someInAttr = om.MObject() def compute(self, plug, data): if plug == MyNode.someOutAttr: # Get an function set for this plugin node: thisNodeFn = om.MFnDependencyNode(self.thisMObject()) # Get an MPlug for the input attr: thisPlug = thisNodeFn.findPlug(MyNode.someInAttr) # Fill an array with MPlug inputs to this attr: plugArray = om.MPlugArray() thisPlug.connectedTo(plugArray, True, False) # Get the node (as a MObject) connected to the MPlug for the input object: inputNode = plugArray[0].node() # And print its name, for confirmation: mFnDependNode = om.MFnDependencyNode(inputNode) nodeName = mFnDependNode.name() print nodeName # The rest of the plugin code.... }}} ---- Also see: *[[API: Querying input and output attrs on nodes and deformers]] The API makes use of a [[MComputation|http://download.autodesk.com/us/maya/2010help/API/class_m_computation.html]] class that allows for the detection of user input (the {{{Esc}}} key), and will cancel it's operation when detected. Here's a really simple example of it in use: {{{ import maya.OpenMaya as om def someOperation(i): print i def doCompute(someFunc, loopRange): """ Parameters: someFunc : A callable function object, taking a single arg for the current loop number. loopRange : int : How many loops to perform. """ computation = om.MComputation() computation.beginComputation() for i in range(loopRange): someFunc(i) if computation.isInterruptRequested(): print "Operation cancled by user." break computation.endComputation() doCompute(someOperation, 10000) }}} After execution, if Maya is given focus and the user presses the {{{Esc}}} key, the computation will quit. This is similar to the behavior of the [[progressWindow|http://download.autodesk.com/us/maya/2010help/CommandsPython/progressWindow.html]] //command//, but without the gui, and executed via the API. This isn't an actual solution, but I posted this question to the forums, and you can find the replies here: https://groups.google.com/forum/?fromgroups=#!topic/python_inside_maya/QK2N0QYYXWI  If you're doing any work via the API, you need to know about these two objects. This is my own personal description: >{{{MObject}}} is a Lego-brick. {{{MDagPath}}} tells you about where that brick lives in the model and what bricks effect it. Online Docs: *[[MObject|http://download.autodesk.com/us/maya/2011help/API/class_m_object.html]] *[[MDagPath|http://download.autodesk.com/us/maya/2011help/API/class_m_dag_path.html]] !!!Difference between {{{MObject}}}s and {{{MDagPath}}}s: ''My personal notes:'' (when I say 'object', I'm referring to a Python class-based instance. When I say 'node', I'm referring to an actual node in Maya). *A {{{MObject}}} is an object that describes a specific instance of a node\attribute in Maya. In non-API related mel scripting, you define a node by it's //string name// in a variable. If the name of the node changes, your code will no longer be able to work on it since the variable name is old. If on the other hand you were working with the {{{MObject}}} for a node directly, the name is but one of many things that describe it: Change the name, and you code will still continue to work on the same node because it's looking directly at the object that describes the node, not just its name. *A {{{MDagPath}}} is an object that describes the path to where in the DAG hierarchy a {{{MObject}}} lives, presuming the {{{MObject}}} is a DAG node. An {{{MDagPath}}} is //not// 'the DAG //node//': It represents the path to the DAG //object//. Picture a bunch of balloons tied together in a huge bunch, with some balloons tied to other balloons in chains, while other balloons have no ropes and are floating around independent from the bunch: A {{{MObject}}} is any balloon (a DG node), and the balloons tied up represent DAG nodes (a subset of the DG). {{{MDagPath}}}s represent the ropes (hierarchy path) connecting a balloon all the way back to the base of the bunch. The non-connected balloons (A non-DAG node, like a {{{set}}} or material, for example) has no rope, so it couldn't have a {{{MDagPath}}} associated with it, since it isn't part of the DAG hierarchy. Another way to think about it: In Windows Explorer (Mac Finder, etc), all files have a path to them, showing where they live in the directory hierarchy. A {{{MObject}}} represents the file, and the {{{MDagPath}}} the path to the file. Basically, every node in Maya can be expressed as a {{{MObject}}}, but only those nodes in the DAG (nodes that can be parented) can have a {{{MDagPath}}} associated with them. ---- (From the {{{MFnDagNode}}} class [[reference|http://download.autodesk.com/us/maya/2011help/API/class_m_fn_dag_node.html]]) There are two ways to specify a DAG node in Maya. #The first is to use an {{{MObject}}} handle, which acts as a pointer to a specific node in the DAG. Given only an {{{MObject}}}, ''it is not possible to do world space operations on a DAG node because there may be more than one path through the DAG to any given node (due to instancing)''. In other words, it is not possible to identify a particular //instance// only given an {{{MObject}}}. #In many cases it is preferable to use a DAG path ({{{MDagPath}}}) to specify a //DAG// node (rather than a //DG// node. Remember, DAG nodes have parent\child hierarchies, DG nodes don't). ''A DAG path always refers to a specific instance of an object''. This makes it possible to perform unambiguous world space transformations. So a {{{MDagPath}}} can find the {{{MObject}}} associated with it, but not easily vice-versa, since if an {{{MObject}}} has been instanced, it can have multiple {{{MDagPath}}}s. ---- ''{{{MObject}}} description (from its docs):'' <<< ~MObject is the generic class for accessing all Maya internal modelling, animation and rendering Objects, collectively referred to as Model Objects, through the API. This includes ''all Dependency Graph (DG) Nodes, of which Directed Acyclic Graph (DAG) Nodes'' are a subset. Each instance of an ~MObject represents a specific ''Node'' or ''Attribute on a Node'' in the DG. Under the C++ class scheme ~MObjects simply have type ~MObject with no base or derived hierarchy. However, ~MObjects may be many different types of Model objects, from Attributes to transforms, including components of geometry such as Control Vertices (CV), faces, edges and vertices. This allows many different types of objects to be moved accessed through the API without undue concern on the part of plug-in developers for the type of object being manipulated. <<< ---- ''{{{MDagPath}}} description (from its docs):'' <<< A DAG path is a path from the world node to a particular object in the DAG. <<< ---- *{{{MObject}}} objects can be assigned to the [[MFnDependencyNode|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_dependency_node.html]] function set. **You can use {{{OpenMaya.MGlobal.getFunctionSetList(MObject)}}} to get a list (of strings) representing which function sets can be used on an {{{MObject}}}. *{{{MDagPath}}} objects can be assigned to the [[MFnDagNode|http://download.autodesk.com/us/maya/2011help/API/class_m_fn_dag_node.html]] function set (which inherits from the {{{MFnDependencyNode}}}). ---- !Get {{{MObject}}} & {{{MDagPath}}} by string name: There are a variety of ways to get {{{MObject}}}s and {{{MDagPath}}}s via the return of API calls. But if you need to generate one based on a given object name in Maya, here's how you can go about doing it: {{{ # Python code import maya.OpenMaya as om # Define our node by name. # Make sure to pass in the full path to avoid any name-clashing: node = 'group1|pCube1' # Create a selection list (they hold MObjects): selList = om.MSelectionList() # Add our node by-name to the sel list. If there is a name-clash, # this will raise a RuntimeError: selList.add(node) }}} Now that we have a populated {{{MSelectionList}}} you can extract data from it. Here we get an {{{MDagPath}}}: {{{ # Create an empty MDagPath object: mDagPath = om.MDagPath() # Extract the first item from our MSelectionList as an MDagPath: selList.getDagPath(0, mDagPath) }}} Here we get an {{{MObject}}}: {{{ # Create an empty MObject object: mObject = om.MObject() # Extract the first item from our MSelectionList as an MObject: selList.getDependNode(0, mObject) }}} !Get {{{MObject}}} from {{{MDagPath}}}: {{{ mObject = mDagPath.node() }}} !Get {{{MDagPath}}} from {{{MObject}}}: Let's go the other way now. A {{{MDagPath}}} can easily find it's {{{MObject}}} (shown above). But to go the other direction requires a bit more work. Continuing from the example above... {{{ dPath = None if om.MObject.hasFn(mObject, om.MFn.kDagNode): dPath = om.MDagPath() om.MDagPath.getAPathTo(mObject, dPath) }}} What's going on? We test to see if our {{{mObject}}} has the {{{kDagNode}}} function. If it does, that means that it //is/// a DAG node, thus has a path we can query. We then make an empty {{{MDagPath}}} object, and fill it with the path to the {{{MObject}}}. It should be noted that if the {{{MObject}}} is an instance, this method will return the first path found. You can use {{{MDagPath.getAllPathsTo()}}} to find all instances. !Get a nodes string name from a {{{MObject}}} or {{{MDagPath}}}: Going the other way again, once you have a {{{MObject}}} or {{{MDagPath}}}, how can you query the string name of the Maya node they wrapper? !!!{{{MObject}}}: Here we create a {{{MFnDependencyNode}}}, and can pull the name from that: {{{ mFnDependNode = om.MFnDependencyNode(mObject) nodeName = mFnDependNode.name() }}} !!!{{{MDagPath}}}: The {{{MDagPath}}} can access the name directly: {{{ nodeName = mDagPath.fullPathName() }}} You can also extract it from a {{{MFnDagNode}}} function set: {{{ mFnDagNode = om.MFnDagNode(mDagPath) nodeName = mFnDagNode.fullPathName() }}}  A really good blog post covers their creation and usage here: http://around-the-corner.typepad.com/adn/2012/10/maya-custom-manipulators-.html All are derived from {{{OpenMayaUI}}}: *[[MFnManip3D|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_manip3_d.html]] (inherits from {{{OpenMaya.MFnTransform}}}). **[[MFnCircleSweepManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_free_point_triad_manip.html]] **[[MFnCurveSegmentManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_curve_segment_manip.html]] **[[MFnDirectionManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_direction_manip.html]] **[[MFnDiscManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_disc_manip.html]] **[[MFnDistanceManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_distance_manip.html]] **[[MFnFreePointTriadManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_free_point_triad_manip.html]] **[[MFnPointOnCurveManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_point_on_curve_manip.html]] **[[MFnPointOnSurfaceManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_point_on_surface_manip.html]] **[[MFnRotateManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_rotate_manip.html]] **[[MFnScaleManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_scale_manip.html]] **[[MFnStateManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_state_manip.html]] **[[MFnToggleManip|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_fn_toggle_manip.html]] Derived from {{{OpenMayaMPx}}}: *[[MPxManipulatorNode|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_px_manipulator_node.html]] (inherits from {{{OpenMayaMPx.MPxNode}}}) The {{{OpenMayaRender}}} package via the the [[MHardwareRenderer|http://download.autodesk.com/us/maya/2011help/API/class_m_hardware_renderer.html]] class provides access to a [[MGLFunctionTable|http://download.autodesk.com/us/maya/2010help/api/class_m_g_l_function_table.html]] class. It is a "utility class which provides wrappers for the basic functions in the ~OpenGL API". As of Maya 2011, it supports up to ~OpenGL 2.0. ~OpenGL is on a more current version than Maya uses. Legacy docs to 2.1 can be found [[here|http://www.opengl.org/sdk/docs/man/]] These calls can be used (among many other places) in the {{{draw()}}} method of the [[MPxLocatorNode|http://download.autodesk.com/us/maya/2010help/api/class_m_px_locator_node.html]], see [[API: Simple scripted plugin locator node]]. Example: {{{ import maya.OpenMayaRender as omr renderer = omr.MHardwareRenderer.theRenderer() # returns MHardwareRenderer glFT = renderer.glFunctionTable() # returns MGLFunctionTable for d in sorted(dir(glFT)): print d }}} This will print a lot of stuff: {{{ ... glProgramParameters4dvNV glProgramParameters4fvNV glProgramString glPushAttrib glPushClientAttrib glPushMatrix glPushName glRasterPos2d glRasterPos2dv glRasterPos2f glRasterPos2fv ... }}} Should note there are no docs for the static (class) method {{{theRenderer()}}} in the {{{MHardwareRenderer}}} docs, weird. ---- ''In addition'', the {{{OpenMayaRender}}} module itself has many ~OpenGL related constants you can access. Which, like the above docs say, are prefixed with a '{{{M}}}'. So if you were looking for the ~OpenGL constant {{{GL_LIGHTING}}}, you would find its value in {{{ OpenMayaRender.MGL_LIGHTING }}} They can be used like so (from a scripted plugin example): {{{ glFT.glPushAttrib(omr.MGL_CURRENT_BIT) }}} Examples using [[OpenMaya.MItDag|http://download.autodesk.com/us/maya/2011help/API/class_m_it_dag.html]] The {{{MItDag}}} class will iterate over all nodes in the DAG (directed, acyclic graph), which is a subset of the DG (dependency graph). DAG nodes have a transform|shape relationship. The below example runs most of the methods on the class, exposing the values to be printed. {{{ import maya.OpenMaya as om def example_MItDag(): # This iterator can be filtered by node type as well, via MFn.k* types dagIterator = om.MItDag() # MItDag print "MItDag iteration info:" while not dagIterator.isDone(): #---------------------------------------------------- # Querying info specifically on the MItDag node: currentItem = dagIterator.currentItem() # MObject root = dagIterator.root() # MObject depth = dagIterator.depth() # int path = om.MDagPath() # MDagPath dagIterator.getPath(path) pathArray = om.MDagPathArray() # MDagPathArray dagIterator.getAllPaths(pathArray) fullPathName = dagIterator.fullPathName() # string partialPathName = dagIterator.partialPathName() # string isInstanced = dagIterator.isInstanced() # bool instances = 0 instanceCount = dagIterator.instanceCount(instances) # unsigned int willTransverseUnderWorld = dagIterator.willTraverseUnderWorld() # bool # Let's get the function sets for this item as well, which basically tell # us what this thing is: funcSets = [] om.MGlobal.getFunctionSetList(currentItem, funcSets) #---------------------------------------------------- # Print info: currentItemFunc = om.MFnDependencyNode(currentItem) rootFunc = om.MFnDependencyNode(root) pathFunc = om.MFnDagNode(path) print "\t", currentItemFunc.name(), " : Current Item (MFnDependencyNode.name())" print "\t\tRoot (MFnDependencyNode.name()):", rootFunc.name() print "\t\tDepth (int):", depth print "\t\tPath (MFnDagNode.fullPathName()):", pathFunc.fullPathName() print "\t\tPath Array Items (MDagPath.fullPathName()):" for i in range(pathArray.length()): arrayItem = pathArray[i] # MDagPath arrayItemFunc = om.MFnDagNode(arrayItem) print "\t\t\t", arrayItemFunc.fullPathName() print "\t\tFull Path Name (string):", fullPathName print "\t\tPartial Path Name (string):", partialPathName print "\t\tIs Instanced? (bool):", isInstanced print "\t\tInstances: (int):", instances print "\t\tWill Transverse Underworld? (bool):", willTransverseUnderWorld print "\t\t'Current Item' function sets (MGlobal.getFunctionSetList()):" for fset in funcSets: print "\t\t\t", fset dagIterator.next() }}} Running it prints: {{{ example_MItDag() MItDag iteration info: world : Current Item (MFnDependencyNode.name()) Root (MFnDependencyNode.name()): world Depth (int): 0 Path (MFnDagNode.fullPathName()): Path Array Items (MFnDagNode.fullPathName()): Full Path Name (string): Partial Path Name (string): Is Instanced? (bool): False Instances: (int): 0 Will Transverse Underworld? (bool): False Function Sets (MGlobal.getFunctionSetList()): kBase kNamedObject kDependencyNode kDagNode kWorld groundPlane_transform : Current Item (MFnDependencyNode.name()) Root (MFnDependencyNode.name()): world Depth (int): 1 Path (MFnDagNode.fullPathName()): |groundPlane_transform Path Array Items (MFnDagNode.fullPathName()): |groundPlane_transform Full Path Name (string): |groundPlane_transform Partial Path Name (string): groundPlane_transform Is Instanced? (bool): False Instances: (int): 0 Will Transverse Underworld? (bool): False Function Sets (MGlobal.getFunctionSetList()): kBase kNamedObject kDependencyNode kDagNode kTransform etc... }}}  Examples using [[OpenMaya.MItDependencyGraph|http://download.autodesk.com/us/maya/2010help/API/class_m_it_dependency_graph.html]] The {{{MItDependencyGraph}}} is used for transversing connections to a node. The below example will print every node connected to the incoming side of the given 'nodeName': {{{ import maya.OpenMaya as om def example_MItDependencyGraph(): # Convert a node name to a MObject, to pass to our MItDependencyGraph class: nodeName = 'myAwesomeNode' selList = om.MSelectionList() selList.add(nodeName) node = om.MObject() selList.getDependNode(0, node) # Iterate through all incoming connections to our node: dependGraphIter = om.MItDependencyGraph(node, om.MItDependencyGraph.kUpstream, om.MItDependencyGraph.kPlugLevel) print "%s: All Incoming Connected Nodes"%nodeName while not dependGraphIter.isDone(): currentItem = dependGraphIter.currentItem() # MObject currentItemFunc = om.MFnDependencyNode(currentItem) name = currentItemFunc.name() print "\t", name dependGraphIter.next() }}} Examples using [[OpenMaya.MItDependencyNodes|http://download.autodesk.com/us/maya/2010help/API/class_m_it_dependency_nodes.html]] The {{{MItDependencyNodes}}} class will iterate over all nodes in the DG (dependency graph), basically all nodes in the scene. The below example runs most of the methods on the class, exposing the values to be printed. {{{ import maya.OpenMaya as om def example_MItDependencyNodes(): # This list can be filtered by type as well, via MFn.k* types # Let's only look for 'partition' nodes: iterFilter = om.MFn.kPartition dependIterator = om.MItDependencyNodes(iterFilter) # MItDependencyNodes print "MItDependencyNodes iteration info:\n" while not dependIterator.isDone(): #---------------------------------------------------- # Querying info specifically on the MItDag node: # Really, this is all it has to query: thisNode = dependIterator.thisNode() # MObject # Let's get the function sets for this item as well, which basically tell # us what this thing is: funcSets = [] om.MGlobal.getFunctionSetList(thisNode, funcSets) #---------------------------------------------------- # Print info: thisNodeFunc = om.MFnDependencyNode(thisNode) print "\t", thisNodeFunc.name(), " : This Node (MObject.name())" print "\t\t'This Node' function sets (MGlobal.getFunctionSetList()):" for fset in funcSets: print "\t\t\t", fset dependIterator.next() }}} Running it prints: {{{ example_MItDependencyNodes() MItDependencyNodes iteration info: renderPartition : This Node (MObject.name()) 'This Node' function sets (MGlobal.getFunctionSetList()): kBase kNamedObject kDependencyNode kPartition characterPartition : This Node (MObject.name()) 'This Node' function sets (MGlobal.getFunctionSetList()): kBase kNamedObject kDependencyNode kPartition }}} Short version: As of Maya 2010, this is not doable. The {{{MSyntax}}} class doesn't support the passing in of array items, and the {{{makeFlagMultiUse}}} method requires you to pass in your args like: {{{ // mel myCmd -arg "a" -arg "b" -arg "c"; }}} rather than an actual array: {{{ // mel myCmd -arg {"a","b","c"}; }}} This is further compounded that (as of this authoring) it seems like Python has problems querying multi-use flags. You can't pass the same parameter name over and over in a Python function: {{{ # Invalid Python syntax: myCmd(arg="a", arg="b", arg="c") }}} So you have to pass in multi-flag data as a //list// (array): {{{ # Python myCmd(arg=["a","b","c"]) }}} But it doesn't seem to work in my own scripted plugins, and other blog posts confirm this :( However, standard Maya commands //do work// this way via Python, but I'm guessing it's because they're authored via c++, rather than Python. Even crazier is that if I execute my scripted plugin command via Python and it fails when querying the mult-use arguments, calling to it from mel (per the top example) //does// work. Buuuuugy. *[[MSyntax|http://download.autodesk.com/us/maya/2010help/API/class_m_syntax.html]] docs *[[Forum Posts|http://groups.google.com/group/python_inside_maya/search?group=python_inside_maya&q=MSyntax+array&qt_g=Search+this+group]] Another thread here discusses it pretty well: http://forums.cgsociety.org/archive/index.php/t-701733.html It appears that the main hack is to pass in a single string you can then later split into the array items of your choice, which is //terrible//... From that form post: ''"MEL commands //don't// have the ability to accept a string array as a single flag argument."'', which seems to be true based on experimentation. ---- !However The giant however: You can pass a list of object names (or a single name) as strings into a command, as the first argument. As it turns out, these names are arbitrary: They don't necessarily have to match any objects in the scene. Code snippets to set this up: {{{ # Inside your scripted plugin class... def doIt(self, argList): argData = om.MArgDatabase(self.syntax(), argList) strings = [] argData.getObjects(strings) if len(strings): print '\tPassed-in string arguments: "%s"'%'", "'.join(strings) # ..... @staticmethod def newSyntax(): syntax = om.MSyntax() syntax.setObjectType(om.MSyntax.kStringObjects) # ..... }}} {{{ # Then calling to the command via Python: myCmd(["stringA", "stringB"]) }}} {{{ // Via mel: myCmd "stringA" "stringB"; }}} As you can see, this allows you to pass an actual list in via Python. Trying to pass these values as a string array in mel causes an error. I have an example of this in action here: [[API: Simple scripted plugin argument passing]] When authoring custom nodes or deformers, you'll sometime want access to their built-in (default, Maya authored) attributes. For example, let's say you're making a deformer, and you want access to the {{{envelope}}} attribute inside the {{{deform()}}} method. How do you get access to that? Maya has provided a magical, semi-secret location for storing those attributes inside the {{{OpenMayaMPx.cvar}}} '{{{swigvarlink}}}' datatype. It appears to have {{{MObject}}} representations of all the standard attrs one would need to access. You extract them via the syntax "<Node Type>_<Attribute Name>". For example, if you wanted to access the '{{{.envelope}}}' attr on a given deformer, you'd access it via '{{{OpenMayaMPx.cvar.MPxDeformerNode_envelope}}}'. I've found two spots in the Maya docs that talk about them: <<< [[Example: Bounding Box Deformer|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/files/GUID-10CE99A6-2C32-49E1-85ED-2E2F6782CF23.htm]] "Built-in Deformer Node Attributes - By inheriting from {{{MPxDeformerNode}}}, your class will have access to the input and output mesh attributes, available via {{{OpenMayaMPx.cvar.MPxDeformerNode_inputGeom}}} and {{{OpenMayaMPx.cvar.MPxDeformerNode_outputGeom}}} respectively. These variables are generated by the SWIG tool during the C++ header file conversion to Python. By the same process, your deformer also has access to the built-in "envelope" attribute, available via {{{OpenMayaMPx.cvar.MPxDeformerNode_envelope}}}. This envelope attribute is used to specify the amount of influence your deformer has on the mesh." <<< <<< [[Using the Maya Python API|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/files/Maya_Python_API_Using_the_Maya_Python_API.htm]] ''Accessing static {{{MObjects}}} of an {{{MPx}}} class'' The proxy classes provide some standard information to a developer about the node that is being used. This includes attribute objects that are used to define the node. To access a static class {{{MObject}}} in the Maya Python API, similar code can be used: {{{ envelope = OpenMayaMPx.cvar.MPxDeformerNode_envelope }}} After making this call, the envelope will be an {{{MObject}}} for {{{MPxDeformerNode::envelope}}}. <<< If you wanted to print a list, you'd have to jump through some hoops since the {{{swigvarlink}}} object doesn't support slicing or index access... but you //can// print it (looks like a {{{tuple}}}): {{{ import maya.OpenMayaMPx as ompx items = [item.strip() for item in str(ompx.cvar)[1:-1].split(',')] for item in sorted(items): print item }}} {{{ MPxCameraSet_active MPxCameraSet_camera MPxCameraSet_cameraLayer MPxCameraSet_order ... }}} With a bit more work on the above code, you can get a list of the supported node types: {{{ MPxCameraSet MPxConstraint MPxDeformerNode MPxEmitterNode MPxFieldNode MPxFluidEmitterNode MPxHardwareShader MPxHwShaderNode MPxImagePlane MPxLocatorNode MPxManipulatorNode MPxNode MPxObjectSet MPxParticleAttributeMapperNode MPxSpringNode MPxSurfaceShape MPxTransform MPxTransformationMatrix }}} So given all that, here are some use cases: {{{ import maya.OpenMayaMPx as ompx }}} ---- Inside the {{{deform()}}} method of a {{{OpenMayaMPx.MPxDeformerNode}}}, you need to query the value of the (standard, built-in) {{{envelope}}} attribute: {{{ envelope_attr = ompx.cvar.MPxDeformerNode_envelope envelopeHandle = dataBlock.inputValue(envelope_attr) # MDataHandle envelopeValue = envelopeHandle.asFloat() }}} ---- In the {{{nodeInitializer()}}} function of a {{{OpenMayaMPx.MPxDeformerNode}}}, you need to setup an {{{attributeAffects}}} relationship between a custom '{{{deform}}}' attribute, and the (standard, built-in) {{{outputGeometry}}} attr on the node: {{{ outputGeom = ompx.cvar.MPxDeformerNode_outputGeom MyDeformer.attributeAffects(MyDeformer.attr_deform, outputGeom) }}} ---- Inside the {{{compute()}}} method of a {{{OpenMayaMPx.MPxNode}}}, you want to query the (standard, built-in) {{{caching}}} attr of the node: {{{ caching_attr = ompx.cvar.MPxNode_caching cachingHandle = dataBlock.inputValue(caching_attr) cachingValue = envelopeHandle.asBool() }}} ---- ---- Also see: *[[API: How can a custom node query a connected object in the compute method?]] This is an expansion on my [[API: Simple scripted plugin command]] example. It shows various ways to capture flag argument data as passed into a scripted-plugin based command. It also exposes an obnoxious Python bug... The below example scripted plugin will create a command called {{{argExample}}}. The command doesn't do anything other than print the arguments passed to the various flags on the command. Furthermore, you can pass object names to it as the first positional argument, and if it finds none, will instead try to operate on what you have selected. {{{ # spArgExampleCmd.py import maya.OpenMaya as om import maya.OpenMayaMPx as ompx # ----------------------------------- # Plugin code class ArgExample(ompx.MPxCommand): """ http://download.autodesk.com/us/maya/2010help/API/class_m_px_command.html """ # Name this command will use when executed via mel\Python: commandName = "argExample" # Setup our flags: flag_floatValue = '-fv'; flag_floatValueLong = '-floatValue' flag_stringValue = '-sv'; flag_stringValueLong = '-stringValue' flag_multiFloat = '-mf'; flag_multiFloatLong = '-multiFloat' flag_justFlag = '-jf'; flag_justFlagLong = '-justFlag' def __init__(self): """ Initalize our superclass: """ ompx.MPxCommand.__init__(self) def doIt(self, argList): """ Superclass override. Parameters : argList : MArgList : http://download.autodesk.com/us/maya/2010help/API/class_m_arg_list.html """ # Query what args, if any, were passed in via the MArgList. MArgDatabase # inherits from MArgParser: # http://download.autodesk.com/us/maya/2010help/API/class_m_arg_parser.html # http://download.autodesk.com/us/maya/2010help/API/class_m_arg_database.html print "%s executed:" % self.commandName argData = om.MArgDatabase(self.syntax(), argList) # See what objects, if any, were passed into the command. It should be # noted that these are actually just arbitrary string names: They don't # necessarily need to match an existing node-name in the Maya scene. objects = [] argData.getObjects(objects) if len(objects): print '\tPassed-in object arguments: "%s"'%'", "'.join(objects) # If no objects were passed in, see if any are selected: else: # If nothing was passed in, see if anything is selected: selList = om.MSelectionList() om.MGlobal.getActiveSelectionList(selList) if selList.length(): print "\t%s objects are selected to work on." % selList.length() # Query the flag argument values: if argData.isFlagSet(ArgExample.flag_floatValue): index = 0 # flags can have multiple multiple arg indices, this only has one floatValue = argData.flagArgumentDouble(ArgExample.flag_floatValue, index) print "\t-floatValue", floatValue if argData.isFlagSet(ArgExample.flag_stringValue): index = 0 floatValue = argData.flagArgumentString(ArgExample.flag_stringValue, index) print "\t-stringValue", floatValue if argData.isFlagSet(ArgExample.flag_multiFloat): multiFloatVals = [] flagUses = argData.numberOfFlagUses(ArgExample.flag_multiFloat) print "\t-multiFloat : Used %s time(s)" % flagUses for i in range(flagUses): # Create a list to grab a single item from another list. Strange but true. flagArgList = om.MArgList() argData.getFlagArgumentList(ArgExample.flag_multiFloat, i, flagArgList) if flagArgList.length(): multiFloatVals.append(flagArgList.asDouble(0)) if multiFloatVals: for val in multiFloatVals: print "\t\t", val else: print "\t\tSorry, it's a known bug you can't query mult-arg values via Python :-(" print "\t\tTry executing this came code via mel, it will work :-S" if argData.isFlagSet(ArgExample.flag_justFlag): print "\t-justFlag : Is set!" #--------------------------------------------------- # Command & syntax creation: # 'Creator Function' needs to return a pointer new instance of this type of class, # based on how the c++ code works. # As you can see, the OpenMayaMPx module has it's own functions (for which # I can find no online documentation). @classmethod def cmdCreator(cls): return ompx.asMPxPtr(cls()) @staticmethod def newSyntax(): """ Returns an MSyntax that can be passed to the MFnPlugin inside the initializePlugin funcition. This syntax is based around the custom flags that the command can accept. Important: Maya's notes state that this function must have a name other than 'syntax()'. """ # http://download.autodesk.com/us/maya/2010help/API/class_m_syntax.html syntax = om.MSyntax() # Tell the syntax to grab any object-names passed in to be accepted as # strings (either a single string, or as a list of strings) rather than # MObjects. This is always the first (but optional) argument of the # command. If needed, you could specify a minimum number of object names # to be passed in as well. syntax.setObjectType(om.MSyntax.kStringObjects) # There isn't a constant for float, so we use double: syntax.addFlag(ArgExample.flag_floatValue, ArgExample.flag_floatValueLong, om.MSyntax.kDouble) # Our string flag: syntax.addFlag(ArgExample.flag_stringValue, ArgExample.flag_stringValueLong, om.MSyntax.kString) # Multi-use float flag: syntax.addFlag(ArgExample.flag_multiFloat, ArgExample.flag_multiFloatLong, om.MSyntax.kDouble) syntax.makeFlagMultiUse(ArgExample.flag_multiFloat) # Just a flag, takes no arguments: syntax.addFlag(ArgExample.flag_justFlag, ArgExample.flag_justFlagLong) return syntax # ----------------------------------- # Plugin registration\degregistration block # initializePlugin & uninitializePlugin are *required functions* def initializePlugin(mobject): # create a MFnPlugin mfnPlugin = ompx.MFnPlugin(mobject, "Eric Pavey", "1.0") try: # Try to register our plug by passing in the kPluginCmdName, 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. mfnPlugin.registerCommand(ArgExample.commandName, ArgExample.cmdCreator, ArgExample.newSyntax) except(Exception): om.MGlobal.displayError("\nArgExample registration failed\n") def uninitializePlugin(mobject): # create a MFnPlugin mfnPlugin = ompx.MFnPlugin(mobject) try: # Try to deregister our plug by passing in the kPluginCmdName, and the command creator: mfnPlugin.deregisterCommand(ArgExample.commandName) except(Exception): om.MGlobal.displayError("\nArgExample deregistration failed\n") }}} After this is loaded in the plugin manager (see [[How can I load \ unload a Python scripted plugin?]]), you should be able to run the command. But you'll get different results if you execute it in mel, or Python: {{{ # Python code import maya.cmds as mc mc.argExample(["spam", "eggs"], floatValue=23, stringValue="chocolate", multiFloat=[6,2,3], justFlag=True) }}} {{{ argExample executed: Passed-in object arguments: "spam", "eggs" -floatValue 23.0 -stringValue chocolate -multiFloat : Used 3 time(s) Sorry, it's a known bug you can't query mult-arg values via Python :-( Try executing this came code via mel, it will work :-S -justFlag : Is set! }}} As you can see, Python can't seem to handle the mult-use flag :( But executing this in mel... {{{ // mel code argExample -floatValue 23 -stringValue "chocolate" -multiFloat 6 -multiFloat 2 -multiFloat 3 -justFlag "spam" "eggs"; }}} {{{ argExample executed: Passed-in object arguments: "spam", "eggs" -floatValue 23.0 -stringValue chocolate -multiFloat : Used 3 time(s) 6.0 2.0 3.0 -justFlag : Is set! }}} Lo and behold, the multi-use args show up! Buggy, buggy, buggy :( Example of authoring a simple scripted plugin //command//. It features value setting, querying, and undoing. This scripted plugin is authored slightly different from other 'simple scripted plugin' examples I've done: *The command name is a class attribute rather than a module constant. *The command creator and new syntax functions have been made class staticmethods, rather than being external functions. It behaves the exact same as other solutions, just a different way of doing it. Why would you want to do it this way, compared to the other alternatives I've presented? The main reason I've found is if you're authoring multiple scripted plugins //in the same module//: To help organize this special data, lumping it in with the class makes it easier to find and distinguish against the others. Here is another [[good, even more simple example|http://download.autodesk.com/us/maya/2011help/API/hello_world_cmd_8py-example.html]] from the Maya API docs. When it comes to creating a scripted plugin //command//, these are the major things required: * A class inheriting from {{{maya.OpenMayaMPx.MPxCommand}}}. **The name of this class is up to you, it is independent from the name of the node being created. The node name is actually defined in {{{initializePlugin()}}} **Must initialize the superclass in {{{__init__()}}} **Override the {{{doIt()}}} method to parse any given arguments, and setup states for undo\redo. **Override the {{{redoIt()}}} method that does the actual work of the command. **Override the {{{undo()}}} method to support undoing, which should reset the state before the command was executed. **Override the {{{isUndoable()}}} method to tell the command it actually //can// undo. *Author 'node creator' and 'new syntax' functions. The names are arbitrary, but {{{cmdCreator()}}} and {{{newSyntax()}}} are good ideas. These are both used by the {{{initializePlugin()}}} function. The syntax function creates a {{{MSyntax}}} object that parses all the incoming flags to the command. * Author {{{initializePlugin()}}} and {{{uninitializePlugin()}}} functions. Other than the overridden methods above, these are the only items in the code that need specific names. It should be noted that if you don't care about flags, or undoing, the only method you need to override is {{{doit()}}}, and you can skip making the syntax function. {{{ # spHelloWorldCmd.py import maya.OpenMaya as om import maya.OpenMayaMPx as ompx # ----------------------------------- # Plugin code class HelloWorldCommand(ompx.MPxCommand): """ This class will create a command that when executed, will print "Hello World" with a number next to it showing how many times the command has been executed. If you edit the command, you can pass in a new value, and if you query the command it will return the current value. http://download.autodesk.com/us/maya/2010help/API/class_m_px_command.html """ # Name this command will use when executed via mel\Python: commandName = "helloWorld" # This will store a value we can query, set, or print. value = 0 # Setup our flags: flag_value = '-v'; flag_valueLong = '-value' flag_help = "-h"; flag_helpLong = "-help" # Text displayed when the user queries its help: helpText = """ The helloWorld command does these things: * Running the command will print 'Hello World #' with the number of times it has been executed. * You can pass in a -value to set the current number. * If you -query it, it will return the current value. * Full undo/redo is supported. * And of course if you see this, you know it has -help...""" def __init__(self): """ Initalize our superclass: """ ompx.MPxCommand.__init__(self) # Is the command being queried or edited? self.isQuery = False self.isEdit = False def doIt(self, argList): """ Superclass override. This method is called from script when this command is called. It should set up any class data necessary for redo/undo, parse any given arguments, and then call redoIt. Parameters : argList : MArgList : http://download.autodesk.com/us/maya/2010help/API/class_m_arg_list.html """ # Query what args, if any, were passed in. MArgDatabase inherits from # MArgParser: # http://download.autodesk.com/us/maya/2010help/API/class_m_arg_parser.html # http://download.autodesk.com/us/maya/2010help/API/class_m_arg_database.html # The syntax being queried is the one that was passed to the MFnPlugin # inside the initializePlugin() funcition. argData = om.MArgDatabase(self.syntax(), argList) # Are we in query mode? self.isQuery = argData.isQuery() # Support undoing by storing the previous state of the command: self.prevVal = HelloWorldCommand.value # If the user is querying help, display it, and reutrn out of the command. if argData.isFlagSet(HelloWorldCommand.flag_help): self.setResult(HelloWorldCommand.helpText) return om.MStatus.kSuccess # If the user passes in a value, and we're not in query mode, set the value: if argData.isFlagSet(HelloWorldCommand.flag_value) and not self.isQuery: index = 0 # flags can have multiple multiple arg indices, this only has one HelloWorldCommand.value = argData.flagArgumentInt(HelloWorldCommand.flag_value, index) # so, we're obviously editing it... self.isEdit = True return self.redoIt() def redoIt(self): """ Superclass override: Any actual work the command should do is done in here. """ # If we're in query mode, return the value: if self.isQuery: self.setResult(HelloWorldCommand.value) # Otherwise, if the command hasn't already been edited, execute the # command code, which is in this case as simple update value and print. # It could obviously be much more robust. elif not self.isEdit: HelloWorldCommand.value += 1 print "Hello World #%s" % HelloWorldCommand.value def undoIt(self): """ Superclass override: Reset the state if undone. """ HelloWorldCommand.value = self.prevVal def isUndoable(self): """ Superclass override: Enable undoing. """ return True #--------------------------------------------------- # Command & syntax creation: # 'Creator Function' needs to return a pointer new instance of this type of class, # based on how the c++ code works. # As you can see, the OpenMayaMPx module has it's own functions (for which # I can find no online documentation). @classmethod def cmdCreator(cls): return ompx.asMPxPtr(cls()) @staticmethod def newSyntax(): """ Returns an MSyntax that can be passed to the MFnPlugin inside the initializePlugin funcition. This syntax is based around the custom flags that the command can accept. Important: Maya's notes state that this function must have a name other than 'syntax()'. """ # http://download.autodesk.com/us/maya/2010help/API/class_m_syntax.html syntax = om.MSyntax() syntax.addFlag(HelloWorldCommand.flag_value, HelloWorldCommand.flag_valueLong, om.MSyntax.kLong) syntax.addFlag(HelloWorldCommand.flag_help, HelloWorldCommand.flag_helpLong) # Allow this syntax to be both queryable, and editable: syntax.enableQuery(True) syntax.enableEdit(True) return syntax # ----------------------------------- # Plugin registration\degregistration block # initializePlugin & uninitializePlugin are *required functions* def initializePlugin(mobject): # create a MFnPlugin mfnPlugin = ompx.MFnPlugin(mobject, "Eric Pavey", "1.0") try: # Try to register our plug by passing in the kPluginCmdName, 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. mfnPlugin.registerCommand(HelloWorldCommand.commandName, HelloWorldCommand.cmdCreator, HelloWorldCommand.newSyntax) except(Exception): om.MGlobal.displayError("\nHelloWorldCommand registration failed\n") def uninitializePlugin(mobject): # create a MFnPlugin mfnPlugin = ompx.MFnPlugin(mobject) try: # Try to deregister our plug by passing in the kPluginCmdName, and the command creator: mfnPlugin.deregisterCommand(HelloWorldCommand.commandName) except(Exception): om.MGlobal.displayError("\nHelloWorldCommand deregistration failed\n") }}} After this is loaded in the plugin manager (see [[How can I load \ unload a Python scripted plugin?]]), you should be able to type in the script editor (works in mel too): {{{ import maya.cmds as mc mc.helloWorld() # Hello World #1 mc.helloWorld() # Hello World #2 }}} {{{ mc.helloWorld(value=23) v = mc.helloWorld(query=True) print v # 23 mc.helloWorld() # Hello World #24 }}} {{{ mc.helloWorld(help=True) # Result: The helloWorld command does these things: * Running the command will print 'Hello World #' with the number of times it has been executed. * You can pass in a -value to set the current number. * If you -query it, it will return the current value. * Full undo/redo is supported. * And of course if you see this, you know it has -help... # }}}  I wanted to build a bare-minimum framework for authoring a deformer node via a scripted plugin. Maya comes with a simple example [[here|http://download.autodesk.com/us/maya/2011help/API/y_twist_node_8py-example.html]], which I stripped down even more, added a bunch of notes, etc. Long after I authored this, found a good blog post on general API deformer stuff: [[Maya's Deformer Architecture|http://around-the-corner.typepad.com/adn/2012/09/maya-deformer-architecture.html]] When the deformer is applied to a node, it will randomly offset the node's points based on its {{{deform}}} attr. When it comes to creating a locator node, these are the major things required: * A class inheriting from {{{maya.OpenMayaMPx.MPxDeformerNode}}}. **It in turn inherits from {{{maya.OpenMayaMPx.MPxNode}}} **The name of this class is up to you, it is independent from the name of the node being created. The node name is actually defined in {{{initializePlugin()}}} **Must initialize the superclass in {{{__init__()}}} **Override the {{{deform()}}} method, put your point deformation code in there. *Author 'node creator' and 'node initializer' functions. The names are arbitrary, but {{{nodeCreator()}}} and {{{nodeInitializer()}}} are good ideas. These are both used by the {{{initializePlugin()}}} function. * Author {{{initializePlugin()}}} and {{{uninitializePlugin()}}} functions. Other than the overridden methods above, these are the only items in the code that need specific names. {{{ # spSimpleDeformer.py import random import maya.OpenMaya as om import maya.OpenMayaMPx as ompx #------------------ # Constants: # c++ uses 'k' to specify constants, Python usually uses all caps. # Using k here since we're dealing with Maya legacy, but ultimately it doesn't # matter. kPluginNodeType = "spSimpleDeformer" # This identifier should be provided by Autodesk, to make sure it will never # clash with any other node type. I just made this one up. # http://download.autodesk.com/us/maya/2011help/API/class_m_type_id.html kPluginNodeId = om.MTypeId(0x00113) class SimpleDeformer(ompx.MPxDeformerNode): """ Scripted deformer node creation http://download.autodesk.com/us/maya/2011help/API/class_m_px_deformer_node.html Inherits from: http://download.autodesk.com/us/maya/2011help/API/class_m_px_node.html """ # Define any input and output attrs at the class level. # These are MObject representations of the attributes on the physical # Maya node. They are just placeholder: They are replaced\populated by # nodeInitializer() # In fact, they could be None, but since ultimately they'll be MObjects, # just stick some dummy ones in there. attr_deform = om.MObject() def __init__(self): """ Make sure to initilaize the superclass. Not much else happens here. """ ompx.MPxDeformerNode.__init__(self) def deform(self, dataBlock, geomIter, matrix, multiIndex): """ Overridden method of MPxDeformerNode, where the deformation magic happens. Parameters: dataBlock : MDataBlock : The node's datablock. Provides storage for the data being received by or sent by the node. It is the data for the plugs and attributes of the node. http://download.autodesk.com/us/maya/2011help/API/class_m_data_block.html geomIter : MItGeometry : An iterator for the current geometry being deformed. http://download.autodesk.com/us/maya/2011help/API/class_m_it_geometry.html matrix : MMatrix : the geometry's world space transformation matrix. http://download.autodesk.com/us/maya/2011help/API/class_m_matrix.html multiIndex : unsigned int : The index corresponding to the requested output geometry. """ # Get the deform attribute value from the datablock: # http://download.autodesk.com/us/maya/2011help/API/class_m_data_handle.html deformHandle = dataBlock.inputValue(self.attr_deform) # MDataHandle deformValue = deformHandle.asDouble() # double # Get the Maya node's envelope attr value. The MPxDeformerNode has a # static ''envelope' attribute that can be queried to access this. # It appears that the envelope attribute on the Maya node is auto-created. # Furthermore, it seems the the OpenMayaMPx.cvar is a 'swigvarlink' # (whatever that is) that holds constants for use elsewhere in the API envelope_attr = ompx.cvar.MPxDeformerNode_envelope envelopeHandle = dataBlock.inputValue(envelope_attr) # MDataHandle envelopeValue = envelopeHandle.asFloat() # Iterate over the objects points to deform it: while geomIter.isDone() == False: # We use the index of each point as the seed to its random function # so that the noise won't change randomly as the 'deform' attr is # changed, or the node is moved through space. random.seed(geomIter.index()) # http://download.autodesk.com/us/maya/2011help/API/class_m_point.html point = geomIter.position() # MPoint # Do a simple random operation on each point: point.x = point.x + random.uniform(-deformValue, deformValue) * envelopeValue point.y = point.y + random.uniform(-deformValue, deformValue) * envelopeValue point.z = point.z + random.uniform(-deformValue, deformValue) * envelopeValue # Re-set the position of the item after it's been recalculated: geomIter.setPosition(point) geomIter.next() #--------------------------------------------------- # Node creation and initialization: # creator def nodeCreator(): return ompx.asMPxPtr(SimpleDeformer()) # initializer def nodeInitializer(): """ This function sets up all the input and output attributes on the node: What type of data they should expect, if they're inputs\outputs, keyable, connectable, default values, etc. Also defines the relationships between attrs using attributeAffects(): If an input changes, you should update the output. """ # Create the deform attribute: nAttr = om.MFnNumericAttribute() SimpleDeformer.attr_deform = nAttr.create("deform", "df", om.MFnNumericData.kDouble, 0.0) nAttr.setKeyable(True) # False by default # Add the attribute to the node: try: # MPxDeformerNode has a static 'outputGeom' attribute that needs to be # told to be affected when deformation occurs. This maps to the # 'outputGeometry' attribute on the Maya node. SimpleDeformer.addAttribute(SimpleDeformer.attr_deform) outputGeom = ompx.cvar.MPxDeformerNode_outputGeom # Make sure that when the user adjusts the attr, it affects the geometry. SimpleDeformer.attributeAffects(SimpleDeformer.attr_deform, outputGeom) except: om.MGlobal.displayError("Failed to create attributes of %s node\n", kPluginNodeType) #------------------------------------------------------ # Loading and unloading: # initialize the script plug-in def initializePlugin(mobject): mplugin = ompx.MFnPlugin(mobject) try: mplugin.registerNode(kPluginNodeType, kPluginNodeId, nodeCreator, nodeInitializer, ompx.MPxNode.kDeformerNode) except: om.MGlobal.displayError("Failed to register node: %s\n"%kPluginNodeType) # uninitialize the script plug-in def uninitializePlugin(mobject): mplugin = ompx.MFnPlugin(mobject) try: mplugin.deregisterNode(kPluginNodeId) except: om.MGlobal.displayError("Failed to unregister node: %s\n"%kPluginNodeType) }}} After this is loaded in the plugin manager (see [[How can I load \ unload a Python scripted plugin?]]), pick a node in your scene, then create the deformer: {{{ import maya.cmds as mc d = mc.deformer(type='spSimpleDeformer') # [u'spSimpleDeformer1'] }}} Since you're creating the node via the {{{deformer}}} command, you can easily capture the return value of your newly created node name. You can now modify the deformer node's {{{deform}}} attribute, and watch the magic happen. I wanted to build a bare-minimum framework for authoring a file translator via a scripted plugin. Maya comes with a simple example here: http://download.autodesk.com/us/maya/2011help/API/custom_node_file_translator_8py-example.html Which I modified and added notes to in the example below. Once this plugin is imported, you can export and import the new 'sft' data via the Maya UI, or a script, just like any other type. This example simply exports the names of all nodes in the scene (or selected items) to a custom text file. The importer will read the text file, and print the contents to the script editor. But obviously this is just a simple framework that can be expanded upon. When it comes to creating a file translator, these are the major things required: * A class inheriting from {{{maya.OpenMayaMPx.MPxFileTranslator}}}. **It in turn inherits from {{{maya.OpenMayaMPx.MPxMayaAsciiFilter}}} **The name of this class is up to you, it is independent from the name of the node being created. The node name is actually defined in {{{initializePlugin()}}} **Must initialize the superclass in {{{__init__()}}} **Depending on the functionality you desire, must override the methods {{{writer}}}, {{{reader}}}, {{{haveWriteMethod}}}, {{{haveReadMethod}}}, {{{filter}}}, {{{defaultExtension}}}, among others. *Author a 'creator' function \ class method (we do it as a class method here). The name is arbitrary, but {{{creator()}}} is a good ideas. It is used by the {{{initializePlugin()}}} function. * Author {{{initializePlugin()}}} and {{{uninitializePlugin()}}} functions. Other than the overridden methods above, these are the only items in the code that need specific names. {{{ # spSimpleFileTranslator.py import maya.OpenMaya as om import maya.OpenMayaMPx as ompx # ----------------------------------- # Plugin code class SimpleFileTranslator(ompx.MPxFileTranslator): """ This class will create a new file translator type that will write out a simple text file of all the nodes in the scene. When reading the save data, it will print the result. http://download.autodesk.com/us/maya/2010help/API/class_m_px_file_translator.html """ # Define the name of our file translator in Maya: name = "spSimpleFileTranslator" # Define the extension it will use. 'sft' = 'simple file translator' extension = 'sft' def __init__(self): """ Initalize our superclass: """ ompx.MPxFileTranslator.__init__(self) #---------------------------------------------------- # Overridden superclass methods: # There are more than this, but we only need to use these for this example. def writer(self, fileObject, optionString, accessMode): """ What is called to when writing a file. In our example, we write out a simple txt file containing all the node names in the scene. This code supports exporting the whole scene, or only what's selected. Parameters: fileObject : MFileObject : http://download.autodesk.com/us/maya/2010help/API/class_m_file_object.html optionsString : string (MString in c++) accessMode : MFileTranslator.FileAccessMode enumerator : Unknown mode (0), import into new scene (1), reference (2), import into current scene (3), save (4), export (5), export selected (6). http://download.autodesk.com/us/maya/2010help/API/class_m_px_file_translator.html#237971b037a8b0cbb697d61a01da4b5d """ fullName = fileObject.resolvedFullName() try: with open(fullName, "w") as f: # If the whole scene is being exported. The enum value is 5: if accessMode == ompx.MPxFileTranslator.kExportAccessMode: f.write("# Simple text file of all nodes in the scene.\r\n") mItDependNode = om.MItDependencyNodes() # http://download.autodesk.com/us/maya/2010help/API/class_m_it_dependency_nodes.html while not mItDependNode.isDone(): # Extract the MObject, and build a function set to query its name: # http://download.autodesk.com/us/maya/2010help/API/class_m_fn_dependency_node.html mFnDependNode = om.MFnDependencyNode(mItDependNode.thisNode()) nodeName = mFnDependNode.name() f.write('%s\r\n'%nodeName) mItDependNode.next() # If we're exporting selected. The enum value is 6: elif accessMode == ompx.MPxFileTranslator.kExportActiveAccessMode: f.write("# Simple text file of selected nodes at time of export.\r\n") selList = om.MSelectionList() om.MGlobal.getActiveSelectionList(selList) # http://download.autodesk.com/us/maya/2010help/API/class_m_it_selection_list.html mItSelectionList = om.MItSelectionList(selList) mObject = om.MObject() while not mItSelectionList.isDone(): mItSelectionList.getDependNode(mObject) mFnDependNode = om.MFnDependencyNode(mObject) nodeName = mFnDependNode.name() f.write('%s\r\n'%nodeName) mItSelectionList.next() else: raise Exception("Invalid accessMode argument: %s"%accessMode) except: om.MGlobal.displayError("Failed to write file information\n") raise def reader(self, fileObject, optionString, accessMode): """ What is called to when reading a file. In our case, it just parses a text file and prints the result in the Script Editor. Parameters: fileObject : MFileObject : http://download.autodesk.com/us/maya/2010help/API/class_m_file_object.html optionsString : string (MString in c++) accessMode : MFileTranslator.FileAccessMode enumerator : Unknown mode (0), import into new scene (1), reference (2), import into current scene (3), save (4), export (5), export selected (6). http://download.autodesk.com/us/maya/2010help/API/class_m_px_file_translator.html#237971b037a8b0cbb697d61a01da4b5d """ lines = [] try: fullName = fileObject.resolvedFullName() with open(fullName,"r") as f: lines = [item.strip() for item in f] except: om.MGlobal.displayError("Failed to read file information\n") raise print "\nContents of file:" for line in lines: print "\t", line om.MGlobal.displayInfo("Contents from file %s"%fullName) def haveWriteMethod(self): """ The default is False: Change to True if this can write a file. """ return True def haveReadMethod(self): """ The default is False: Change to True if this can read a file. """ return True def filter(self): """ Return the custom filter used for this file type. Default is "*.*" """ return "*.%s"%SimpleFileTranslator.extension def defaultExtension(self): """ Return the custom extension for this file type. Default is an empty string. """ return SimpleFileTranslator.extension #--------------------------------------------------- # Translator creation method: # 'Creator Function' needs to return a pointer new instance of this type of # class, based on how the c++ code works. # As you can see, the OpenMayaMPx module has it's own functions (for which # I can find no online documentation). @classmethod def creator(cls): return ompx.asMPxPtr(cls()) # ----------------------------------- # Plugin registration\degregistration block # initializePlugin & uninitializePlugin are *required functions* def initializePlugin(mobject): # create a MFnPlugin mfnPlugin = ompx.MFnPlugin(mobject, "Eric Pavey", "1.0") try: # Try to register our plug by passing in the commandName, and the # translatorCreator: we use the '.registerFileTranslator()' method since # we're trying to build a file translator. Other plugin types use # different registration functions. The args do differ, check docs. # http://download.autodesk.com/us/maya/2010help/API/class_m_fn_plugin.html mfnPlugin.registerFileTranslator(SimpleFileTranslator.name, None, SimpleFileTranslator.creator) except: om.MGlobal.displayError("SimpleFileTranslator registration failed") raise def uninitializePlugin(mobject): # create a MFnPlugin mfnPlugin = ompx.MFnPlugin(mobject) try: # Try to deregister our plug by passing in the kPluginCmdName, and the command creator: mfnPlugin.deregisterFileTranslator(SimpleFileTranslator.name) except: om.MGlobal.displayError("SimpleFileTranslator deregistration failed") raise }}} After this is loaded in the plugin manager (see [[How can I load \ unload a Python scripted plugin?]]), you can export and import the data either via the Maya UI (File->Export All, File->Export Selected, File->Import) and choose the new 'sft' file-type, or you can use these commands: {{{ import maya.cmds as mc # To export: mc.file("C:/temp/sftTest.sft", type="spSimpleFileTranslator", exportAll=True) # To import: mc.file("C:/temp/sftTest.sft", type="spSimpleFileTranslator", i=True) }}} I wanted to build a bare-minimum framework for authoring a locator node via a scripted plugin. Maya comes with a pretty simple example here: http://download.autodesk.com/us/maya/2011help/API/foot_print_node_8py-example.html Which I stripped down even more, added a bunch of notes, etc. The below example creates a new square-shaped node called '{{{spSimpleLocator}}}' with a single '{{{size}}}' custon attribute. It will be a semi-transparent green in shaded mode. There is a bug in the ~OpenGL code however: It will occlude wireframe display behind it. I don't know much about ~OpenGL code, hopefully can address in the future. When it comes to creating a locator node, these are the major things required: * A class inheriting from {{{maya.OpenMayaMPx.MPxLocatorNode}}}. **It in turn inherits from {{{maya.OpenMayaMPx.MPxNode}}} **The name of this class is up to you, it is independent from the name of the node being created. The node name is actually defined in {{{initializePlugin()}}} **Must initialize the superclass in {{{__init__()}}} **Optional: Override the {{{compute()}}} method to do any extra internal-node computation. Not needed for standard locator behavior. **Override the {{{draw()}}} method, put your ~OpenGL draw calls in there. **Override the {{{isBounded()}}} and {{{boundingBox()}}} methods. *Author 'node creator' and 'node initializer' functions. The names are arbitrary, but {{{nodeCreator()}}} and {{{nodeInitializer()}}} are good ideas. These are both used by the {{{initializePlugin()}}} function. * Author {{{initializePlugin()}}} and {{{uninitializePlugin()}}} functions. Other than the overridden methods above, these are the only items in the code that need specific names. {{{ # spSimpleLocator.py import maya.OpenMaya as om import maya.OpenMayaMPx as ompx import maya.OpenMayaRender as omr import maya.OpenMayaUI as omui #------------------ # Constants: # c++ uses 'k' to specify constants, Python usually uses all caps. # Using k here since we're dealing with Maya legacy, but ultimately it doesn't matter. kPluginNodeType = "spSimpleLocator" # This identifier should be provided by Autodesk, to make sure it will never # clash with any other node type. I just made this one up. # http://download.autodesk.com/us/maya/2011help/API/class_m_type_id.html kPluginNodeId = om.MTypeId(0x00112) #------------------ # globals # This static (class) method creates an instance of MHardwareRenderer. There is # no web documentation for the 'theRenderer()', weird. # http://download.autodesk.com/us/maya/2011help/API/class_m_hardware_renderer.html renderer = omr.MHardwareRenderer.theRenderer() # This creates an instance of MGLFunctionTable, giving us access to OpenGL calls. # http://download.autodesk.com/us/maya/2011help/API/class_m_g_l_function_table.html glFT = renderer.glFunctionTable() # Vert positions that define the shape of our locator. If these change, you'll # also need to update the boundingBox() method. locShape = ([-.5, .5, 0], [.5, .5, 0], [.5, -.5, 0], [-.5, -.5, 0], [-.5, .5, 0]) class SimpleLocator(ompx.MPxLocatorNode): """ Scripted locator node creation. http://download.autodesk.com/us/maya/2011help/API/class_m_px_locator_node.html Inherits from MPxNode: http://download.autodesk.com/us/maya/2011help/API/class_m_px_node.html """ # Define any input and output attrs at the class level. # These are MObject representations of the attributes on the physical # Maya node. They are just placeholder: They are replaced\populated by nodeInitializer() # In fact, they could be None, but since ultimately they'll be MObjects, # just stick some dummy ones in there. attr_size = om.MObject() def __init__(self): """ Make sure to initilaize the superclass. Not much else happens here. """ ompx.MPxLocatorNode.__init__(self) def compute(self, plug, dataBlock): """ Overridden method of the MPxNode superclass. Doesn't do much since we're not computing any internal values. But in theory, you could. """ return om.kUnknownParameter def draw(self, view, path, style, status): """ Overridden method of MPxLocatorNode. Allows the drawing of the locator via OpenGL. Parameters: view : M3dView, http://download.autodesk.com/us/maya/2011help/API/class_m3d_view.html, the 3D view that is being drawn into. path : MDagPath, http://download.autodesk.com/us/maya/2011help/API/class_m_dag_path.html, the path to the locator in the DAG. style : M3dView MDisplayStyle enum value, http://download.autodesk.com/us/maya/2011help/API/class_m3d_view.html. the style to draw the locator in. status : M3dView MDisplayStatus enum value, http://download.autodesk.com/us/maya/2011help/API/class_m3d_view.html, the selection status of the locator. """ thisNode = self.thisMObject() # superclass method, returns MObject plug = om.MPlug(thisNode, self.attr_size) # returns MPlug sizeVal = plug.asMDistance() # returns MDistance multiplier = sizeVal.asCentimeters() # returns double # Start the OpenGL drawing. Time to put your OpenGL hat on. view.beginGL() # If we're in any kind of shaded view, draw a 'filled-in' view of the locator: if style == omui.M3dView.kFlatShaded or style == omui.M3dView.kGouraudShaded: # To enable alpha blending on our locator faces, we need to set this GL state: # http://www.opengl.org/sdk/docs/man/xhtml/glEnable.xml glFT.glEnable(omr.MGL_BLEND) # Methods starting with 'gl' are direct calls to OpenGL. # Also, OpenMayaRender has many OpenGL related constants that begin # with capital 'M', that can be passed into the GL calls. # Both of these concepts are shown in this doc: # http://www.opengl.org/sdk/docs/man/xhtml/glPushAttrib.xml glFT.glPushAttrib(omr.MGL_CURRENT_BIT) # Setup the draw colors based on if the locator is the lead picked # object or not, or not picked at all. For the below examples they're # all the same value. if status == omui.M3dView.kLead: # If it is the first object picked. glFT.glColor4f(0, 1, 0, .5) if status == omui.M3dView.kActive: # If it's picked, but not lead. glFT.glColor4f(0, 1, 0, 0.5) # Here is another way to set colors based on Maya's color palette, # but you can't do alpha: #view.setDrawColor( 13, omui.M3dView.kActiveColors ) if status == omui.M3dView.kDormant: glFT.glColor4f(0, 1, 0, 0.5) #view.setDrawColor( 13, omui.M3dView.kDormantColors ) # Draw the physical triangles to fill in the shape: # http://www.opengl.org/sdk/docs/man/xhtml/glBegin.xml glFT.glBegin(omr.MGL_TRIANGLE_FAN ) for i in range(len(locShape)-1): glFT.glVertex3f(locShape[i][0]*multiplier, locShape[i][1]*multiplier, locShape[i][2]*multiplier) glFT.glEnd() # Turn off alpha blending: glFT.glDisable(omr.MGL_BLEND) glFT.glPopAttrib() # need to pop for each push. # Now start drawing the wireframe as well. I've yet to figure out what # defines the wireframe color in Maya, since the 'locator' color in the # prefs don't seem to apply to this custom node. glFT.glBegin(omr.MGL_LINES) for i in range(len(locShape)-1): glFT.glVertex3f( locShape[i][0]*multiplier, locShape[i][1]*multiplier, locShape[i][2]*multiplier ) glFT.glVertex3f( locShape[i+1][0]*multiplier, locShape[i+1][1]*multiplier, locShape[i+1][2]*multiplier ) glFT.glEnd() view.endGL() # End all OpenGL drawing. def isBounded(self): """ Overridden method of MPxLocatorNode. If return True, must supply an overridden boundingBox method to compute bounding box. """ return True def boundingBox(self): """ Overridden method of MPxLocatorNode. Must return a MBoundingBox object specifying the bounding box of this node. http://download.autodesk.com/us/maya/2011help/API/class_m_bounding_box.html """ thisNode = self.thisMObject() # MObject plug = om.MPlug(thisNode, self.attr_size) # MPlug sizeVal = plug.asMDistance() # MDistance multiplier = sizeVal.asCentimeters() # double # Two corners of our square shaped locator: corner1 = om.MPoint(-.5, .5, 0) corner2 = om.MPoint(.5, -.5, 0) corner1 = corner1 * multiplier corner2 = corner2 * multiplier bbox = om.MBoundingBox( corner1, corner2 ) # MBoundingBox return bbox #--------------------------------------------------- # Node creation and initialization: # creator def nodeCreator(): return ompx.asMPxPtr( SimpleLocator() ) # initializer def nodeInitializer(): """ This function sets up all the input and output attributes on the node: What type of data they should expect, if they're inputs\outputs, keyable, connectable, default values, etc. Also defines the relationships between attrs using attributeAffects(): If an input changes, you should update the output. """ # Create the 'size' attribute. This will end up overriding the locators # default 'localScale' attr. # http://download.autodesk.com/us/maya/2011help/API/class_m_fn_unit_attribute.html unitFn = om.MFnUnitAttribute() # Returns an MObject, assignes it to the class variable: SimpleLocator.attr_size = unitFn.create("size", "in", om.MFnUnitAttribute.kDistance) unitFn.setChannelBox(True) # default is False unitFn.setKeyable(True) # default is False unitFn.setDefault(1.0) SimpleLocator.addAttribute( SimpleLocator.attr_size ) #------------------------------------------------------ # Loading and unloading: # initialize the script plug-in def initializePlugin(mobject): mplugin = ompx.MFnPlugin(mobject) try: mplugin.registerNode(kPluginNodeType, kPluginNodeId, nodeCreator, nodeInitializer, ompx.MPxNode.kLocatorNode) except: om.MGlobal.displayError("Failed to register node: %s"%kPluginNodeType) # uninitialize the script plug-in def uninitializePlugin(mobject): mplugin = ompx.MFnPlugin(mobject) try: mplugin.deregisterNode(kPluginNodeId) except: om.MGlobal.displayError("Failed to deregister node: %s"%kPluginNodeType) }}} After this is loaded in the plugin manager (see [[How can I load \ unload a Python scripted plugin?]]), you should be able to create it and play with it: {{{ import maya.cmds as mc mc.createNode('spSimpleLocator') }}} I should note that after I authored this, I found another good resource on scripted plugin locator creation here: http://www.fevrierdorian.com/blog/post/2010/02/12/Creating-custom-locator-with-Maya-s-Python-API I wanted to build a bare-minimum framework for authoring nodes via a scripted plugin. Maya comes with a pretty simple example here: http://download.autodesk.com/us/maya/2011help/API/circle_node_8py-example.html Which I stripped down even more, added a bunch of notes, etc. The below example creates a new node called '{{{simpleNodeA}}}' that has two inputs. Internally, it simply adds those values together, which can then be captured from its single output attribute. When it comes to creating a node, these are the major things required: *A class inheriting from {{{maya.OpenMayaMPx.MPxNode}}}. **The name of this class is up to you, it is independent from the name of the node being created. The node name is actually defined in {{{initializePlugin()}}} **Must initialize the superclass in {{{__init__()}}}. **Must override the {{{compute()}}} method: This is where the node does it's work. *Author 'node creator' and 'node initializer' functions. The names are arbitrary, but {{{nodeCreator()}}} and {{{nodeInitializer()}}} are good ideas. These are both used by the {{{initializePlugin()}}} function. The 'node creator' does very little, while the 'node initializer' does a bunch of attribute setup (presuming your node has attributes). *Author {{{initializePlugin()}}} and {{{uninitializePlugin()}}} functions. Other than the overridden methods above, these are the only items in the code that need specific names. {{{ # spSimpleNode.py import maya.OpenMaya as om import maya.OpenMayaMPx as ompx #------------------------------------------------------------- # Constants # c++ uses 'k' to specify constants, Python usually uses all caps. # Using k here since we're dealing with Maya legacy, but ultimately it doesn't matter. # What the node-type will be called in Maya. kPluginNodeType = "simpleNodeA" # This identifier should be provided by Autodesk, to make sure it will never # clash with any other node type. I just made this one up. # http://download.autodesk.com/us/maya/2011help/API/class_m_type_id.html kPluginNodeId = om.MTypeId(0x00111) kVendor = "My Name or organization" kVersion = "1.0" #---------------------------------------------------------- # Plugin code: # Node definition class SimpleNode(ompx.MPxNode): """ Scripted plugin node creation. http://download.autodesk.com/us/maya/2011help/API/class_m_px_node.html """ # Define any input and output attrs at the class level. # These are MObject representations of the attributes on the physical # Maya node. They are just placeholder: They are replaced\populated by nodeInitializer() # In fact, they could be None, but since ultimately they'll be MObjects, # just stick some dummy ones in there. attr_inputA = om.MObject() attr_inputB = om.MObject() attr_output = om.MObject() def __init__(self): """ Make sure to initilaize the superclass. Not much else happens here. """ ompx.MPxNode.__init__(self) def compute(self, plug, dataBlock): """ This is an overridden method of the MPxNode class. Does the "work" the node is to perform. Parameters: plug : MPlug : http://download.autodesk.com/us/maya/2010help/API/class_m_plug.html dataBlock : MDataBlock : http://download.autodesk.com/us/maya/2010help/API/class_m_data_block.html """ # Check that the requested recompute is our output value if (plug == SimpleNode.attr_output): # Read the input values, returns a MDataHandle, which is a smart pointer # back into the MDataBlock (dataBlock) # http://download.autodesk.com/us/maya/2010help/API/class_m_data_handle.html dataHandleA = dataBlock.inputValue(SimpleNode.attr_inputA) dataHandleB = dataBlock.inputValue(SimpleNode.attr_inputB) # Compute the output values. In this case, it is a simple addition operation. outVal = dataHandleA.asFloat() + dataHandleB.asFloat() # Get a handle to the output value and store the new value. handle = dataBlock.outputValue(SimpleNode.attr_output) handle.setFloat(outVal) # From the docs: Tells the dependency graph that the given attribute # has been updated and is now clean. This should be called after the # data in the plug has been recalculated from the inputs of the node. dataBlock.setClean(plug) #else: # c++ requires this return, but it appears that the Python API doesn't: #return om.MStatus.kUnknownParameter #--------------------------------------------------- # Node creation and initialization: # Creator function. It should be noted that while historically this is an external # function, you could instead, inside the initializePlugin function, call to this # via a lambda. Meaning, instead of this line (below): # mfnPlugin.registerNode(kPluginNodeType, kPluginNodeId, nodeCreator, nodeInitializer) # You could do this: # mfnPlugin.registerNode(kPluginNodeType, kPluginNodeId, lambda:ompx.asMPxPtr(SimpleNode()), nodeInitializer) # And in the process completely remove the below function. def nodeCreator(): return ompx.asMPxPtr( SimpleNode() ) # initializer def nodeInitializer(): """ This function sets up all the input and output attributes on the node: What type of data they should expect, if they're inputs\outputs, keyable, connectable, default values, etc. Also defines the relationships between attrs using attributeAffects(): If an input changes, you should update the output. """ # http://download.autodesk.com/us/maya/2010help/API/class_m_fn_numeric_attribute.html mfnNumericAttribute = om.MFnNumericAttribute() # Setup the input attributes. This returns an MObject. SimpleNode.attr_inputA = mfnNumericAttribute.create("inputA", "ina", om.MFnNumericData.kFloat, 1.0) # The setStorable() method is inheirted from MFnAttribute mfnNumericAttribute.setChannelBox(True) # default is False mfnNumericAttribute.setKeyable(True) # default is False SimpleNode.attr_inputB = mfnNumericAttribute.create("inputB", "inb", om.MFnNumericData.kFloat, 10.0) mfnNumericAttribute.setChannelBox(True) # default is False mfnNumericAttribute.setKeyable(True) # default is False # Setup the output attributes SimpleNode.attr_output = mfnNumericAttribute.create("output", "o", om.MFnNumericData.kFloat, 0.0) mfnNumericAttribute.setWritable(False) # Default is True. This is an output, can't write to it. mfnNumericAttribute.setStorable(False) # Default is True. Do not save value with scene, since it's an output. # Now that we've made the attributes, add them to the node. # addAttribute() is a static method inherited from MPxNode. SimpleNode.addAttribute(SimpleNode.attr_inputA) SimpleNode.addAttribute(SimpleNode.attr_inputB) SimpleNode.addAttribute(SimpleNode.attr_output) # Set the attribute dependencies: This tells the system that when an input # is modified, the output needs to be recomputed. # attributeAffects() is a static method inherited from MPxNode. SimpleNode.attributeAffects(SimpleNode.attr_inputA, SimpleNode.attr_output) SimpleNode.attributeAffects(SimpleNode.attr_inputB, SimpleNode.attr_output) #------------------------------------------------------ # Loading and unloading: def initializePlugin(mobject): # Initialize the script plug-in # http://download.autodesk.com/us/maya/2011help/API/class_m_fn_plugin.html mfnPlugin = ompx.MFnPlugin(mobject, kVendor, kVersion) try: mfnPlugin.registerNode(kPluginNodeType, kPluginNodeId, nodeCreator, nodeInitializer) except: om.MGlobal.displayError("Failed to register node: %s" % kPluginNodeType) raise def uninitializePlugin(mobject): # Uninitialize the script plug-in mplugin = ompx.MFnPlugin(mobject) try: mplugin.deregisterNode(kPluginNodeId) except: om.MGlobal.displayError("Failed to deregister node: %s" % kPluginNodeType) raise }}} After this is loaded in the plugin manager (see [[How can I load \ unload a Python scripted plugin?]]), you should be able to create it, and connect it's output to something else. Below examples are in Python, but you can just as easily access this via mel. {{{ import maya.cmds as mc simpleNode = mc.createNode('simpleNodeA') null = mc.group(empty=True) mc.connectAttr('%s.output'%simpleNode, '%s.tx'%null) # Result: Connected simpleNodeA1.output to null1.translate.translateX. # }}} Optionally, you could do this: {{{ import maya.cmds as mc import spSimpleNode simpleNode = mc.createNode(spSimpleNode.kPluginNodeType) null = mc.group(empty=True) mc.connectAttr('%s.output'%simpleNode, '%s.tx'%null) # Result: Connected simpleNodeA1.output to null1.translate.translateX. # }}}  While Python comes with its own built-in types, sometimes you need to access a specific API type wrapper. This is not a rigid: What one considers a 'type' is up for consideration here, I just grabbed classes that fit the bill in my own head. All are part of {{{maya.OpenMaya}}} !!!Angle *[[MAngle|http://download.autodesk.com/us/maya/2010help/API/class_m_angle.html]] !!!Color *[[MColor|http://download.autodesk.com/us/maya/2010help/API/class_m_color.html]] *[[MColorArray|http://download.autodesk.com/us/maya/2010help/API/class_m_color_array.html]] !!!Dag Path: *[[MDagPath|http://download.autodesk.com/us/maya/2010help/API/class_m_dag_path.html]] *[[MDagPathArray|http://download.autodesk.com/us/maya/2010help/API/class_m_dag_path_array.html]] !!!Distance *[[MDistance|http://download.autodesk.com/us/maya/2010help/API/class_m_distance.html]] !!!Double *[[MDoubleArray|http://download.autodesk.com/us/maya/2010help/API/class_m_double_array.html]] !!!Euler Rotation *[[MEulerRotation|http://download.autodesk.com/us/maya/2010help/API/class_m_euler_rotation.html]] !!!Float *[[MFloatArray|http://download.autodesk.com/us/maya/2010help/API/class_m_float_array.html]] !!!Int *[[MIntArray|http://download.autodesk.com/us/maya/2010help/API/class_m_int_array.html]] !!!Point *[[MPoint|http://download.autodesk.com/us/maya/2010help/API/class_m_point.html]] *[[MFloatPoint|http://download.autodesk.com/us/maya/2010help/API/class_m_float_point.html]] *[[MPointArray|http://download.autodesk.com/us/maya/2010help/API/class_m_point_array.html]] *[[MFloatPointArray|http://download.autodesk.com/us/maya/2010help/API/class_m_float_point_array.html]] !!!Matrix *[[MMatrix|http://download.autodesk.com/us/maya/2010help/API/class_m_matrix.html]] *[[MFloatMatrix|http://download.autodesk.com/us/maya/2010help/API/class_m_float_matrix.html]] *[[MTransformationMatrix|http://download.autodesk.com/us/maya/2010help/API/class_m_transformation_matrix.html]] *[[MMatrixArray|http://download.autodesk.com/us/maya/2010help/API/class_m_matrix_array.html]] !!!Object: *[[MObject|http://download.autodesk.com/us/maya/2010help/API/class_m_object.html]] *[[MObjectArray|http://download.autodesk.com/us/maya/2010help/API/class_m_object_array.html]] !!!Plug *[[MPlug|http://download.autodesk.com/us/maya/2010help/API/class_m_plug.html]] *[[MPlugArray|http://download.autodesk.com/us/maya/2010help/API/class_m_plug_array.html]] !!!Pointers & References *See [[OpenMaya.MScriptUtil]] !!!Quaternion *[[MQuaternion|http://download.autodesk.com/us/maya/2010help/API/class_m_quaternion.html]] !!!String Note: These aren't available in the Python ~OpenMaya API *[[MString|http://download.autodesk.com/us/maya/2010help/API/class_m_string.html]] *[[MStringArray|http://download.autodesk.com/us/maya/2010help/API/class_m_string_array.html]] !!!Time *[[MTime|http://download.autodesk.com/us/maya/2010help/API/class_m_time.html]] *[[MTimeArray|http://download.autodesk.com/us/maya/2010help/API/class_m_time_array.html]] !!!Unsigned Int *[[MUintArray|http://download.autodesk.com/us/maya/2010help/API/class_m_uint_array.html]] *[[MUint64Array|http://download.autodesk.com/us/maya/2010help/API/class_m_uint64_array.html]] !!!Vector *[[MVector|http://download.autodesk.com/us/maya/2010help/API/class_m_vector.html]] *[[MFloatVector|http://download.autodesk.com/us/maya/2010help/API/class_m_float_vector.html]] *[[MVectorArray|http://download.autodesk.com/us/maya/2010help/API/class_m_vector_array.html]] *[[MFloatVectorArray|http://download.autodesk.com/us/maya/2010help/API/class_m_float_vector_array.html]]  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  Scratchpad of API stuff. 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/ *The {{{OpenMaya.MScriptUtil}}} is a common class the Python API programmer will need to use. Some examples here: **http://www.chadvernon.com/blog/resources/maya-api-programming/mscriptutil/ Good quality official documentation: *http://download.autodesk.com/us/maya/2009help/API/ *http://download.autodesk.com/us/maya/2010help/API/ *http://download.autodesk.com/us/maya/2011help/API/ *http://download.autodesk.com/global/docs/mayasdk2012/en_us/index.html *http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/index.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 This appears to be free, I have yet to actually look at it yet: *http://download.autodesk.com/media/adn/Maya_Intermediate_Webcast.zip ---- ''Differences between {{{c++}}} and {{{Python}}}'' (based on my ignorance of c++, take this with a grain of salt.) *Pointers: **See online reference for an overview: http://oreilly.com/catalog/pcp3/chapter/ch13.html *Namespaces: **c++ uses double colon {{{::}}} to define namespaces, Python uses single period '{{{.}}}': {{{ # c++: MGlobal::displayInfo() # Python: OpenMaya.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... }}} It should be noted that in the Python API, these two values can be considered the same thing for the most part: Pass by reference. ---- 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. *{{{k}}} is an old-school 'Hungarian-notation' way of specifying a constant value. Just a convention. *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 }}} ---- 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''@@.  This topic name is a bit abstract, but here's the idea: Via the {{{OpenMaya}}} API, various classes 'go together'. These are my notes to help show which ones connect to which. *[[OpenMaya.MItDag|http://download.autodesk.com/us/maya/2011help/API/class_m_it_dag.html]] : Use the DAG iterator to traverse the DAG either depth first or breadth first, visiting each node. **[[OpenMaya.MFnDagNode|http://download.autodesk.com/us/maya/2011help/API/class_m_fn_dag_node.html]] Generic function set which can be used on any dag node. *[[OpenMaya.MItDependencyGraph|http://download.autodesk.com/us/maya/2011help/API/class_m_it_dependency_graph.html]] : Iterate over Dependency Graph (DG) Nodes or Plugs starting at a specified root Node or Plug. **OpenMaya.MObject **OpenMaya.MPlug **OpenMaya.MObjectArray **OpenMaya.MPlugArray **OpenMaya.MDGModifier **OpenMaya.MFnAttribute *[[OpenMaya.MItDependencyNodes|http://download.autodesk.com/us/maya/2011help/API/class_m_it_dependency_nodes.html]] : Traverse all the nodes in Maya's Dependency Graph. **[[OpenMaya.MFnDependencyNode|http://download.autodesk.com/us/maya/2011help/API/class_m_fn_dependency_node.html]] : creation and manipulation of dependency graph nodes *[[OpenMaya.MSelectionList|http://download.autodesk.com/us/maya/2011help/API/class_m_selection_list.html]] **[[OpenMaya.MItSelectionList|http://download.autodesk.com/us/maya/2011help/API/class_m_it_selection_list.html]] Classes are organized into these main groups: {{{M}}}, {{{MFn}}}, {{{MIt}}}, & {{{MPx}}}. Also see: [[API: Commonly used classes]] *@@{{{M}}}@@ classes are Maya's base utility classes. ''M'' stands for "Model". Most, although not all, of these classes are “Wrappers”. Examples of this class are: **@@{{{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. All {{{MFn}}} classes (other than {{{MFn}}} itself) live as part of a class inheritance hierarchy, the root being {{{MFnBase}}}. This differentiates them from the other class types ({{{M}}}, {{{MIt}}}, {{{MPx}}}) which //don't// have a class inheritance hierarchy. Examples: **@@{{{MFnDagNode}}}@@ / @@{{{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. Examples: **@@{{{MItDagNode}}}@@ iterates through the entire DAG (all nodes) **@@{{{MITMesh}}}@@ **@@{{{MItGeometry}}}@@ iterates through all aspects of a mesh. **many more... *@@{{{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. Examples: **@@{{{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... All Maya classes are contained in different Python packages (which closely mirror the c++ ones). All the below classes in ''{{{bold}}}'' are children of the {{{maya}}} package (as in {{{maya.OpenMaya}}}). It should be noted that the organization of Maya's API ''docs'' and its Python ''packages'' //are not the same//. Below describes how the Python //packages// are organized. For example, Maya's {{{OpenMaya}}} //docs// will list that the {{{MPxBakeEngine}}} class is part of it, but in fact it isn't (it's part of {{{OpenMayaMPx}}}). I think the docs try to lump similar classes together, which is nice, but it makes it harder to find them in the API, IMO. *''{{{maya.OpenMaya}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya.html]]) **Contains {{{M}}}, {{{MFn}}}, & {{{MIt}}} classes. *''{{{maya.OpenMayaMPx}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___m_px.html]]) **Contains {{{MFn}}} & {{{MPx}}} classes. *''{{{maya.OpenMayaAnim}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_anim.html]]) Animation related classes. **Contains {{{M}}}, {{{MFn}}}, & {{{MIt}}} classes. *''{{{maya.OpenMayaUI}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_u_i.html]]) Clases for manipulating the UI. **Contains {{{M}}} & {{{MFn}}} classes. *''{{{maya.OpenMayaFX}}}'' ([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_f_x.html]]) API modules for FX. **Contains {{{M}}} & {{{MFn}}} classes. *''{{{maya.OpenMayaRender}}}''([[docs|http://download.autodesk.com/us/maya/2009help/API/group___open_maya_render.html]]) API module for rendering. **Contains {{{M}}} & {{{MFn}}} classes. *''{{{maya.OpenMayaCloth}}}'' API for cloth systems **Contains {{{M}}} classes. I'm guessing those are closely tied to these .dll files: *{{{\Autodesk\Maya20XX\bin\}}} **{{{OpenMaya.dll}}} **{{{OpenMayaAnim.dll}}} **{{{OpenMayaFX.dll}}} **{{{OpenMayaRender.dll}}} **{{{OpenMayaUI.dll}}} Furthermore, the c++ header files they wrapper live here: {{{ \Autodesk\Maya20XX\include\maya\*.h }}} ---- If you want to see how the modules are organized, you can use the below code to have a view into some of the more popular ones. It will collect all the class types into lists (in addition to any constants in the module, and 'other' classes), and display them grouped together: {{{ import maya.OpenMaya as om import maya.OpenMayaMPx as ompx import maya.OpenMayaAnim as oma import maya.OpenMayaUI as omui import maya.OpenMayaFX as omfx for mod in [om, ompx, oma, omui, omfx]: M = [] MFn = [] MIt = [] MPx = [] others = [] constants = [] desc = ["M", "MFn", "MIt", "MPx", "others", "constants"] for item in sorted(dir(mod)): if item.startswith("MPx"): MPx.append(item) elif item.startswith("MFn"): MFn.append(item) elif item.startswith("MIt"): MIt.append(item) elif item.startswith("M"): if item[1].isupper() or item[1].isdigit(): M.append(item) else: others.append(item) elif item.startswith('k') and item[1].isupper(): constants.append(item) else: others.append(item) print "\n",mod for i,items in enumerate([M, MFn, MIt, MPx, others, constants]): print "\n\t----%s----"%desc[i] for m in sorted(items): print "\t\t", m }}} Prints (a //lot// of stuff. Truncated below): {{{ <module 'maya.OpenMaya' from 'c:\Program Files\Autodesk\Maya2010\Python\lib\site-packages\maya\OpenMaya.pyc'> ----M---- MAngle MAngle_internalToUI MAngle_internalUnit MAngle_setInternalUnit MAngle_setUIUnit MAngle_swigregister etc... ----MFn---- MFn MFnAmbientLight MFnAmbientLight_swigregister MFnAnisotropyShader MFnAnisotropyShader_swigregister etc... ----MIt---- MItCurveCV MItCurveCV_swigregister MItDag MItDag_swigregister MItDependencyGraph etc... ----MPx---- ----others---- NULL _OpenMaya __builtins__ __doc__ etc... ----constants---- kDefaultNodeType kEulerRotationEpsilon kMFnMeshInstanceUnspecified kMFnMeshPointTolerance kMFnMeshTolerance etc... etc... }}}  I give a more robust example of this in action here: [[API: undoing commands]] But to make it concise here: If you want a scripted plugin based on a [[MPxCommand|http://download.autodesk.com/us/maya/2010help/API/class_m_px_command.html]] to return some type of value, you need to do that via its {{{setResult}}} method. Code snippet below: {{{ import maya.OpenMayaMPx as ompx class Foo(ompx.MPxCommand): def __init__(self): ompx.MPxCommand.__init__(self) # A list later filled with data to return: self.returnList = [] def doIt(self, argList): # bunch of code, filling self.returnList... # then at the end, to set the return value: self.setResult(self.returnList) }}} I've found one downside to using Maya Python API calls that change the DG in scripts (rather than scripted plugins): It bypasses Maya's undo queue: I've tried wrappering the API code chunks in an [[undoQueue|How can I author an undo context manager?]], but it's had no effect. Why? Because Maya's command engine only undoes actual registered //commands//. What counts as a valid thing that //can// be undone? *A command (based on [[MPxCommand|http://download.autodesk.com/us/maya/2011help/API/class_m_px_command.html]]) authored via the Maya Python API as a scripted plugin (that supports undoing). *A command (based on [[MPxCommand|http://download.autodesk.com/us/maya/2011help/API/class_m_px_command.html]]) authored via c++ as a 'regular' plugin (that supports undoing). *Any standard Maya commands (whether executed via mel or Python). *A mel procedure \ script or Python function \ class \ module with calls to the above items. What can't be undone? *Piecemeal bits of Maya Python API code put in a function. A Maya Python API class isn't a //command//: It's a piece of code which can be used to //create a command// (or custom node, etc..). Maya undoes //whole registered commands//, not the parts and pieces which make those commands work. Which is explained in [[this note|http://download.autodesk.com/global/docs/mayasdk2012/en_us/index.html?url=files/Maya_Python_API_Differences_between_the_C_Maya_API_and_Maya_Python_API.htm,topicNumber=d28e15702]] in the Maya docs: >It is possible to mix Maya Python API calls along with Maya command calls in the same script. Undoing the operations of such a script will not be correct if the Maya Python API calls modify the model and do not properly support undo using the [[MPxCommand|http://download.autodesk.com/us/maya/2011help/API/class_m_px_command.html]] class. !!How to support undoing: ''If you want to undo API calls, you need to author them in a scripted plugin that supports undoing''. There is a general rule that "any API call that modifies the DG will need to be able to be undone". I've found two main techniques for doing this, I'll call them 'easy', and 'hard': *The easy method makes use of a {{{MDGModifier}}}, which more or less handles all the nasty undoing stuff for you by encapsulating all of the changes within it: You register the changes with the {{{MDGModifier}}} inside the {{{doIt()}}} method, but they aren't 'done' until the {{{redoIt()}}} method is called, or 'undone' when the {{{undoIt()}}} method is called. *But there are instances of API calls that modify the DG that don't seem to play nicely with the {{{MDGModifier}}} ^^([[MFnMesh.setVertexColors()|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_mesh.html#7394a3ef2fb9663c11018447270f1e33]] is an example of one that, while giving an arg to pass in a {{{MDGModifier}}}, seems to have no effect)^^. In that case you need to track the before & after states of the DG when the command executes and manually author code to set the state back when undone. This method is obviously, harder. Pseudo-code for each is listed here: ''Easy Method'', example using a {{{MDGModifier}}} to handle doing, and undoing. In this case we call to some fictitious mel {{{awesomeCommand}}} that will change the DG in some way. But you could have used any of the other methods on the {{{MDGModifier}}} to make changes as well. {{{ import maya.OpenMaya as om import maya.OpenMayaMPx as ompx class Eggs(ompx.MPxCommand): def __init__(self): ompx.MPxCommand.__init__(self) # Create the MDGModifier that will execute and undo our changes: self.dgMod = om.MDGModifier() def doIt(self): # This example registers a mel command to be # executed. It stores the change to be executed # later, by the dgMod.doIt() and dgMod.undoIt() methods. self.dgMod.commandToExecute("awesomeCommand") # Return the method that does the work: return self.redoIt() def redoIt(self): # Here the MDGModifier executes the command: return self.dgMod.doIt() def undoIt(self): # Here the MDGModifier 'undoes' the command: return self.dgMod.undoIt() def isUndoable(self): return True }}} ''Hard method'', example *not* using a {{{MDGModifier}}}. Extra custom methods are added to query and set the before\after states. {{{ import maya.OpenMayaMPx as ompx class Spam(ompx.MPxCommand): # Superclass overidden methods: def __init__(self): ompx.MPxCommand.__init__(self) def doIt(self): # Get the current (will be 'previous' when undone) state: self.prevValue = self.getCurrentState() # Define what the new state should be: self.newValue = self.getNewState() return self.redoIt() def redoIt(self): # Do work to apply the new state. self.action(self.newValue) def undoIt(self): # Do work to apply the previous state. self.action(self.prevValue) def isUndoable(self): return True # Custom methods, fill in with your own code. def getCurrentState(self): # Return the current\previous state of things pass def getNewState(self): # Return the new\updated state of things pass def action(self, state): # Do the work on the passed in state value, whether it's the previous # state, or the new state. pass }}} !!Practical Example The below example is a fully-functional "easy version" from above: We author a scripted plugin that creates a command that will find all nodes with duplicate names in the scene, and rename them to have unique names. And, you can undo the operation of course. Additionally (just for additional example) it does one other important thing: It ''returns a list of strings'' of the new names, using {{{OpenMayaMPx.MPxCommand.setResults}}}. The key magic is managing all changes to the DG through a [[MDgModifier|http://download.autodesk.com/us/maya/2010help/API/class_m_d_g_modifier.html]]. You're also required to implement and override the [[MPxCommand|http://download.autodesk.com/us/maya/2010help/API/class_m_px_command.html]] methods {{{undoIt()}}}, {{{redoIt()}}}, and {{{isUndoable()}}}. Also note the special return value from {{{doIt()}}}. {{{ # spRenameDupesCmd.py import re import maya.cmds as mc import maya.OpenMaya as om import maya.OpenMayaMPx as ompx # Define what our command's name is. Prefixing with 'k' is old-skool Hungarian-notation # convention for defining something as a constant. In Python this is usually done with # all caps, but this makes it more consistent with Maya c++ plugin code. kPluginCmdName= "renameDupes" # ----------------------------------- # Plugin code # We're building a *command* (rather than say, a DAG node), so we # instance the MPxCommand object: class RenameDupesCmd(ompx.MPxCommand): """ Rename all duplicate nodes in the scene. Return a list of the newly named nodes, and select them upon completion. """ # plugin initializer: def __init__(self): # initalize our superclass: ompx.MPxCommand.__init__(self) # The key to getting Maya to undo is to manage it all through the # MDGModifier class: self.dgMod = om.MDGModifier() # Keep track of the things we want to return: self.returnList = [] def doIt(self, argList): # Keep track of the nodes we rename, so they can be later picked: picker = [] # DG nodes always have unique names, since they're not parened. So we only need # to iterate over the DAG. dagIterator = om.MItDag() # MItDag while not dagIterator.isDone(): currentItem = dagIterator.currentItem() # MObject currentItemFunc = om.MFnDependencyNode(currentItem) # MFnDependencyNode # If the object doesn't have a unique name, and if it's not from a # referenced file, and if it's not 'locked', then rename it: if not currentItemFunc.hasUniqueName() \ and not currentItemFunc.isFromReferencedFile() \ and not currentItemFunc.isLocked(): # Generate a name for the node that is it's leaf name, with no numbers # on the end, with the addition of the hash char, which will cause the # new name to have an incremented number: fullname = dagIterator.fullPathName() leaf = fullname.split('|')[-1] endNum = re.findall('[0-9]+$', leaf)

# These calls to the dgMod only store the change for later execution,
# they don't actually do the change now.
if len(endNum):
noNum = leaf[:-len(endNum[0])]+"#"
self.dgMod.renameNode(currentItem, noNum)
else:
self.dgMod.renameNode(currentItem, '%s#'%leaf)

# Get the new name, and add it to our list:
dPath = om.MDagPath()
om.MDagPath.getAPathTo(currentItem, dPath)
picker.append(dPath.fullPathName())
# Add the renamed item to our return list:
self.returnList.append(dPath.fullPathName())
dagIterator.next()

if len(picker):
self.displayInfo('Renamed %s node(s)'%len(picker))
# We use a mel command here, rather than the API.  Why not?
mc.select(picker)
else:
self.displayInfo('No duplicate nodes to rename')

# This tells the command what to return upon completion:
self.setResult(self.returnList)
# Required to support undoing:
return self.redoIt()

def isUndoable(self):
return True

def undoIt(self):
# Undo any renaming previously done.
return self.dgMod.undoIt()

def redoIt(self):
# Actually do the renaming.
return self.dgMod.doIt()

# 'Creator Function' needs to return a pointer new instance of this type of class,
# based on how the c++ code works.
# As you can see, the OpenMayaMPx module has it's own functions (for which
# I can find no online documentation).
@classmethod
def cmdCreator(cls):
return ompx.asMPxPtr(cls())

# -----------------------------------
# Plugin registration\degregistration block
# initializePlugin & uninitializePlugin are *required functions*

def initializePlugin(mobject):
# create a MFnPlugin
mfnPlugin = ompx.MFnPlugin(mobject)
try:
# Try to register our plug by passing in the kPluginCmdName, 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.
mfnPlugin.registerCommand(kPluginCmdName, RenameDupesCmd.cmdCreator)
except Exception:
om.MGlobal.displayError("\nRenameDupesCmd registration failed\n")

def uninitializePlugin(mobject):
# create a MFnPlugin
mfnPlugin = ompx.MFnPlugin(mobject)
try:
# Try to deregister our plug by passing in the kPluginCmdName, and the command creator:
mfnPlugin.deregisterCommand(kPluginCmdName)
except Exception:
om.MGlobal.displayError("\nRenameDupesCmd deregistration failed\n")
}}}
After this is loaded in the plugin manager (see [[How can I load \ unload a Python scripted plugin?]]), you should be able to run the comnmand in either mel or Python (and capture the return if you want to):
{{{
import maya.cmds as mc
mc.renameDupes()
# Renamed 2 node(s) #
}}}
http://packages.python.org/MRV/
*"MRV is a multi-platform python development environment to ease rapid development of maintainable, reliable and high-performance code to be used in and around Autodesk Maya."
<<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.  Starting with Maya 2010, I finally got around to teaching myself the ~OpenMaya API, in 2012 ~PyMel, and in 2014 ~PySide/~PyQt, so there's newness for that showing up.
*//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 (2000?) 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).  Only thing I don't like is you can't add comments :(
''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)
*@@You are free to use any of this information  in your personal or professional work.  I only ask that you give me credit where credit is due.@@ : [[Copyright Information]].
*I give credit to those I get info from, whenever possible.
''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://tiddlyspot.com/  -- Get your TiddlyWiki hosted online for free (what you're using right now)
All of these will open the documentation in a web browser.
{{{
# Python code
import maya.cmds as mc

# show Python docs for xform:
mc.help('xform', language='python', doc=True)
# show mel docs for ls:
mc.help('ls', language='mel', doc=True)

# show the global documentation for mel:
mc.help(documentation=True, language='mel')
# show the global documentation for Python:
mc.help(documentation=True, language='python')

# show the global documentation for the API:
mc.showHelp("API/main.html", docs=True)

# show the global documentation for node types:
mc.showHelp("Nodes/index.html", docs=True)
}}}
When you access the Maya menu command here:
*Skin -> Edit Smooth Skin -> Paint Skin Weights Tool [Options]
What code does it execute?

That menu command calls to this {{{runTimeCommand}}}:
{{{
ArtPaintSkinWeightsToolOptions;
}}}
Which in turn is a wrapper for this command:
{{{
artAttrSkinToolScript 3;
}}}
Which is a global procedure called to in this script (on Vista 64):
{{{
C:\Program Files\Autodesk\Maya<version>\scripts\others\artAttrSkinToolScript.mel
}}}
----
The code that generates the tool options in the Attribute Editor lives here:
{{{
C:\Program Files\Autodesk\Maya2013\scripts\others\artAttrSkinProperties.mel
}}}
Super easy way to access your computers clipboard data from within Maya, via Python:
{{{
from PySide import QtGui

def copy(text):
QtGui.QClipboard().setText(text)

def paste():
return QtGui.QClipboard().text()
}}}
{{{
copy("this is my text")
print paste()
# this is my text
}}}
Maya comes with a few built-in texel density mel scripts:

C:\Program Files\Autodesk\Maya2019\scripts\others
* texCalculateTexelDensity.mel
* texGetTexelDensity.mel
* texSetTexelDensity.mel
That can be accessed via the 'UV Editor -> UV Toolkit Panel -> Transform -> Texel Density'
Or just call to them directly.
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>>
The [[cacheEvaluator|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/cacheEvaluator.html]] command is generated based on the {{{cacheEvaluator.mll}}} plugin.

It appears to be managed via a {{{maya.plugin.evaluator.cache_preferences.py}}} module, living here by default:
{{{
c:\Program Files\Autodesk\Maya20XX\Python\Lib\site-packages\maya\plugin\evaluator\cache_preferences.py
}}}
That directory is filled with other evaluator-related Python moudles.

This system is initialized during Maya startup via the {{{/Maya20XX/scripts/startup/initEvluationManager.mel}}}, by running the Python {{{cache_preferences_initialize()}}}.

There is an int {{{optionVar}}} called {{{cachedPlaybackEnable}}} that tracks whether it should be enabled or not:  It appears Maya immediately tracks any changes to that {{{optionVar}}}, and will enable/disable based on its state.
What are the current selected animation layers?
{{{
// global string array:
print $gSelectedAnimLayers; }}} You can also use these mel procs (living in {{{layerEditor.mel}}}) to both find the selected anim layer, and pick the contents (this is executed by 'Layers -> Select Objects'): {{{ string$lLayers[] = getSelectedAnimLayer("AnimLayerTab");
layerEditorSelectObjectAnimLayer($lLayers); }}} All {{{getSelectedAnimLayer()}}} is really doing is this: {{{ string$list[] = treeView -query -selectItem ("AnimLayerTabanimLayerEditor");
}}}
----
Pop the animLayer editor into it's own window:
{{{
openFloatingAnimLayerEditor();
}}}
Use {{{createAnimLayerEditor(string $parentLayout, string$toolName )}}} To put it into a UI of your choosing.
----
Delete empty animLayers:
{{{
deleteEmptyAnimLayers();
}}}
----
How can I highlight/select the root animation layer (by default, named {{{BaseAnimation}}}) and no others?
{{{
string $rootLayer = animLayer -query -root; if(size($rootLayer)){
string $animLayers[] = ls -type "animLayer"; for($i=0;$i<size($animLayers);$i++){ animLayerEditorOnSelect($animLayers[$i], 0); // From layerEditor.mel } animLayerEditorOnSelect($rootLayer, 1);
}
}}}
Or via ~PyMel:
{{{
import pymel.core as pm

rootLayer = pm.animLayer(query=True, root=True)
if rootLayer:
layers = pm.ls(type='animLayer')
for layer in layers:
pm.animLayer(layers, edit=True, selected=False)
pm.animLayer(rootLayer, edit=True, selected=True)
}}}
NOTE:  I've encountered that on some people's machines the pymel commands fail, and I had to revert back to mel wrappers :S
----
Commands:
*{{{animLayer}}}
----
Where is the anim layer mel code at?
{{{
\Autodesk\Maya20XX\scripts\startup\layerEditor.mel
}}}
----
How is the UI structured?
*The 'Layer Editor' ui in the Channel Box is managed by a form Layout.
*The section below the buttons is a {{{treeView}}} control
----
How are new anim layers made when you press the button?
* A 'Run Time Command' called {{{SelectedAnimLayer}}} is executed, which calls to the global proc {{{layerEditorCreateAnimLayer( true, false)}}} (which lives in {{{layerEditor.mel}}}).
*This in turn runs the {{{animLayer}}} command which physically builds the layer.


Some links:
*http://www.akeric.com/blog/?p=1463 (my own blog)
*http://zoomy.net/2009/07/26/basic-arduino-to-maya-communication/
This is a scratchpad of info as I learn more about this subject.  A lot of this is just pulled directly from the Maya docs.
More notes over on [[API: basics]]
Official online examples here: [[API : Example Code]]
Or you can find examples on your local drive:
{{{
C:\Program Files\Autodesk\Maya20XX\devkit\plug-ins\scripted\*.py
}}}
----
The [[Maya Learning Channel|http://www.youtube.com/user/MayaHowTos/videos?view=0]] Youtube page has a growing series of videos as well:
*[[Creating a Python command plug-in - Part 1: Undo and redo|http://www.youtube.com/watch?v=BZyXe3MhEyI&list=PL2611366C5C04BF9B&index=2]]
----
Collection of simple example scripted plugins I've been authoring for the wiki can be shown by clicking on this tag:
*[[scripted plugin]]
----
Notes:
*May be good to prefix 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. All scripted plug-in must:
*Define {{{initializePlugin()}}} and {{{uninitializePlugin()}}} entry and exit functions.
**Register and unregister the proxy class within these entry points.
*Implement a creator function (arbitrarily named, but something like {{{creator()}}} is probably a good idea).
*Properly initialize the plugin proxy class via the {{{__init__()}}} method.
Depending on the type of plugin you're authoring, there will be other required functions (an 'initializer' in the case of node creation) or proxy class methods ({{{doIt()}}} in the case of a command, {{{compute()}}} in the case of a node, etc).
----
Plugins have different 'flavors', or classes.
Plugins have different categories.  All subclasses of the {{{maya.OpenMayaMPx}}} module.  It should be noted that this module has functions the subclasses can inherit (like {{{asMPxPtr}}} for example), but I can find no online documentation for the {{{OpenMayaMPx}}} specifically.
>In the plugin class's documentation, the sections listed as "__Public Member Functions__" are the inherited superclass methods at your disposal when writing your own plugins.
*{{{maya.OpenMayaMPx}}}
**{{{MPxCommand}}} : make custom mel commands.
***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...
**{{{MPxNode}}} : For making custom DG nodes.
**{{{MPxLocator}}} : A type of {{{MPxNode}}} for custom drawing
**{{{MPXContext}}} : (and {{{ContextCreatorCmd}}}) : For handling mouse\user inputs
** many 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.
**Specific methods needing implementation based on plugin type:
***'doIt' method:
****What actually does the work in the instance of the plugin //command// object.
***'compute' method:
****What actually does the work in the instance of the plugin //node// object.
***Different plugins have other required method implementations.
*Special functions (I author these as classmethods):
**Creator Function:
***Returns a new instance of the class
***Needs to be one per plugin class being authored.
**Syntax Function:
***When creating commands that can accept flags, this special function needs to be setup to create them.
*Registration/Deregistration:
**If multiple plugins classes are being authored in the same Python module, they can share the same registration\deregistration block
**Tells Maya: about new plugins, their types, hooks needed to instance them.
Common Python plugin modules:
{{{
# All plugin functionality lives here:
import maya.OpenMayaMPx as ompx
}}}
I like to wrapper Maya's nodes in classes, and write mini-API's around them.  Often times, to make sure the users won't accidentally mess with the nodes, I use Maya's {{{lockNode}}} command to lock the node after I modify it.  But it can become a pain to constantly have to author blocks of code that 'unlock the node.... do some work... relock the node'.
To help make this easier, one can author either a {{{with}}} 'context manager' for use within a method or function, or a 'decorator' that can wrapper a method or function.
!Decorator
{{{
import maya.cmds as mc

def unlockRelock(func):
"""
An 'unlock/relock' decorator: It's designed specifically to look for a
self.node attr on the instance method it wrappers, and attempt to unlock
that Maya node, execute the method, then relock that Maya node.  Note, the
node/attr doesn't have to exist at time of method call.
"""
def wrapped(*args, **kwargs):
itself = args[0] # This is the 'self' arg passed to the method this wrappers.
if hasattr(itself, "node"):
if mc.objExists(itself.node):
try:
mc.lockNode(itself.node, lock=False)
except Exception, e:
pass
result = func(*args, **kwargs)
if hasattr(itself, "node"):
if mc.objExists(itself.node):
try:
mc.lockNode(itself.node, lock=True)
except Exception, e:
pass
return result
return wrapped
}}}
! {{{with}}} Context Manager:
{{{
class UnlockRelock(object):
"""
Context Manager for unlocking and relocking the provided node. Note, the node doesn't
yet have to exist at time of context manager creation.
"""
def __init__(self, node):
"""
Parameters:
node : string : The node to unlock/relock.
"""
self.node = node
def __enter__(self):
if self.node:
if mc.objExists(self.node):
try:
mc.lockNode(self.node, lock=False)
except Exception, e:
pass
def __exit__(self, exc_type, exc_value, traceback):
if self.node:
if mc.objExists(self.node):
try:
mc.lockNode(self.node, lock=True)
except Exception, e:
pass
# Raise any exceptions:
return False
}}}
! Both In Action:
{{{
class Spam(object):
def __init__(self, node):
self.node = node

@unlockRelock
def usingDecorator(self):
mc.setAttr('%s.translateX'%self.node, 10)

def usingContextManager(self):
with UnlockRelock(self.node):
mc.setAttr('%s.translateX'%self.node, 10)
}}}
You can find a link to a pdf for Maya certified hardware here:
https://knowledge.autodesk.com/support/maya/troubleshooting/caas/simplecontent/content/maya-certified-hardware.html
That PDF will tell you the certified drivers per graphics card per version.
Common ones:
| Maya Version | Card | Driver | Link |
| 2019 | ~NVidia |  411.95 |  |
Maya has no built-in way to average a matrix.  However, if you have {{{numpy}}} installed, this is actually surprisingly easy.
The below example works if the array was a single list of numbers, or the list of list of numbers.  Maya matrices are 16 long, or 4 groups of 4.  For simplicity, 2 groups of 2:
{{{
from numpy import array

a1 = array( [[2.0, 2.0], [2.0, 2.0]] )
a2 = array( [[3.0 ,3.0], [3.0 ,3.0]] )
arrList = [a1, a2]
avg = sum(arrList)/len(arrList)

print avg
[[ 2.5  2.5]
[ 2.5  2.5]]
}}}
If you don't have numpy, you'd need to write a function that would, for each value in each array, add it to the corresponding indices in the other arrays and  then divide by total arrays (basic average math) and plug that into a new array.
Had issues where shifting large amounts of keys coud hang Maya for a //very long time//.  Reported this with repro to Autodesk, and they said the below code should resolve:
{{{
import maya.cmds as mc
for dagObject in mc.ls(dagObjects=True):
mc.setAttr('%s.ghosting'%dagObject, 0)
}}}
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?]] This subject is about batching Maya files in a session of Maya //other than the one that is currently open//, via Python. It's one thing to (via the //currently opened Maya//) loop over a list of files opening them in succession and executing code. But if you want to launch //external// Maya's in batch mode (so as to not disturb your current Maya session), open a file, and execute code on that file (all in Python), it's a different story. To batch file via Maya with //mel//, it's pretty straightforward via {{{maya.exe}}}: {{{ > maya -batch -file someMayaFile.ma -command someMelProc }}} There is {{{mayapy.exe}}} as well, but this doesn't launch Maya: It launches Maya's version of Python. It is a Maya mirror of {{{python.exe}}}. However, once it has been launched, Maya itself can be imported in as a Python module, and then batching can take place. Method A explains this below. ---- Different methods below. I came up with Method C first, got suggested Method B, then sort of combined the two to come up with Method A. * Method A & C launches a new Maya in batch mode from an //existing// Maya. * Method B launches a new Maya in batch from from a .bat file on disk. !Method A The example below will batch over all the passed in Maya files, and saves a new group node in them as proof it worked. Some of the magic is using {{{subprocess.call}}} to call to {{{mayapy.exe}}}, and passing in an additional argument that is the name of the current file to work on. Another bit of trickery is having the module do double-duty: When executed in //GUI Maya// the user runs its main() function to start it up, but when it is called to via {{{mayapy.exe}}} putting it in 'batch mode', the top & bottom section '{{{if __name__ == "__main__":}}}' are executed as well which both imports {{{maya.standalone}}} (thus now physically has Maya running inside of {{{mayapy.exe}}}) and calls to the {{{batchOperation()}}} function, which can then query, via {{{sys.argv}}} the command-line arguments passed in (which are the names of the files to batch over). I have a feeling that's not terribly clear. I hope the code makes more sense ;) I'll label each thing has it's executed so you can see the data flow. GUI mode starts with GUI A and ends with GUI D, which then starts BATCH A and ends with BATCH E. {{{ # batchTest.py import imp import subprocess if __name__ == "__main__": # If we're in 'batch' mode, it means that mayapy.exe is running, # so load maya.standalone into it: import maya.standalone maya.standalone.initialize(name='python') # If you have a userSetup.py that is needed for happy Maya execution, call # to it here: # Optional: Starting sometime around Maya 2016, I ran into issues where Maya's MASH # package's userSetup.py would be sourced before mine: This solution allows you to # forcefully inject yours on top if it: # imp.load_source('userSetup', 'c:/path/to/my/userSetup.py') import userSetup userSetup.main() import maya.cmds as mc def main(paths): """ Launched in the gui version of Maya, manually, or via some other script. Parameters : paths : string\list : Path(s) to Maya files to batch over. """ if not isinstance(paths, list): paths = [paths] args = ['mayapy', __file__, '-'] args.extend(paths) # This fires off another version of Maya in 'batch' mode, calling back to # this code, executing the 'batch only' parts. Luckily, the current version # of Maya calling it waits for the other version to complete doing it's thing # before continuing on in this code... subprocess.call(args) def batchOperation(): """ The work done by the batch: This has been called to while Maya is in batch mode, via the subprocess.call() func in the main() function: Because of this, we capture the incoming args via sys.argv. A single instance of Maya is launched, and then all the files are batched over in that instance, and processed """ # The sys.argv vals match the subprocess call above # sys.argv[0] : The path to this module ( __file__ ) # sys.argv[1] : '-' # sys.argv[2] + : Path(s) to the files to batch over paths = sys.argv[2:] # If there is an error, the shell will just disappear. So we wrap it in a # try\except, allowing it to stay open to help track down bugs. try: mc.file(prompt=False) for fil in paths: mc.file(fil, force=True, open=True) mc.group(empty=True, name='I_was_made_in_batch_mode', world=True) mc.file(force=True, save=True) except Exception, e: print "Exception:", e raw_input("Encountered an exception, see above. Press Enter to exit...") finally: mc.file(prompt=True) # When called to via batch mode, run this code. This line must come after the # function it's calling. if __name__ == "__main__": batchOperation() }}} To run in Maya: {{{ import batchTest batchTest.main(['c:/temp/myFileA.ma', 'c:/temp/myFileB.ma']) }}} You should see a shell pop up for the new instance of Maya that is launched (via mayapy.exe), and then each path is opened and batch processed in that shell. The gui version of Maya will wait until completion of the batch to continue on with its code. !Method B This snippet will launch a myModule.py file living in the same directory (per the special {{{%~dp0}}} variable) as the bat script, using a pre-made env-var storing the path to the mayapy.exe file. It will also pass the name of the item dragged&dropped onto it via the {{{"%1"}}}. Note that either of these examples could then call to the code in Example A, above. {{{ "%MAYA_PY_EXECUTABLE%" "%~dp0\myModule.py" "%1" }}} This (modified) example was given to me by Keir Rice. Thanks Keir! Make a batch script: {{{batchTest.bat}}} . It runs {{{mayapy.exe}}}, which is //Maya's// version of Python. {{{ REM Launcher batch file: batchTest.bat "%ProgramFiles%\Autodesk\Maya2010\bin\mayapy.exe" "c:\temp\test.py" pause }}} *Add {{{pause}}} to the end of the batch script if you want the shell to stay open upon completion. Note that if mayapy is part of your Windows path, you can leave off the directory before it, and the .exe on the end of it. Then make the python module it calls to: {{{ # python script to run: c:\temp\test.py import maya.cmds def TestFunction(): print maya.cmds.about(version=True) if __name__ == "__main__": # Init Maya: import maya.standalone maya.standalone.initialize(name='python') # If you have one: import userSetup userSetup.main() # Run your code: TestFunction() }}} Finally, run the bat script. Results should be printed in the shell. !Method C: This is one I came up with before I knew about Method A or B, above. Key Components: (the file-names aren't important, just illustrating the example) *{{{mainModule.py}}} : This is the Python module that orchestrates the whole thing. It can be executed from Maya, or the command line. *{{{melScript.mel}}} : This is a 'wrapper' script that our batch operation will call to. It in turn, via the mel {{{python}}} command, will execute the Python code we want to have effect the scene files. *{{{pythonModule.py}}} : This is the Python module that our mel script calls to. In this example, it executes the {{{main()}}} function inside. ---- Here is a bit of code from {{{mainModule.py}}}. By calling to {{{subprocess.call()}}}, we're able to launch Maya in batch mode on a specific file, executing our wrapper mel command on it: http://docs.python.org/library/subprocess.html {{{ # mainModule.py import subprocess subprocess.call(['maya', '-batch', '-file', 'c:\\myFile.ma', '-command', 'melScript']) }}} ---- This is the guts of our wrapper mel script. All it does is call to the Python code we care about: {{{ // melScript.mel global proc melScript(){ python("import pythonModule"); python("pythonModule.main()"); } }}} ---- {{{pythonModule.py}}} and its {{{main()}}} function can prettymuch do whatever you'd like to that scene file: {{{ # pythonModule.py import maya.cmds as mc def main(): # do stuff! }}}  Ran across this post describing how to code in-between blendshapes,... and the absolute chaos that is needed to pull it off: http://forums.cgsociety.org/showthread.php?t=1079399 <<gradient horiz #ffffff #ddddff #8888ff >> The mel wiki blog. *[[2010 01 20]] - Added the HARDWARE category. *[[2010 01 13]] - Added the INFO category. *[[2009 05 13]] - Added API category *[[2008 11 19]] - Updated Python tagging *[[2008 08 14]] - Created the Mel Wiki Guest Book *[[2008 07 05]] - A new mel tiddlywiki online! *[[2008 06 08]] - Blog about my new blog (blog blog blog) *[[2008 05 21]] - tiddlywiki upgraded *[[2008 05 20]] - Python Wiki! *[[2008 03 31]] - More Processing. *[[2008 03 11]] - Added 'Other Links' *[[2008 03 07]] - Added the TROUBLESHOOTING category. *[[2008 02 18]] - Processing! *[[2008 01 29]] - Added [[All Subjects]] tiddler. *[[2008 01 08]] - New feature: [[Visual Guide of UI Controls]] *[[2008 01 07]] - Tiddlyspot back up. And... robots! *[[2008 01 04]] - rain! Tiddlyspot down... *[[2008 01 02]] - New [[PYTHON]] category, update wiki UI formatting. *[[2007 04 27]] - mel wiki is born Where can you download Maya's 'bonus tools'? http://area.autodesk.com/bonus_tools  The below code was originally on [[Brian Ewerts 'match' web page|http://ewertb.soundlinker.com/mel/mel.094.htm]], which went down for a while. It appears to be back up! But, I've left this info in here. http://ewertb.soundlinker.com/maya.htm *Strip component {{{ string$node = "pCube1.f[2]";
string $no_component = match "^[^\.]*"$node;
// Result: "pCube1" //
}}}
*Extract component or attribute, with '.'
{{{
string $node = "pCube1.f[2]"; string$component = match "\\..*" $node; // Result: ".f[2]" // string$nodeAttr = "blinn1.color";
string $attrName = match "\\..*"$nodeAttr;
// Result: ".color" //
}}}
*Extract attribute name, without '.'
{{{
string $node = "pCube1.f[2]"; string$component = substitute "^[^.]*\\." $node ""; // Result: "f[2]" // string$nodeAttr = "blinn1.color";
string $attrName = substitute "^[^.]*\\."$nodeAttr "";
// Result: "color" //
}}}
*Extract parent UI control from full path
{{{
string $uiControl = "OptionBoxWindow|formLayout52|formLayout55|button6"; string$uiParent = substitute "|[^|]*$"$uiControl "";
// Result: OptionBoxWindow|formLayout52|formLayout55 //
}}}
*Strip trailing CR, if any. This is useful when processing text input read from a file using 'fgetline'. (or you could just use the 'strip' command, but the concept is important)
{{{
string $input = "line\n";$string $line = match "^[^(\r\n)]*"$input;
// Result: "line" //
}}}
*Extract directory from path Note that this leaves a trailing slash. This is typically desired, as it makes it easy to simply append a filename, and the slash is required for the ‘getFileList?’ command to return anything useful.
{{{
string $path = "C:/AW/Maya5.0/bin/maya.exe"; string$dir = match "^.*/" $path; // Result: "C:/AW/Maya5.0/bin/" }}} *Extract file from path {{{ string$path = "C:/AW/Maya5.0/bin/maya.exe";
string $filepart = match "[^/\\]*$" $path; // Result: "maya.exe" }}} *Strip numeric suffix {{{ string$node = "pCube1|pCubeShape223";
string $noSuffix = match ".*[^0-9]"$node;
// Result: "pCube1|pCubeShape"
}}}
*Extract numeric suffix
{{{
string $node = "pCube1|pCubeShape223"; string$suffix = match "[0-9]+$"$node;
// Result: "223" //
}}}
*Extract short name of DAG or control path
{{{
string $dagPath = "pCube1|pCubeShape223"; string$shortName = match "[^|]*$"$dagPath;
// Result: pCubeShape223 //
}}}
Ran into an issue in Maya 2019 around mocap, humanIK, and animation caching:
* If you have mocap assets that have been characterized via HIK, //and// you have animation caching turned on, if you start importing multiple mocap assets into your scene, Maya's performance will grind to a halt, and memory will skyrocket.
* If you disable animation caching, and import in multiple mocap files with HIK, the memory stays low, and performance stays good.
* If you delete the HIK data in the mocap, and reimport it with caching on, the performance drops, but it's not as bad as with HIK there.

Basically, if you're using mocap with or without HIK, you need to disable animation caching.

Furthermore, I can't get the memory used by animation caching to clear out:
After you get the slowdown \ memory hit: Even if I disable caching, clear the cache, and open a new Maya scene, Maya's memory can still be very,very high.  The only way to fix is to restart Maya.
This does it all, and all written in Python:
* https://github.com/chrisdevito/OBB

Pi is the ratio of the circumference of a circle to its diameter, approximately {{{3.14159}}}.
So if you wanted to calculate this yourself:
{{{
# Python code:

# diameter is circumference / pi:
diameter = 5.0

# circumference is pi * diameter:
circumference = 15.7079632679

# so:
pi = circumference/diameter
# 3.14159265359
}}}
Of course, you can always just:
{{{
from math import pi
print pi
3.14159265359
}}}
But it's nice to actually see how it's done ;)
This post gives a nice solution;
https://stackoverflow.com/questions/24438720/arbitrary-barycentric-coordinates-on-a-polygon-3d-maya-api
{{{
def baryInterp(vecA, vecB, vecC, vecP):
'''
Calculates barycentric Interpolation of a point in a triangle.

:param vecA - OpenMaya.MVector of a vertex point.
:param vecB - OpenMaya.MVector of a vertex point.
:param vecC - OpenMaya.MVector of a vertex point.
:param vecP - OpenMaya.MVector of a point to be interpolated.

Returns list of 3 floats representing weight values per each point.
'''
v0 = vecB - vecA
v1 = vecC - vecA
v2 = vecP - vecA

d00 = v0 * v0
d01 = v0 * v1
d11 = v1 * v1
d20 = v2 * v0
d21 = v2 * v1

denom = d00 * d11 - d01 * d01
v = (d11 * d20 - d01 * d21) / denom
w = (d00 * d21 - d01 * d20) / denom
u = 1.0 - v - w

return [u, v, w]
}}}
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.
When you execute a playblast via its UI, you can open a 'Video Compression' sub-ui allowing you to adjust the compression settings.  The {{{playblast}}} command has {{{compression}}} arg you can pass a string value to that, will, from the docs:
>Specify the compression to use for the movie file. On Unix, valid values are "mvc1", "mvc2", "jpeg", "rle", "none". (Default is "mvc2".)  On Windows, if the argument is an empty string ("") a dialog comes up allowing the user to set the codec, data rate, keyframing, compression quality, and codec settings. (This dialog will also come up with playblast -options.) Use of any other string will utilize the codec that was previously set using the dialog.
So on Unix, you can specify values.  On Windows, if you pass it an empty string, it will pop up a dialog box letting you set values.  And, if you "use any other string" will "use the previous setting".

So this tells me that ''NO, you can NOT specify compression options via mel''.  Which kind of sucks.

Searching the internets, it appears that all the other brains have come to the same conclusion.  There are {{{optionVar}}}s that store certain playblast values, but changing them seems to have no effect on the final product, or causes errors.
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
It basically turns your joystick into a mocap device, which Maya can then interface with.
[img[http://static.highend3d.com/downloadsimages/1086/screen1.jpg]]
In Maya 2019, when certain custom attrs were keyed on one of our rigs, after you saved the scene, quit Maya, and reopened the scene, once you changed the frame, Maya would crash.

After a *lot* of research, it came down to a weird evaluation graph bug.  I found that if I opened Maya, set it to DG mode, then opened the file, the file would work.  But if I put it back in EG mode, it would crash again.

After a lot more research, I found out that if I turned off (in Maya's prefs) 'GPU Override' & 'Include controllers in evaluation graph', and then turned off GPU Overide, after I reopened the scene, if I turned EG back on, refreshed the scene (set the frame to itself and {{{refresh -force}}}), then enabled GPU Override (& refresh) and then turned the 'Include controllers...' back on, the crash went away.

To resolve this, I made pre-and post scene open callbacks via :
{{{
# and
}}}
That managed all this, and it solved the crash.
----
Figured out what was actually crashing it though:
I had a {{{scriptJob}}} that was triggering on {{{timeChange}}}, that would update the visibility state of our rigs based on what state they were in (IK, FK): By wrappering the function the {{{scriptJob}}} called to in an {{{evalDeferred -lowestPriority}}}, it made the bug go away.  :-S

Update:  I figured out why //that// code was crashing:  The visibility code was querying all attrs with a certain name in the scene that control the IK/FK state ("ikFk"), then iterating over them.  The issue was, those were //proxy// (aka, instance) attrs,  So if each limb had an IK & FK controller setup, each with three controllers, each with a proxy attr, that woudl be 6 attrs per limb that are all proxy.  You don't want to iterate over all 6, you only need to use 1, since... they're instanced.  Once I instead filtered by the root proxy attr and only updated it, this resolve the remaining bug.  Something about iterating over proxy attrs in the Evaluation Graph causes instability.  See: [[Working with proxy attrs]]
As of Maya 2016, there's an annoying bug:
* If you open a scene, and change its file type (from ma to mb or vice versa), it will lose the scene name.
* If you open a scene, save the scene, then change the filetype, the bug doesn't show up.
Example of it acting broken:
{{{
import maya.cmds as mc

mc.file(r"C:/path/to/my/file.mb", open=True, force=True)
fileType1 = mc.file(query=True, type=True)
fileName1 = mc.file(query=True, sceneName=True)
print fileType1, fileName1
['mayaBinary'] C:/path/to/my/file.mb

# Change the file type:
mc.file(type='mayaAscii')

fileType2 = mc.file(query=True, type=True)
fileName2 = mc.file(query=True, sceneName=True)
print fileType2, fileName2
[u'mayaAscii']  # !!!! no more filename, it is an empty string !!!!!!!
}}}
Here is it not broken:
{{{
import maya.cmds as mc

mc.file(r"C:/path/to/my/file.mb", open=True, force=True)
fileType1 = mc.file(query=True, type=True)
fileName1 = mc.file(query=True, sceneName=True)
print fileType1, fileName1
['mayaBinary'] C:/path/to/my/file.mb

# Save the file
mc.file(save=True, force=True)

# Change the file type:
mc.file(type='mayaAscii')

fileType2 = mc.file(query=True, type=True)
fileName2 = mc.file(query=True, sceneName=True)
print fileType2, fileName2
[u'mayaAscii'] C:/path/to/my/file.ma
}}}
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....
{{{
// mel
int $exists = attributeQuery -ex -n nodeName attrName; }}} {{{ # Python import maya.cmds exists = mc.attributeQuery('attrName', node='nodeName', exists=True) }}} 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  {{{polyEvaluate}}} is a really versatile command. Things it can do: *For a whole polygonal object, or just a component selection, return the number of: **vertex **edge **face **uv coordinate **triangle **shell *Bounding box data: **For the given object in 3d space, or it's uv's in 2d space. **For the selected components in 3d space, or for the selected uv's in 2d space. **The surface area of the faces in worldspace, or local space ---- Also see: *[[Command: polyInfo]]  {{{polyInfo}}} aids in finding dependencies between polygonal components: *How can I find the specific poly components connected to another poly component? **faces that share the specified edge **faces that share the specified vertex **edges defining a face **vertices defining a face **vertices defining an edge **Edges connected to a vertex *Find non-manifold vertices, edges, and lamina faces? *How can I find the "normal" value of face? Example, get a face's normal value: {{{ polyInfo -fn; // however this will return a string that will need to be tokenized. }}} ---- Also see: *[[Command: polyEvaluate]]  ''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?]]. Here is a growing list of commands for the new coder that I consider super useful. Data Query: *[[ls|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/ls.html]] : Used for querying nodes in Maya. By selection, by type, by name, wildcard (*) by any number of different rules. *[[optionVar|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/optionVar.html]] : Store and query data that is persistent between Maya scenes. *[[objExists|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/objExists.html]] : Query a nodes existence. *[[objectType|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/objectType.html]] : Query what type of object a node is. *[[polyListComponentConversion|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/polyListComponentConversion.html]] : Great for converting between poly data: Faces to verts, mesh to faces, etc. * {{{type(variable)}}} : Python builtin, tells you what type of data your variable is as a string name. * {{{dir(variable)}}} : Python builtin to return the attributes on a Python variable, as a list of strings. * {{{print "spam"}}} : Python 2 statement to print the given data. In Python 3 it's a function: {{{print("spam")}}}. Node Transformation: *[[move|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/move.html]] : Translate the node. *[[rotate|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/rotate.html]] : Rotate the node. *[[scale|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/scale.html]] : Scale the node. *[[xform|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/xform.html]] : All sorts of translate,rotate, and scale operations can be performed, including matrix application. *[[makeIdentity|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/makeIdentity.html]] : 'Freeze' a nodes transforms. *[[pointPosition|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/pointPosition.html]] : Query component level (vertex, cv) or transform-level pivot information, in local or worldspace. Attribute Manipulation: *[[getAttr|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/getAttr.html]] : Query the value of a given attribute. *[[setAttr|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/setAttr.html]] : Set the value of the given attr. *[[addAttr|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/addAttr.html]] : Add a custom attribute to a node. *[[deleteAttr|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/deleteAttr.html]] : Delete a custom attr from a node. *[[attributeQuery|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/attributeQuery.html]] : Query an attributes existence, and many other things about it. * [[listAttr|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/listAttr.html]] : like {{{ls}}} but for attributes on a node; query by different things: User defined, keyable, locked, etc. Connection Info: * [[connectAttr|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/connectAttr.html]] : Connect one attribute to another. * [[disconnectAttr|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/disconnectAttr.html]] : Disconnect one attr from another. *[[listConnections|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/listConnections.html]] : Find incoming and outgoing connections to nodes. Family Tree: *[[listRelatives|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/listRelatives.html]] : Find parent and children nodes. *[[listHistory|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/listHistory.html]] Find the nodes upstream & downstream from this one. Node Modification: *[[select|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/select.html]] : Select (or deselect) one or more nodes. *[[delete|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/delete.html]] : Delete nodes *[[parent|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/parent.html]] : Parent nodes together. *[[rename|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/rename.html]] : Change the name of given node. Node Creation: *[[createNode|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/createNode.html]] : Create a node of a given type. * [[shadingNode|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/shadingNode.html]] : Much like {{{createNode}}}, but can flag nodes as a certain type (for example {{{asUtility=True}}}) so Maya will make them display in different edtiros (like the Hypershade). *[[duplicate|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/duplicate.html]] : Duplicate the given nodes. *[[group|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/group.html]] : Create a new empty {{{transform}}} node (aka, 'group'). *[[joint|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/joint.html]] : Create a new {{{joint}}} node. *[[spaceLocator|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/spaceLocator.html]] : Create a new {{{locator}}} node. *[[sets|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/sets.html]] : Create or modify sets of nodes. Animation: *[[keyframe|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/keyframe.html]] : Keyframe query and modification. *[[setKeyframe|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/setKeyframe.html]] : Create keyframe data. *[[findKeyframe|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/findKeyframe.html]] : Tools to find keyframe data. *[[currentTime|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/currentTime.html]] : Query & set the current frame. *[[playbackOptions|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/playbackOptions.html]] : Set/query the framerange, framerate, etc. Scene Access: *[[file|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/file.html]] : Query scene name, open/import/reference a file, many other file-related options. *[[referenceQuery|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/referenceQuery.html]] & [[referenceEdit|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/referenceEdit.html]] : Dealing with references in the scene. *[[namespace|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/namespace.html]] & [[namespaceInfo|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/namespaceInfo.html]] : For dealing with namespaces. Just comparing what the Output Window spits out on Maya 2016 startup, for different graphics cards. !NVIDIA ~GeForce GTX TITAN {{{ Initialized VP2.0 renderer { Version : 6.3.16.0. Feature Level 5. Adapter : NVIDIA GeForce GTX TITAN Vendor ID: 4318. Device ID : 4101 Driver : nvwgf2umx.dll:10.18.13.5560. API : DirectX V.11. Max texture size : 16384 * 16384. Max tex coords : 32 Shader versions supported (Vertex: 5, Geometry: 5, Pixel 5). Active stereo support available : 0 GPU Memory Limit : 6144 MB. CPU Memory Limit: 31089.4 MB. } OpenCL evaluator is attempting to initialize OpenCL. Detected 1 OpenCL Platforms: 0: NVIDIA Corporation. NVIDIA CUDA. OpenCL 1.2 CUDA 7.5.0. Supported extensions: cl_khr_byte_addressable_store cl_khr_icd cl_khr_gl_sharing cl_nv_compiler_options cl_nv_device_attribute_query cl_nv_pragma_unroll cl_nv_d3d9_sharing cl_nv_d3d10_sharing cl_khr_d3d10_sharing cl_nv_d3d11_sharing cl_nv_copy_opts OpenCL evaluator choosing OpenCL platform NVIDIA Corporation. Choosing OpenCL Device GeForce GTX TITAN. Device Type: GPU Device is available. }}} !AMD Radeon HD 7900 Series {{{ Initialized VP2.0 renderer { Version : 6.3.16.0. Feature Level 5. Adapter : AMD Radeon HD 7900 Series Vendor ID: 4098. Device ID : 26520 Driver : aticfx64.dll:13.152.1.8000. API : DirectX V.11. Max texture size : 16384 * 16384. Max tex coords : 32 Shader versions supported (Vertex: 5, Geometry: 5, Pixel 5). Active stereo support available : 0 GPU Memory Limit : 3072 MB. CPU Memory Limit: 31089.4 MB. } OpenCL evaluator is attempting to initialize OpenCL. Detected 1 OpenCL Platforms: 0: Advanced Micro Devices, Inc.. AMD Accelerated Parallel Processing. OpenCL 1.2 AMD-APP (1268.1). Supported extensions: cl_khr_icd cl_amd_event_callback cl_amd_offline_devices cl_khr_d3d10_sharing cl_khr_d3d11_sharing cl_khr_dx9_media_sharing OpenCL evaluator choosing OpenCL platform Advanced Micro Devices, Inc.. Choosing OpenCL Device Tahiti. Device Type: GPU Device is available. OpenCL device does not support out of order execution }}} This article has a good short overview: https://3deeplearner.com/binary-comp/ : Under 'Solving binary compatibility'. But for prosperity: <<< ''Solving binary compatibility'' So, if you want to run any of these libraries, you will probably need to build many of their dependencies from source using the appropriate version of Visual Studio for your Maya installation. Here is a list: Maya 2013-2014 – Visual Studio 2010 (MSC v.1600) Maya 2015-2017 – Visual Studio 2012 (MSC v.1700) Maya 2018 – Visual Studio 2015 (MSC v.1900) After installing the appropriate version of Visual Studio, you’ll need to download the libraries from their ~GitHub repos. Just Google for Github repo and the name of the library. Here is Numpy’s repo as an example (https://github.com/numpy/numpy). After that, you will need to run the repo’s {{{setup.py}}} in build mode, so it builds a version of the repo using the proper compiler. There are some tricks involved in doing this. I recommend you read this article for a thorough explanation: http://around-the-corner.typepad.com/adn/2017/05/how-to-build-pillow-on-windows-with-maya-2017.html. Apart from using the adequate compiler you want setup.py to run on the proper Python interpreter (Maya’s). You might bump into additional complications in each library, which, for the most part, can be solved by reading and interpreting the error messages. ''Share'' Finally, if you want to save other people’s time you can then distribute this Maya friendly Python extensions building a Wheel, that can be installed using PIP. For that, I recommend reading this article: https://packaging.python.org/tutorials/distributing-packages/#id74. <<< Also see: * [[Getting pywin32 working in Maya]] * [[API : How to compile a plugin]] * [[Python & Qt versions in Maya]] * [[Maya compiler versions]] The below ~PyMel code will generate a completely new local axis matrix by providing a single vector. The code is very simple, and only works a fixed way: You provide a vector that specifies the 'x leg' of the new local axis, and it will compute the corresponding vectors for the y & z legs: The z leg will attempt to match the world z, and the new y leg will be the cross product of the new x & z. This is basically what an {{{aimConstraint}}} is doing, and the code could be expanded to be far more robust, emulating similar features of an {{{animConstraint}}}. {{{ import pymel.core as pm def computeLocalAxis(newX): """ By providing a newX Vector leg for the new local axis, this will compute the Y & Z legs. The Z leg will be most closely aligned with the worldZ, Y leg will be a cross of the X & Z legs. Return is a PyMel Matrix of the new local axis, with the position zeroed: [newX, computedY, computedZ, [0,0,0,1]] """ worldZ = pm.dt.Vector.zAxis newY = newX.cross(worldZ).normal() newZ = newX.cross(newY).normal() # Make sure Z is aligned correctly: if worldZ.dot(newZ) < 0: newZ *= -1 newY *= -1 return pm.dt.Matrix(newX, newY, newZ) }}} Here's an example of it in action: Two cones are created, A and B. B is translated and rotated differently from A. The localX axis vector from A is assigned to B: {{{ import pymel.core as pm # Get PyNode Transforms for our two nodes: nodeA = pm.PyNode("coneA") nodeB = pm.PyNode("coneB") # Get the worldspace matrix for nodeA m_world_nodeA = nodeA.getMatrix(worldSpace=True) # Extract its localX vector: newX = pm.dt.Vector(m_world_nodeA[0][:3]) # Compute the new local axis: newMtx = computeLocalAxis(newX) # Get the position of nodeB v_nodeB_rotPiv = pm.dt.Vector(nodeB.getRotatePivot(worldSpace=True).cartesian()) # Insert that position into our matrix: newMtx[3] = v_nodeB_rotPiv # Set nodeB to this new matrix: nodeB.setTransformation(newMtx) }}} ~PyMel Docs (2015): *[[Vector|http://help.autodesk.com/cloudhelp/2015/ENU/Maya-Tech-Docs/PyMel/generated/classes/pymel.core.datatypes/pymel.core.datatypes.Vector.htm]] *[[Matrix|http://help.autodesk.com/cloudhelp/2015/ENU/Maya-Tech-Docs/PyMel/generated/classes/pymel.core.datatypes/pymel.core.datatypes.Matrix.html]] *[[Transform|http://help.autodesk.com/cloudhelp/2015/ENU/Maya-Tech-Docs/PyMel/generated/classes/pymel.core.nodetypes/pymel.core.nodetypes.Transform.html]] I had a situation where I needed a small polygonal ball to stay affixed inside a large polygonal ball, by connecting springs between specific verts on each. This is the solution I found: Via the 'FX' Maya menu... # Select the outside sphere and 'nCloth -> Create Passive Collider'. ## Disable its collision for performance. # Select the inside sphere and 'nCloth -> Create nCloth'. ## Set it's 'Rigidity' (under 'Dynamic Properties') to something large, like 1000: This will keep it from deforming. ## Set it's 'collide' and 'selfCollide' off for performance. # Find the nucleus node that was created, and turn off its gravity (unless you want gravity effecting the inside sphere). # Then, picking pairs of inside/outside sphere verts: ## 'nConstraint -> Component to Component' : This will create the constraint, which is a 'spring' by default. ## Under the 'Connection Density Range', you can start changing the 'Rest Length Method' to something like 'Constant', and supplying values (always in cm, regardless of what you're UI unit is) to have some springs pull more than others.  (This is all based on a Windows system) First, you need to get some type of com object solution working in Python for Maya: *See my notes here: [[Getting pywin32 working in Maya]] Once you have that package (or something similar) up and running, you can start having Maya talk to Photoshop. Here's a simple example: {{{ # Presuming you have the package installed.... # Need to import in order import pythoncom import win32com.client app = win32com.client.Dispatch("Photoshop.Application") # Application psdFile = 'c:/temp/myPrettyPic.psd' document = app.Open(psdFile) # Document # For each layer in the psd: for layerSet in document.LayerSets: # LayerSet in LayerSets, which PS calls 'groups' print layerSet.Name layerSet.Resize(50, 50) layerSet.Translate(128,128) # Some optional fun: #document.ResizeImage(64, 64) #document.Save() }}} How about some docs? The root for all related scripting docs for multiple versions of Photoshop is here: http://www.adobe.com/devnet/photoshop/scripting.html I would recomend taking a look at the "''~VBScript Reference''" for your version of Photoshop, compared to the ~JavaScript or ~AppleScript: The VB seems to more closely match the Python bindings\syntax.  'Skin -> Bake Deformers to Skin Weights' opens the 'Bake Deformer Tool'. I commonly use this when placing blocked weights on an asset, then applying a deltaMush deformer to it, the converting that back to smooth (linear) skinning. Does not support dualQuat skinning. The UI for it is a bit clunky: While they let you bake this from one skeleton/mesh to another, I never use it this way. Plus. you need to manually enter the name of the mesh and root joint (or the 'most root joint' effecting that mesh) into both the 'Source' and 'Destination' sections. For the 'Skeleton', it's really important you only pick the 'root most joint' that influences your mesh. For example, if you were applying this operation to a head, you'd probably use a neck or spine joint as the root. The tool puts every joint through a range of motion to determine the deformation effect it has on the mesh, and the more joints in the root hierarchy, the more complex/time consuming it becomes. Official docs for the tool, [[HERE|https://knowledge.autodesk.com/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2020/ENU/Maya-CharacterAnimation/files/GUID-DD430C9B-95E7-4EBB-8D2B-A566018B4AC4-htm.html]] And the [[bakeDeformer|http://help.autodesk.com/cloudhelp/2020/ENU/Maya-Tech-Docs/CommandsPython/bakeDeformer.html]] command Example script usage: {{{ bakeDeformer -srcMeshName pCylinder1 -srcSkeletonName joint1 -dstMeshName pCylinder1 -dstSkeletonName joint1 -maxInfluences 4; }}} Note, I've see this error pop up often: {{{ # Error: Failed to calculate the weights: points (4,5,47,53,159,164,349,1015) }}} This seems somehow related to the mesh topology, even though doing a cleanup on it has no effect. Not sure yet how to resolve. ~PyMel has a nice utility to convert from mel straight to Python: {{{ import pymel.tools.mel2py as mel2py print mel2py.mel2pyStr('aimConstraint -mo -weight 1 -aimVector 0 -1 0 -upVector 0 0 1 -worldUpType "vector" -worldUpVector 0 0 1 -skip y;') }}} {{{ aimConstraint(weight=1,upVector=(0, 0, 1),skip='y',mo=1,worldUpType="vector",aimVector=(0, -1, 0),worldUpVector=(0, 0, 1)) }}} Tip from Mason Sheffield.  Much of this I have learned from others, we all ride on shoulders of giants. But for what I present as my own work, it's under the below licence: ---- All information on this wiki is under the Apache Licence, v 2.0: {{{ Copyright 2015 Eric Pavey Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. }}} To understand how this licence works see [[this overview|http://developer.kde.org/documentation/licensing/licenses_summary.html]]. But it pretty much means you can use the information on this wiki however you want, but I always appreciate credit where applicable. Good overview here: http://help.autodesk.com/view/MAYAUL/2018/ENU/?guid=__files_to_wrap_GUID_3F96AF53_A47E_4351_A86A_396E7BFD6665_htm Example file lives here: {{{ C:\Program Files\Autodesk\Maya2018\devkit\devkit\pythonScripts\widgetHierarchy.py }}} This example also provides you data on every window attached to the main Maya window, so it's a bit interesting on its own. This whole thing is very non-trivial. Below is some boilerplate that makes it work though. The only major change is once this is saved in a module, you need to update teh TB.uiScript string, to properly import and execute it. To launch the window, execute {{{TB()}}} {{{ # thisModuleName.py from __future__ import print_function import maya.cmds as mc import maya.OpenMayaUI as omui from maya.app.general.mayaMixin import MayaQWidgetDockableMixin from PySide2 import QtWidgets, QtCore #----------------------- # This stores an instance to the Dockable class when first instanced. # The downside is reloading this module will totally break this system and you'll # need to resart Maya ever time you want to see a change to your widget. gDockableWin = None class Dockable(MayaQWidgetDockableMixin, QtWidgets.QWidget): """ Create a dockable window. """ name = "aDockableWindow" title = "MrDock" # Will need to update this with the import for this module correctly: uiScript = 'import thisModuleName as tmn; tmn.Dockable.dockableScript(restore=True)' @staticmethod def dockableScript(restore=False): ''' To launch the dockable window, this staticmethod is called to by the calling code. Boilerplate code. ''' # -------------- # All boilerplate code global gDockableWin parentControl = None if restore == True: parentControl = omui.MQtUtil.getCurrentParent() if gDockableWin is None: gDockableWin = Dockable() # Set it to be docked by default: # Or not. This worked at one point, but now errors because it says # the workspaceCtrl doesn't work. Leaving in to troubleshoot later... #workspaceCtrl = self.getWorkspaceControl() #mc.workspaceControl(workspaceCtrl, edit=True, floating=False) if restore == True: mixinPtr = omui.MQtUtil.findControl(Dockable.name) omui.MQtUtil.addWidgetToMayaLayout(long(mixinPtr), long(parentControl)) else: # Show is a method on the MayaQWidgetDockableMixin superclass gDockableWin.show(dockable=True, uiScript=Dockable.uiScript) # -------------- return gDockableWin def __init__(self): """ Init our widget. Boilerplate code. """ super(Dockable, self).__init__() self.setObjectName(Dockable.name) self.setWindowTitle(Dockable.title) self.setWindowFlags(QtCore.Qt.Window) self.setProperty("saveWindowPref", True) # Save prefs on exit. self.populate() self.show() def getWorkspaceControl(self): """ Return the string name of the workspaceControl this widget is associated with. Maya automatically appends the "WorkspaceControl" string to the workspace control child object name. """ return "%sWorkspaceControl"%self.objectName() def populate(self): """ Create the primary contents of our window. """ self.layout_main = QtWidgets.QVBoxLayout() self.setLayout(self.layout_main) if self.layout_main: widget_but = QtWidgets.QPushButton("A Button") self.layout_main.addWidget(widget_but) }}} Maya has no built-in curvature shader which I find surprising. Some resources below: * https://github.com/Byron/bsuite/tree/master/src/shaders * http://www.stepanjirka.com/maya-api-curvature-shader/ ** https://github.com/sjirka/CurvatureShader * http://www.tomcowland.com/mentalray/tc_curvature/ * http://wiki.polycount.com/wiki/Curvature_map#Maya ** https://polycount.com/discussion/comment/1162740#Comment_1162740 * The old school one by Duncan Brinsmead. Only works on NURBS: ** https://www.highend3d.com/maya/script/curvaturetexture-for-maya {{{ // // Alias|Wavefront Script File // MODIFY THIS AT YOUR OWN RISK // // // Creation Date: April 1999 // Author: Duncan Brinsmead // // Procedure Name: // curvatureTexture // // Description: // Creates an expression that uses 3 pointOnSurface nodes // to determine local curvature in u and v and then uses // this curvature to lookup the u values in a ramp texture. // Extra attributes are added to the ramp texture to // control the curvature range and method of combining // the u and v curvature. // The resulting ramp texture should be assigned to // some component of the shader for the surface // to have an effect. // // Input Arguments: // None. // // Return Value: // None. // global proc curvatureTexture( ) { inti;
string $pickedNurbs[] = ls -dag -sl -lf -ap -typ nurbsSurface; if( !size($pickedNurbs ))
{
print("// No nurbs surfaces are selected! Can't create curveTexture.//");
return;
}
string $samplerNode = "curvatureTextureSamplerUtility"; if( !objExists($samplerNode ) )
{
$samplerNode = shadingNode -asUtility samplerInfo -name$samplerNode;
}

for( $i = 0;$i < size( $pickedNurbs );$i++ )
{
string $p = pointOnSurface -top on -ch on$pickedNurbs[$i]; string$pU = pointOnSurface -top on -ch on $pickedNurbs[$i];
string $pV = pointOnSurface -top on -ch on$pickedNurbs[$i]; if( size($p) && size($pU) && size($pV) )
{
string $rampNode = shadingNode -asTexture ramp -name ($pickedNurbs[$i] + "Curvature"); if( size($rampNode ))
{
addAttr -sn cmn  -ln curvatureMin  -dv -0.05 -min -1 -max 1 $rampNode; addAttr -sn cmx -ln curvatureMax -dv 0.05 -min -1 -max 1$rampNode;
addAttr -sn csw  -ln curveSampleWidth -dv 0.01 -min 0.00001 -max 0.2 $rampNode; addAttr -sn muv -ln useMaxOfUorV -dv 0 -at bool$rampNode;
addAttr -sn wrc  -ln wrapCurvature -dv 0 -at bool $rampNode; expression -s ( "float$u = " + $samplerNode + ".u;\n" + "float$v = " + $samplerNode + ".v;\n" +$p + ".u = $u;\n" +$p + ".v = $v;\n" +$pU + ".u = $u + " +$rampNode + ".curveSampleWidth;\n" +
$pU + ".v =$v;\n" +
$pV + ".u =$u;\n" +
$pV + ".v =$v + " + $rampNode + ".curveSampleWidth;\n" ); expression -s ( "float$c,$dotU,$dotV;\n" +
"$dotU = " +$p+".tux * "+$pU+".nnx + " +$p+".tuy * "+$pU+".nny + " +$p+".tuz * "+$pU+".nnz;\n" + "$dotV = " +$p+".tvx * "+$pV+".nnx + " +
$p+".tvy * "+$pV+".nny + " +
$p+".tvz * "+$pV+".nnz;\n" +
"if( " + $rampNode + ".useMaxOfUorV ){\n" + "if( abs($dotU) > abs($dotV) ) \n"+ "$c = $dotU;\n"+ "else\n"+ "$c = $dotV;\n"+ "}else{\n" + "$c = ($dotU +$dotV) * 0.5;\n" +
"}\n"+
"$c = ($c - " + $rampNode + ".cmn) / (" +$rampNode + ".cmx - " +$rampNode+ ".cmn);\n" + "if( !" +$rampNode + ".wrapCurvature ) { \n" +
"if( $c > 1.0 ) \n"+ "$c = 1.0;\n"+
"else if( $c < 0 )\n"+ "$c = 0;\n"+
"}\n"+
$rampNode + ".u = -$c;\n" +
$rampNode + ".v =$c;\n" );
print( "\n// Result texture: " + $rampNode + " (map to shader to view) //"); } } } } }}} Great reference showing interactive curve easing functions: !Reference 1 https://www.desmos.com/calculator/2rnqgoa6a4 This loads up a generic function. Click on the menu in the top left to get others. !Reference 2 http://www.gizma.com/easing/ !Reference 3 http://easings.net/ And the code for it all (~JavaScript) is here: https://github.com/gdsmith/jquery.easing/blob/master/jquery.easing.1.3.js Here's some Python conversions: ~JavaScript: {{{ easeInOutCubic: function (x, t, b, c, d) { if ((t/=d/2) < 1) return c/2*t*t*t + b; return c/2*((t-=2)*t*t + 2) + b; }, easeInQuad: function (x, t, b, c, d) { return c*(t/=d)*t + b; }, }}} Python: (note I left out the 'x', since it isn't needed in these funcs) {{{ """ t: current time b: beginning value c: change In value d: duration """ def easeInQuad(t, b, c, d): T = t/d return c*T*T+b def easeInOutCubic(t, b, c, d): T = t / (d / 2) if T < 1: return c/2*T*T*T + b else: TT = T - 2 return c / 2 * ( TT*TT*TT + 2) + b }}} For example, here we have a curve 'ten units' long, that has a min/max value of 0 -> 1.0, and we query the frame in the middle (5), giving us a value of .5 (the middle of the curve, the one spot not being modified from a linear curve): {{{ currentTime = 5 beginningValue = 0.0 changeInValue = 1.0 duration = 10.0 print easeInOutCubic(t=currentTime, b=beginningValue, c=changeInValue, d=duration) .5 }}} Note with the Python, you can't modify values in-place like they do in ~JavaScript: {{{ if ((t/=d/2) < 1)}}}. While this is legal in Python: {{{ a = 4 a += 3 }}} This would fail: {{{ a = 4 b = a += 3 }}} Because of that, I broke them out into their own lines, an it works just fine, and is slightly less confusing ;) [[Introduced in Maya 2018|http://help.autodesk.com/view/MAYAUL/2018/ENU/?guid=GUID-94DA2210-6FF1-4993-8EB9-9A6D87562D35]] ---- To activate the Dash Script Editor #Select one or more attributes in the Channel Box. #Alt + right-click anywhere in the Channel Box. ##Maya displays the Dash Script Editor where you can enter Dash commands. | Command |Effect | | l(number) |Distributes the selected objects linearly over the total distance denoted by 'number' (requires 2 or more objects to be selected). | | r(number) |Assigns a random number between 0 and 'number'. | | r(low, high) |Assigns a random number between 'low' and 'high'. | | e() |Auto ease animation channel. | | e(number) |Eases acceleration / deceleration of an animation channel based on a number between -1 and 1. A value of -1 represents slow acceleration and rapid deceleration, while a value of o1 produces the opposite. | | ts(number) |Time offset any keyframes for the selected attributes by 'number' of frames. | In addition to these, anything from the Python Random module is also available for use. Customized Dash commands You can create your own Dash commands by editing the Dash.json file, located in Windows: {{{ C:/Program Files/Autodesk/Maya2018/plug-ins/MASH/scripts }}} Mac: {{{ /Applications/Autodesk/Maya2018/plug-ins/MASH/scripts }}}  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}}}

[[Welcome]]
* https://www.ea.com/seed/news/open-source-dem-bones
* https://github.com/electronicarts/dem-bones
"An automated algorithm to extract the linear blend skinning (LBS) from a set of example poses".
* Converting any animated mesh sequence, e.g. geometry cache, to LBS, which can be replayed in popular game engines,
* Solving skinning weights from shapes and skeleton poses, e.g. converting blendshapes to LBS,
* Solving bone transformations for a mesh animation given skinning weights.
"In software engineering, a design pattern is a general repeatable solution to a commonly occurring problem in software design. A design pattern isn't a finished design that can be transformed directly into code. It is a description or template for how to solve a problem that can be used in many different situations."

Great overview & examples can be found here:
https://sourcemaking.com/design_patterns
The more I learn about matrices in Maya, the more ways I find to access, manipulate, and apply them.  Need to write it down to keep my head straight ;)
The below doc is split into these sections:
*Gotcha's
*Docs
*Access
*Manipulation
*Application
!Matrix gotcha's:
*Internally, API matrix translational data is saved in cm in Maya, regardless of what you have the UI set to (inches, etc).  So if you're //not// working in cm, you'll need to convert the cm data to your 'ui units' if using application methods such as {{{setAttr}}}, or if you're extracting and modifying the data before reapplication.
*"Freezing transforms" on a node will screw up where Maya thinks the world matrix position is:  It will no longer return the correct positional values after that.
**If you freeze transforms on a node, it will think the new world matrix is at the location defined by the 'world rotate pivot' minus the 'local rotate pivot'.  Really, it's just the 'world rotate pivot'.
**Often, I need to rebuild the world matrix with correct worldspace positional information, acquired form the {{{pointPosition}}} command querying the world {{{roatePivot}}} of the given node, and re-inserting it back into the matrix (converting to cm first if need be).
** For more info: [[What happens when you 'freeze transforms' behind the scenes?]]
* Trying to get the {{{worldInverseMatrix}}} with the above two issues present can be a pain.  To do it, you first need to fixup your worldMatrix with the correct positional values, then execute the {{{inverse()}}} method on it (presuming this is through the API), rather than querying the {{{worldInverseMatrix}}} directly.
*Matrix attributes on nodes like transforms and joints (unless user-created) are output\read only:  You can't use {{{setAttr}}} to change them.
To help keep the code clean, the below examples //aren't going to handle any of that//.  It's your job to modify them and fix-up where you see fit.
!Matrix-related Docs:
(not all of these are discussed below)  There are additional matrix related nodes and commands here: [[Matrix info]]
!!!Python ~OpenMaya API
*Query\Access matrix data:
**[[OpenMaya.MFnDagNode|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_dag_node.html]] : Can be used to return an //local// (not worldspace) {{{MMatrix}}} for the given {{{MDagPath}}}.
***[[OpenMaya.MFnTransform|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_transform.html]] : Can be used to return a {{{MTransformationMatrix}}} for the given {{{MObject}}} or {{{MDagPath}}}.  Note this will be the //local// matrix of the node, not the worldspace matrix.
**[[OpenMaya.MDagPath|http://download.autodesk.com/us/maya/2010help/API/class_m_dag_path.html]] : Can be used to query a world-space (inclusive) or parent-space (exclusive) {{{MMatrix}}}.
**[[OpenMayaUI.M3dView|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m3d_view.html]] : It has a variety of methods ({{{viewToObjectSpace}}}, {{{projectionMatrix}}}, {{{modelViewMatrix}}}) used for getting and transforming matrices (and vectors) based on the camra, between camera-space, object-space,and world-space.
*Matrix data types:
**[[OpenMaya.MFloatMatrix|http://download.autodesk.com/us/maya/2010help/API/class_m_float_matrix.html]] : This is like a {{{MMatrix}}} but stores the internal data as {{{floats}}}, rather than {{{doubles}}}.
**[[OpenMaya.MTransformationMatrix|http://download.autodesk.com/us/maya/2010help/API/class_m_transformation_matrix.html]] : This provides many helper methods for transforming matrix data.  Note you can't access matrix indices individually or do math-operator (like multiplying two {{{MTransformationMatrix}}}'s together).  In both cases need to be converted to {{{MMatrix}}} first.
*Matrix containers:
*Scripted Plugin related matrix data:
**[[OpenMayaMPx.MPxTransformationMatrix|http://download.autodesk.com/us/maya/2010help/API/class_m_px_transformation_matrix.html]] : Not covered here, can be used to create your own custom scripted plugin based {{{matrix}}} nodes.
**[[OpenMaya.MFnMatrixAttribute|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_matrix_attribute.html]] : Not covered here, used to add matrix attributes on custom nodes authored via scripted plugins.  Used inside the 'initializer' function of the scripted plugin.
**[[OpenMaya.MFnMatrixData|http://download.autodesk.com/us/maya/2010help/API/class_m_fn_matrix_data.html]] : Not covered here, used to access matrix data passed to, or generated by a custom node authored via a scripted plugin.  Code used inside the scripted plugin itself.
!!!Python (& mel)  commands:
*[[xform|http://download.autodesk.com/us/maya/2010help/CommandsPython/xform.html]] : Used to query or set matrix data (in a variety of spaces) by returning or passing a list of 16 floats.
*[[getAttr|http://download.autodesk.com/us/maya/2010help/CommandsPython/getAttr.html]] : Used to query the variety of matrix attributes on a node.  Usually returns a list of 16 floats.
*[[dagPose|http://download.autodesk.com/us/maya/2010help/CommandsPython/dagPose.html]] : Used to store and query matrix data in a {{{dagPose}}} //node//. Most commonly used during skinning operations.
*{{{pointMatrixMult}}}   (Mel only)
**This Mel proc 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}}}
!!!Mel variables
!!!Nodes:
*[[dagNode|http://download.autodesk.com/us/maya/2010help/Nodes/dagNode.html]] : Base node type that stores these attributes: {{{matrix}}} (local), {{{inverseMatrix}}}, {{{worldMatrix}}}, {{{worldInverseMatrix}}}, {{{parentMatrix}}}, {{{inverseParentMatrix}}}, (starting in Maya 2020) {{{offsetParentMatrix}}}.
**[[transform|http://download.autodesk.com/us/maya/2010help/Nodes/transform.html]] : Base transform node (that inherits from {{{dagNode}}}).  Adds the attribute {{{xformMatrix}}} which is the same as the parental {{{matrix}}} (local) attribute.
*[[dagPose|http://download.autodesk.com/us/maya/2010help/Nodes/dagPose.html]] : Created via the {{{dagPose}}} command, used to store matrix data as a pose. Most often used during skinning operations.
*[[fourByFourMatrix|http://download.autodesk.com/global/docs/maya2013/en_us/Nodes/fourByFourMatrix.html]] : 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".
*[[pointMatrixMult|http://download.autodesk.com/global/docs/maya2013/en_us/Nodes/pointMatrixMult.html]] : The dependency graph node to multiply a point by a matrix.  Pairs with the {{{pointMatrixMult}}} mel proc (above).
* {{{matrixNodes.mll}}} plugin : Ships with Maya 2013.  Includes these nodes:
**{{{decomposeMatrix}}} : Previous to Maya 2013, created via the {{{decomposeMatrix.mll}}} plugin.  Exposes the matrix data for a given transform.
**{{{composeMatrix}}}
**{{{inverseMatrix}}}
**{{{transposeMatrix}}}
* New in Maya 2020, based on the {{{offsetParentMatrix}}} updates
** [[pickMatrix|http://help.autodesk.com/cloudhelp/2020/ENU/Maya-Tech-Docs/Nodes/pickMatrix.html]]
** [[blendMatrix|http://help.autodesk.com/cloudhelp/2020/ENU/Maya-Tech-Docs/Nodes/blendMatrix.html]]
** [[aimMatrix|http://help.autodesk.com/cloudhelp/2020/ENU/Maya-Tech-Docs/Nodes/aimMatrix.html]]
!Accessing Matrix Data:
!!!API Only:
Here, we have functions that return either {{{MMatrix}}} or {{{MTransformationMatrix}}} objects.
----
^^Calls used in the below //API// examples:^^
{{{
import maya.OpenMaya as om
}}}
{{{
def getMDagPath(node):
"""
Convenience function to make getting MDagPaths easier:
Get a MDagPath for a node.  node is a string name.
"""
selList = om.MSelectionList()
mDagPath = om.MDagPath()
selList.getDagPath(0, mDagPath)
return mDagPath
}}}
----
{{{
def get_MDagPath_MMatrix(node):
"""
This will return the worldspace MMatrix of a node. node is a string name.
"""
mDagPath = getMDagPath(node)
mMatrix = mDagPath.inclusiveMatrix()
return mMatrix
}}}
{{{
def get_MFnTransform_MTransformationMatrix(node):
"""
Using a MFnTransform function, return a MTransformationMatrix for the node.
This is the *local* matrix of the node (not worldspace).  node is a string name.
"""
mDagPath = getMDagPath(node)
transformFunc = om.MFnTransform(mDagPath)
mTransformMtx = transformFunc.transformation()
return mTransformMtx
}}}
!!!API and getAttr / xform:
The below to functions are almost the same.  The {{{getAttr}}} method however gives you a few more matrix querying options.
----
^^Calls used in the below //API// examples:^^
{{{
import maya.cmds as mc
}}}
----
{{{
def get_getAttr_MMatrix(node, matrixType):
"""
Access the matrix attr data directly on a node.
node : string name of node to query.
matrixType : "matrix", "inverseMatrix", "worldMatrix", "worldInverseMatrix",
"parentMatrix", "parentInverseMatrix", "xformMatrix"
return a MMatrix
"""
mList = mc.getAttr(node+"."+matrixType)
mMatrix = om.MMatrix()
om.MScriptUtil.createMatrixFromList(mList, mMatrix )
return mMatrix
}}}
{{{
def get_xform_MMatrix(node):
"""
Access the matrix attr data directly on a node.
This will return the worldspace MMatrix for the node.
node : string name of node to query.
return a MMatrix
"""
mList = mc.xform(node, query=True, matrix=True)
mMatrix = om.MMatrix()
om.MScriptUtil.createMatrixFromList(mList, mMatrix )
return mMatrix
}}}
!!!Mel only:
Python has no built-in matrix [[type|http://docs.python.org/library/stdtypes.html]], that's why above we use the Maya API's version.  Here we do it all in mel:
{{{
global proc matrix get_getAttr_matrix(string $node, string$matrixType){
// Access the matrix attr data directly on a node.
// string $node : Name of node to query. // string$ matrixType : "matrix", "inverseMatrix", "worldMatrix",
//     "worldInverseMatrix", "parentMatrix", "parentInverseMatrix",
//     "xformMatrix"
float $m[] =getAttr ($node+"."+$matrixType); matrix$mtx[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 $mtx; } // example: matrix$myMatrix[4][4] = get_getAttr_matrix("pCube1", "worldMatrix")
// Result: << 1 0 0 0;  0 1 0 0;  0 0 1 0;  0 0 0 1 >> //
}}}
{{{
global proc matrix get_xform_matrix(string $node){ // returns a matrix type for the given node's world matrix. float$m[] = xform -query -matrix  $node; matrix$mtx[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 $mtx; } // example: matrix$myMatrix[4][4] = get_xform_matrix("pCube1")
// Result: << 1 0 0 0;  0 1 0 0;  0 0 1 0;  0 0 5.667197 1 >> //
}}}
!Manipulating matrix data:
Once you have API {{{MMatrix}}} data, you can manipulate it like standard types.  //Usually// this is matrix multiplication, and it creates a new matrix:
{{{
cubeMtx = get_MDagPath_MMatrix('pCube1')
sphereMtx = get_MDagPath_MMatrix('pSphere1')
newMtx = cubeMtx * sphereMtx
}}}
Same thing goes for the mel {{{matrix}}} type:
{{{
matrix $cubeMtx[4][4] = getAttr_matrix("pCube1", "worldMatrix"); matrix$sphereMtx[4][4] = getAttr_matrix("pSphere1", "worldMatrix");
matrix $newMtx[4][4] =$cubeMtx * $sphereMtx; }}} It should be noted that {{{MTransformationMatrix}}} has no operator operations, so you can't do multiplication like above. If you wanted to, you'd need to convert it to a {{{MMatrix}}}, do the multiplication, then convert the new {{{MMatrix}}} back to a {{{MTransformationMatrix}}}, see examples of this below. It should be noted however that the {{{MTransformationMatrix}}} class has many methods for setting its values, check the docs. ---- If you want to query the individual elements of a {{{MMatrix}}}, there are a couple different ways: {{{ # Note how you call ( ) to the matrix, rather than access the index directly [ ] ? mtxItem = someMMatrix(2,2) mtxItem = om.MScriptUtil.getDoubleArrayItem(someMMatrix[2], 2) }}} Which is similar for mel {{{matrix}}} types: {{{ float$mtxItem = $newMtx[2][2]; }}} The {{{MTransformationMatrix}}} provides no index access to its internal elements. You first need to convert it to a {{{MMatrix}}} type, then query the values you're after via the above methods: {{{ newMMatrix = myMTransformationMatrix.asMatrix() }}} ---- If you want to set an individual element of a {{{MMatrix}}} you need to use a {{{MScriptUtil}}} convenience function. Here, we set the 2rd row (index 1), 3rd column (index 2) to 3.1: {{{ om.MScriptUtil.setDoubleArray(someMMatrix[1], 2, 3.1) }}} This is pretty easy in mel: {{{$newMtx[2][2] = 3.1;
}}}
Like above you can't directly access the elements of a {{{MTransformationMatrix}}}.  So if you want to set one of them, based on the above solution you'd need to convert it to a {{{MMatrix}}}, modify the element, then re-create the {{{MTransformationMatrix}}} based on the new {{{MMatrix}}} data.  This is lossy however, and you could loose other local transformation data the original {{{MTransformationMatrix}}} was storing:
{{{
myMTransformationMatrix = om.MTransformationMatrix(someMMatrix)
}}}
!Apply matrix data:
Once you have updated\new matrix data, how do you get it back on your nodes?
!!!API Only
It's interesting that I can't find anything via the API to apply a {{{MMatrix}}} back to a node, it only has functions for applying {{{MTransformationMatrix}}} data.  So in theory you could create a new {{{MTransformationMatrix}}} based around a given {{{MMatrix}}}, and use this code:
{{{
def set_MFnTransform_MTransformationMatrix(node, mtf_mtx):
"""
Using a MFnTransform function, apply a MTransformationMatrix to a node.
node is a string name.
"""
mDagPath = getMDagPath(node)
transformFunc = om.MFnTransform(mDagPath)
transformFunc.set(mtf_mtx)

# For example:
sphereMtx = get_MFnTransform_MTransformationMatrix('pSphere1')
set_MFnTransform_MTransformationMatrix('pCube1', sphereMtx)
}}}
!!!API & xform:
{{{
def set_xform_MMatrix(node, mMatrix):
"""
Apply the given MMatrix to the given node in worldspace
"""
mList = [mMatrix(i,j) for i in range(4) for j in range(4)]
mc.xform(node, worldSpace=True, matrix=mList)

# For example:
pCubeMtx = get_MDagPath_MMatrix('pCube1')
set_xform_MMatrix('pSphere1', pCubeMtx, 'worldSpace')
}}}
!!!Mel Only:
{{{
global proc set_xform_matrix(string $node, matrix$matrix){
// Apply the given matrix to the given node in worldspace
// First we need to convert the matrix to a float array:
float $ml[] = {}; for($i=0;$i<4;$i++){
for($j=0;$j<4;$j++){$ml[size($ml)] =$matrix[$i][$j];
}
}
xform -matrix $ml[0]$ml[1] $ml[2]$ml[3] $ml[4]$ml[5] $ml[6]$ml[7] $ml[8]$ml[9] $ml[10]$ml[11] $ml[12]$ml[13] $ml[14]$ml[5]
-worldSpace $node; } // For example: set_xform_matrix("pCube1",$matrix);
}}}

I wanted a way to reference a node by a persistent ID, that would remain the same between Maya sessions, //even if the name of the node changed//.  Meaning, reference a node by a value other than its string name.

Short answer is yes, but //they're only persistent for the given Maya session//:  Restarting Maya rests the id's... :(

And to clarify, I'm not referring to the {{{MTypeId}}} class, which provides access to the unique id provided to a given type\classification of node:  I was looking for a persistent id for a specific instance of a node, like it's location in memory, or a representative hash.
----
What I found id this:  Given an {{{MObject}}} for a node, you can access the secretive {{{myMObject.__hash__()}}} method, which will return a unique hash for that instance of an {{{MObject}}} for the given node.  If you create another {{{MObject}}}, it will have it's own unique hash.  AND, obviously, if you quit Maya, reopen it, and generate a new {{{MObject}}}, it too will have its own unique hash.  There's also the {{{MObjectHandle}}} class that has a {{{hashCode()}}} method, which I presume references the one on the {{{MObject}}}.

~PyMel's {{{PyNode}}} has a similar {{{__hash__()}}} method, which has the same limitations.

So basically, in a given Maya session, you can create a {{{MObject}}} for a node, and then (via the authoring of new tools) access the node by it's 'hash name'.  But creating a new {{{MObject}}} or reloading the scene will break this by generating all new hash values.
----
I brought this up with Autodesk support, and after much troubleshooting\research they said 'no such thing exists'.
----
The current solution I'm working on is to leverage Python's  [[uuid|http://docs.python.org/2/library/uuid.html]] module ([[Universally Unique Identifier|http://en.wikipedia.org/wiki/UUID]]) into a tool that will emulate this functionality by adding\querying custom 'uuid' attrs applied to nodes.
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. 
It's not too often I run across quality skinning tools, but if I do, //I put them here//:
*http://www.ngskintools.com/
**player.vimeo.com/video/95829347 - video overview
*https://github.com/chrisevans3d/skinWrangler
**https://www.youtube.com/watch?v=b__ABAKjDRI
The Maya [[connectControl|http://download.autodesk.com/global/docs/maya2014/en_us/CommandsPython/connectControl.html]] command can setup a bi-directional link between an object.attribute, and a UI control.  Like linking the rotateZ of something to a slider:  Move the slider, the object rotates.  Rotate the object, and the slider updates.

How is this achievable in Qt?  I'm just learning Qt myself, and there could quite possible be a less convoluted solution, but what I've come up with behaves just fine.

Here's an overview of how it works:
* Window is created.  A signal/slot combo is setup so that whenever the dial is changed, it will modify the {{{rotateZ}}} on the defined object.
* User picks some object, and presses the "Define Object" button.
* This executes the {{{butCmd}}} method, which:
** Sets the {{{self.transform}}} attr with the current selected node.
** Deletes any pre-existing attr-changed callbacks.
** Fires the {{{createAttrChangedCallback}}} method, which creates a callback that will execute the {{{attrChangedCallbackCmd}}} method whenever the nodes {{{rotateZ}}} is changed.
* When the node is rotated by the user and the {{{attrChangedCallbackCmd}}} method executes:
** The first thing it does is to {{{blockSignals}}} on the {{{QDial}}}:  It then sets the new dial value, then stops blocking the signals.
** If that signal blocking //didn't// happen, Maya will effectively go into a cyclic loop:  The user rotates the node, which updates the dial, which updates the node, which updates the dial, etc.
Code:
{{{
# dialer.py

from math import degrees

import pymel.core as pm
import maya.OpenMayaUI as omui
import maya.OpenMaya as om

from PySide import QtCore
from PySide import QtGui

from shiboken import wrapInstance

def maya_main_window():
# Get a pointer to Maya's main menu qt widget:
main_window_ptr = omui.MQtUtil.mainWindow()
return wrapInstance(long(main_window_ptr), QtGui.QWidget)

def getMObject(node):
"""
# Return a MObject for the passed in node name
"""
selList = om.MSelectionList()
mObject = om.MObject()
selList.getDependNode(0, mObject)
return mObject

#-------------------------------------------------------------------------------

class App(QtGui.QDialog):

def __init__(self):
"""
Create the window.
"""
super(App, self).__init__(maya_main_window())

# Delete the window if it already exists:
self.title = "Dialatron"
for widget in QtGui.QApplication.topLevelWidgets():
if self.title == widget.windowTitle():
widget.close()

# This tracks the ID for our attr changed callback, so it can be later
# removed when the window is closed, or a new node is picked:
self.cid = None
# This tracks the selected object:
self.transform = None

self.setWindowTitle(self.title)
self.setWindowFlags(QtCore.Qt.Tool)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

self.createLayout()
self.createSignals()
self.show()

def closeEvent(self, event):
"""
Called when a Qt window is closed.  We use it to remove the attr changed
callback.
"""
if self.cid:
om.MMessage.removeCallback(self.cid)

def createLayout(self):
"""
Create the main ui layout and widgets
"""
self.button = QtGui.QPushButton("Define Object")
self.dial = QtGui.QDial(minimum=0, maximum=360)
self.dial.setNotchesVisible(1)
self.dial.setWrapping(1)

# Acts like a columnLayout in Maya:
mainLayout = QtGui.QVBoxLayout()
mainLayout.setContentsMargins(10,10,10,10)
mainLayout.setSpacing(10)
self.setLayout(mainLayout)

#---------
# Signals:

def createSignals(self):
"""
These create the *signals* based on ui interaction, and connects them
to *slots*.
"""
self.button.clicked.connect(self.butCmd)
self.dial.valueChanged.connect(self.dialCmd)

#----------
# Slots:

def butCmd(self):
"""
Slot for the button signal.  It both defines the object to 'dial', and
creates the attr changed callback.
"""
sel = pm.ls(selection=True)
if not len(sel):
pm.displayWarning("Nothing selected")
return
self.transform = sel[0]
self.createAttrChangedCallback()
print "Defined '%s' for dialation"%self.transform

def dialCmd(self):
"""
Slot for the dial signal:  When happens when the user turns the dial.
"""
sender = self.sender()
val = sender.value()
if self.transform and pm.objExists(self.transform):
pm.PyNode(self.transform).rotateZ.set(val)

#---------
# Callbacks:

def createAttrChangedCallback(self):
"""
Create the callback that will be executed when an attribute changes.  It
calls to attrChangedCallbackCmd.
"""
if self.cid:
om.MMessage.removeCallback(self.cid)
if pm.objExists(self.transform):
self.attrChangedCallbackCmd, self.dial)

def attrChangedCallbackCmd(self, msg, plug, otherPlug, *args):
"""
Function executed when an attr is changed.
"""
if 'rotateZ' in plug.name():
qdial = args[0]
val = degrees(plug.asDouble()) # Rotations are returned as radians.
# If we don't block the signal emitted from the dial, we'll get into
# a cyclic loop.
qdial.blockSignals(1)
qdial.setValue(val)
qdial.blockSignals(0)
}}}
To execute:
{{{
import dialer
dialer.App()
}}}
When we upgraded to Maya 2015, many plugins weren't loading for some (but not all) people. They'd get this error when they'd try to load them:
{{{
// Error: line 1: The specified module could not be found.
}}}
As it turns out, the plugins were compiled with the wrong version of Visual Studio (2010): Recompiling with VS 2012 fixed it.
When running the evaluation graph in parallel mode, there are certain node types that will stop it in its tracks, and push it back into serial mode.  These nodes include:
!!!Expressions
Avoid them, they are horrible for the EG.  Try to re-write as nodal networks.  They are flagged as "untrusted".
//However//, if all the outputs of an expression are directly related to the inputs, they can be set to "globally serial".
For example, this is bad, since it's using {{{getAttr}}} : Never use {{{getAttr}}} in an expression this way, if you want it to work in the EG.  Plus I'm not sure if {{{sin}}} is ok either...
{{{
float $val = getAttr foo.tx * sin(6); spam.ty =$val;
}}}
However, this would work:
{{{
spam.ty = foo.tx*6;
}}}
Since the output is directly related to the input, with no external calls.
!!!Python Scripted Plugins
Since Python Scripted Plugins use Python, and Python is inherently single-threaded, any node it hits will throw the EG back into serial mode.  Re-write as c++ plugin.  If they are used, they're evaluated as "globally serial".
!!!Cycle Clusters
While a 'cycle cluster' isn't a node, it's either a node, or collection of nodes that has a cycle.  This is different from things that cause 'cycleCheck' warnings:  A simple constraint can cause a cycle cluster.  Sometimes they're unavoidable, but if you can, they should be, since they'll force the EG back to a serial mode during their eval.


''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)
# 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)
# 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)
# 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)
# 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")
# 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.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.

Special values can be used in the Expression Editor.  What are they?
*{{{frame}}} : Will return the current frame.
*{{{time}}} : Will return a float based on the current second value.  Frame 15 @ 30fps would return .5
Are there more?  That's a good question :)
How do you go about extracting/connecting an individual X, Y, or Z axis vector, or its position, from a nodes worldMatrix **attribute**?  There //seems// to be no built in node-type for this.
{{{
(right column ignored in 4x4 matrix in Maya)
1 0 0 0 <-- X axis vector
0 1 0 0 <-- Y axis vector
0 0 1 0 <-- Z axis vector
0 0 0 1 <-- Positional component
}}}
There is, it's just not obvious.  Thanks to this thread, all has become clear:
http://discourse.techart.online/t/maya-accessing-matrix-components-with-dg-nodes/4470
Below is a modified version of the info provided by user 'Theodox':
!Use a vectorProduct node
Connect the output {{{worldMatrix}}} attr of a node to the {{{matrix}}} input of the {{{vectorProduct}}}.
Then:
!! Extract A Vector
Set the {{{vectorProduct.operation}}} to {{{VectorMatrixProduct}}}.
You can then extract the X, Y or Z vectors of the matrix by setting the {{{input1}}} to:
* (1,0,0) for the X vector component.
* (0,1,0) for the Y vector component.
* (0,0,1) for the Z vector component.
This is equivalent to a standard vector-matrix multiply, so it will give you orientation but not translations, or scales.
There’s also a flag to normalize the output or not.
!! Extract The Position
Set the {{{vectorProduct.operation}}} to {{{PointMatrixProduct}}}, you’ll get the point-matrix multiply with scale and translation (but you have to watch out for {{{unitConversion}}} nodes sneaking in and screwing with your numbers).
Then set this on {{{input1}}} (default values, but worth noting) :
* (0,0,0) : The positional component of the matrix.
@@IMPORTANT@@ : If the transform you're querying has ever had it's transforms frozen, the position this returns will be incorrect :S
!! Get the result
In either case, you can connect the {{{vectorProduct.output}}} attr to another node that takes a triple float input.
!Use a decomposeMatrix node
You can connect the {{{worldMatrix}}} of your node to a {{{decomposeMatrix}}} node, and get the {{{outputTranslate}}} attr from that.
@@IMPORTANT@@ :  Like above, if the transform you're querying has ever had it's transforms frozen, the position this returns will be incorrect :S


@@IMPORTANT@@  : If you're using version control (like P4) and storing your FBX files there, make sure that P4 and your FBX file format are both the same type.  Meaning, if you're saving binary FBX files, make sure P4 is storing them as binary files.  If you're saving ascii FBX files, make sure P4 is storing them as ascii files.  If there is any unalignment here, the FBX files will be corrupted on the server: The person that submitted them will have legit local copies, but anyone else who syncs them will get the corrupted versions.

!Export style comparison
Based on the {{{fbxmaya.mll}}} plugin.

''mel'':
{{{
FBXExport -f "path to file" -s;
}}}
''Python'':
{{{
import maya.mel as mm
mm.eval('FBXExport -f "path to file" -s;')
}}}
''~PyMel'':
{{{
import pymel.core as pm
pm.mel.FBXExport(f="path to file", s=True)
}}}
!Examples
{{{
import pymel.core as pm

# EXAMPLE SYNTAX
# pm.mel.FBXCommand(args)

#for example
pm.mel.FBXExport(f="FILENAME")
pm.mel.FBXImport(f="FILENAME", t=takeNumber)
}}}
Take numbers are:
* {{{-1}}} :The last take in the array
* {{{0}}} : Import no animation
* {{{Any other number}}} : That take.  Presumably 1 is a good starting point...
! Commands vs Properties
To perform an FBX export (or import), you call to a number of commands, or set a number of properties, then execute the import/export.  Because of that, it's a good idea to reset them all to defaults each time so no old values screw up a future export (shown below).

There are many FBX plugin-based commands available (see below docs).  However, they don't appear to cover all the bases, based on what the export/import UI expose.  For example, let's say you want to export mesh, but no animation.   In the UI, you can uncheck 'animation', but there's no //command// for that.  How to resolve?
{{{
import maya.mel as mm
mm.eval('FBXResetExport') # Reset all previous export settings via a command.
mm.eval('FBXExportSmoothingGroups -v true')  # Set mesh to export via a command.
mm.eval('FBXProperty Export|IncludeGrp|Animation -v false;') # Don't export animation via a property.
mm.eval('FBXExport -f "path to my file" -s') # Do the export via a command.
}}}
While the commands can be found in the below docs, or by looking at 'Plug-in information' provided by the {{{fbxmaya.mll}}} via the 'Plugin Manager', how you can you get a list of properties?
Executing this mel:
{{{
FBXProperties;
}}}
Will print them all, and their state to the Script Editor.  From there, you can find the ones you want, their current values, which give you a good idea on how to set them.
!Docs
Maya 2019 docs:
** [[FBX Plugin UI|http://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=GUID-A525A2DF-AEBE-48FB-8783-C712C877BB62]]
*** [[FBX Export|http://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=GUID-BD85FA4C-4D40-457C-BE66-47BC08B82FC3]]
*** [[FBX Import|http://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=GUID-A31311AB-6FEB-4408-9937-48621CE24EA0]]
**[[FBX Mel Scripting|http://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=GUID-F48E3B78-3E56-4869-9914-CE0FAB6E3116]]
*** [[FBX Export Mel Commands|http://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=GUID-6CCE943A-2ED4-4CEE-96D4-9CB19C28F4E0]]
*** [[FBX Import Mel Commands|http://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=GUID-699CDF74-3D64-44B0-967E-7427DF800290]]
*** [[FBX General Mel Commands|http://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=GUID-335F2172-DD4D-4D79-B969-55238C08F2EF]]
http://en.wikipedia.org/wiki/Facial_Action_Coding_System
From Wikipedia:
<<<
Using FACS, human coders can manually code nearly any anatomically possible facial expression, deconstructing it into the specific Action Units (AU) and their temporal segments that produced the expression. As AUs are independent of any interpretation, they can be used for any higher order decision making process including recognition of basic emotions, or pre-programmed commands for an ambient intelligent environment.
<<<
This is good for facial rigging, since there are (quoted from Jeremy Ernst's [[GDC paper|http://www.unrealengine.com/files/downloads/Jeremy_Ernst_FastAndEfficietFacialRigging2.pdf]] ) "32 action units of the face that can achieve the entirety of the range of (e)motion of the face.  An action unit is deﬁned as a contraction or relaxation of 1 or more muscles in the face".

This allows the rigger a set of guidelines to go by when creating facial rigs, that can more closely match what the human face is actually doing.

Good resource:
* https://melindaozel.com/facs-cheat-sheet/
From here:
http://help.autodesk.com/view/MAYAUL/2018/ENU/?guid=__files_to_wrap_GUID_3F96AF53_A47E_4351_A86A_396E7BFD6665_htm
{{{
from maya import OpenMayaUI as omui
widgetStr = mel.eval( 'string $tempString =$gMainCreateMenu' )
ptr = omui.MQtUtil.findControl( widgetStr )
widget = wrapInstance(long(ptr), QWidget)
}}}
----
Also see:
* [[PySide : How can I search Maya for Qt created objects?]]

Given a matrix, how do you find the corresponding Euler rotations?  I've been working on this problem, and my "Third Solution, Method A" gives a good result.  But you can see a history of research below.
!Third Solution
After learning more from the API, I realized the differences between the [[MMatrix|http://download.autodesk.com/us/maya/2010help/api/class_m_matrix.html]] class and the [[MTransformationMatrix|http://download.autodesk.com/us/maya/2010help/api/class_m_transformation_matrix.html]] class:  {{{MMatrix}}} purely holds matrix data, not unlike a {{{MVector}}} storing vector data and nothing else.  the {{{MTransformationMatrix}}} does everything a {{{MMatrix}}} does, but it has knowledge of how Maya wants it, so provides many convenience methods for accessing the data and interaction with a Maya transform node..

The big takeaway I found is this:  {{{MTransformationMatrix}}} objects will keep track of rotations past 360 degrees & rotation order of the node, while {{{MMatrix}}} objects will not. It makes sense:  A matrix itself stores orientations as vectors, which have no concept how how far they've been 'rotated', so the {{{MMatrix}}} object doesn't track this data.  But behind the scenes, the {{{MTransformationMatrix}}} //does// track this info, which the below examples illustrate.

One more very important piece of data:  When extracting a {{{MTransformationMatrix}}} from a node via a {{{MFnTransform}}} function, what you're actually getting is the //local transformation matrix// of that node (since the {{{MTransformationMatrix}}} appears to track transformations //specific to that node//, relative to its parent).  So if you were expecting the world-space matrix, prepare to be disappointed.  You can build a {{{MTransformationMatrix}}} out of a worldspace-acquired {{{MMatrix}}}, but at that point 'rotations past 360' would be lost.
{{{
# Python code
import math
import maya.cmds as mc
import maya.OpenMaya as om

# Define a node to pull a matrix from.
node = 'pCube1'
# Set some rotation values for comparison later:
mc.setAttr('%s.rotate'%node, 15, -45, 1000)
mc.setAttr('%s.scale'%node, 3, 3, 3)
# Change the rot order, to make sure returned euler values are correct:
mc.setAttr('%s.rotateOrder'%node, 3)
}}}
''Method A:  Using {{{MTransformationMatrix}}}''
This, in my opinion, is the best solution.  Via the API, you get an {{{MDagPath}}} object for your node, wrapper it in a {{{MFnTransform}}} object, and then directly extract a {{{MTransformationMatrix}}} object from it.  This object tracks the rotation order (so you don't have to), and rotation values past 360
{{{
#-------------------------------------------
# Part 1: Get a MTransformationMatrix from an object for the sake of the example.
# You can use your own MTransformationMatrix if it already exists of course.

# get a MDagPath for our node:
selList = om.MSelectionList() # make a sel list # MSelectionList
mDagPath = om.MDagPath() # create an empty dag path # MDagPath
selList.getDagPath(0, mDagPath) # fill the dag path with our node

# Create a MFnTransform object for our MDagPath,
# and extract a MTransformationMatrix from it:
transformFunc = om.MFnTransform(mDagPath) # MFnTransform
mTransformMtx = transformFunc.transformation() # MTransformationMatrix

#-------------------------------------------
# Part 2, get the euler values
# Get an MEulerRotation object
eulerRot = mTransformMtx.eulerRotation() # MEulerRotation
# note, we *don't* have to set the rot order here...

# Convert from radians to degrees:
angles = [math.degrees(angle) for angle in (eulerRot.x, eulerRot.y, eulerRot.z)]
print angles, "MTransformationMatrix"
}}}
''Method B: Using {{{MMatrix}}}''
While this method works well, it doesn't track rotation values past 360:  We create a {{{MMatrix}}} object directly from the {{{worldMatrix}}} attr queried on our node.  We then convert that to a {{{MTransformationMatrix}}} object so we can extract the {{{MEulerRotation}}} object.  However, the {{{MMatrix}}} doesn't pass along the rotation order of the node, and has no knowledge of rotations past 360, so the resultant {{{MTransformationMatrix}}} won't know that stuff either.  To get accurate rotation values, we have to save the rotation order value ahead of time, and then apply it back before retrieving values from our {{{MEulreRotation}}} object.
{{{
#-------------------------------------------
# Part 1:  Get a MMatrix from an object for the sake of the example.
# You can use your own MMatrix if it already exists of course.

# Get the node's rotate order value:
rotOrder = mc.getAttr('%s.rotateOrder'%node)
# Get the world matrix as a list
matrixList = mc.getAttr('%s.worldMatrix'%node) # len(matrixList) = 16
# Create an empty MMatrix:
mMatrix = om.MMatrix() # MMatrix
# And populate the MMatrix object with the matrix list data:
om.MScriptUtil.createMatrixFromList(matrixList, mMatrix)

#-------------------------------------------
# Part 2, get the euler values
# Convert to MTransformationMatrix to extract rotations:
mTransformMtx = om.MTransformationMatrix(mMatrix)
# Get an MEulerRotation object
eulerRot = mTransformMtx.eulerRotation() # MEulerRotation
# Update rotate order to match original object, since the orig MMatrix has
# no knoweldge of it:
eulerRot.reorderIt(rotOrder)

# Convert from radians to degrees:
angles = [math.degrees(angle) for angle in (eulerRot.x, eulerRot.y, eulerRot.z)]
print angles, "MMatrix"
}}}
Running the above code prints (and I reformatted the float precision issues, just so it's easier to compare to the above values):
{{{
[15, -45, 1000] MTransformationMatrix
[15, -45, -80] MMatrix
}}}
As you can see, the {{{MTransformationMatrix}}} stores the rotation values past 360 deg, while the {{{MMatrix}}} doesn't.
!Second Solution
When I came up with this solution I thought it was pretty slick.  But solution 3 above is even better since it skips the need to create the {{{MQuaternion}}} objects.
{{{
import math
import maya.cmds as mc
import maya.OpenMaya as om

# Define a node to pull a matrix from.  No reason you
# can't use your own matrix of course.
node = 'pCube1'
# Get this nodes rotate order:
rotOrder = mc.getAttr('%s.rotateOrder'%node)

# Get the world matrix as a list
matrixList = mc.getAttr('%s.worldMatrix'%node)
# create an MMatrix to hold this data:
mMatrix = om.MMatrix()
om.MScriptUtil.createMatrixFromList(matrixList, mMatrix)

# Convert MMatrix to MTransformationMatrix object:
mTransformationMatrix = om.MTransformationMatrix(mMatrix)

# Get rotation as MQuaternion:
mQuaternion = mTransformationMatrix.rotation()

# Convert to MEulerRotation:
mEulerRotation = mQuaternion.asEulerRotation()

# Update rotate order to match original object:
mEulerRotation.reorderIt(rotOrder)

# Convert from radians to degrees:
angles = [math.degrees(angle) for angle in (mEulerRotation.x,
mEulerRotation.y,
mEulerRotation.z)]
print angles
# [-26.706627346943492, -79.553892761509957, -58.387916674342371]
}}}
Matrices store rotations as vectors, and because of this (and due to conversion to quaternions) they have no concept of rotations past 360 degrees.  So if you have a source rotation value of 400, this will convert it to 40 (400 - 360 = 40).

While reading the API docs, I saw that the {{{OpenMaya.MTransformationMatrix}}} has a {{{getRotation()}}} method.  I thought it would be better using this method  so I could skip the {{{MQuaternion}}} \ {{{MEulerRotation}}} conversion.  However, I wasn't ever able to get it to work.  Doing some research, I ran across [[this post|http://www.rtrowbridge.com/blog/2009/02/python-api-mtransformationmatrixgetrotation-bug/]] that confirms that it __is a bug__.
!First Solution
[[This post|http://forums.cgsociety.org/archive/index.php/t-617014.html]] gave me the knowledge of how to tap into Maya's API (via Python) to find a solution.
Below is the //first// solution I came up with:  It just uses {{{MMatrix}}} , no {{{MTransformationMatrix}}} is involved.  The problem was that if you //scaled// the node being queried, it would throw off the ~MQuaternion ahead of time.  The above solution fixes this issue.
{{{
# Python code

import math
import maya.cmds as mc
import maya.OpenMaya as om

# Define a node to pull a matrix from.  No reason you
# can't use your own matrix of course.
node = 'pCube1'

# Get the world matrix as a list
matrixList = mc.getAttr('%s.worldMatrix'%node)

# Convert list to MMatrix object
mMatrix = om.MMatrix()
om.MScriptUtil.createMatrixFromList(matrixList, mMatrix)

# Convert matrix to quaternion (MQuaternion object)
quatRotate = om.MQuaternion()
quatRotate.assign(mMatrix)

# Pull Euler rotation values from quaternion (MEulerRotation object)
eulers = quatRotate.asEulerRotation()

# Convert from radians to degrees:
angles = [math.degrees(angle) for angle in (eulers.x, eulers.y, eulers.z)]
print angles

# [-26.706627346943492, -79.553892761509957, -58.387916674342371]
}}}

Presume you have line (vector) defined by the points {{{vStart}}}, {{{vEnd}}}.  You have a point {{{somePoint}}} living off in space.  You want to find the closest point on the line to the point off in space.  As it turns out, this point defines a vector from {{{somePoint}}} to the line which is perpendicular to the line.
{{{
# Python code
from maya.OpenMaya import MVector

# point off in space
somePoint = MVector(5,7,0)

# start and end of our line:
vStart = MVector(0,0,0)
vEnd = MVector(10,10,0)

# Vector of our line from start to end
vStartEnd = vEnd - vStart
# Find our line length:
vStartEndLen = vStartEnd.length()
# Normalize our line vector length:
vStartEnd.normalize()
# Vector from the start of our line to the point in space:
vStartToPoint = somePoint - vStart

# * = dot product.  This is the distance along our vStartEnd
# where the new point will live.
projectLen = vStartToPoint * vStartEnd

# Find the new point position.  Here, * is multiply,
# since projectLen is a float, not a MVector.
newPoint = vStartEnd * projectLen + vStart

# Bonus:  Track if this point lies within the bounds of our line.  Picture a cylinder
# with infinite radius surrounding our line.  Does the point fall in that?
if projectLen < 0 or projectLen > vStartEndLen:
print "Out Of Bounds:", newPoint.x, newPoint.y, newPoint.z
else:
print "In Bounds:", newPoint.x, newPoint.y, newPoint.z
}}}
{{{
In Bounds: 6.0 6.0 0.0
}}}
For compound attrs like translate, rotate, scale (etc), find their child attrs:
{{{
print mc.attributeQuery("translate", node="null1", listChildren=True)
# [u'translateX', u'translateY', u'translateZ']
}}}
----
Also see:
* [[API: Find all child attributes under a compound attribute]]
You can use....
*The API class method {{{MFnNurbsCurve.closestPoint}}}
**This node can be created via the [[pointOnCurve|http://download.autodesk.com/global/docs/maya2012/en_us/CommandsPython/pointOnCurve.html]] command.
This is a nice little implementation in pure Python:
https://github.com/Vectorized/Python-KD-Tree
https://en.wikipedia.org/wiki/K-d_tree

Easy to use in Maya:
{{{
from random import randrange
import maya.api.OpenMaya as om2
import kdtree

def distBetween(ptA, ptB):
# Return the distance between two MPoint instances.
return ptA.distanceTo(ptB)

numPoints = 10000
points = [om2.MPoint(randrange(-100,100), randrange(-100,100), randrange(-100,100)) for i in range(numPoints)]
testPoint = om2.MPoint(0,0,0)
kd_tree = kdtree.make_kd_tree(points, dim=3)

# Get the closest neighbor:
nearest = kdtree.get_nearest(kd_tree, testPoint, 3, distBetween, return_distances=True)
print nearest
# [5.477225575051661, maya.api.OpenMaya.MPoint(-2, 5, 1, 1)]

# Get the closest neighbors:
numNeighbors = 3
nearNeighbors = kdtree.get_knn(kd_tree, testPoint, k=numNeighbors, dim=3, dist_func=distBetween)
for nn in nearNeighbors:
print nn
# (5.477225575051661, maya.api.OpenMaya.MPoint(-2, 5, 1, 1))
# (8.06225774829855, maya.api.OpenMaya.MPoint(4, 0, 7, 1))
# (8.774964387392123, maya.api.OpenMaya.MPoint(8, -2, -3, 1))
}}}
I recently ran into this problem:  I had code written that would snap one object to another via their matrices quite successfully.  I was using this in a rigging 'snap' tool.  Then, the design of the rig changed, and there became an offset needed between the source and target nodes (in my case some rotational values).  Since matrices are such nice things to use for transformational snapping, I wanted to keep using this method, but needed a way to calculate the difference of two matrices, store that value, then later re-apply it after the snap to make my target have the same relative transformations to the source based on their initial state.  This is hard to describe, so here's the proof of concept example I came up with:
*Two nodes (cubes) in Maya.
*Transform them both to some new location in space (both in the same location).
*Now apply an //additional// transformation to the target node... maybe a rotational offset?
Our goal is to allow us to transform the target to any new value, and have the source match the new transformation with relative offset previously assigned.  Meaning, if you'd rotated the source cube 90 deg's off from the target, wherever we transform the target, we want the source to match, rotated the same 90 deg's off.

----
This is all implemented through Python, and the code for the imported {{{matrixutils}}} module can be found in my subject [[here|Working with matrices with Python]].  It accesses the API's (~OpenMaya) {{{MMatrix}}} object and provides wrapper code for using it in Python.
{{{
# Python code
import maya.cmds as mc
import matrixutils as matrix

# Setup names for our source node, and our target node.
source = 'src'
target = 'dest'
The ''source'' node is the one being later trasformed.  The ''target'' node is what the difference matrix is applied relative to, to move the source.
}}}
As described above, transform both nodes to some location in space.  Then apply an additional transformation to the source.

Find the worldspace inverse matrix of our target object, and the worldspace matrix of our source:
{{{
srcWorldMatrix= matrix.getMMatrix(source, 'worldMatrix')
targetWorldInverseMatrix= matrix.getMMatrix(target, 'worldInverseMatrix')
}}}
Create the difference matrix.  This is done by multiplying the source world matrix by the target inverse world matrix ... sort of like subtracting the target matrix from the source to find the difference.
Maya matrix application order is <- right to left -
{{{
differenceMatrix = sourceWorldMatrix * targetWorldInverseMatrix
}}}
Set target to the difference matrix, just for visualization:  Should snap it to (or near, depending on the initial difference between the nodes) the origin, showing the difference in trans, rot, scale, and shear vals that were stored:
{{{
matrix.matrixXform(source, differenceMatrix, 'worldSpace')
}}}
Now move the source node somewhere new in the Maya scene.
Execute this code to find the //new// source worldspace matrix:
{{{
newTargetWorldMatrix = matrix.getMMatrix(target, 'worldMatrix')
}}}
Finally move our source to the new location, with the {{{differenceMatrix}}} applied.
{{{
offsetMatrix = differenceMatrix * newTargetWorldMatrix
matrix.matrixXform(source, offsetMatrix, 'worldSpace')
}}}
It should snap to the new location of the target node, but have the additional difference matrix applied, putting it into the same relative state that was originally captured.

I'd been looking for a LONG time, trying to find an IDE for Python that mirrored //one// important feature from Maya's Script Editor:  ''Evaluate Selection'', which is the ability to @@highlight blocks of code@@, and execute just that code block interactively.  This is also known as [[REPL|http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop]].

//Maya's// implementation of Python in the Script Editor can do this (since version 8.5), but what if you want to author Python //outside// of Maya, but still have this functionality?

After asking a lot of people, and doing a lot of searching, I finally found Wing:
http://www.wingware.com/
''Wings docs on integrating with Maya'' [[here|http://www.wingware.com/doc/howtos/maya]] (which actually links back to this wiki :-) )
It has three versions:
*Wing IDE 101 (free, but //doesn't// have 'Evaluate Selection')
**Update:  It appears you can hack it:  I saw this post in a forum, from Wingware itself:
<<<
It looks like the keyboard config pref is omitted in 101.  You could
hack it by editing the keyboard.normal file in the Wing installation
{{{'Ctrl-F5': 'evaluate-file-in-shell'}}}
Or to evaluate the current selection:
{{{'Ctrl-Shift-F5': 'evaluate-sel-in-shell'}}}
You'll need to restart Wing to read those and you can of course alter
the bindings.  Also, if you're using one of the other keyboard
personalities you should edit the associated keyboard.* file instead.
<<<
*Wing IDE Personal $*Wing IDE Professional ($ - Lets you access the Wing API via Python, to write your own plugins for the tool + other features)
----
Compare version features:
http://www.wingware.com/wingide/features
----
@@Interaction With Maya@@:
*I've come up with three different ways that allow Wing to interact with Maya and vice-versa (Maya Script Editor replacement, Remote debugging Maya data in Wing, Run Maya in Wings Python shell).  See that subject here:
**[[Interaction between Wing and Maya]]
----
Notes:
*Wing itself runs a different version of Python than you may have installed. This is only really important if you get the "Pro" version, and start making your own 'plugin scripts':  The plugins you author will be running Wings version of Python, not your installed version of Python.  Furthermore, the Wing distribution isn't the full install of Python, so not all modules are available.  This isn't a huge deal, but it's important to be aware of if your plugins don't work.  For example, I had one that called to the {{{webbrowser}}} module:  That module isn't distributed with Wing.  Luckily, they have their own function ({{{wingapi.gApplication.OpenURL(url)}}}) that could be used in its place.
Pretend you have 1000 separate leaf mesh.  They're all UV'd the same, but have been polycombined.  You need to split them into separate mesh, and have their pivots placed at the root of the leaf (which is a consistent UV point).  How to do?

After separation, select all the mesh and open the UV Texture Editor.  Drag-select over the UV you want to snap the pivot too:  Since all the mesh have the same uv's, you should now have 1000 uv's picked.  Run the below code:   It will find the vert for each UV, the mesh for the vert, then snap its transform-level pivot to that UV's position.
{{{
import pymel.core as pm

def snapPivToSelUv():
sel = pm.ls(selection=True)
if any([item for item in sel if not isinstance(item, pm.MeshUV)]):
pm.displayError("Only UVs can be selected")
return
pm.undoInfo(openChunk=True)
pm.waitCursor(state=True)
try:
for uv in sel:
# Convert from UV to vert.
# polyListComponentConversion returns a strings, rather than PyNodes
vert = pm.PyNode(pm.polyListComponentConversion(uv, toVertex=True)[0])
pos = pm.pointPosition(vert, world=True)
vert.node().getParent().setPivots(pos, worldSpace=True)
pm.displayInfo("Done: Snapped pivots to UVs on %s nodes!"%len(sel))
finally:
pm.undoInfo(closeChunk=True)
pm.waitCursor(state=False)

snapPivToSelUv()
}}}
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; }}} or: {{{ string$panel = playblast -activeEditor;
}}}
{{{playblast}}} has the advantage of always returning back a {{{modelPanel}}}: The {{{getPanel}}} command can return back other editor types that may be active.
Also see:
*[[How do I query the name of the camera in the active panel?]]
I needed to get a list of faces sorted by their surface area.  Came up with a nifty one-line solution (not counting the function definition) using ~PyMel:
{{{
from operator import itemgetter
import pymel.core as pm

def getSortedFaceArea(mesh):
return sorted( [[face, face.getArea(space='world')] for face in mesh.f], key=itemgetter(1) )
}}}
Sorts from smallest to largest:
{{{
mesh = pm.ls(selection=True)
sortFace = getSortedFaceArea(mesh[0])
for data in sortFace:
print data

[MeshFace(u'myMeshShape.f[280]'), 0.21133190393447876]
[MeshFace(u'myMeshShape.f[293]'), 0.21990084648132324]
[MeshFace(u'myMeshShape.f[456]'), 0.2264084368944168]
[MeshFace(u'myMeshShape.f[611]'), 0.23773804306983948]
# etc...
}}}
Also see: [[Compiling Python Libraries for Maya]]

Had to do some hoop-jumping to get "[[pywin32|http://sourceforge.net/projects/pywin32/]]" working in Maya.  Here's what started the whole shebang:
*Wanted to have Maya control Photoshop.  You can do this via the aforementioned package.
*I'm using Maya2010 64-bit (Win7), with the 64-bit cut of Python 2.6.
*My system-level Python is 32-bit, 2.6.
*When you install {{{pywin32}}}, it installs into the root Python {{{\site-packages}}} folder, which is entirely different from the one Maya's cut of Python uses.
*Furthermore, it doesn't install under a single package dir:  It creates a lot of dirs and loose files, pretty messy.
These were the steps I went through to get it working in Maya:
#Download the latest cut:  This is the 64-bit cut of the package that will match up with Maya's 64-bit cut of Python.
##https://github.com/mhammond/pywin32/releases
#Presuming you already have other pre-existing site-packages installed, rename Python's default {{{C:\Python26\Lib\site-packages}}} dir to {{{C:\Python26\Lib\site-packages-old}}} before the install:  When then install runs, it will create a new {{{\site_package}}} dir.
#Run the downloaded package executable.  When complete, the new {{{C:\Python26\Lib\site-packages}}} dir will be populated with the new (messy) package contents.
#Copy all of that new stuff to Maya's site-package dir here: {{{C:\Program Files\Autodesk\Maya20XX\Python\Lib\site-packages}}}
#Delete the new {{{C:\Python26\Lib\site-packages}}} dir.
#Rename the old dir {{{C:\Python26\Lib\site-packages-old}}} back to {{{C:\Python26\Lib\site-packages}}}.
#Launch Maya, and in the script editor, execute:
{{{
# Need to import in order
import pythoncom
import win32com.client
}}}
It should work without a hitch.
And continuing our Photoshop example, launch Photoshop from Maya:
{{{
photoshop = win32com.client.Dispatch("Photoshop.Application")
}}}
Blam.
----
Other notes
* If you get an error like this:
{{{
# ImportError: No system module 'pywintypes' (pywintypes27.dll) #
}}}
It means you need to add this directory to your Windows PATH env var:
{{{
c:\path\to\pywin32_system32
}}}
----
*It appears that you //must// put the {{{pywin32}}} package contents in Maya's {{{/site-packages}}} dir:  I tried other package paths I had defined, but they didn't work.  This package comes with a {{{pywin32.pth}}} file.  According to the docs for the Python [[site|http://docs.python.org/library/site.html]] module, {{{.pth}}} files are only recognized when they live in one of four special directories.
----
Nice tutorial on how to compile ~PyWin32 here:
----
Also see:
*[[Control Photoshop from Maya]]
{{{basename}}}
{{{dirname}}}
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
}}}
There's also the {{{nodeCast}}} command.
''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}}} --
{{{
Name\Handle:  Joe Bobson
Email (optional): joebob@bob.com
Comment:  Wow, what a great wiki!
}}}
----
----
----
Date: Feb 08, 2010
Name: Zhi Min Chen
Comment:  Great website.  I use it on a regular basis to help me on most my projects at work.  Your wiki's question format is very well thought out and it makes finding answers extremely quick.  Keep up the great work!
----
Date: Aug 15, 2008
Name: Ben Brac
Email: brac@sonic.net
Comment: Awesome wiki ~WarpCat! I use it all the time when the usual documentation leaves much to be desired. Keep the updates coming!
----
Date:  Aug 06, 2008
Name:  Bob Aunum
"Thank you sincerely.  The subject says it all. since i decided to teach myself mel,  i've been looking for a good web resource, but i really didn't expect to find anything as elegantly functional as your wiki.  It's great. Really."
----
Date:  May 19th, 2008
Name:  Fridi Kühn
I am a student from Germany and have recently started helping a games developer to automate their process of animating.  Since I am a beginner to MEL I wanted to state that your mel wiki helped me alot to overcome the mysteries of that language.  I just wanted to thank you for your website and hope you will keep it alive! Please know that I will frequently return and see what's new...
----
<<timeline>>
Had a really weird bug where whenever the hotbox was shown, it will take a 'screen capture' of the previous window inside it.

Come to find out, on Windows 7, if you have it's theme set to 'classic mode', it causes this problem. If you set it back to 'Areo', it seems to resolve.
These settings seem to change on me, so I need to get them all down in one place so I know how to set them back again...
http://help.autodesk.com/view/MAYAUL/2016/ENU//index.html?guid=GUID-8C8BA1F4-59EE-4A02-BFE7-9193C0D6F5BE
* Maya Selection Preferences:
** Modifiers (If something //isn't// listed, it means its off)
*** Affects active : If you change from object to component selection mode, the selected object is not affected. This option lets you select objects and components at the same time.
*** Preselection Highlight : Highlights components in a different color as the mouse cursor passes over them.
*** Preserve Component Selections : This option applies to polygon components only. When on, this option backs up your component selection when you switch selection type, and restores the type that you are switching to.
*** Track selection order : This option allows Maya to start tracking the selection order of components.
** Click box size : 8 : This option controls the size of the selection area around the mouse pointer, or click box.
** Select dead space : 8 : Lets you select edges and vertices when your cursor is //outside// of your object, in dead space, by setting a preselection tolerance.
** Tweak dead space : 120 : Lets you tweak edges and vertices when your cursor is outside of your object, in dead space, by setting a preselection tolerance.
* Modeling Toolkit:
** Pick/Marquee : On
** Camera Based Selection : OFF : This will only pick components you can see.
** All others:  OFF

Ran across an animators file where their ~animCurves were getting templated, thus making them un-editable.  I wrote a little script to unlock them:
{{{
import maya.cmds as mc
nodeAttrs = mc.ls("*.keyTimeValue")
for na in nodeAttrs:
mc.setAttr("%s[0]"%na, lock=False)
}}}
But it didn't explain why this was happening.

As it turns out, there's an option in the Anim Layers -> Options -> 'Template Curves Of Locked Layers' that is on by default, that causes this.  Neither of us realized this, but turning that off fixed it.
{{{
lockNode -lock
}}}
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.
You can also make connected attributes 'non-deletable' as well via:
{{{
lockNode -lockUnpublished;
}}}
If you don't do that, and run a {{{delete -ch}}} on a node with inputs, it's history (both connections and node) can be deleted as well.
Requested\suggestion from Matti Grüner:

You can use the {{{addRecentFile.mel}}} script that comes with Maya.  Lives here by default:
{{{
}}}
Easy to call to via mel:
{{{
addRecentFile(string $file, "mayaAscii"); // or "mayaBinary" }}} If that succeeded, and you go to {{{File -> Recent Files -> }}}You should see the newly added entry. ---- If you want to do this in Python: {{{ import maya.cmds as mc import maya.mel as mm # Open the file! openMe = r'c:\some\awesome\file.ma' mc.file(openMe, open=True, force=True) # Then update the menu! fileType = '' if openMe.endswith('.ma'): fileType = 'mayaAscii' elif openMe.endswith('.mb'): fileType = 'mayaBinary' # Must convert slashes before calling to the mel: mm.eval('addRecentFile("%s", "%s")'%(openMe.replace('\\', '/'), fileType)) }}} You can lock nodes and attrs, how do you lock an animCurve so it can't be modified? {{{ mc.setAttr('myCurveName.keyTimeValue', lock=True) }}} Insert on a PC appears to be 'function+left arrow' on a Mac. :-S If the Outliner is active, you can use the //mel procedure//: {{{ fitPanel -selected; }}} Which lives here: {{{ C:\Program Files\Autodesk\Maya20XX\scripts\others\fitPanel.mel }}} I knew this worked for cameras, but didn't know it worked in an {{{outlinerPanel}}} \ {{{outlinerEditor}}} as well. It appears this is the default '{{{f}}}' hotkey... went for many years without knowing this... ---- If the Outliner isn't active, and you want to force this via code through say a custom hotkey: {{{ import maya.cmds as mc # Name of the default Outliner: outliner = "outlinerPanel1" mc.setFocus(outliner) mc.outlinerEditor(outliner, edit=True, showSelected=True) }}} {{{setFocus}}} isn't needed, but it doesn't hurt either. 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) >See [[How can I find all instanced nodes in the scene?]] for an updated version. ---- Update 2: Some ~PyMel to uninstance all the mesh in the scene. Note, this will destroy any children of any of the mesh. {{{ import pymel.core as pm mesh = pm.ls(type='mesh') for m in mesh: instances = m.getInstances() if len(instances) < 2: continue for instMesh in instances: dupeT = pm.duplicate(instMesh)[0] dupeM = dupeT.getChildren(type='mesh')[0] parentT = instMesh.firstParent() pm.delete(parentT) unique = parentT.nextUniqueName() dupeT.rename(parentT) unique = dupeM.nextUniqueName() dupeM.rename(unique) }}} ---- Update 1: As of Maya 2010, there is a Run Time Command called: {{{ ConvertInstanceToObject }}} ...that will do this. I'm not too sure when this was introduced. In in turn calls to the global procedure: {{{ convertInstanceToObject() }}} ...which is found in the mel script: {{{ C:\Program Files\Autodesk\Maya2010\scripts\others\convertInstanceToObject.mel }}} It should be noted that running this is destructive: If the instance has any children with any kind of connections, those connections will be lost as part of the procs duplication\deletion operation. The code I have below works around this issue, in a non-destructive way: ---- 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.  If it's not coming up in your Playblast options, this may be the answer: https://knowledge.autodesk.com/support/maya/troubleshooting/caas/sfdcarticles/sfdcarticles/Maya-2015-and-2016-with-qt-playblast-on-Windows-absent.html Basically, add this: {{{ C:\Program Files (x86)\QuickTime\QTSystem\ }}} To you windows {{{PATH}}} system variable, presuming you have that dir to begin with. There is a handy command for this: {{{toolPropertyWindow}}} ---- Also see: *[[I want to add a UI to the tool settings window, how do I do that?]] Simple tool to add Python code as a button in the active shelf. {{{ import pymel.core as pm def addPyToShelf(label, pyCode, annotation=""): """ Add the provided Python code as a shelf button, to the current shelf. Parameters: label : The 5-character label to display on the shelf icon. pyCode : string : The code to add to the shelf button. annotation : string : Default empty string : Any additional annotation to use as a tooltip popup. """ gShelfTopLevel = pm.language.melGlobals['gShelfTopLevel'] if pm.tabLayout(gShelfTopLevel, query=True, exists=True): currentShelf = pm.tabLayout(gShelfTopLevel, query=True, selectTab=True) pm.setParent(currentShelf) sBut = pm.shelfButton(label=label, imageOverlayLabel=label, command=pyCode, image1='commandButton.png', sourceType='python', ann=annotation, style=pm.shelfLayout(currentShelf, query=True, style=True), width=pm.shelfLayout(currentShelf, query=True, width=True), height=pm.shelfLayout(currentShelf, query=True, height=True)) else: pm.displayWarning("The shelf doesn't appear to exist") }}} {{{ addPyToShelf("test", "import pymel.core as pm\nprint pm.ls(type='transform')", annotation="Print all the transforms in the scene") }}} ---- Also see: *[[How can I make a shelf button via mel?]] Maya 2011(?) introduced the {{{fileDialog2}}} command that supplants the {{{fileBrowserDialog}}}. It has a option on the left for 'folder bookmarks', and for the longest time I couldn't figure out how to update that field. As it turns out, you just drag-and-drop a folder into that field to make a bookmark. That's it. {{{ # Python code import maya.cmds as mc node = 'myNode' colorAttr = 'color' defaultColor = [.9,.9,.9] mc.addAttr(node, longName=colorAttr, attributeType='float3', usedAsColor=True) mc.addAttr(node, longName='%sR'%colorAttr, attributeType='float', parent=colorAttr, defaultValue=defaultColor[0]) mc.addAttr(node, longName='%sG'%colorAttr, attributeType='float', parent=colorAttr, defaultValue=defaultColor[1]) mc.addAttr(node, longName='%sB'%colorAttr, attributeType='float', parent=colorAttr, defaultValue=defaultColor[2]) }}} Some control types, like {{{iconTextButton}}} and {{{shelfButton}}} have a {{{commandRepeatable}}} arg that can be set True. {{{menuItem}}} has a {{{enableCommandRepeat}}}. Strange that the {{{button}}} command doesn't support this? :S ---- Also, Maya has a {{{repeatLast}}} command, that appears to be undocumented (as of Maya 2016). Here's the interactive help: {{{ help repeatLast; // Result: Synopsis: repeatLast [flags] Flags: -e -edit -q -query -ac -addCommand String -acl -addCommandLabel String -cl -commandList Int -cnl -commandNameList Int -hl -historyLimit Int -i -item Int -nhi -numberOfHistoryItems Command Type: Command // }}} You can pass a string to the {{{addComand}}} flag. //However// it only parses ''mel'', not Python. To executePython, you need to jump through some hoops: {{{ import pymel.core as pm def doStuff(): print "doing stuff" pm.repeatLast(addCommand="python(\"doStuff()\")", addCommandLabel="doStuff") }}} {{{ import maya.cmds as mc mc.text(label='<a href="http://lmgtfy.com/?q=HTML+link">hey click here.</a>', hyperlink=True) }}} Thanks to this post: https://groups.google.com/forum/?fromgroups#!topic/python_inside_maya/BvwoSWBnu4E {{{ mayaWin = mm.eval("string$__convert__ = $gMainWindow;") mc.setParent(mayaWin) mc.menu(label="My Main Menu!") }}} In this example, I'll add a multi attr of type 'message' to a {{{null1}}} node, and then start connecting the message attrs of other nodes to it: {{{ # Python code import maya.cmds as mc mc.addAttr('null1', longName='myMulti', attributeType='message', multi=True ) mc.connectAttr('object1.message', 'null1.myMulti[0]') mc.connectAttr('object1.message', 'null1.myMulti[1]') }}} To get a list of which attr indices are currently in use, and their values: {{{ # indices are in the form of ["myMulti[0]", "myMulti[1]", etc...] indices = mc.listAttr('null1.myMulti', multi=True) for i in indices: print "null1."+i, " --> ", mc.listConnections('null1.'+i)[0] }}} {{{ null1.myMulti[0] --> object1 null1.myMulti[1] --> object2 }}} Notice how you use {{{listConnections}}}, not {{{getAttr}}} to query {{{message}}} attributes? ---- Also see: *[[How can I get a list of all the child multi-attrs, of a given attr name?]] First, you need to get the existing names: {{{ string$enumAttrs = addAttr -q -enumName "myNode.myEnumAttr";
"enumA:enumB:enumC"
}}}
Then you can append your new attr to those names, and add:
{{{
string $newEnumName = "enumD"; addAttr -e -enumName ($enumAttrs + ":" + $newEnumName) "myNode.myEnumAttr"; }}} ---- Same thing in Python: {{{ import maya.cmds as mc enumAttrs = mc.addAttr("myNode.myEnumAttr", query=True, enumName=True) newEnumName = "enumD" mc.addAttr("myNode.myEnumAttr", edit=True, enumName='%s:%s'%(enumAttrs,newEnumName)) }}} ---- Also see: * [[PyMel : enum attr access]] Adding a new index to a {{{layeredTexture}}} node takes a lot of work: Multiple nodes are created and connected together. I found the mel global proc {{{createRenderNodeCB}}} living in the {{{createRenderNode.mel}}} script, that actually does all the heaving lifting for you. You just need to pass in the {{{layeredTexture}}} node to update, and which index you're adding to. You'll note in the string formatting that a {{{'%node'}}} is being passed in: That's actually part of the //mel// syntax, but since it includes a percent sight, //Python// thinks its string formatting, if left in the main string. So, we actually use Python's string formatting to add it in, to avoid the error. Wacky. If you check the print in the Script Editor after you execute this, you'll see all the work it did. If you want it to add a 2d texture projected into 3d space, check this out: [[How can I project a 2d texture in 3d space]] : And run that code first. {{{ import pymel.core as pm layeredTex = "layeredTexture1" index = 0 mel = 'source createRenderNode; createRenderNodeCB -as2DTexture "" file "defaultNavigation -force true -connectToExisting -source %s -destination %s.inputs[%s].color; ";'%('%node', layeredTex, index) pm.mel.eval(mel) }}} Presuming you have a head mesh called {{{head}}}, that already has a blendshape node input called {{{blendShape1}}}, add a new mesh called {{{headShape3}}} as the next target in the list: {{{ import pymel.core as pm bsHeadMesh = pm.PyNode("head") bsNode = pm.PyNode("blendShape1") newTargMesh = pm.PyNode("headShape3") weightCount = pm.blendShape(bsNode, query=True, weightCount=True) pm.blendShape(bsNode, edit=True, target=[bsHeadMesh, weightCount, newTargMesh, 1]) }}} The {{{weightCount}}} is the number of current targets, which also gives you the 0-based index to add the next new target. The trailing {{{1}}} is how much influence should be used for the new target. Shoud probably always be {{{1}}}. Here's a tool that will add a new blendshape target mesh based on selection: {{{ import pymel.core as pm def addBsTarget(): """ Select two mesh in order: A new target mesh, and the mesh with blendshape to add it to. """ sel = pm.ls(selection=True) if not len(sel) == 2: pm.displayError("Please select exactly two mesh, the target, and the base head.") return newTargetMesh = sel[0] bsMeshNode = sel[1] bsMeshShape = pm.listRelatives(bsMeshNode, shapes=True, type='mesh')[0] bsNode = pm.listConnections(bsMeshShape, source=True, destination=True, type='blendShape') if not bsNode: pm.displayError("Could find no blendShape node on mesh: %s"%sel[1]) return weightCount = pm.blendShape(bsNode, query=True, weightCount=True) pm.blendShape(bsNode, edit=True, target=[bsMeshNode, weightCount, newTargetMesh, 1]) pm.displayInfo("Added new blendshape target: %s"%sel[0]) }}} http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/blendShape.html {{{scriptedPanel}}}s seem like magical things: Popping between UI's, docking in the UI and floating on top. How can you easily add one to your own UI? If they're already authored by Maya, it's a simple act of finding the name, then adding it to your own UI with the {{{scriptedPanel}}} command. Below, I create a window that has both a graph editor and the dope sheet, with a time-slider below them. And, it's actually really easy. How can you find the names of the existing scriptedPanels? In the Script Editor, turn on "echo all commands". Then pop off a window you want to use in your own ui. It should print a bunch of stuff, including a line like this: {{{ addDopeSheetPanel dopeSheetPanel1; }}} In this case, "{{{dopeSheetPanel1}}}" is the name of the scriptedPanel we care about, and can now embed into our own UI: {{{ # Python code: import maya.cmds as mc class App(object): """ Create the Maya UI object. """ def __init__(self): self.name = "scriptedPanelTest" self.title = "scriptedPanel test" if mc.window(self.name, exists=True): mc.deleteUI(self.name) mc.window(self.name, title=self.title, resizeToFitChildren=True) form = mc.formLayout() self.paneLayout = mc.paneLayout( configuration='horizontal2' ) mc.scriptedPanel( 'graphEditor1', e=True, parent=self.paneLayout) mc.scriptedPanel( 'dopeSheetPanel1', e=True, parent=self.paneLayout) mc.setParent(form) self.timePort = mc.timePort() mc.formLayout(form, edit=True, attachForm=[(self.paneLayout, "top", 1), (self.paneLayout, "left", 1), (self.paneLayout, "right", 1), (self.timePort, "left", 1), (self.timePort, "right", 1), (self.timePort, "bottom", 1)], attachControl=[(self.paneLayout, "bottom", 0, self.timePort)]) mc.showWindow() App() }}} The core command is the [[editorTemplate|https://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/editorTemplate.html]] Generally this is called to when making a some new plugin based {{{MPxNode}}} subclass. ---- One way to support a custom node is to make a custom {{{AE<nodeType>Template.mel}}} script that lives in your {{{MAYA_SCRIPT_PATH}}}. You can find an example of that here, for the custom {{{transCircle}}} node: {{{ C:\Program Files\Autodesk\Maya2016\devkit\plug-ins\transCircleNode\AEtransCircleTemplate.mel }}} ---- Optionally, if authoring a scripted plugin, here is an example setup I've seen done that embeds this data directly into the authoring class. Only the necessary code has been included. {{{ import maya.OpenMaya as om import maya.OpenMayaMPx as ompx class MyCustomNode(ompx.MPxNode): kPluginNodeName = "myCustomNode" # Bunch of code... @classmethod def nodeInitializer( cls ): # Bunch of code... # Then add the template cls.init_AETemplate() @classmethod def init_AETemplate( cls ): melCmd= ''' global proc AE%sTemplate( string$nodeName )
{
editorTemplate -beginScrollLayout;
editorTemplate -beginLayout "Main Attributes" -collapse 0;
editorTemplate -endLayout;
editorTemplate -endScrollLayout;
}
'''.%(cls.kPluginNodeName )
om.MGlobal.executeCommand( melCmd )
}}}
The "{{{AttrA}}}" , "{{{AttrB}}}", etc would be any custom attr name created on this node:  My guess is behind the scenes Maya uses the {{{connectControl}}} control type to hook into the attrs, appropriately displaying them in the Attribute Editor.

The idea is a custom classmethod called {{{init_AETemplate}}} is created, that defines the template for the given custom node.
It is called to inside the {{{nodeInitializer}}} classmethod, that is ultimately called to via the {{{initializePlugin}}} function inside that module, via the {{{OpenMayaMPx.MFnPlugin}}}'s {{{registerNode}}} method.
Just like anything else:
{{{
}}}
It's fun to add images to your UI's, but the biggest problem is when you give your UI to someone else, usually the hard-coded paths to the imagery break.
!!!Python
One nice thing about authoring modules in Python is they come with a 'special' {{{__file__}}} attribute.  This can let the module itself (or any module that calls to it) know its save location.  If you store your images in the same directory as the module, you're good to go.
In the below example, presume there is a {{{smiley.bmp}}} image saved in the same directory as the module:
{{{
# iconTextButtonTest.py

import os
import maya.cmds as mc

class App(object):
def __init__(self):
# define save location of this module:
self.path = os.path.dirname(os.path.abspath(__file__))

if mc.window(self.name, exists=True):
mc.deleteUI(self.name)

self.window = mc.window(self.name, resizeToFitChildren=True)
# Create icon based on relative path:
self.button = mc.iconTextButton( style='iconOnly', image=os.path.join(self.path, "smiley.bmp"))

mc.showWindow()

App()
}}}
!!!Mel
A older mel convention I've used is this:  Author an "image finder" script, and save it in the same dir as your images.  You can then use that script to define your image path during UI creation.
{{{
// save this as imageFinder.mel, and save it in the same directory as your images.
global proc string imageFinder()
{
string $description = whatIs imageFinder; string$path = substitute "Mel procedure found in: " $description ""; string$dir = dirname($path); return$dir;
}
}}}
Now, inside of your UI, you can call to this script to find the location of your images, and embed that path at UI creation time:
{{{
// UI code here......
string $imagePath = imageFinder; string$image = ($imagePath + "/foo.bmp"); image -image$image -h 128 -w 128;
// finish UI code....
}}}
Notes:
*This is just and example:  You will need a uniquely named "imageFinder.mel" script for each directory you'll be searching for images in.  I usually name them based on the script that is calling to the images.
*The first time you enter the 'imageFinder' script in Maya, it won't work when called to, since Maya will think the imageFinder //procudrue// was 'entered interactively'.  You'll either need to call the {{{source}}} command on the imageFinder script after it's been saved, or simply restart Maya.  It will work from that point on.
*You can use this same technique to have a script's "help" menu\button point to a 'help.html' file; again, with non hard-coded paths.
----
*Also see [[How can I have a script return where it lives on disk?]]
*{{{skinCluster}}} : name of a given {{{skinCluster}}} node.
*{{{joints}}}  :  List of joints
{{{
# Python code
import maya.cmds as mc

}}}
Say you have a set hierarchy in the Outliner, and you want to quickly pick one or more sets, add select one or more objects, and add the selected objects to the selected sets.  Maya has the 'Sets Relationship Editor', but I find it's implementation very clunky.
{{{
# Python code
import maya.cmds as mc

sel = mc.ls(selection=True)
nodes = []
sets = []
for item in sel:
if mc.objectType(item) == "objectSet":
sets.append(item)
else:
nodes.append(item)

for s in sets:
}}}
A slightly different way:  Add selected items to the single selected set.  If more than one set is picked, error.  Done in ~PyMel:
{{{
# Python / PyMel
import maya.OpenMaya as om
import pymel.core as pm

sel = pm.ls(selection=True)

allsets = []
nonset = []
for item in sel:
if isinstance(item, pm.nt.ObjectSet):
allsets.append(item)
else:
nonset.append(item)

if not len(allsets)==1:
om.MGlobal.displayError("Please select exactly one set node")
else:
pm.sets(allsets[0], forceElement=nonset)
print "Added items to set '%s':"%theset, [item.name() for item in nonset]
}}}
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 (or any other node) you want to add as a subset called '{{{subset}}}':
@@Mel:@@
{{{
}}}
@@Python:@@
{{{
import maya.cmds as mc
}}}
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.

@@~PyMel:@@
~PyMel flipped it around, so it makes a lot more sense:
{{{
import pymel.core as pm
}}}
Regardless of the method use, in the Outliner this would give you the effect:
{{{
- parentSet
|
---- subSet
}}}
{{{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.

It appears that at one point the below work was needed to be done on the //material//.  But as of Maya 2016, the work needs to be done on the node feeding //into// the material.

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.
The {{{animView}}} command will let you adjust the start\end time (x-axis), and the min\max value (y-axis) of an {{{animCurveEditor}}} (Graph Editor).

The docs say these values can be queried as well, but via mel it does nothing, and via Python I get a {{{RuntimeError}}}.
See: [[Why does adding an item to a Maya main menu remove all the items?]]
When you RMB in the Graph Editor, a context sensitive menu pops up.  How can you append your own items to this?
Actually, I have yet to figure out how, and here's why:
The script
{{{
C:\Program Files\Autodesk\Maya20XX\scripts\others\graphEditorPanel.mel
}}}
Holds all the code the creating these menus.  The proc {{{buildGraphEditorPopupMenuGeneral()}}} specifically is what creates the menu items, and the parental  menu's name is {{{graphEditor1GraphEdanimCurveEditorMenu}}}.  The issue is, the proc {{{buildGraphEditorPopupMenu()}}} is called whenever the RMB is clicked in the Graph Editor, and it firsts deletes all the menu items, then rebuilds them.
{{{
}}}
You'll never actually see it, since it will be deleted before the menu is rebuilt for display.

The proc {{{defineGraphEditorPopupMenu()}}} physically creates the actual {{{popupMenu}}} that houses the various menu items.  In //theory// this proc could be overridden to point to your own {{{popupMenu}}} creation code.
The time slider menu's name is {{{TimeSliderMenu}}}.  However, if you start appending items to it before the user has first clicked on it, all the standard menu items will be replaced with yours.  This issue is described here: [[Why does adding an item to a Maya main menu remove all the items?]]

The workaround for the time slider is a bit different than the main menu.
There is a script (and global proc) called {{{
}}}
When you execute the {{{TimeSliderMenu}}} global proc, it in turn calls to another global proc in that file called {{{updateTimeSliderMenu}}} which actually builds the menu.  You need to do these steps first before you add your own items:
{{{
}}}

Presuming you know the {{{shadingEngine}}} for the given material (use {{{listSets}}} to track down the {{{shadingEngine}}} for a given material), it's pretty easy.  In the below example we force a cube to be shaded by the default material:
Mel:
{{{
string $node = "pCube1"; string$shadingEngine = "initialShadingGroup";
sets -forceElement $shadingEngine$node;
}}}
~PyMel:
{{{
import pymel.core as pm
node = "pCube1"
}}}
Note how ~PyMel flips the arguments around (compared to mel) so it makes a bit more sense.
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'> #
}}}


Whenever you ctrl+shift ~LMB-click on a menu item in one of Maya's main menus, it'll add that command as an {{{shelfButton}}} in the active shelf (via the mel proc {{{menuItemToShelf}}}).  You can add your own menus to Maya's main menu as well, for easy user access.  If you add these menus via Python, and if the commands they call to are via Python, depending on how the commands are authored they won't later work when assigned to the shelf via ctrl+shift ~LMB-click.  The below code examples explain how to author them so they //will// work after Maya restarts.

In the below example, three files are authored:  One that holds a command to execute, one that builds the Maya menu, and another that then creates the menu.  Make sure they're saved in a directory that is part of Maya's Python path.
----
This creates a command to execute, that needs to be passed multiple arguments:
{{{
# someModule.py
import maya.cmds as mc
def someCommand(*args):
# A function that can take multiple arguments.
print "Executing someCommand():"
print "\t", args
}}}
This code creates the menu, and menuItems that passe multiple arguments to our command:
{{{
import someModule
mc.menuItem(label="Good command to shelf", command='import someModule; someModule.someCommand("A","B","C")')
}}}
This code actually builds the menu.  Could be added to the {{{userSetup.py}}} so the menu is built every time Maya launches:
{{{
# userSetup.py (for example)
}}}
----
*Execute the bottom code in whatever way you see fit:  A new menu will appear in Maya's main menubar.
*Make sure you have a shelf open: ctrl+shift ~LMB-click on both menu-items to create shelf buttons.  If you press both shelf buttons they should both work.
*Restart Maya, press the shelf buttons again:  The one created via "Bad command to shelf" should give you some type of error like this:
{{{
<function <lambda> at 0x0000000022BEC2E8>
# Error: invalid syntax
#   File "<maya console>", line 2
#     <function <lambda> at 0x0000000022BEC2E8>
#     ^
# SyntaxError: invalid syntax #
}}}
*It's often very convenient to wrapper command callbacks via a Python {{{lambda}}}, since it allows you to embed arguments into it on the fly, without having to hard-code them as a string.  Problem is, the anonymous function it generates to wrapper the command is only valid during the current Maya session:  When Maya restarts, that memory has been reclaimed, the function no longer exists.
*Pressing the shelf item authored via "Good command to shelf" should work correctly, printing out the given text assigned to it at creation:
{{{
('A', 'B', 'C')
}}}
*It works because of a few specific things:
**The command is entirely embedded as a string:  It can be re-evaluated as a string successfully, no anonymous {{{lambda}}} function floating around in memory.
**It embeds the module import directly in the command-string:  If that import wasn't in there, there's a good chance the module wouldn't have yet been imported into proper scope, and it wouldn't know how to execute the given function.
*The downside to the command-string is that it looses the capacity to have dynamically changing variables assigned to it (like the {{{lambda}}} allows)... but in my experience this isn't very common, and in most cases probably worth the trade-off if the users want easy access to the commands.

[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:
// standard docs:
}}}
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";
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;
showWindow;
}
tempWin;
}}}
{{{
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.

Credit to 'Vlad Dubovoy' on highend3d.com for his posting on this subject that I was able to pull from.
Great tutorial here:
The code for it is here:
https://gist.github.com/vshotarov/cdf79835a0e30a9cbdeaaf2eb4151077

Starting with Python 2.5, they introduced the new {{{with}}} statement, which is known as a 'context manager'.  See official docs:
*http://docs.python.org/reference/compound_stmts.html#with
*http://docs.python.org/reference/datamodel.html#context-managers
*http://docs.python.org/library/stdtypes.html#context-manager-types
*http://www.python.org/dev/peps/pep-0343/
And notes on my Python Wiki:
*http://pythonwiki.tiddlyspot.com/#%5B%5Bwith%20statement%5D%5D
In a nutshell, it lets you wrap a block of code in a 'context' that controls how that block will be entered, and exited.  It also catches exceptions for you, and returns any that occurred, so it takes care of the {{{try}}}\{{{except}}} clause automatically.

Maya has {{{progressWindow}}}'s, which provide a nice graphical representation of how long an operation is taking on screen, and allows you to exit out during the operation via the {{{Esc}}} key.  However, they're a bit clunky to author.  I have a subject on setting them up here:
[[How do I create a "Status Window" that shows the graphical progression of some operation?]]

It dawned on me, a Python 'context manager' would be a perfect place to author a {{{progressWindow}}}, since you can wrapper all the ugliness of the {{{progressWindow}}} syntax in the context manager, making a much cleaner interface for the user.  Contrast this solution with the above link:  While it's more code, the bulk of it (the {{{ProgressWindow}}} class) can be hidden from the user.

Author the {{{progressWindow}}} context manager.  Code has been updated to support an 'enable' arg upon creation:  It's True by default, but by setting this to False, it allows calling code to suppress the progress window... in the chance that they themselves have a progress window (there can only be one at a time).
{{{
# Python code
import maya.cmds as mc

class ProgressWindow(object):
"""
A context manager wrappering Maya's progressWindow functionality.
"""

def __init__(self, minF, maxF, enable=True):
self.progressSteps = 100.0 / (maxF-minF)
self.progress = 0.0
self.enable = enable

def __enter__(self):
"""
Enter the context manager, setup the progress window:
"""
if self.enable:
mc.progressWindow(title='Progress Window!',
progress=0, minValue=0, maxValue=100,
status='Not started yet...',
isInterruptable=True )
return self

def update(self):
"""
Call this every loop once the context has been entered.  It detects for
progress window canceling, and updates the current progress.
"""
ret = True
if self.enable:
if mc.progressWindow( query=True, isCancelled=True ):
ret = False
else:
mc.progressWindow(edit=True, progress=int(self.progress), status='%.1f%%'%self.progress)
self.progress += self.progressSteps
return ret

def __exit__(self, exc_type, exc_value, traceback):
"""
Called when the context manager is exited, exits the progress window.
"""
if self.enable:
mc.progressWindow(endProgress=True)
if exc_type:
print "Exiting ProgressWindow, caught exceptions:", exc_type, exc_value
return True
}}}
So how is this used?  Here is a simple example:
{{{
# Define the frame range the progressWindow should act in:
minF = 1
maxF = 500

# We wrapper our loop with the Progress Window context manager:
with ProgressWindow(minF, maxF) as progress:
for i in range(minF,maxF+1):
# call to our update() every frame, and exit out if a cancel event is detected:
if not progress.update():
break

# Do any work on the current frame (print it in this case), then advance it:
print i
mc.currentTime(i, edit=True)
}}}
In the above example, we want to loop over a certain frame range, from frames 1 to 500.  In the loop, we have some work to do, and in this case it's just printing the frame number.  But where the print statements goes, any amount of other code could go.

As you can see, contrasting the 'for loop' code of this example against the above link, it's //much// easier to read:
*We wrapper our for loop with our {{{ProgressWindow}}} context manager.
*At the start of each looop we {{{update()}}} the progress window, detecting for cancel events, and if so, breaking out of the loop.
*When the loop is exited, the {{{__exit__()}}} method of the context manager is automatically called, closing the {{{progressWindow}}}, and handling any exceptions.
nice!
*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:
{{{
// result:
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. {{{ import pymel.core as pm class Timer(object): def __init__(self, name="Default", precision=2): self.name = name self.precision = precision def __enter__(self): pm.timer( startTimer=True, name=self.name) def __exit__(self, exc_type, exc_value, traceback): fullTime = pm.timer( endTimer=True, name=self.name ) print "# Timer '%s' : Ran for %s seconds"%(self.name, round(fullTime, self.precision)) return False }}} {{{ with Timer(name="Time my thing"): # all your code goes here # Timer 'Time my thing' : Ran for 0.89 seconds }}}  Starting with Python 2.5, they introduced the new {{{with}}} statement, which is known as a 'context manager'. See official docs: *http://docs.python.org/reference/compound_stmts.html#with *http://docs.python.org/reference/datamodel.html#context-managers *http://docs.python.org/library/stdtypes.html#context-manager-types *http://www.python.org/dev/peps/pep-0343/ And notes on my Python Wiki: *http://pythonwiki.tiddlyspot.com/#%5B%5Bwith%20statement%5D%5D In a nutshell, it lets you wrap a block of code in a 'context' that controls how that block will be entered, and exited. It also catches exceptions for you, and returns any that occurred, so it takes care of the {{{try}}}\{{{except}}} clause automatically. I realized this would be a great way to wrap a block of code in an undo queue, which is often needed when authoring Python\API code in Maya. If this isn't done (based on certain circumstances, usually commands attached to button-press callbacks) when the user undo's, they have to undo every command executed based on the undone function. And if the function had a hundred loops, the user has to press undo a hundred times... Wrapping the code in a {{{undoInfo}}} open\close chunk call, it solves for this. In addition, you need to put your {{{undoInfo}}} calls inside a {{{try}}}\{{{except}}} clause to guarantee that no matter what, you'll close the undo chunk (or your users could get pretty upset with you), and as mentioned above, the {{{with}}} statement takes care of this for you. I have a solution to this via Python decorators here: [[How can I author an undo decorator?]] But why not have //yet another//? :-) {{{ import maya.cmds as mc import maya.OpenMaya as om class UndoManager(object): """ This is the context manager class. """ def __enter__(self): # Open our undo chunk mc.undoInfo(openChunk=True) def __exit__(self, exc_type, exc_value, traceback): # Close the undo chunk, warn if any exceptions were caught: mc.undoInfo(closeChunk=True) if exc_type: om.MGlobal.displayWarning('%s : %s'%(exc_type, exc_value)) # If this was false, it would re-raise the exception when complete return True }}} Simple example usage: {{{ def myFunc(): print "Executing some code... then:" with UndoManager(): print "Running code in the UndoManager!" raise Exception("Oops, an exception :-(") print "Finish executing code..." myFunc() }}} When executed prints: {{{ Executing some code... then: Running code in the UndoManager! # Warning: <type 'exceptions.Exception'> : Oops, an exception :-( # Finish executing code... }}} !!!With UI's: This works really well when authoring UI's as well. In the below example, we create a UI with button that will create a sphere for every node in the scene, wrapping the sphere creation in our {{{UndoManager()}}}: {{{ import maya.cmds as mc class App(object): def __init__(self): if mc.window('ucmt', exists=True): mc.deleteUI('ucmt') mc.window("ucmt", title="Undo ContextManager Test", resizeToFitChildren=True) mc.columnLayout(adjustableColumn=True, columnAttach=('both', 5)) mc.button(label='FOO!', command=self.buttonCmd) mc.showWindow() def buttonCmd(self, *args): # Wrap everything below in our context manager: with UndoManager(): for i in range(len(mc.ls())): mc.polySphere() # launch the window App() }}} If you press the button to make the spheres, you can undo the operation with a single call to undo. However, if you modify the above code and comment out this line: {{{ with UndoManager(): }}} and try again, you'll see that you'll have to no undo for every sphere that was made. Yuck! A common problem I run into when coding in Python in Maya, is the ability to undo: Sometimes, and often this happens when issuing button commands via a UI, when the user wants to undo the last operation, it will undo //ever step the function took in the operation//. For example, if you had a function that iterated over a loop making spheres, if you wanted to undo this execution, you'd have to undo for every sphere created, rather than undo the whole operation at once. I've found the way to make this behave properly is wrap the executed commands inside an {{{undoInfo}}} open\close chunk call, and inside that wrapper the code execution in try\finally clause: You need to make sure that no matter what happens, even if the command fails for some reason, you can get out and close your undo chunk. At first I'd embed this {{{undoInfo}}}\try\finally clause directly in the function being called. But if there were many functions evoking this concept, this could lead to a lot of duplicated code. Enter the Python ''decorator''. I consider decorators 'convenience wrappers' (that's how I think of them in my head) for your functions. I have docs explaining their usage and creation over on my Python Wiki [[here|http://pythonwiki.tiddlyspot.com/#decorator]]. In the below example, I create a Python class that will act as our decorator code. The great thing about Python and passing arguments to parameters, is that via {{{*args}}} and {{{**kwargs}}} our decorator will intercept all incoming arguments and pass them along properly to the function. When the decorator runs, it first opens and undo chunk, then tries to execute the decorated function, catching any exceptions it may throw. When it finishes, it closes the undo chunk, and if there was an exception, it raises it. ---- Updated code here. This works fine decorating functions and methods. While the below function-based decorator works, it doesn't pass out the return of the wrappered function: This one does. {{{ import pymel.core as pm def undoable(f): def wrapper(*args, **kwargs): pm.undoInfo(openChunk=True) try: result = f(*args, **kwargs) finally: pm.undoInfo(closeChunk=True) return result return wrapper }}} ---- Other examples: {{{ # python code import maya.cmds as mc class Undo(object): """ Create an undo decorator """ def __init__(self, f): # Called when the Undo decorator is created self.f = f def __call__(self, *args, **kwargs): # Called when the decorated function is executed mc.undoInfo(openChunk=True) try: self.f(*args, **kwargs) finally: mc.undoInfo(closeChunk=True) @Undo # This wraps the below function in the decorator def sphere(rad=3): # Some function that may have a problem undoing. # This code shouldn't, just used as placeholder example. mc.polySphere(radius=rad) # Run our code. We can now easily undo it if we don't like the results. sphere() }}} ^^({{{try}}}\{{{except}}} clauses cleaned up to {{{try}}}\{{{finally}}} thanks to suggestion Keir Rice)^^ The irony of the above system is that I authored it to work with button-presses, but the //above system// can't wrapper a class-based method with {{{@Undo}}}, it will get angry (due to the passing of {{{self}}} to the method.. Here is a different approach using a function-based decorator, rather than a class-based one that will work on class methods. Note how we use the {{{functools.wraps}}} as a decorator //inside our decorator//? Chaos! {{{ from functools import wraps import maya.cmds as mc def undo(fn): """ Create an undo decorator """ @wraps(fn) def wrapper(*args, **kwargs): mc.undoInfo(openChunk=True) try: fn(*args, **kwargs) finally: mc.undoInfo(closeChunk=True) return wrapper # Make an arbitrary class, with a method to decorate: class Foo(object): @undo # This wraps the below method in the decorator def sphere(self, rad=3): mc.polySphere(radius=rad) # Now call to our method: f = Foo() f.sphere() }}} ---- Also see: *[[How can I author an undo context manager?]] *This will open a new Command Prompt: {{{ >> maya -batch -file someMayaFile.ma -command "file -save" }}} *Same as "maya -batch" but this runs in the same Command Prompt as it was executed in: {{{ >> mayaBatch -file someMayaFile.ma -command "file -save" }}} {{{fileBrowserDialog}}} *Notes: The docs on this suck, so this is what you need to do: ---- This subject shows how to save the last directory accessed, so you can consistently open your dialogs to the same dir: [[How can I define a start location for my fileBrowserDialog?]] ---- Example with Python, for picking directories. Since you can nest functions in Python, it makes it easier to wrapper the functionality of this command together into one cohesive unit. {{{ import maya.cmds as mc def fbd(): def fileCommand(directory, ignore): # This is called by the fileBrowserDialog command after a dir is picked. print directory mc.fileBrowserDialog(mode=4, fileCommand=fileCommand, fileType='directory', actionName='Choose a directory:') fbd() }}} ---- *Mel Examples: **First, built the file brower dialog: -mode is telling it what to do: {{{ fileBrowserDialog -mode 2 -fileCommand "procName" -actionName "whatYouAreDoing"; }}} **-mode 0 is for reading files, so if you type in a name that dosn't exist, the browers will give an error **-mode 1 & 2 are pretty much the same and are for for writing to file, so you can speficy your own filename in the browers. **-mode 4 is for selecting directories only. **-fileCommand is the name of the below proc, that the fileBrowserDialog? passes its info to. **-actionName is simply what is printed in the UI, telling the user what is going on **If you're using mode 1 or 2, and you want to specify your own "file types" for filtering, you can use the below code snippet (thanks to Mason Sheffield). ***The {{{-filterList}}} can be called to multiple times to define different file types. The {{{-filterType}}} is simply the default UI filterList choice. Also, you //must// set {{{-dialogStyle 1}}} for some reason, or the filtering won't work :( {{{ -filterList "My file type(*.mft),*.mft" -filterList "otherFileType(*.oft),*.oft" -fileType "My file type(*.mft)" -dialogStyle 1 }}} *Next, you need to build a proc, that can take the return from the FBD, and then do something with it. It needs to be in the below format, but the proc name, and var names can change. {{{ global proc procName(string$result, string $type){ textFieldButtonGrp -e -tx$result controlName;}
}}}
**So, the {{{fileBrowserDialog}}} passes its info, via the {{{$result}}} argument, into the "procName" proc. Then procName will update controlName with$result.
*Another issue: By default you can't specify WHERE the dialog will open too. It appears that it bases where it opens on where Maya's current workspace is. SO, to tell it where to go, you'd need to do something like this:
{{{
// query Maya's current workspace:
string $mayaWorkspace = workspace -q -dir; // set the workspace to where you want the dialog to open: workspace -dir$myCustomPath;
// run the dialog code:
fileBrowserDialog ....
// and set the workspace back again:
workspace -dir $mayaWorkspace; }}} *Annoying! ---- ---- I got an emailed suggestion from Nicolas Combecave. Thanks Nicolas! *After scrutining into the fileBrowser mel //command//, I found that the //script// {{{fileBrowserWithFilter}}}, does both in one single command, plus adds an items filtering mechanism. Here is its syntax from the procedure declaration: {{{ global proc int fileBrowserWithFilter( string$callBack,
string $action, string$title,
string $type, int$mode,
string $filters[], string$dir)
}}}
*We can translate it into a mode understandable text:
**{{{string $callBack}}} > your procedure to launch upon validation **{{{string$action}}} > text label on the validation button
**{{{string $title}}} > browser title **{{{string$type}}} > a filetype to select in the dropdown list. Even custom ones (see below).
**{{{int $mode}}} > choose for read/write/listDirs... (see description above) **{{{string$filters[]}}} > fileTypes in the dropdown menu. You can define yours here: {"myFileType,*.mft":"yourFileType,*.yft":"theirFileType,*.tft":etc...}
**{{{string $dir}}} > the starting directory *So you can use it when you want to specify a file that doesn't exists, into a specified destination, filtering only special file types, even custom ones: {{{ string$startDir  ="c:/tmp/";
fileBrowserWithFilter(
"procName",
"myLabel",
"myTitle",
"afileType",
1,
{"aFileType,*.aft","anotherFileType,*.anft"},
$startDir); }}} Given the 'Focal Length' and 'Horizontal Film Aperature'? (since it isn't an actual attribute) *Code pulled from Maya's {{{AEcameraTemplate.mel}}} script: {{{ float$focal = getAttr "cameraShape1.focalLength";
float $aperture = getAttr "cameraShape1.horizontalFilmAperture"; float$fov = (0.5 * $aperture) / ($focal * 0.03937);
$fov = 2.0 * atan ($fov);
$fov = 57.29578 *$fov;
}}}
Given the 'Horizontal Film Aperature' and 'Angle of View' (Field of View)?
*Code pulled from Maya's {{{AEcameraTemplate.mel}}} script. In this example we query the camera's Attribute Editor floatSliderGrp for the FOV, since the FOV isn't an actual camera attribute.
{{{
float $fov = floatSliderGrp -q -value fovGrp; float$aperture = getAttr "cameraShape1.horizontalFilmAperture");
float $focal = tan (0.00872665 *$fov);
$focal = (0.5 *$aperture) / ($focal * 0.03937); }}} Given the 'Focal Length' and 'Angle of View'? * I don't know... someone please tell me.... With the Python API: http://docs.python.org/library/math.html#math.acos http://docs.python.org/library/math.html#math.degrees http://download.autodesk.com/us/maya/2010help/API/class_m_vector.html {{{ # Python code from math import degrees, acos from maya.OpenMaya import MVector # Define two vectors: vA = MVector(1,0,0) vB = MVector(0,0,1) # Get the normalized versions of the vectors: vA_norm = vA.normal() vB_norm = vB.normal() # Find the dot product: dot = vA_norm * vB_norm # Get the angle in degrees from the acos of the dot: angle = degrees(acos(dot)) }}} {{{ print angle # 90.0 }}} ---- Via mel's vector types. In this example, we calculate our two vectors based on three 3d points. {{{ vector$vA = <<0,0,0>>;
vector $vB = <<1,0,0>>; vector$vC = <<0,.5,.5>>;

// Define two vectors:
vector $vAB =$vB - $vA; vector$vAC = $vC -$vA;

// Get the normalized versions of the vectors:
vector $vAB_norm = unit($vAB);
vector $vAC_norm = unit($vAC);

// Find the dot product:
float $dot = dot($vAB_norm, $vAC_norm); // Get the angle in degrees from the acos of the dot: float$angle = rad_to_deg(acos($dot)); // Result: 90.000001 // }}} ---- You can also use, in Maya: {{{angleBetween}}} //command//. OR {{{angleBetween}}} //node//. Not a code snippet, but a description of what you need to do. You can use this to reverse-engineer where a ik handle's pole vector should live, based on the shape of the joint chain itself. *Get a nice vector library. *multVal = some val : This is used to move the location of the pole vector. *startPoint = vector(first joint worldspace position) *midPoint = vector(average of all worldspace positions of all joints between start and end) *endPoint = vector(end joint worldspace position) *endVector = endPoint - startPoint *midVector = midPoint - startPoint *perpendicularVec = crossProduct(endVector ,midVector) *poleVector = startPoint + (unit( crossProduct(perpendicularVector, endVector ) ) * multVal) You can then use multVal to 'slide' the pole vector towards and away from the start joint. 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?]]


Was recently working with Autodesk to capture crash data on a reproable bug, where when a condition was met, Maya would 'just disappear'.
The recommended some Microsoft tools for this: "Debug Diagnostic Tool" (v2 at this time)
When it first launches, it will (should, did for me) open a configuration wizard to make a new 'rule', and from what I remember, you should do this:
* It will ask what process to attach itself too:  You can just enter "maya.exe"
* It will ask where to save it's logs, and provides a default location.  I moved mine to an easier to find dir like c:/temp/maya/crashlogs
Next, make sure the tool is up and running:
{{{
C:\Program Files\DebugDiag\DebugDiag.Collection.exe
}}}
Then open Maya, and repro your error.

When Maya crashes, it will store out two files into the dir you set:
* {{{maya__PID__31976__Date__12_05_2018__Time_03_38_48PM__657__Log.txt}}}   < not too big usually, few hundred k
* {{{maya__PID__31976__Date__12_05_2018__Time_03_39_53PM__242__Second_Chance_Exception_C0000005.dmp}}} < about 2.5 //gigs//
To view usable info form this data, launc this app:
{{{
C:\Program Files\DebugDiag\DebugDiag.Analysis.exe
}}}
When it opens:
*Check the '~CrashHangAnalysis' checkbox
*Add Data Files: Browse to the big .dmp file.
*Start Analysis  : This can take a while the first time you run it.
When complete, it will open Internet Explorer window with the results.
* There will be for colored boxes (Error, Warning, Information, Notification), that may all be '0'. If you click on the 'Error' box, IW may ask you to 'allow blocked content', if so, say yes, and then the boxes should populate.
* You can then save this webpage if needed, and provide to Autodesk, or read through it yourself if you know what to look for ;)

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;}}}
**{{{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}}}.

The easiest way is to just call to the {{{CycleBackgroundColor}}} runtimeCommand over and over to run through the presets.  It in turns calls to the {{{cycleBackgroundColor.mel}}} script, which:
Queries the background color via:
{{{
float $rgb[3] = displayRGBColor -query background; }}} Queries if the gradient is on via: {{{ int$gradOn = displayPref -q -displayGradient;
}}}
{{{
displayPref -displayGradient 1; // or 0
}}}
And changes the colors via presets with:
{{{
displayRGBColor background 0.36 0.36 0.36;//default color
}}}

{{{
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;
}}}

Double the height:
{{{
layout -e -height 80 ShelfLayout;
}}}
Or if you have the tabs visible:
{{{
layout -e -height 101 ShelfLayout;
}}}
It  appears the default height is 40 pixels with no tabs, and 61 pixels with tabs visible: the tab takes up 21 pixels.
So if you have tabs visible, multiply the number of rows you want by 40, then add 21.

Maya {{{window}}}s have an {{{iconName}}} property that can be set and queried.  However, I've never got it to actually change the icon that's in the top-left corner of the window.
Since Maya has adopted ~PySide, even if you create a window via the Maya {{{window}}} command, the end result is a Qt object.  And because of that, we can use ~PySide to change the icon.  Surprisingly easy actually:
{{{
import maya.OpenMayaUI as omui
from shiboken import wrapInstance
from PySide.QtGui import QWidget, QIcon

def setWindowIcon(winName, iconPath):
"""
Convert from a Maya window string name to the QWidget window instance.
Then set its icon.

winName : string : The name of the maya 'window' control
iconPath : string : Full path to icon.png file.
"""
ptr = omui.MQtUtil.findWindow(winName)
qwin = wrapInstance(long(ptr), QWidget)
qwin.setWindowIcon(QIcon(iconPath))
}}}
{{{
winName = "myAwesomeWindowName"
iconPath = "C:/path/to/some/icon.png"
setWindowIcon(winName, iconPath)
}}}
I personally dislike the view-cube.  Just yet one more thing to clutter the UI.  But maybe now I'll give it a second chance :)
Based on this post (by Shawn ~McClelland):
http://forums.cgsociety.org/showpost.php?p=6009050&postcount=216
It says:
<<<
In your install location ({{{C:\Program Files\Autodesk\Maya2009\icons\AutoCam\}}}) there is a file called {{{VCfacemap.png}}} which is basically a simple cube map that the ~ViewCube uses to texture itself.
...
Save the {{{VCfacemap.png}}} file and reload Maya, voila you now have a textured viewcube
<<<
To turn on\off:
Window > Settings/Preferences > Preferences -> Interface -> ~ViewCube

Some examples :)
http://www.flickr.com/photos/7875859@N03/3772730145
When you set Maya to the 'move' tool (w hotkey), you hold w+LMB, you can bring up a marking menu that lets you translate in different spaces: Object, World, Component.  Plus, their are //other// spaces the marking menu doesn't expose, that the Tools Settings window does, like Parent (Local), Normal, and Custom.  How can you change what mode you're in via code?

{{{
import maya.cmds as mc
mc.setToolTo("moveSuperContext")
mc.manipMoveContext("Move", edit=True, mode=1) # Set to 'Parent/Local'
}}}


~PyMel provides a nice {{{pivots}}} parameter to the {{{xform}}} command that doesn't exist in normal Python or mel.  You can set both the rotate and scale pivot simultaneously:
{{{
import pymel.core as pm
node = pm.PyNode("pSphere1")
pivotPos = [1,2,3]
pm.xform(node, pivots=pivotPos, absolute=True, worldSpace=True)
}}}
In regular commands, you need to move both pivots:
{{{
import maya.cmds as mc
node = "pSphere1"
pivotPos = [1,2,3]
pm.xform("%s.rotatePivot"%node, translation=pivotPos, absolute=True, worldSpace=True)
pm.xform("%s.scalePivot"%node, translation=pivotPos, absolute=True, worldSpace=True)
}}}
{{{jointDisplayScale}}}
{{{setStartupMessage}}}
*Notes: You'll need to edit the {{{\MayaX.X\scripts\startup\initialLayout.mel}}} script to see this work.

Starting with Maya 2018 (maybe 2017), if you like to run your hotbox with a single toolset in it (like 'just animation', or 'just rigging') there was a bug introduced that every time you'd restart Maya, Maya would add 'another' menu to it.  I've logged this as a defect in 2018 & 2019, but Autodesk has yet to take any action on it.  Having used the hotbox since Maya v1, and Alias Poweranimator before it, I find this incredibly annoying.

If you put the below code in your userSetup.py, you can have Maya auto-set the Hotbox back to a single menu on startup to work around this. FYI, this is basically a Python rewrite of the 'echo-all commands' that spit out when opening the hotbox, and enabling a specific menu mode:
{{{
import maya.cmds as mc
import maya.mel as mm

def fixHotbox():
mm.eval('ShowHotbox; hotBox;')
mm.eval("updateHotboxOptionVarSettings()")
}}}
{{{setToolTo}}}
*Example: Set the current tool to the "select tool"
{{{
setToolTo moverSuperContext;
}}}
{{{connectionInfo}}} only requires a single object.attr.  {{{listConnections}}} also works, but this is a bit more simple:
{{{
// Is the object.attr a source of a connection?
connectoinInfo -isSource object.tx;
}}}
{{{
// Is the object.attr the destination of a connection?
connectionInfo -isDestination object.tx;
}}}
{{{
// List an object.attr's source connection, meaning, list the input connection.
// This will return a single object.attr.
connectionInfo -sourceFromDestination object.tx
}}}
{{{
// List an object.attr's destination connection, meaning, list the all output connections:
// This could return multiple object.attrs.
connectionInfo -destinationFromSource object.tx;
}}}


Sometimes when running a {{{getAttr}}} on an float node attribute, Maya will return back some very tiny value:
{{{
# Python code:
print mc.getAttr('myNode.myFloat')
2.7912680472872789e-017
}}}
The {{{print}}} statement turns the {{{float}}} into a {{{string}}} for display to the screen.  And in the process, does some shorthand so it's not an incredibly long number displayed before you.

If you see {{{e-}}} in the number (exponent), it basically means the value is zero for all intensive purposes.
But how can you really make that value zero?  At least so it prints nicer?

If you're using Python (and you should be), you can use Python's {{{round()}}} function to take care of this.  Just pass in how many places to the right of the decimal you want it rounded to:
{{{
val = 2.7912680472872789e-017
print round(val, 4)
0.0
}}}
Matt Estela pointed me to this posting over on tokeru.com:
http://www.tokeru.com/t/bin/view/Maya/MayaMelAndExpressions#Strip_spaces_and_punctuation_mak
Here's the source from the site (thanks Matt!):
{{{
string $comment = "this, is a line with numbers like 0.25 and: stuff!"; string$regex = "[^a-zA-Z0-9_]";
string $replace = "_"; print ($comment+"\n");
int $i = 0; string$match;
int $totalChars = size($comment);

for ($i = 0;$i < $totalChars;$i++) {
$match = match$regex $comment;$comment = substitute $regex$comment $replace; if ($match == "") break;
}

print $comment; print "\n"; // returns "this__is_a_line_with_numbers_like_0_25_and__stuff_" }}} I thought I'd see how I could author this in Python, and posted an alternative over at my [[Python Wiki|http://pythonwiki.tiddlyspot.com/#%5B%5BHow%20can%20I%20strip%20illegal%20characters%20from%20a%20string%3F%5D%5D]] By default, when a UI is created, Maya will remember its size. If later in your script you change it's size, those changes may not be reflected on screen. {{{ windowPref -remove "UI_nameGoesHere"; }}} Old example: {{{ // compute current position: float$posNowX = obj.translateX;
float $posNowY = obj.translateY; float$posNowZ = obj.translateZ;

// compute previous position:
float $posThenX = getAttr -t (frame - 1) obj.translateX; float$posThenY = getAttr -t (frame - 1) obj.translateY;
float $posThenZ = getAttr -t (frame - 1) obj.translateZ; // compute distance traveled per frame: float$distance = sqrt( pow(($posNowX -$posThenX), 2) +
pow(($posNowY -$posThenY), 2) +
pow(($posNowZ -$posThenZ), 2) );

print ($distance + "\n"); }}} Newer example: (thanks Jamie ~McCarter) {{{ float$t = currentTime -q;
vector $currentP = getAttr object.translate; vector$previousP = getAttr -t ($t-1) object.translate; float$dist = mag( $currentP -$previousP );
print( "distance traveled " + $dist + "\n"); }}} Newer, with matrices! ;) Probably the most accurate way of doing it? >Note: It appears that this line "{{{float$prevM[] = getAttr -t ($t-1) object.worldMatrix;}}}" causes a cycleCheck warning in Maya. Not sure why? {{{ float$t = currentTime -q;

float $curM[] = getAttr object.worldMatrix; float$prevM[] = getAttr -t ($t-1) object.worldMatrix; vector$currentP = <<$curM[12],$curM[13],$curM[14]>>; vector$previousP = <<$prevM[12],$prevM[13],$prevM[14]>>; float$dist = mag( $currentP -$previousP );

print( "distance traveled " + $dist + "\n"); }}} Example pulled from [[here|https://stackoverflow.com/questions/31462276/maya-python-how-to-calculate-the-reflection-angle-vector-of-an-given-face-to-ca]], and modified from ~PyMel to the ~API2.0 ~MVector: Here, we'll reflect our vector across the Y axis: {{{ import maya.api.OpenMaya as om2 ray = om2.MVector(-2, -3, 1) nrm = om2.MVector(0,1, 0).normal() reflection = ray - (2 * (ray*nrm) * nrm) print reflection # (-2, 3, 1) }}} From the above link: <<< Where {{{ray}}} is the ray from the eyepoint to the surface and {{{nrm}}} is the (normalized) surface normal at the intersection point. <<< Sure would be nice if this was a //built in method//... ;) {{{connectControl}}} ---- Also see: * [[Emulating connectControl in Qt]] * [[How can I create a UI control for a given attribute type?]] Always have trouble trying to connect to the next index in an array attr. The {{{connectAttr}}} command has a '{{{nextAvailable}}}' arg, but I've never been able to get it to work. So instead, query the size of the array attr, then connect into that index. {{{ import pymel.core as pm nodeArrayAttr = pm.PyNode("myNode.someArrayAttr") # PyMel Attribute arraySize = len(pm.listAttr(nodeArrayAttr, multi=True)) pm.connectAttr(someOtherNodeAttr, nodeArrayAttr[arraySize]) }}} Maybe there's a better way? I'd like to hear it... It should be noted that while undoing this operation will disconnect the attrs, the new index created will be persistent. So if you re-execute the code, it will connect to a new index, leaving a gap. Bug? 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. The {{{animView}}} command give access. Look at the area between frames 0 and 30, and the value range -10 - 100: {{{ import maya.cmds as mc mc.animView( 'graphEditor1GraphEd', startTime=0, endTime=30, minValue=0, maxValue=100 ) }}} {{{graphEditor1GraphEd}}} is the name of the default Graph Editor. 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. As I figured, Maya actually does most the heavy lifting for you, if you know what to call. [[This blog post|http://around-the-corner.typepad.com/adn/2012/08/convert-maya-world-coordinates-to-screen-coordinates.html]] by Cyrille Fauvel describes another way to do it. I've condensed those notes down into my own method here: {{{ import maya.cmds as mc import maya.OpenMaya as om import maya.OpenMayaUI as omui def getScreenSpace(node): """ Return the screen-space position of the node. 0,0 is the bottom left. +X goes to the right, +Y goes up. """ activeView = omui.M3dView.active3dView() # Can't use 'pointPosition', since that will return values in the 'ui units', # and we need them returned in 'internal units', which is what the matrix # attr stores: mtxList = mc.getAttr("%s.worldMatrix"%node) # Extract just the position: pt = om.MPoint(mtxList[12], mtxList[13], mtxList[14]) # Pointer hoop-jumping... xPtrInit = om.MScriptUtil() yPtrInit = om.MScriptUtil() xPtr = xPtrInit.asShortPtr() yPtr = yPtrInit.asShortPtr() activeView.worldToView(pt, xPtr, yPtr) x = xPtrInit.getShort(xPtr) y = yPtrInit.getShort(yPtr) return x,y }}} Docs for ~OpenMayaUI's [[M3dView|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m3d_view.html]]  Via the mel script {{{polyConvertToShell.mel}}} {{{ \Maya20XX\scripts\others\polyConvertToShell.mel }}} The {{{convertSolidTx}}} command. Also see this script: {{{copyConvertSolidTx.mel}}} This command can be execute from the Hypershade by selecting a material + a mesh it's assigned to, and 'Edit -> Convert To File Texture []' Notes: * I've learned that if you set {{{antiAlias=True}}}, if you're trying to compute the alpha channel, the command will set it entirely black (usually very wrong). My guess this is a bug in the tool. By default it is {{{False}}} and should be left that way. * If you want to project and convert multiple materials //with alpha//, use a {{{layeredTexture}}} node, not a {{{layeredShader}}} : {{{layeredShader}}} nodes //don't// pass alpha out to the convert to solid command. 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 }}} I wanted a way to turn mesh wireframe into a solid volume, so I could 3d-print it. Sort of like turning a sphere into a solid geodetic dome. This would be like extruding a circle down every edge of a polygonal mesh, and welding up all the spots they touch. One of the modelers were I work (Sean Marino) tipped me off to this technique: *Pick all the faces *Turn off "Keep faces together" *Extrude Face : Set Divisions to 1, Increase offset to set desired edge width. *Delete new faces. *Re-select all faces. *Turn on "Keep faces together" *Extrude Face : Set divisions to 1. Increase thickness. Done Based on that workflow, here's a simple script to do it: {{{ import maya.cmds as mc def wireframeToSolid(offset): """ Based on the given selection of a single polygonal object, convert it's wireframe to a solid volume. Will create a 'thickness' attribute on the mesh object, for adjustment of the size of the wireframe volume after creation. Parameters: offset : float : The thickness of the wires. """ sel = mc.ls(selection=True) assert len(sel)==1, "Please select a single polygonal object" # Create the custom attr: if not mc.objExists("%s.thickness"%sel[0]): mc.addAttr(sel[0], longName="thickness", defaultValue=offset, keyable=True) mc.setAttr("%s.thickness"%sel[0], offset) mc.select(sel) origFaces = mc.polyListComponentConversion(toFace=True) mc.select(origFaces) extrudeA = mc.polyExtrudeFacet(constructionHistory=True, keepFacesTogether=0, divisions=1, twist=0, taper=1, off=offset, thickness=0)[0] mc.delete() # Delete the interior faces creating holes. mc.select(sel) # Reselect the whole object newFaces = mc.polyListComponentConversion(toFace=True) mc.select(newFaces) extrudeB = mc.polyExtrudeFacet(constructionHistory=True, keepFacesTogether=1, divisions=1, twist=0, taper=1, off=0, thickness=offset)[0] # Hook up the custom attr: mc.connectAttr('%s.thickness'%sel[0], '%s.offset'%extrudeA) mc.connectAttr('%s.thickness'%sel[0], '%s.thickness'%extrudeB) print "wireframeToSolid: Complete!" }}} To use, pick a single poly object and execute: {{{ wireframeToSolid(.05) }}} ---- There also appears to be a tool that does this too on Creative Crash: http://www.creativecrash.com/maya/downloads/scripts-plugins/modeling/poly-tools/c/convert-to-polywire The {{{vectorProduct}}} node can aid with this, concepts below, using a joint. *Create a new {{{vectorProduct}}} node, and set it's 'operation' to 'Vector Matrix Product'. *Connect the output {{{myJoint.matrix}}} attr to the {{{myVectorProduct.matrix}}} input attr. *Set the 'input1' section of the {{{vectorProduct}}} node to which local axis of the joint you'd like to track. *The 'output' XYZ values of the {{{vectorProduct}}} node should now reflect the position in space that local axis is, one unit away from origin. http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/Nodes/vectorProduct.html  {{{polyClipboard}}} This will copy\paste data in the top half (the 'reporter') of the Script Editor: {{{ cmdScrollFieldReporter -e -copySelection "cmdScrollFieldReporter1"; cmdScrollFieldReporter -e -pasteSelection "cmdScrollFieldReporter1"; }}} This will copy\paste data in the bottom half (the 'executer') of the Script Editor: {{{ cmdScrollFieldExecuter -e -copySelection "cmdScrollFieldExecuter1"; cmdScrollFieldExecuter -e -pasteSelection "cmdScrollFieldExecuter1" }}} Maya has no built-in way to do this, but it does ship with the Image Magick {{{imconvert.exe}}}, for doing stuff like this along the commandline. I had to come up with this solution since Maya's {{{surfaceSampler}}} tool can generate color diffuse maps, and alpha transparency maps, but //not in the same map// :S I need a 32 bit tga //with alpha//, so this is the solution I came up with. {{{ import os import sys import subprocess def getImconvertExe(): binDir = os.path.dirname(sys.executable) exe = '%s\\imconvert.exe'%binDir if not os.path.isfile(exe): raise Exception("Missing this file from disk: %s"%exe) return exe def copyAlpha(sourceTex, alphaTex, outputTex): imconvert = getImconvertExe() commandCalls = [imconvert, sourceTex, alphaTex, "-alpha", "off", "-compose", "copy_opacity", "-composite", outputTex ] # Need to use communicate, to halt Maya from continuing in execution until this is over. fail = subprocess.Popen(commandCalls, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() return fail colTex = r"C:\temp\maya\transferMaps\sampledDiffuseColor.tga" alphaTex = r"C:\temp\maya\transferMaps\sampledAlpha.tga" targetTex = r"C:\temp\maya\transferMaps\sampledDiffuseColorAlpha.tga" copyAlpha(colTex, alphaTex, targetTex) }}} {{{popupMenu}}} See the docs on these subjects: * [[attrControlGrp|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/attrControlGrp.html]] ** This command creates a control of the type most appropriate for the specified attribute, and associates the control with the attribute. Any change to the control will cause a change in the attribute value, and any change to the attribute value will be reflected in the control. Not all attribute types are supported. * [[attrFieldGrp|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/attrFieldGrp.html]] ** This command creates a pre-packaged collection of label text, plus two to four float fields. These fields will be attached to the specified vector attribute, so that changes in either will be reflected in the other. * [[attrColorSliderGrp|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/attrColorSliderGrp.html]] ** Create a color slider group consisting of a label, a color canvas, a slider and a button. Clicking on the canvas will bring up the color editor. If the button is visible, it will allow you to map a texture to the attribute. * [[attrNavigationControlGrp|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/attrNavigationControlGrp.html]] ** This command creates a pre-packaged label navigation button. The group is used to help the user manage connections to a particular attribute. ---- Also, see: * [[connectControl|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/connectControl.html]] * [[Emulating connectControl in Qt]]  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?]]
Check out the docs on the {{{draggerContext}}} command.
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.
A combo of the mel {{{copyAEWindow}}} & {{{showEditor}}} global procs, which lives in this script:
{{{
C:\Program Files\Autodesk\Maya20XX\scripts\others\showEditor.mel
}}}
{{{
string $sel[] = ls -sl; showEditor$sel[0];
copyAEWindow;
}}}
This will display an Attribute Editor docked in the main panel, then pop a dupe off.
----
Also see:
*[[I want to open an Attribute Editor for a certain object. How to do so?]]
Say you have a hierarchy of joints, and you want to walk over each joint in the hierarchy, and do something to that joint, and its children?  This simple Python generator has you covered:  For each item in the hierarchy it will return the current item, and any children it has.  It will keep doing this until the hierarchy fully walked.
Given this example joint hierarchy:
*grandparent
**grandchildA
***greatGrandchildA1
***greatGrandchildA2
**grandchildB
***greatGrandchildB1
***greatGrandchildB2
Run the below code:
{{{
import pymel.core as pm

def jointWalk(root):
kids = [pm.PyNode(root)]
for k in kids:
children = pm.listRelatives(k, type='joint')
kids.extend(children)
yield (k, children)
}}}
{{{
root = 'grandparent'
for parent, children in jointWalk(root):
print parent, children

nt.Joint(u'grandparent') [nt.Joint(u'grandchildA'), nt.Joint(u'grandchildB')]
nt.Joint(u'grandchildA') [nt.Joint(u'greatGrandchildA1'), nt.Joint(u'greatGrandchildA2')]
nt.Joint(u'grandchildB') [nt.Joint(u'greatGrandchildB1'), nt.Joint(u'greatGrandchildB2')]
nt.Joint(u'greatGrandchildA1') []
nt.Joint(u'greatGrandchildA2') []
nt.Joint(u'greatGrandchildB1') []
nt.Joint(u'greatGrandchildB2') []
}}}
Simple function to build the material & shading engine, and connect them together.
{{{
# Python code
import maya.cmds as mc

# The material will be selected when created:  Need to de-select before
# Creating our shadingEngine (since it's a set).
mc.select(clear=True)
# Nope, can't do it this way, won't shade properly later for some reason:
# Need to do it this way:
# Can use this rather than a call to connectAttr:
}}}
Or ~PyMel Code:
{{{
import pymel.core as pm

name='%sSG'%material.nodeName())
}}}
{{{
matName = 'myPhong'
matType = 'phong'
}}}
{{{
(u'myPhong', u'myPhongSG')
}}}
http://download.autodesk.com/global/docs/maya2014/en_us/CommandsPython/layoutDialog.html

Similar to both {{{confirmDialog}}} & {{{promptDialog}}}, the  {{{layoutDialog}}} allows for a more robust modal-dialog solution by embedding a {{{formLayout}}} into the dialog itself.

The main problem I've found with more complex {{{layoutDialog}}}s is the retrieval of any internal data defined within:  The below two examples show ways to capture this info either through a global dictionary, or via optionVars:

----
In this example, a simple window is craeted that lets the user enter two values.  The values are later retireved via a ''global dictionary'':
{{{
from functools import partial as callback
import maya.cmds as mc

# This dict will hold values created\changed inside the layoutDialog:
modalData = {}

def updateModealData(key, *args):
# Callback used to update the dict from inside the layoutDialog
modalData[key]=args[0]

def myModalWindow():
"""
Create a modal dialog allowing for the user to set certain values.
"""
# Get the formLayout created via the layoutDialog:
form = mc.setParent(query=True)
# layoutDialog's are not resizable, so hard code a size here,
# to make sure all UI elements are visible.
mc.formLayout(form, edit=True, width=300)

mc.text(align='left', label="Insert some interesting text here!")
mc.separator(height=20)

defaultValA = mc.currentTime(query=True)
defaultValB = 0
# update these with the default values, in case the user doesn't change anything
modalData["valA"]=defaultValA
modalData["valB"]=defaultValB

mc.intFieldGrp(label="Value A:", value1=defaultValA, changeCommand=callback(updateModealData, "valA"))
mc.intFieldGrp(label="Value B:", value1=defaultValB, changeCommand=callback(updateModealData, "valB"))
mc.separator(height=10, style='none')
mc.button(label="Do Something!", backgroundColor=[.2,.9,.2], command="import maya.cmds as mc; mc.layoutDialog(dismiss='doit')")

mc.formLayout(form, edit=True, attachForm=[(col, 'top', 5),
(col, 'bottom', 5),
(col, 'left', 5),
(col, 'right', 5)])

def main():
result = mc.layoutDialog(ui=myModalWindow, title="My Modal Window")
if result == "doit":
print modalData

main()

}}}
----
In this example, a simple window is created that lets the user enter two values. the values are later retrieved via ''optionVars''
{{{
import maya.cmds as mc

ovA = "ov_startFrame"
ovB = "ov_offset"

def myModalWindow():
"""
Create a modal dialog allowing for the user to set certain values.
"""
# Get the formLayout created via the layoutDialog:
form = mc.setParent(query=True)
# layoutDialog's are not resizable, so hard code a size here,
# to make sure all UI elements are visible.
mc.formLayout(form, edit=True, width=300)

mc.text(align='left', label="Insert some interesting text here!")
mc.separator(height=20)

defaultValA = mc.currentTime(query=True)
defaultValB = 0
mc.optionVar(intValue=[ovA, defaultValA])
mc.optionVar(intValue=[ovB, defaultValB])

mc.intFieldGrp(label="Value A:", value1=defaultValA, changeCommand=lambda *args: mc.optionVar(intValue=[ovA, args[0]]) )
mc.intFieldGrp(label="Value B:", value1=defaultValB, changeCommand=lambda *args: mc.optionVar(intValue=[ovB, args[0]]) )
mc.separator(height=10, style='none')
mc.button(label="Do Something!", backgroundColor=[.2,.9,.2], command="import maya.cmds as mc; mc.layoutDialog(dismiss='doit')")

mc.formLayout(form, edit=True, attachForm=[(col, 'top', 5),
(col, 'bottom', 5),
(col, 'left', 5),
(col, 'right', 5)])

def main():
result = mc.layoutDialog(ui=myModalWindow, title="My Modal Window")
if result == "doit":
valA = mc.optionVar(query=ovA)
valB = mc.optionVar(query=ovB)
print valA, valB

main()
}}}
Often when making UI's you want a {{{fileBrowserDialog}}} to consistently open in the same location.  Or, remember the last place it was opened from.  The below example shows how to store a setting  that remembers the last dir that was accessed, and repoint the browser there on later browsing expeditions.

The magic is the the {{{workspace}}} command's {{{directory}}} arg:  Need to set that sucker to your saved values.  Took me a while to figure that out...

I've labeled each section in the order it is executed so it makes more sense ;)
{{{
# Python code:
import os
import maya.cmds as mc

# Section 1
# Query the current workspace, since we'll end up changing it while we
# open our browsers, and need to set it back to normal later.
currentWorkspace = mc.workspace(query=True, active=True)

# Section 4
# What is called to via the fileBrowserDialog.  This is where the 'action code'
# would take place once a file from the browser was selected:
def fbdCommand(fileName, fileType):
# Do stuff:
#    stuff, stuff, stuff....

# Then store the path preset:
path = os.path.split(fileName)[0]
mc.optionVar(stringValue=['testPath', path])

# Section 2
# Load our directory preset (if it exists) before launching the dialog:
if mc.optionVar(exists='testPath'):
savedPath = mc.optionVar(query='testPath')
if os.path.isdir(savedPath):
# This is the magic that lets us configure where the browser opens:
mc.workspace(directory=savedPath)

# Make a file-save dialog (for example).  We wrap its execution in a try\finally
# clause to make sure that even if it fails, we can set our workspace back.
try:
# Section 3
mc.fileBrowserDialog(mode=1, fileCommand=fbdCommand, fileType='mayaAscii',
operationMode='SaveAs', actionName="Save_as")
finally:
# Section 5
# Now set our workspace back to normal:
if os.path.isdir(currentWorkspace):
mc.workspace(directory=currentWorkspace)
}}}
If you're trying to open the dialog in 'directory mode', which is mode 4, then this subject has additional notes:
[[How can I modify the current project \ workspace?]]
*Use an 'if statement' with correct variable scope:
*Example 1 (keep scope within the variable):
{{{
string $foo = "abcd"; if(1)$foo = "dcba";
print $foo; // result: "dcba" // }}} **This works for two reasons: {{{$foo}}} is defined first, outside the scope of the 'if statement'.  Second, when {{{$foo}}} is updaetd inside the 'if statement', since it's being updated by '{{{$foo = ...}}}', //rather// than '{{{string $foo = ...}}}', then it keeps the original scope of {{{$foo}}}.  If we had //instead//  updated it with '{{{string $foo = ...}}}', then the print statement would have printed the original value, since we would have broken out of its original scope. In a nutshell, if you put the variable descriptor before variable name (like {{{string}}}), then that defines the start of a new scope for that variable name. Every time after that, if you call direcltly to the variable name without the descriptor, then you're still acting within the scope of that original variable. I really hope this makes some sense. *Example 2 (//loose// original variable scope due to re-declaring the variable inside the 'if statement'): {{{ string$foo = "abcd";
if(1)
string $foo = "dcba"; print$foo;
// result: "abcd" //
}}}

*Example 3:  //Or//, use a 'conditional statement':
{{{
string $bob = "asdf"; string$lary = $bob == "asdf" ? "happy" : "sad"; }}} **so, //if//$bob = asdf, then $lary is "happy". Otherwise,$lary is "sad".
**The conditional statements can be nested, to provide different return values.
There is the //mel// procedure
{{{
source cleanUpScene;
deleteUnknownNodes();
}}}
Which lives in this script:
{{{
C:\Program Files\Autodesk\Maya<VERSION>\scripts\startup\cleanUpScene.mel
}}}
Optionally you can roll your own.  But I've found the above method to be a bit more robust.
{{{
import maya.cmds as mc
unknown = mc.ls(type='unknown')
if unknown:
mc.delete(unknown)
}}}
{{{
camera -e -startupCamera 0 $camName; delete$camName;
}}}
Default camera's ({{{persp}}}, {{{top}}}, {{{front}}}, {{{side}}}) have a special flag that is set to let Maya know they are "startup cameras".  This keeps the cameras persistent between different Maya files when you're opening them, importing, etc.  Have you even wondered why when you import multiple maya files the cameras don't start piling up?

There have been issues when external tools start to monkey with the cameras, renaming them, or evening creating duplicates, that can't be deleted.  Using this method, you can now delete them.  FYI, there always needs to be at least one cam in the scene listed as a "startup camera".
!!!mel:
I'm not sure what version of Maya introduced it, but //at least// by Maya 2009 the {{{sysFile}}} command has a {{{-removeEmptyDir}}} arg (thanks for the tip from Matti Grüner)
{{{
sysFile -removeEmptyDir "c:/temp";
}}}
!!!Python:
Of course if you have Maya 8.5 or newer you can just use Python:
This only works if the dir is empty:
{{{
import os
os.rmdir("c:/temp")
}}}
if you want to remove a directory, and all the stuff in it:
{{{
import shutil
shutil.rmtree("c:/temp")
}}}
!!!Direct Windows system call:
The DOS {{{rmdir}}} command.
Example: Delete a directory and all files in it ({{{/s}}}), without prompt ({{{/q}}}):
{{{
system("rmdir /s /q c:/temp");
}}}


{{{deleteUI "buttonName";}}}
And then be sure to save the shelfs\prefs
Here is some code to figure out the members of what the current shelf is.
{{{
string $buttons[]; clear$buttons;
global string $gShelfTopLevel; if (tabLayout -exists$gShelfTopLevel)
{
string $currentShelf = tabLayout -query -selectTab$gShelfTopLevel;
$buttons = shelfLayout -q -ca$currentShelf;
}
print $buttons; }}} While you're working on contexts, it's often handy to delete them and remake them (not unlike windows) to see your updates: {{{ if(contextInfo -exists "myContextName == 1){ deleteUI -toolContext "myContextName"; } // remake context here: }}} The below code will search the scene for all materials with the given string in their name, then delete anything assigned to them, and finally delete the material and shader as well. {{{ import pymel.core as pm def delDumbMat(matString): mats = pm.ls("*%s*"%matString, materials=True) for m in mats: shader = pm.listConnections(m, source=False, destination=True, type='shadingEngine') stuff = pm.sets(shader[0], query=True) if stuff: pm.delete(stuff) pm.delete([m, shader]) }}} Often times I want to cleanup a scene by deleting all leaf nodes: Nodes that don't have children. Or, hierarchies of the same kind of node that don't have any meaningful children. {{{ # Python code import maya.cmds as mc def delLeafByType(types): """ types : list : List of given DAG node types to delete, presuming they're leaf of a hierarchy. """ totalDel = 0 triedToDel = 0 leaf = mc.ls(dag=True, leaf=True) noDel = [] while len(leaf) > 0 and len(leaf) >= len(noDel): for i in leaf: try: if mc.objectType(i) in types: mc.delete(i) totalDel += 1 else: noDel.append(i) except Exception, e: noDel.append(i) triedToDel += 1 print str(e).strip(), i leaf = mc.ls(dag=True, leaf=True) if triedToDel: print "Deleted %s leaf nodes, tried to delete %s others but couldn't. Check Script Editor."%(totalDel, triedToDel) else: print "Deleted %s leaf nodes"%totalDel }}} {{{ delLeafByType(["transform", "locator"]) }}} {{{ Deleted 14 leaf nodes }}} Often, when I'm cleaning a scene for export to game, I want to make sure only nodes of a certain type live in it. Here's one way to simply remove everything from the scene that isn't part of the list of "acceptable nodes" you want. {{{ # Python code import maya.cmds as mc def delAllByTypes(exceptThese, dag=False): """ Delete all nodes in the scene, except the ones provided by the user. If anything is flagged for deletion and can't be, print exception results. exceptThese : list : node types that *shouldn't* be deleted. dag : bool : Optional. If True, will limit node types to only DAG nodes (transforms & shapes and their associates). Otherwise, look to ALL node types. """ nodes = [] if dag: nodes = mc.ls(dag=True) else: nodes = mc.ls() nodes.reverse() removal = [] for d in nodes: if mc.objectType(d) not in exceptThese: removal.append(d) for r in removal: try: mc.delete(r) except Exception, e: print str(e).strip(), r }}} {{{ # delete all DAG nodes in the scene that aren't of type 'good': good = ["joint", "transform", "mesh"] delAllByTypes(good, dag=True) }}} Execute the proc: {{{ MLdeleteUnused; }}} Which lives in the script here: {{{../MayaX.X/scripts/others/MLdeleteUnused.mel}}} "ML" stands for "~MultiLister" the old version of the Hypershade (which was brought into Maya from Alias). This code is also called to when you use "Optimize Scene Size" from the File menu, or in the '~HyperShade -> Edit -> Delete Unused Nodes'. Had an animator request a way to deselect any highlighted channels in the channel box: I could find no way via the {{{channelBox}}} command to do this. The docs list it having a {{{-select}}} flag, but when you try to run it, the command says it's unsupported. I came up with the below hack: You can't simply deselect\reselect the current nodes via code to clear the channel box highlighting (but it works doing it by hand): Maya will keep the channels highlighted. But by running an {{{evalDeferred}}} on the reselect, it'll clear the highlighting. {{{ # Deselect the highlighted channels in the Channel Box import maya.cmds as mc reselectMe = [] def doReselect(): global reselectMe if reselectMe: mc.select(reselectMe, replace=True) def deselChannelBox(): global reselectMe reselectMe = mc.ls(selection=True) mc.select(clear=True) mc.evalDeferred(doReselect) deselChannelBox() }}} [[pointCurveConstraint|http://download.autodesk.com/global/docs/maya2012/en_us/CommandsPython/pointCurveConstraint.html]] This command will constraint a point on a curve to //something else//. It does not constrain things //to// a curve. It implements this via a [[leastSquaresModifier|http://download.autodesk.com/global/docs/maya2012/en_us/Nodes/leastSquaresModifier.html]] node. In Maya 2015-ish you can change viewport gamma. Based on our production, this caused problems. To disable: {{{ import maya.cmds as mc mc.colorManagementPrefs(edit=True, cmEnabled=False) }}} This doesn't just 'turn it off', but physically disables it, graying out the button in the viewport menubar. If you don't want to physically disable the //whole system//, and just want to turn it off in a given viewport: {{{ import maya.cmds as mc mc.modelEditor("modelPanel4", edit=True, cmEnabled=False) }}} Below code will find all the incoming and outgoing connections to a node, and //DESTROY// them. {{{ # Python code import maya.cmds as mc node = 'myNode' incoming = mc.listConnections(node, source=True, destination=False, plugs=True, connections=True) outgoing = mc.listConnections(node, source=False, destination=True, plugs=True, connections=True) if incoming: for i in range(0,len(incoming),2): mc.disconnectAttr(incoming[i+1], incoming[i]) if outgoing: for i in range(0,len(outgoing),2): mc.disconnectAttr(outgoing[i], outgoing[i+1]) }}} ---- Also see: *[[How can I disconnect an attribute?]] {{{disconnectAttr}}} ''Some examples'': ''mel'': {{{ // Disconnect only keyable attrs. string$obj = "null1";
string $keyable[] = listAttr -k$obj;

for($i=0;$i<size($keyable);$i++)
{
string $incoming[] = listConnections -s 1 -d 0 -p 1 -c 1 ($obj+"."+$keyable[$i]);
if (size($incoming) ) disconnectAttr$incoming[1] $incoming[0]; } }}} {{{ // Disconnect ALL incoming connections. string$obj = "null1";
string $incoming[] = listConnections -s 1 -d 0 -p 1 -c 1$obj;

for($i=0;$i<size($incoming);$i=$i+2) disconnectAttr$incoming[$i+1]$incoming[$i]; }}} ''Python'': {{{ import maya.cmds as mc string obj = "null1" string keyable = mc.listAttr(obj, keyable=True) for k in keyable: incoming = mc.listConnections('%s.%s'%(obj,k), source=True, destination=False, plugs=True, connections=True) if incoming is not None: mc.disconnectAttr(incoming[1], incoming[0]) }}} In both examples we have {{{listConnections}}} return back both plugs and connections, which gives us a list of source\destination objec.attr pairs, making them easy to feed directly into {{{disconnectAttr}}}. ---- Here's another Python example using {{{connectionInfo}}}, which simplifies things a bit: {{{ objAttr = 'muNode.rotateX' con = mc.connectionInfo(objAttr, sourceFromDestination=True) if con: mc.disconnectAttr(con, objAttr) }}} ---- Also see: *[[How can I disconnect all connections to a node?]] In years past I've had success with {{{polyGeoSampler}}}... but not lately. ---- You can also use this method: *Map a texture to the displacement channel of the mesh's materials shading engine (via the ui, so it creates the appropriate {{{displacementShader}}} node and connections in the process. *On the mesh's shape node, under the {{{Displacement Map}}} dropdown, set the {{{initialSampleRate}}} and {{{extraSampleRate}}} attrs to 1. *Modify -> Convert -> Displacement to polygons. This will triangulate the mesh in the process, but shouldn't add any extra verts. {{{waitCursor}}} Best to make the border size bigger too in the process: {{{ polyOptions -activeObjects -displayBorder true -global -sizeBorder 10; }}} You can also add the {{{-relative}}} flag, which will turn it into a toggle. To turn it back off: {{{ polyOptions -activeObjects -displayBorder false; }}}  Around Maya 2010 (maybe earlier) they have a wiz-bang feature that displays a soothing gradient color in the background of the viewports. To turn it on, the default hotkey is {{{ALT+B}}}. Or, you can use code (after hacking apart {{{cycleBackgroundColor.mel}}}): {{{ # Python code import maya.cmds as mc mc.displayPref(displayGradient=True) mc.optionVar(intValue=["displayGradient", 1]) }}} I 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. You could do it, but that would be silly: {{{ 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 {{{MVector}}} ([[docs|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_vector.html]]) goodness. ''This is the recommended way.'' {{{ 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 }}} *{{{*}}} operator: The dot product of two {{{MVector}}}s. *{{{^}}} operator: The cross product of two {{{MVector}}}s. ---- ''Method C'': Write your own Python vector module, or get someone else's. But why do that when the API gives you all you need? *I document the math behind some of the basic vector functions [[here|Vector Math]] *[[Vector|http://code.activestate.com/recipes/578006-vector/]] : A fairly robust looking 2d vector solution. *The 2d and 3d vector classes in the [[gameobjects|http://code.google.com/p/gameobjects/]] package (over at Google Code). *~PyGame have contributed [[Vec2d|http://pygame.org/wiki/2DVectorClass]] and [[Vec3d|http://pygame.org/wiki/3DVectorClass]] classes. ---- ''Method D'': Get {{{NumPy}}} http://numpy.scipy.org I've not tested it, but it's possible you could get a speed boost based on Numpy's ndarray objects. 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) }}}  Based on the example in [[Query which standard Maya dockControl is visible]] : you can learn the default control names for the Channel Box, Attribute Editor, etc. Based on that, how can you dock \ undock them via code? {{{ import pymel.core as pm # To dock the Channel Box: pm.dockControl("MayaWindow|dockControl1", edit=True, floating=False) # To undock the Attribute Editor: pm.dockControl("MayaWindow|dockControl4", edit=True, floating=True) }}} Also see: *[[Creating a dockable window via PySide and workspaceControl]] *[[Understanding window docking and 'workspaceControls']] {{{inViewMessage}}} creates a great little bubble-shaped message that can be different colors and fade out, etc. http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/inViewMessage.html {{{ import maya.cmds as mc mc.inViewMessage(assistMessage='In-view message <hl>test</hl>.', position='midCenter', fade=True, backColor=0x00990000) }}} ---- 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."; }}} For the {{{backColor}}}, it's hexadecimal: * Sets the background color for the message using the format 0xAARRGGBB, alpha is not taken into account. https://www.hexcolortool.com  {{{createDrawCtx}}} I often end up with long chains of joints, like so: *tentacleA1 **tentacleA2 ***tentacleA3 ****etc... Which I want to duplicate. Maya has an option to duplicate a node and give all children unique names. But what I want is to duplicate the hierarchy and rename the hierarchy in the process. So I'd get something like this: *tentacleB1 **tentacleB2 ***tentacleB3 ****etc... And, code: {{{ import pymel.core as pm def dupeAndRename(replaceThis, withThis): oldNames = [node.nodeName() for node in sel] new = pm.duplicate(sel) for i,n in enumerate(new): newName = oldNames[i].replace(replaceThis, withThis) n.rename(newName) kids = pm.listRelatives(n, type='transform') for k in kids: newKName = k.nodeName().replace(replaceThis, withThis) k.rename(newKName) return new }}} To use it, I'd just select the {{{tentacleA1}}} root, and run the code like so: {{{ sel = pm.ls(selection=True) dupeAndRename("A", "B") }}} Presuming you have a node with children, how can you duplicate it, but not its children? You can use the {{{parentOnly}}} argument of the {{{duplicate}}} command {{{ # Python code import maya.cmds as mc sel = mc.ls(selection=True, long=True) for s in sel: new = mc.duplicate(s, parentOnly=True) }}} ---- Old data, incorrect concept (the execution still works, but obviously it's unneeded). <<< To my knowledge, you can't: The children are always created during the duplication. However, you can duplicate it, and then delete the new children: {{{ # Python code import maya.cmds as mc sel = mc.ls(selection=True, long=True) for s in sel: new = mc.duplicate(s, renameChildren=True, returnRootsOnly=True) kids = mc.listRelatives(new, children=True, fullPath=True) if kids is not None: mc.delete(kids) }}} <<< If a node lives in a set, when you duplicate the node, the new duplicate well live in that set as well. But, what if you don't want this behavior? {{{ import maya.cmds as mc def dupeNoSet(nodesInSet): """ Duplicate the passed in nodes, and remove them from any sets the original nodes were in. Args: nodesInSet : ["node", ...] : List of node names to duplicate. return : ["node", ...] : List of duplicated node nams. """ if len(nodesInSet) == 0: return [] newNodes = mc.duplicate(nodesInSet, renameChildren=True) # Rename the new joints, and fill our jointMap dict: for i in range(len(newNodes)): outgoing = mc.listConnections(newNodes[i], source=False, destination=True, plugs=True, connections=True) if outgoing is not None: for i in range(0, len(outgoing), 2): if "instObjGroups" in outgoing[i]: try: mc.disconnectAttr(outgoing[i], outgoing[i+1]) except: # there can be dupe reports in our outgoing list, so skip # if its already been disconnected. pass return newNodes }}} {{{ nodesInSet = mc.ls(selection=True, long=True) dupes = dupeNoSet(nodesInSet) }}} {{{ objectName.f[*]; }}} 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?]]
Autodesk has a handy-dandy screencast utility:
https://screencast.autodesk.com/

The {{{animCurveEditor}}} command lets you access a named Graph Editor.
It's confusing, since this is a UI element, you'd think in the Maya mel docs it'd be under the general "Windows" section, or maybe even "Panels \ Controls" sections.  But no, it's under "Animation".  Which makes sense too I suppose.  But I don't like it...

{{{graphEditor1GraphEd}}} is the name of the default Graph Editor.

From the Maya docs on {{{animCurveEditor}}}:
{{{
// Check to see if the "default" graph editor has been created
animCurveEditor -exists graphEditor1GraphEd;

// Show result curves
animCurveEditor -edit -showResults on graphEditor1GraphEd;

// Decrease the sampling rate for the result curves
animCurveEditor -edit -resultSamples 5 graphEditor1GraphEd;
}}}
What I'm still trying to figure out:  What kind of UI element //is// the 'graph-side' of a 'Graph Editor'?  According to the {{{animCurveEditor}}} docs, it gets created by a {{{scriptedPanel}}}.  The name of the panel that the default Graph Editor is associated with is {{{graphEditor1}}}:
{{{
animCurveEditor -q -pnl graphEditor1GraphEd;
// Result: graphEditor1 //
}}}
But that still dosn't answer the above question....
----
FYI, the Outliner to the left of the Graph Editor is an {{{outlinerEditor}}} called {{{graphEditor1OutlineEd}}}, and it's parent is a {{{scriptedPanel}}} called {{{graphEditor1}}}.  This appears to be the same {{{scriptedPanel}}} that is associted with the default Graph Editor (from above, which would make some sense).
{{{
scriptedPanel -q -ex graphEditor1;
outlinerEditor -q -ex graphEditor1OutlineEd;
}}}
If you want to be able to figure out what you have highlighted in that {{{outlinerEditor}}}, see here:
[[How can I get a string array of the contents for a selectionConnection?]]
Also:
[[How can I highlight channels in the Graph Editor based on what's selected in the Channel Box?]]
*Every DG node in Maya has the ability to have a "note" added to it, which exposed in the bottom of the Attribute Editor. Not really a convention, but a feature they exposed in the Attribute Editor around Maya 5? How do you update that via mel?
*It would seem simple with a setAttr command. The issue is, that attr dosen't exist by default, so if you try to set it without creating it first, it won't work. When entering notes directly in the Attribute Editor, it creates the attr for you behind the scenes.
{{{
// mel
if(!attributeQuery -n $node -ex "notes") addAttr -ln notes -dt "string"$node;
setAttr ($node + ".notes") -type "string" "My notes"; }}} {{{ # Python import maya.cmds as mc if not mc.attributeQuery('notes', node=node, exists=True): mc.addAttr(node, longName='notes', dataType='string') mc.setAttr('%s.notes'%node, "My notes", type='string') }}} There is a 'Gradient' dropdown in the 'Paint Skin Weights' tool, that allows you to "Use Color Ramp", then set that color. But how can you set it through script? The default values for the gradient (black on left, white on right) look like this: {{{ artAttrCtx -e -colorRamp " 1,1,1,1,1,0,0,0,0,1 " artAttrSkinContext; }}} What do all those numbers mean? It's interest that in fact it appears that Maya intersects a {{{1}}} between each chucnk of 4 values. This is what it means: {{{ r,g,b,p1, 1, r,g,b,p2, 1 }}} Where rgb & p1 (position 1) go together as a unit, and rgb& p2 (position2) go together as a unit, with a {{{1}}} separating them, and an additional {{{1}}} at the end. Weird\interesting. So if you want to add more in, just append in new chunks of {{{"r, g, b, pX, 1 "}}} to the end of that string. You can also set the special min/max colors with the {{{-rampMinColor r g b}}} and {{{-rampMaxColor r g b}}} flags. {{{fileInfo}}} *Note: This seems similar to the {{{optionVar}}} command, but stores the var's relative to the file, not wherever the optionVar's are stored. *Example: store a "keyword" with a value in the scene: {{{ fileInfo "secretData" "WarpCat was here..."; string$secretData[] = fileInfo -q "secretData";
}}}
Based on a new install of Maya, I believe the default frame rate is set to '{{{Film (24 fps)}}}'.  I work in games, where most of the time the frame rate should be set to '{{{NTSC (30 fps)}}}'.  Based on the tools we use, if an animator loads a scene up, and starts animating at 24 fps, then loads it into our animation sequencer that expects things to be at 30fps,... bad things will happen.

I've isolated two locations that need to be updated to ensure that whenever a scene is created \ opened, it will be a the right frame rate.  For this example, we'll be using '{{{NTSC (30 fps)}}}'.

!Location #1: Maya Preferences:
Maya has its default preferences (''Window -> Settings / Preferences -> Preferences -> Settings -> Working Units -> Time'') that set the overall time for the scene.  When you change the value, Maya calls to the {{{currentUnit}}} command to update the system.  What's interesting, is that it //doesn't// seem to modify any {{{optionVar}}}s to store these settings.
Look in the {{{global proc prefWndUnitsChanged()}}} in the script:
{{{C:/Program Files/Autodesk/Maya<version>/scripts/startup/createPrefWndUI.mel}}}
...to see what's really going on.

However, this value can be changed by incoming files with different values.  So we need to do something else to ensue we have the correct settings when we open //any// scene...

!Location #2:  New Scene Options
When you make a //new// scene (''File -> New -> Options''), there are //new scene options// where you can set the framerate.  These will //override// anything set in your Maya preferences (see #1, above).  This is a main gotcha I see for most people:  They don't realize that these need updated as well.
Changing the 'time' in these options //does// store the change in an {{{optionVar}}}.
Look in {{{global proc setDefaultUnits()}}} in the script:
{{{C:/Program Files/Autodesk/Maya<version>/scripts/startup/performNewScene.mel}}}}
... for more details.

!The Solution:
You need these options to be set every time you access a //new// scene, or open an //existing// scene.  The best way I've found to do this is add a {{{scriptJob}}} your {{{userSetup.mel}}} that executes on any file access:
{{{
// userSetup.mel

global proc setNTSC(){
// Set our 'Maya preferences':
currentUnit -t ntsc;
// Set our 'new scene options':
optionVar -sv workingUnitTimeDefault ntsc;
}

scriptJob -event SceneOpened setNTSC;
scriptJob -event NewSceneOpened setNTSC;
}}}
Done:  Anytime you open access a file, you're sure to know that it's at the correct framerate.  With a bit more 'detection code', you could query what the values were before modification, and alert the user if any changes were taking place.
{{{
string $format = "mayaAscii"; optionVar -sv defaultFileOpenType "mayaAscii"; optionVar -sv "defaultFileSaveType"$format;
file -type $format; }}} Adding this to your {{{userSetup.mel}}} file will make sure that things are saved in that format by default, and the new scene that Maya opens with is also of that format. When doing a "Save As", this will set the "File Type" under "General Options" to the defined {{{$format}}}.

Optionally, if you really wanted to make sure your users always saved as a given format no matter what:  Presuming you push a studio-wide {{{userSetup.mel}}}, in that file, you can set, via the {{{file}}} command, a {{{-preSaveScript}}} arg.  That points to a script that will be ran before save.  In that script, you can query for the file type, and if not the format you want, change the format, then save.
@@Note:@@  Wing can interact with Maya in three different ways.  This is one of the three.  See an overview here on the whole system:  [[Interaction between Wing and Maya]].

See ''Wing Setup Notes'' below for how to setup Wing to use this information.
----
Maya comes with its own cut of Python (see [[Python & Qt versions in Maya]]).  The executable for that lives here on Windows:
{{{
c:\Program Files\Autodesk\Maya<version>\bin\mayapy.exe
}}}
Or here on Mac:
{{{
/Applications/Autodesk/maya<version>/Maya.app/Contents/bin/mayapy
}}}
<<<
Note, on the mac, you have to pass in the full path to the mayapy app to execute it, or the below initialize call will fail:
{{{
$bash /Applications/Autodesk/maya2017/Maya.app/Contents/bin/mayapy }}} <<< You can execute this directly to launch an interactive prompt of Python (most commonly for batching purposes). Or, if you're running a Python IDE external to Maya, but you want Maya's Python calls to exist in it, you can point your IDE To that exe. There is often an option in your IDE to allow for this (Wing allows you to do this, for example). Once that executable has been started, you still need load and initialize Maya into that session. Per Maya's docs: "{{{These commands take a significant amount of time to execute as they are loading all of the Maya's libraries and initializing the scene.}}}" This is the code I use: {{{ try: import maya.standalone maya.standalone.initialize() print "Imported maya.standalone\n" except: print "Couldn't find 'maya.standalone' for import\n" }}} If you want that code to execute //every time// you launch Maya's version of Python (I do this in Wing), you can author the above code as a module, and point Python's {{{PYTHONSTARTUP}}} environment variable to it: {{{ PYTHONSTARTUP = <somePath>\<somePythonStartupModule>.py }}} I have [[Wing|For Python: Maya 'Script Editor' style IDE]] execute this code for my 'Maya Python coding sessions'. ---- The {{{maya.cmds()}}} library is actually empty on disk (go take a look for yourself) other than the {{{__init__.py}}} module that defines that dir structure //as// a library: {{{ C:\Program Files\Autodesk\Maya<VER>\Python\lib\site-packages\maya\cmds\ }}} Although you can fully access all Maya commands through Python like '{{{maya.cmds.ls()}}}', the Python {{{ls()}}} command wrapper has no physical location on disk: Until you execute the above code example ({{{import maya.standalone}}} ...), Python won't see any of the commands. If you want to get a better understanding, see the subject [[How does Maya populate maya.cmds?]]. !Wing Setup Notes *In Wing, create a new Project, and access the "Project Properties". *In the Environment tab: **Set "Python Executable" to "Custom", and browse to the path mentioned above with {{{mayapy.exe}}} in it. **Set "Environment" to "Add to inherited environment", and paste in the {{{PYTHONSTARTUP}}} path you authored above. **If you want Wing to be able to analyze your ~Maya-Python modules and give you functionality like "Go To Selected Symbol Definition" (ctrl+LMB on a symbol name, F4 hotkey on a symbol, etc), you can add the paths where they're stored to the "Python Path" section of the ui. If you're using Python's packaging system, you only need to add the root dir of each package to this field. **Hit OK *Be sure to "Save Project" ;)  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?]]

It's possible you may want to execute a Python module, external from Maya, and not part of the system Python path, via mel.  Maybe it launches a custom window or something exciting like that.  Here's one solution:
{{{
string $module = "C:\\path\\to\\my\\module.py"; string$mName = basename($module, ""); string$dName = dirname($module); string$dosCmd = ("chdir " + $dName + " & start " +$mName);
system($dosCmd); }}} There is a {{{python}}} mel command, that will take the given string, and execute it as Python code. It's easy to do single line stuff like this: {{{ // mel code python("import sys; print sys.path"); }}} You can call to Python builtins, your own modules, etc. But what if you want to call to a large block of Python code that way? For example, marking menus only accept mel commands: You have a bunch of Python you want to call to via a marking menu, but you don't want to have to make it it's own module. Via the below technique, you can convert a block of Python code into a multiline Python string, then convert that string into a bunch of print statements that can in turn be used as mel. For example, let's write a tool via ~PyMel that will convert the names of everything selected to capital letters. Here is the pure Maya Python: {{{ # Rename selected nodes to uppercase import pymel.core as pm sel = pm.ls(selection=True) for s in sel: upper = s.nodeName().upper() if upper == s.nodeName(): continue s.rename(upper) }}} Now that we know that works, re-write it as a multi-line string: {{{ pyStr = """# Rename selected nodes to uppercase import pymel.core as pm sel = pm.ls(selection=True) for s in sel: upper = s.nodeName().upper() if upper == s.nodeName(): continue s.rename(upper)""" }}} Now, split that multiline string, and reformat it for use as a mel: {{{ splitStr = pyStr.split("\n") print "string$pyStr = ("
for line in splitStr[:-1]:
print r'"%s\n"+'%line
print r'"%s\n");'%splitStr[-1]
print "python($pyStr);" }}} Which ends up printing this //mel// code: {{{ string$pyStr = (
"# Rename selected nodes to uppercase\n"+
"import pymel.core as pm\n"+
"sel = pm.ls(selection=True)\n"+
"for s in sel:\n"+
"    upper = s.nodeName().upper()\n"+
"    if upper == s.nodeName():\n"+
"        continue\n"+
"    s.rename(upper)\n");
python($pyStr); }}} Which you can copy and stick it into whatever mel script \ hotkey \ marking menu as needed.  Via Maya's [[scriptJob|http://download.autodesk.com/us/maya/2010help/CommandsPython/scriptJob.html]] command, you can easily execute callbacks. And via Maya's [[scriptNode|http://download.autodesk.com/global/docs/maya2013/en_us/CommandsPython/scriptNode.html]] command, you can create 'script nodes' that can execute 'script jobs' when a scene opens. Via this method, its fairly trivial to create a {{{sciptJob}}} that will cause a script to evaluate whenever a certain attribute on a certain node changes. But this all breaks down if that file is referenced: Since the name of the node has now changed (due to the namespace), the {{{scriptJob}}} no longer detects it. Thanks to an undocumented tip from Mason Sheffield, you can actually put a wildcard ({{{*:objName.attr}}}) in front of the object name during the {{{scriptJob}}} authoring: It will now detect the {{{namespace:obj.attr}}} name in the reference. Awesome. However, there are two problems with this system: *Upon open of the un-referenced scene, you'll get a {{{RuntimeError}}} saying (for example): <<< {{{ # RuntimeError: Could not find attribute named "*:mynode.tx" # }}} <<< *If you reference the referenced scene (which can happen) : The {{{scriptJob}}} will no longer evaluate, since it's only looking 'one level deep' for the namespace. If you know this will be consistent, you can embed {{{*:*:}}} in front of your node, instead of just {{{*:}}}. ---- Here's an example of how to set it up: Create this script, and save it as {{{attrCallback.py}}}. Save it in Maya's script path. What does this script do? A: It creates a {{{scriptNode}}} that lives in the current scene, that will execute a {{{scriptJob}}} when that scene is opened. B: Whenever the {{{translateX}}} attr on a node called {{{mynode}}} is modified (in a scene referenced one level deep), it will execute the {{{attrCallback}}} //function//, which simply prints some text. {{{ # attrCallback.py import maya.cmds as mc def attrCallback(): print "mynode.tx changed!" def makeScriptNode(): scriptStr = 'import maya.cmds as mc\n'+\ 'import attrCallback\n'+\ 'mc.scriptJob(attributeChange=["*:mynode.tx", attrCallback.attrCallback])' mc.scriptNode( scriptType=2, beforeScript=scriptStr, name='myScriptNode', stp='python') }}} Make a new scene, and in it create a new node. Rename that node to {{{mynode}}}. In the script editor, execute: {{{ import attrCallback attrCallback.makeScriptNode() }}} This will create the {{{scriptNode}}}. Save your scene as {{{attrCallback.ma}}}. If you modify {{{mynode.translateX}}} nothing will happen, since the {{{scriptNode}}} is searching for the node in a sub-namespace (and it currently lives in the root namespace). Create a new scene. Reference in {{{attrCallback.ma}}}. Adjust the {{{mynode.translateX}}} : You should see the Script Editor starting printing the results from {{{attrCallback()}}}.  So simple, but I always seem to forget the syntax... :-S {{{ import maya.cmds as mc sc = mc.skinCluster(someListOfJoints, aMesh, dropoffRate=5.0, maximumInfluences=4, toSelectedBones=True) print sc # [u'skinCluster1'] }}} note the {{{bindSkin}}} command is the (much) older system of setting up 'rigid skinning', using basically clusters. yuk. Often times I want to call a function when I close/delete a window. Unfortunately there is no 'delete detection' for this via the {{{window}}} command and no way via the {{{window}}} command to assign callbacks. However I've found two ways to implement callback systems that do detect for window deletion: *Via the {{{OpenMayaUI}}} API, one can create a {{{MUiMessage_addUiDeletedCallback}}} object that will track when the given window is deleted, and execute the provided code when that happens. It also allows for an optional {{{clientData}}} parameter allowing you to easily pass data to the function upon window delete. **Note: It appears that this code runs just before, or possibly at the same time as the window is deleted: It can be used to execute code to safely delete {{{modelPanel}}}s before the window is deleted, when that same code would fail in the {{{scriptJob}}} callback. *It's easy to make a {{{scriptJob}}} which attaches itself to the window, executes when the window is deleted, and for safety can be set to only 'run once'. We have the {{{scriptJob}}} call to the a method on our {{{Window}}} object at time of death telling it what to do. It should be noted that this will execute the code after the window is closed. It should be noted that the {{{scriptJob}}} needs to be called before {{{showWindow}}} or it won't seem to attach correctly. Finally, even though these callbacks are executed when the window is closed, they appear to execute after the window is deleted. If you try to query part of the window ui in the callback, it will fail. I've got around this by storing the values I need to query in optionVar's. {{{ # Python code import maya.cmds as mc import maya.OpenMayaUI as omui class App(object): def __init__(self): self.name = 'myWin' if mc.window(self.name, exists=True): mc.deleteUI(self.name) mc.window(self.name) # OpenMayaUI callback example: omui.MUiMessage_addUiDeletedCallback(self.name, self.MUiMessageCallback, "clientData") # scriptJob callback example: mc.scriptJob(uiDeleted=[self.name, self.scriptJobCallback], runOnce=True) mc.showWindow() def MUiMessageCallback(self, clientData): print "MUiMessage_addUiDeletedCallback", clientData def scriptJobCallback(self, *args): print "scriptJobCallback", args }}} To launch the window: {{{ App() }}} When the window is closed, it will print: {{{ MUiMessage_addUiDeletedCallback clientData scriptJobCallback () }}} Notice how the {{{MUiMessage_addUiDeletedCallback}}} is executed first? {{{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]] Via pure Python, you can use {{{subprocess.Popen}}} to do this. My Python Wiki has a [[subject covering this|http://pythonwiki.tiddlyspot.com/#%5B%5BHow%20can%20I%20execute%20a%20system%20command%2C%20and%20capture%20the%20return%3F%5D%5D]]. In Maya though, ~PyMel makes this even easier via its [[pymel.util.shellOutput|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/PyMel/generated/functions/pymel.util/pymel.util.shellOutput.html?highlight=stdout]] command (which appears to wrapper {{{subprocess.Popen}}}): {{{ import pymel.core as pm data = pm.util.shellOutput("dir /A:L", cwd="c:\\some\\path", convertNewlines=True) for d in data.split("\n"): print d }}} The above example tests to see if the given directory is a junction. Some advantages I've found using this over the raw {{{subprocess.Popen}}}: *It auto-prevents shells from popping up on screen. *It prevents spam from hitting the Script Editor / Output Window. Say you have some nodes in a namespace, either manually created, or because the nodes are referenced. You want to export those new nodes to a new file, but have them *not* be in a namespace in that new file: How to do? Maya's {{{file}}} command has a {{{relativeNamespace}}} parameter: If you pass it the same namespace they're currently in, that namespace will be removed from them in the new file they've been exprorted too. Note, the namespace must be provided as an absolute namespace, so it must have a leading colon. So this is vaild: {{{:myNamespace:}}}, but not this: {{{myNamespace:}}}. In action: {{{ mc.file(myExportFilePath, exportSelected=True, force=True, relativeNamespace=":myNamespace:", type="mayaBinary") }}} When you query the selection of vert via {{{ls()}}}, it will give you back data like so: {{{pPlane1.vtx[60]}}}. How can you get just the vert id number {{{60}}} from that string? Python's {{{re}}} module makes this pretty easy: {{{ # Python code import re myStr = 'pPlane1.vtx[60]' pattern = r'\d+' # This will return a list ["1", "60"], so grab the last item, & turn it into an int: vid = int(re.findall(pattern, myStr)[-1]) # 60 }}} 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. ---- For the opposite, see: *[[How can I find what percentage between a start and end value a mid value is?]] 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?]]
For the opposite, see:
*[[How can I find what percentage between a start and end value a mid value is?]]
The default name for the Outliner is {{{outlinerPanel1}}}, but I've ran into intsances where there is either more than one, or it's somehow got renamed :S
{{{
import maya.cmds as mc
outliners = []
panels = mc.lsUI(panels=True)
for p in panels:
if mc.outlinerPanel(p, query=True, exists=True):
outliners.append(p)
print outliners
}}}
Thanks to my bud Demetrius Leal on the tip with {{{netstat}}}:

On a //Windows// system, you can use {{{subprocess.Popen}}} to run the system {{{netstat}}} command.  It returns a file-object which you can then parse.  It should be noted that this doesn't need to be executed //in Maya//, it could be in an external Python shell.  But Maya needs to be running of course.  And you can run it in Maya if you feel like it ;)
{{{
# Python code
import subprocess

pipe = subprocess.Popen(['netstat', '-ab'], stdout=subprocess.PIPE)
data = [line.strip() for line in pipe.stdout]
for i,e in enumerate(data):
if 'maya.exe' in e and 'LISTENING' in data[i-1]:
print e, data[i-1].strip()
}}}
{{{-a}}} : Displays all connections and listening ports.
{{{-b}}} : Displays the executable involved in creating each connection or listening port.

Will print something like this (presuming you have active commandPorts in Maya...):
{{{
}}}
It removes other ports that aren't 'LISTENING', since they'd probably do you no good anyway ;)
I recently had to find all the {{{animCurves}}} that were controlling a node.  At the time, it turned out to be a bit more difficult that it seemed so I came up with the API solutions (B, C) shown below:  See, the curves were in animation layers, and I wasn't getting the result I wanted with the {{{listConnections}}} or {{{listHistory}}} commands.
!Solution A:
{{{
My latest solution that seems pretty rock solid:

def getAnimCurves(node):
inputs = mc.listConnections(node, source=True)
if not inputs:
return []
for inpt in inputs:
inptType = mc.objectType(inpt)
# Only pass types that related to keyframes or anim layers.  But leave out
# actual 'animLayer' nodes, since they're connected to so much more that
# will get pulled in.
if "animBlend" in inptType or "animCurve" in inptType:
theseInpts = mc.listConnections(inpt, source=True)
if not theseInpts:
continue
for ti in theseInpts:
if ti not in inputs:
inputs.append(ti)
curves = mc.ls(inputs, type='animCurve')
return curves
}}}
!Solution B:
Thanks to a tip from Han Jiang (that he found over on [[Stack Overflow|http://stackoverflow.com/questions/18738371/how-to-find-all-upstream-dg-nodes-with-maya-python-api]], this works well, but sometimes still returns too much.

This will get keys on the node, and dig through the anim layers to find them as well.
{{{
import maya.cmds as mc
curves = mc.ls(mc.listHistory('joint1'), type = 'animCurve')
# if that doesn't work, try this:
curves = mc.ls(mc.listHistory(mc.listConnections('joint1', source=True)), type = 'animCurve')
}}}
!Solution C: Use ~MAnimUtil
This is great, since it does all the heavy lifting for you:  Find all the attrs on the node that have incoming connections, and then for those connections, find the {{{animCurve}}}s controling them.
{{{
import maya.OpenMaya as om
import maya.OpenMayaAnim as oma

# Get a MDagPath for the given node name:
node = 'myNodeName'
selList = om.MSelectionList()
mDagPath = om.MDagPath()
selList.getDagPath(0, mDagPath)

# Find all the animated attrs:
mPlugArray = om.MPlugArray()
oma.MAnimUtil.findAnimatedPlugs(mDagPath, mPlugArray)
animCurves = []

# Find the curves ultimately connected to the attrs:
for i in range(mPlugArray.length()):
mObjArray = om.MObjectArray()
oma.MAnimUtil.findAnimation(mPlugArray[i], mObjArray)
for j in range(mObjArray.length()):
depNodeFunc = om.MFnDependencyNode(mObjArray[j])
animCurves.append(depNodeFunc.name())

# See what we found:
for ac in sorted(animCurves):
print ac
}}}
!Solution D: Iterate the DG:
The below example will print a list of all the animation curves on the selected objects, in animation layers or not.
{{{
# Python code
import maya.OpenMaya as om

animCurves = []
# Create a MSelectionList with our selected items:
selList = om.MSelectionList()
om.MGlobal.getActiveSelectionList(selList)

# Create a selection list iterator for what we picked:
mItSelectionList = om.MItSelectionList(selList)
while not mItSelectionList.isDone():
mObject = om.MObject()  # The current object
mItSelectionList.getDependNode(mObject)
# Create a dependency graph iterator for our current object:
mItDependencyGraph = om.MItDependencyGraph(mObject,
om.MItDependencyGraph.kUpstream,
om.MItDependencyGraph.kPlugLevel)
while not mItDependencyGraph.isDone():
currentItem = mItDependencyGraph.currentItem()
dependNodeFunc = om.MFnDependencyNode(currentItem)
# See if the current item is an animCurve:
if currentItem.hasFn(om.MFn.kAnimCurve):
name = dependNodeFunc.name()
animCurves.append(name)
mItDependencyGraph.next()
mItSelectionList.next()

# See what we found:
for ac in sorted(animCurves):
print ac
}}}
!Solution E: findKeyframe
Well, honestly, this really isn't a solution, but I wanted to put it in here anyway:
{{{
import maya.cmds as mc
node = "pCube1"
curves = mc.findKeyframe(node, curve=True, attribute='rotateX')
}}}
The {{{findKeyframe}}} command will return back the {{{animCurve}}} for a specific attribute, based on the //current {{{animLayer}}}//.
{{{listHistory}}}
I found the source of this code below:
{{{
..\Autodesk\Maya2010\Python\Lib\site-packages\maya\app\general\zipScene.py
}}}
The version Maya provides does more error checking, but below I add the ability to //not// store the dir structure of the zipped files, if you don't want to.

Surprisingly easy.

{{{
# Python code
# zipScene.py

import os
import locale
import zipfile
import maya.cmds as mc

def main(storePaths=False):
"""
If storePaths == True, then full directory paths are saved to each file in
the zip.  If False, then all files are at root level of zip.
"""
theLocale = locale.getdefaultlocale()[1]
files = mc.file(query=True, list=True, withoutCopyNumber=True)
zipFileName = os.path.splitext(files[0])[0]+'.zip'
zipper = zipfile.ZipFile(zipFileName, 'w', zipfile.ZIP_DEFLATED)

for f in files:
name = f.encode(theLocale)
if storePaths:
zipper.write(name)
else:
zipper.write(name, os.path.basename(name))
zipper.close()

for f in files:
print "\t",f
print "Added above files to zip:", zipFileName
}}}
I have a post on [[my blog|http://www.akeric.com/blog/?p=1087]] for this topic as well.

I'd tried a variety of methods using mel commands to try and trawl the scene looking for instances.  This worked well on shape nodes.  However, when it dawned on me you can instance transforms (or anything I suppose) just as easily as shapes, that code went out the window.  The Python ~OpenMaya API came to the rescue:
{{{
# Python code
import maya.OpenMaya as om

def getInstances():
instances = []
iterDag = om.MItDag()
while not iterDag.isDone():
instanced = om.MItDag.isInstanced(iterDag)
if instanced:
instances.append(iterDag.fullPathName())
iterDag.next()
return instances
}}}
This function will return a list of the full path to all instanced nodes in the scene.

The below function will then uninstance all those nodes:
{{{
import maya.cmds as mc
def uninstance():
instances = getInstances()
while len(instances):
parent = mc.listRelatives(instances[0], parent=True)[0]
mc.duplicate(parent, renameChildren=True)
mc.delete(parent)
instances = getInstances()
}}}
Note this is slightly destructive to any child nodes to those instances:  Any connections they have will be lost.  I have a mel workaround below (too lazy to port to Python):
----
~PyMel also makes it pretty easy:
{{{
sel = pm.ls(selection=True)
for s in sel:
print s, s.isInstanced()
}}}
----
Also see:
*[[How can I 'uninstance' a node?]] for other methods.
Say you have a polygonal mesh, with a bunch of joints bound to it.  How can you find, for a given joint, all the verts that have non-zero weights?
{{{
# Python code

import maya.cmds as mc
def getWeightedVerts(joint, geo):
geoShape = mc.listRelatives(geo, shapes=True, noIntermediate=True)
skinCluster = mc.listConnections(geoShape, source=True, destination=False,
type="skinCluster",
skipConversionNodes=True)[0]
vertCount = mc.polyEvaluate(geo, vertex=True)
nonZero = []
for i in range(vertCount):
weightVal = mc.skinPercent(skinCluster, geo+".vtx["+str(i)+"]",
transform=joint, query=True)
if weightVal > 0:
nonZero.append(geo+".vtx["+str(i)+"]")
return nonZero
}}}
Usage: (in Python)
{{{
joint = "l_knee"
geo = "legs"
print getWeightedVerts(joint, geo)
['legs.vtx[499]', 'legs.vtx[500]', 'legs.vtx[501]', etc...]
}}}
{{{ls}}}
{{{
// find all objects with an "outTime" attr in your scene:
string $attr = "outTime"; string$objs[] = ls -o ("*." + $attr); print$objs;
// time1
}}}
Notes:
*If you leave off the {{{-o}}} flag, it will return back the whole obj.attr name
*Sometimes, it will return back duplicate names for everything.  Weird.
*I can't believe it's taken me 8.5 versions of Maya to learn this... shame...  Thanks to Doug Brooks for showing me this :) 
!!!~PyMel:
This will filter the edits by type, and by namespace:
{{{
import pymel.core as pm

def removeRefEditCmd(editCommand, namespace=None):
"""
Tool to remove reference edits.

editCommand : string : Valid values are: "addAttr", "connectAttr", "deleteAttr",
"disconnectAttr", "parent", "setAttr", "lock" and "unlock".
namespace : bool\string : Default None.  If None, act on all references in the
scene.  Otherwise a valid namespace (without semicolon ":")
"""
allRefs = pm.getReferences()
for r in allRefs:
ref = allRefs[r]
if namespace and namespace != ref.fullNamespace:
continue
edits = ref.getReferenceEdits(editCommand=editCommand)
if edits:
print "Found %s edits in: %s"%(editCommand, r)
ref.removeReferenceEdits(editCommand=editCommand, force=True,
successfulEdits=True, failedEdits=True)
for es in edits:
print "\t", es
print "\tRemoved the above %s edits"%editCommand

removeRefEditCmd(editCommand="disconnectAttr")
}}}
Here's another way, to only do on certain attrs on certain nodes in a certain namespace:
{{{
import pymel.core as pm

nodes = pm.ls("namespace:nodePfx_*.translate")
for node in nodes:
pm.referenceEdit( node, editCommand='setAttr', removeEdits=True, failedEdits=True, successfulEdits=True )
}}}

!!!Python
(Slightly older)
Note, this can print a LOT of stuff depending on what's changed in the reference:
{{{
# Python Code
import maya.cmds as mc

allRefs = mc.file(query=True, reference=True)
for ref in allRefs:
refNode = mc.file(ref, query=True, referenceNode=True)

print refNode, ref
editStrings = mc.referenceQuery(ref, topReference=True, editStrings=True, successfulEdits=True)
for es in editStrings:
print "\t", es

# Remove all edits:
mc.file(cleanReference=refNode)
}}}
----
This tip comes from over at the [[Maya Station|http://mayastation.typepad.com/maya-station/2010/01/remove-reference-edits.html]] blog:
<<<
If you have made changes to your reference file which has then caused incorrect results, For example making changes to an expression then saving the reference file causes you to lose your expression information; simply reloading the reference file will not be enough to get the original data back.

You will need to remove the wrong edits.   In order to do this you need to unload your reference from the Reference Editor, and then go to Reference Editor ->List Reference Edits.   In the window that pops up select the wrong edits and click on Remove Selected Edits.  Once this is done you can Reload you reference, through the reference editor.
<<<

{{{file}}}
*Example:
{{{
string $alldependencies[] = file -q -l; }}} Also check out the {{{filePathEditor}}} command. I should note that this tool only lists dependencies it can actually find. For example, if the user types in a path to a texture that's not on disk, this tool won't return that path, since the texture can't be found. ---- Also see: *[[How can I find all the references in the scene?]]  {{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*"; }}} And by references, I mean file references. !!!Commands: This Python command would seem to do it: {{{ import maya.cmds as mc topRefs = mc.file(query=True, reference=True) }}} But in fact it will only return the top-level references. Meaning, if you've referenced {{{C.ma}}} into your scene, but in fact it references {{{B.ma}}} and {{{A.ma}}}, only {{{C.ma}}} would be returned. You can use //this// solution instead to get all the references, //and// subreferences. However, it will return back everything 'referenced' in the scene, including textures, so we need to filter it by only maya files. Plus we sort it, and remove any dupes: {{{ allrefs = mc.file(query=True, list=True, withoutCopyNumber=True) fileRefs = sorted(list(set([item for item in allrefs if item.endswith(".ma") or item.endswith(".mb")]))) }}} You can optionally use this ~PyMel code. It returns a dict of {"namespace":"filepath"}: {{{ import pymel.core as pm refs = pm.getReferences(recursive=True) fileRefs = [str(refs[r]) for r in refs] }}} !!!API (Starting in Maya 2011) {{{ import maya.OpenMaya as om topRefs = [] om.MFileIO.getReferences(topRefs) }}} However, this suffers the same fate as the top {{{file}}} command example: Only returns top-level references. This emulates the second command example above, but via the API call, giving you all the subreferences as well: {{{ allrefs = [] om.MFileIO.getFiles(allrefs) fileRefs = sorted(list(set([item for item in allrefs if item.endswith(".ma") or item.endswith(".mb")]))) }}}  {{{listSets}}} Let's you query convenient things like object sets, or rendering sets (shadingEngines. or shaders).  {{{ import maya.cmds as mc topRefs = mc.file(query=True, reference=True) unloaded = [] for ref in topRefs: if mc.file(ref, query=True, deferReference=True): unloaded.append(ref) print unloaded }}} 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! !!! Via ~PyMel: Pretty darn easy. This creates a dictionary where each key is an index on the mesh, and each value is a list of verts that connect to it by edge. However, ~PyMel can be much slower than the API. {{{ import pymel.core as pm mesh = pm.PyNode("myAwesomeMesh") neighbors = {} for vert in mesh.vtx: connected = vert.connectedVertices() neighbors[vert.currentItemIndex()] = [v.currentItemIndex() for v in connected] print neighbors # {0: [1, 11, 12], 1: [0, 2, 13], etc... }}} In below examples, you pass in a mesh\vertex ID, it returns back the names of all the verts connected to that one based on common edges. The API version is a lot less code... half of it is just getting an ~MObject... !!! Via the Maya API: !!!! Newer Way: {{{ import maya.api.OpenMaya as om2 def getConnectedVerts(verts): """ Return a list of all connected verts based on the list of passed in verts. All verts are presumed to be on the same mesh. """ if not isinstance(verts, (list, tuple)): verts = [verts] connectedVerts = [] node = verts[0].split(".")[0] selList = om2.MSelectionList() verts = mc.ls(selection=True, flatten=True) for vert in verts: selList.add(vert) iterSelList = om2.MItSelectionList(selList) while not iterSelList.isDone(): nodeDagPath, componentsObject = iterSelList.getComponent() if not componentsObject.isNull(): iterGeo = om2.MItMeshVertex (nodeDagPath, componentsObject) while not iterGeo.isDone(): connectedVertIds = iterGeo.getConnectedVertices() for vid in connectedVertIds: vertName = '%s.vtx[%s]'%(node, vid) if vertName not in connectedVerts: connectedVerts.append(vertName) iterGeo.next() iterSelList.next() return connectedVerts }}} !!!! Older Way: {{{ import maya.OpenMaya as om def getConnectedVerts(mesh, vtx): """ Parameters: mesh : string : Name of the mesh to query. vtx : int :The specific vert index to query. return : list : List of full mesh.vtx[#] items that connect. """ # Get an MObject for the passed in mesh: selList = om.MSelectionList() selList.add(mesh) mObject = om.MObject() selList.getDependNode(0, mObject) ret = [] iterVert = om.MItMeshVertex(mObject) while not iterVert.isDone(): if iterVert.index() == vtx: intArray = om.MIntArray() iterVert.getConnectedVertices(intArray) ret = ['%s.vtx[%s]'%(mesh, intArray[i]) for i in range(intArray.length())] break iterVert.next() return ret }}} {{{ mesh = 'pPlane1' vtx = 61 print getConnectedVerts(mesh, vtx) # ['pPlane1.vtx[62]', 'pPlane1.vtx[50]', 'pPlane1.vtx[60]', 'pPlane1.vtx[72]'] }}} !!! Via Maya commands: The {{{polyInfo}}} command returns back its info in a really weird way, so we have to chop it up to make it usable. {{{ import re import maya.cmds as mc def getConnectedVerts(mesh, vtx): """ Parameters: mesh : string : Name of the mesh to query. vtx : int :The specific vert index to query. return : list : List of full mesh.vtx[#] items that connect. """ sourceV = '%s.vtx[%s]'%(mesh,vtx) eStr = mc.polyInfo(sourceV, vertexToEdge=True)[0].strip() edges = ['%s.e[%s]'%(mesh, item) for item in re.split('[: ]', eStr.strip().split(':')[-1])[1:] if item] vStrs = mc.polyInfo(edges, edgeToVertex=True) verts = [] for vStr in vStrs: ids = [] for item in re.split('[: ]', vStr.strip().split(':')[-1])[1:]: if not item: continue try: # I once saw a values passed in as 'Hard': only ints allowed: ids.append(int(item)) except: continue # Remove any dupes: ids = list(set(ids)) tempVs = ['%s.vtx[%s]'%(mesh, item) for item in ids] for item in tempVs: if item not in verts: verts.append(item) verts.remove(sourceV) return verts }}} {{{ mesh = 'pPlane1' vtx = 61 print getConnectedVerts(mesh, vtx) # ['pPlane1.vtx[72]', 'pPlane1.vtx[60]', 'pPlane1.vtx[50]', 'pPlane1.vtx[62]'] }}} Via ~PyMel, this is pretty trivial: {{{ import pymel.core as pm myNs = "spam" topFileRefs = pm.getReferences() refFile = topFileRefs.get(myNs) }}} {{{topFileRefs}}} is a dict, with the key being a namespace. {{{refFile}}} will either be a ~PyMel [[FileReference|http://download.autodesk.com/global/docs/maya2014/en_us/PyMel/generated/classes/pymel.core.system/pymel.core.system.FileReference.html]] class instance (the string version of which is the path to the file) or {{{None}}} if it doesn't come from a reference. {{{getPanel}}} {{{fileExtension}}} {{{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}}}
Quickly written snippet so an animator could easily know how fast a node was moving in Maya.  This is based on linear units set to inches (yuck) and framerate @ 30fps.  Adjust maths below as needed.
{{{
from math import sqrt
import pymel.core as pm

def printSpeed():
"""
Print the speed in MPH for the selected node on the given frame.  Expects the
units to be inches, and the framerate to be 30 fps.
"""
sel = pm.ls(selection=True)
if not len(sel) == 1:
return
node = sel[0]

thisFrame = node.translate.get()
prevFrame = node.translate.get(time=pm.currentTime()-1)
inchPerFrame = sqrt( sum( map( lambda x:pow(x[0]-x[1], 2), zip(thisFrame, prevFrame) ) ) )
inchPerHour = inchPerFrame * 30 * 60 * 60
mph = inchPerHour / 12 / 5280
print "%s is traveling at %.1f MPH"%(node, mph)

printSpeed()
}}}
{{{
nurbsSphere1 is traveling at 5.3 MPH
}}}
{{{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]; }}} ~PyMel has this shorthand: {{{ uv = meshShape.vtx[52].getUV() }}} However, I've had it return {{{[0,0]}}} values more than once: Seems broken. Best use the above method: {{{ import pymel.cpre as pm def getUv(vertex): uv = pm.polyListComponentConversion(vertex, toUV=True) # the uValue or vValue argument both return U & V values, weird: return pm.polyEditUV(uv[0], query=True, uValue=True) }}} Where {{{vertext}}} is in the form of a ~PyNode poly mesh, like: {{{myMesh.vtx[52]}}} {{{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.
!!!Python:
{{{
import os
msp = os.getenv("MAYA_SCRIPT_PATH")
}}}
!!!Mel:
{{{getenv}}}
*Example:
{{{
string $msp = getenv MAYA_SCRIPT_PATH; }}} The documentation: Example: *Essentials -> Basic Features -> Setting Environment Variables -> Standard Maya environment variables {{{currentCtx}}} {{{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}}} 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 def average(*args): # find the average of any number of passed in values return sum(args) / len(args) }}} This function is so simple however, it could be written as a lambda: {{{ lambda *args:sum(args)/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: {{{ import maya.cmds as mc avg = map(lambda *args:sum(args)/len(args), *[mc.pointPosition(p+".rotatePivot", world=True) for p in mc.ls(selection=True)]) }}} ---- But that one line can be hard to read. Below illustrates how to 'simplify' it into multiple lines: Simplification step #1: Move out list of selected objects to its own line and variable. {{{ import maya.cmds as mc transforms = mc.ls(selection=True) avg = map(lambda *args:sum(args)/len(args), *[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. {{{ import maya.cmds as mc transforms = mc.ls(selection=True) positions = [mc.pointPosition(p+".rotatePivot", world=True) for p in transforms] avg = map(lambda *args:sum(args)/len(args), *positions) }}} Simplification step #3: Get rid of the lambda, go back to the average() function: {{{ import maya.cmds as mc def average(*args): return sum(args) / len(args) 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 \ {{{lambda}}} statement 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/]] ~PyMel makes this pretty easy. You can mix and match whatever you want (transforms, joints, verts, etc), and this will consume it all, returning a ~PyMel {{{dt.Point}}}: {{{ import pymel.core as pm def getCenterPoint(elements): pos = [] for element in elements: if pm.attributeQuery('rotatePivot', node=element, exists=True): # joint / transform pp = pm.pointPosition("%s.rotatePivot"%element, world=True) else: # component pp = pm.pointPosition(element, world=True) pos.append(pp) return (sum(pos)) / len(pos) }}} 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. A solution using [[MMeshIntersector|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m_mesh_intersector.html]] works well. There is an example using it online [[here|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/index.html?url=cpp_ref/closest_point_cmd_8cpp-example.html,topicNumber=cpp_ref_closest_point_cmd_8cpp_example_html,hash=_a33]] Here's a modified snipped from [[Chad Vernon|https://groups.google.com/forum/#!topic/python_inside_maya/EznnAtM0Hgw]]: {{{ import maya.OpenMaya as om targetPoint = om.MPoint(1,2,3) selList = om.MSelectionList() mesh_mdp = om.MDagPath() selList.add(myMeshShapeStringName) selList.getDagPath(0, mesh_mdp) matrix = mesh_mdp .inclusiveMatrix() mesh_mo = mesh_mdp.node() resultPoint = om.MPoint() polyIntersect = om.MMeshIntersector() polyIntersect.create(mesh_mo , matrix) pointOnMesh = om.MPointOnMesh() polyIntersect.getClosestPoint(targetPoint, pointOnMesh); hitPoint = pointOnMesh.getPoint() }}} Note, as of Maya 2018 I've been unable to get the {{{polyIntersect.create}}} method to work in {{{maya.api.OpenMaya}}}, throws an error.  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 // }}} Say you have multiple joints selected in a given hierarchy, and you want to know what are the top most roots of your selection. For example, given this joint hierarchy *''root'' (this is the shared root) **''A'' (this is a common root) ***''A1'' ***''A2'' **''B'' (this is a common root) ***''B1'' ***''B2'' Say you select A, A1, A2 B, B1, B2 : Based on that selection, you want to know what the top common roots are (which would be A & B), //based on the current selection//. The below code will find that: {{{ import pymel.core as pm def getCommonRoots(joints): """ Based on the passed in list of joints, return back a list of their common roots, that live within the list. """ roots = [] for j in joints: allParents = [node for node in j.getAllParents() if pm.nodeType(node)=='joint'] allParents.reverse() # Did we find the parent in our current list of joints? listParent = None for p in allParents: if p in joints: listParent = p break if listParent: if not listParent in roots: roots.append(listParent) elif not j in roots: roots.append(j) return roots }}} {{{ joints = pm.ls(selection=True) print getCommonRoots(joints) # [nt.Joint(u'A'), nt.Joint(u'B')] }}} ---- Now how about based on the same selection of joints, you want to know the root joint they all share (which would be 'root'): {{{ def getSharedRoot(joints): """ Based on a passed in list of joints, return back the *one parent* joint they all share. Presuming there is a shared root. If two different joint hierarchies are passed in, will return None. """ comRoots = getCommonRoots(joints) if len(comRoots) == 1: return comRoots[0] parentList = [] for joint in comRoots: parents = [node for node in joint.getAllParents() if pm.nodeType(node)=='joint'] parentList.append(parents) # First, do a set intersction that returns only what all lists share. Then # sort them by their full paths, grabbing the last item for return. shared = sorted(list(set.intersection(*[set(item) for item in parentList])), key=lambda x:x.fullPath()) if not shared: return None else: return shared[-1] }}} {{{ joints = pm.ls(selection=True, long=True) print getSharedRoot(joints) # root }}} {{{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:

!!!~PyMel:
This works on joints, transforms, or components, or combinations thereof:
{{{
import pymel.core as pm
def distBetween(elementA, elementB):
posA = None
posB = None
if pm.attributeQuery('rotatePivot', node=elementA, exists=True):
posA = pm.pointPosition("%s.rotatePivot"%elementA, world=True)
else:
posA = pm.pointPosition(elementA, world=True)
if pm.attributeQuery('rotatePivot', node=elementB, exists=True):
posB = pm.pointPosition("%s.rotatePivot"%elementB, world=True)
else:
posB = pm.pointPosition(elementB, world=True)
return posA.distanceTo(posB)
}}}

!!!Python:
{{{
import maya.cmds as mc
from math import sqrt

def distBetween(nodeA, nodeB):
posA = mc.xform(nodeA, query=True, worldSpace=True, rotatePivot=True)
posB = mc.xform(nodeB, query=True, worldSpace=True, rotatePivot=True)
dist = sqrt( sum( map( lambda x:pow(x[0]-x[1], 2), zip(posA, posB) ) ) )
return dist
}}}
I think this is a fairly Pythonic way of doing it:  Grab the worldspace rotate pivot positions of the two nodes.  Wrapper a  {{{lambda}}} statement and {{{zip}}} inside a {{{map}}} function. The {{{lambda}}} statement creates an 'anonymous function' that will receive each index in our zipped list as passed to it from the {{{map}}}.   The {{{zip}}} groups their xyz position values into a list, giving you: {{{[ [posA[0],posB[0]], [posA[1],posB[1]], [posA[2],posB[2]] ]}}}.   The {{{lambda}}} then splits out the two values from each list index, subtracts B from A, and raises it to the power of two.  Finally, the three 'powered' values are summed together, and the square root is generated giving us the distance!

This is exactly what the mel example is doing below, it's just doing it on a single line using using Python's built-ins.
!!!Maya Python API:
Via the Python API, you can use the {{{MPoint}}} class, which does all this for you.
{{{
from maya.OpenMaya import MPoint

p1 = MPoint(0,0,0)
p2 = MPoint(3,0,0)
distance = p1.distanceTo(p2)
}}}
{{{
print distance
3.0
}}}
!!!Mel
{{{
float $pointA[] = {0,0,0}; float$pointB[] = {3,0,0};
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) }}} The {{{maxValue}}} attribute on the shape node of your NURBS curve will tell you this. {{{ import maya.cmds as mc uVal = mc.getAttr('myCurveShape.maxValue') }}} You could then (for the sake of example) select that point on the curve via: {{{ mc.select('myCurve.u[%s]'%uVal) }}} It should be noted that this length is in 'internal units' (cm), not necessarily what your 'ui units' are. I seem to do this just not quite often enough to remember how to do it, so I'll make a note here. Func returns a tuple containing the min and max frame range based on the list of nodes passed in. {{{ # Python code import maya.cmds as mc def getMinMaxAnimRange(nodes): first = 0 last = 0 curves = mc.listConnections(nodes, source=True, destination=False, type='animCurve') if curves is not None: first = mc.findKeyframe(curves, which='first') last = mc.findKeyframe(curves, which='last') return (first, last) }}} @@Note A@@ : I've seen bugs where the {{{findKeyframe}}} command fails on the passed in {{{animCurves}}}, and instead needs a passed in list of nodes. Car-azy. @@Note B@@ : I've seen other bugs where //referenced// curves can cause it to always return 0,0 : You'll need to filter out any referenced curves first. ---- Also see: *[[How can I find the min and max keyframe values for all objects in the scene?]]  Will return back a float array with two items, the [start, end] frames: !!!Mel: Will select the curves in the process: {{{ global proc float[] returnFrameRange(){ select -r ls -type "animCurve"; return {float(findKeyframe -which first), float(findKeyframe -which last)}; } }}} !!!Python Not based on selection. {{{ import maya.cmds as mc def sceneFrameRange(): animCurves = [curve for curve in mc.ls(type='animCurve', recursive=True) if not mc.referenceQuery(curve, isNodeReferenced=True)] minF = mc.findKeyframe(animCurves, which="first") maxF = mc.findKeyframe(animCurves, which="last") return minF, maxF }}} @@Note@@ : I've seen bugs where //referenced// curves can cause it to always return 0,0 : You'll need to filter out any referenced curves first, per the above code. ---- Also see: *[[How can I set the range slider to match the keyframe range on the selected object(s)?]] *[[How can I find the min and max keyframe range for the provided objects?]] !!!Method A, via Python: {{{ import maya.cmds as mc menus = mc.window('MayaWindow', query=True, menuArray=True) # [u'mainFileMenu', u'mainEditMenu', u'mainModifyMenu', u'mainCreateMenu', ... }}} !!!Method B, via mel: 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?]]
When referencing multiple files into the same scene while passing each the same namespace, Maya will automatically increment the namespace name.  But via code, this can be tricky to find out.  ~PyMel makes this pretty easy via its {{{createReference}}} command:  It returns back a {{{FileReference}}} object on which you can query a {{{.namespace}}} attr, that holds the namespace name that was used (and possibly incremented):
{{{
import pymel.core as pm

assetPath = "c:/temp/myAwesomeAsset.ma"
ns = "myAwesomeNamespace"
myRef = pm.createReference(assetPath,namespace=ns)
newNS = myRef.namespace
}}}
Or via regular Python:  You can use this to query after the reference has been made:
{{{
import maya.cmds as mc
assetPath = "c:/temp/myAwesomeAsset.ma"
namespace = mc.file(assetPath , query=True, namespace=True)
}}}
~PyMel Docs :
----
Thanks to Mason Sheffield for the tip.
!!! Easiest
lol, I can't believe it took me this long to learn this.  Of course, only works if the node in question is referenced.
{{{
print mc.referenceQuery(nodeName, namespace=True)
# ":namespace2:namespace1"
}}}
!!!~PyMel:
{{{
import pymel.core as pm
myNode = pm.PyNode("namespace2:namespace1:objectName")
print myNode.namespace()
print myNode.parentNamespace()
# namespace2:namespace1:
# namespace2:namespace1
}}}
!!!Python:
Single-line solution //not// using regular expressions:
{{{
name = "namespace2:namespace1:objectName"
ns  = name[:-len(name.split(':')[-1])]
# namespace2:namespace1:
}}}
----
Single-line regular-expression solution:
{{{
import re
name = "namespace2:namespace1:objectName"
ns = re.findall('.*:', name)[0] if len(re.findall('.*:', name)) else ""
# namespace2:namespace1:
}}}
----
Older multi-liner regular expression solution:
{{{
import re
name = "namespace2:namespace1:objectName"
ns = ""
try:
ns = re.match('.*:', name).group(0)
except AttributeError:
pass
# namespace2:namespace1:
}}}
!!!Via the API
{{{
# Python code:
import maya.OpenMaya as om

node = 'foo:objectName'
MSelectionList = om.MSelectionList()
MDagPath = om.MDagPath()
MSelectionList.getDagPath(0, MDagPath)

MFnDependencyNode = om.MFnDependencyNode(MDagPath.node())
namespace = MFnDependencyNode.parentNamespace()
# foo
}}}
!!!Mel:
Single line using regular expressions:
{{{
string $name = "assG:bzspsG:asdf:bob"; string$namespace = match ".*:"  $name; // Result: assG:bzsps:asdf: // }}} What the match is saying is: 'as long as I can find some sequence of characters 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;
}}}
{{{pointOnCurve}}}
{{{
string $sel[] = ls -sl; referenceQuery -f$sel[0];
}}}

Via Maya's Python API 2.0, it's pretty easy via [[MImange|http://help.autodesk.com/cloudhelp/2015/ENU/Maya-SDK/py_ref/class_open_maya_1_1_m_image.html]]:
{{{
import maya.api.OpenMaya as om2

def getResolution(imagePath):
image = om2.MImage()
x,y = image.getSize()
return [int(x), int(y)]
}}}
----
It should be noted that if you add an image to an {{{imagePlane}}}, you can query the resolution via:
{{{
imagePlane -query -imageSize "myAwesomeImagePlane";
}}}
----
If you're using older API 1.0, it's a bit clunkier:
{{{
# Python code:
import maya.OpenMaya as om

def getResolution(imagePath):

image = om.MImage()

# MScriptUtil magic found here:
# But there was a bug in their code:  You need to make a unique MScriptUtil object
# for each pointer:  They were sharing one, which caused the result to be the same
# x,y resolutions, even on rectangular images

# To get the width and height of the image an MScriptUtil is needed
# to pass in a pointer to the MImage::getSize() method
scriptUtilW = om.MScriptUtil()
scriptUtilH = om.MScriptUtil()
widthPtr = scriptUtilW.asUintPtr()
heightPtr = scriptUtilH.asUintPtr()

image.getSize( widthPtr, heightPtr )

width = scriptUtilW.getUint(widthPtr)
height = scriptUtilH.getUint(heightPtr)

return (width, height)
}}}
{{{
imagePath = r'C:\temp\myImage.BMP'
print getResolution(imagePath)
# (64x64)
}}}

----
{{{getAttr}}} & the {{{file}}} node:

Presuming a texture has been assigned to a {{{file}}} texture node, you can use the {{{.outSizeX}}} and {{{.outSizeY}}} attrs on the {{{file}}} node to get the pixel dimensions.
{{{
string $xSize = getAttr "file1.outSizeX"; }}} ---- Maya also ships with the {{{fileStats}}} app in its bin folder, that will return this. Given some arbitrary leaf joint in a hierarchy, how can you find the root joint? This code limits the selection to the given namespace, which in general is what you want. {{{ import pymel.core as pm leafJoint = pm.PyNode("someNamespace:someLeafJoint") rootJoint = [node for node in joint.getAllParents() if pm.nodeType(node)=='joint' and node.namespace() == leafJoint .namespace()][-1] print rootJoint # someNamespace:someRootJoint }}} The below examples expects {{{$object}}} to be some transform-level name:

Method A:
{{{
// Query the object directly (transform level, presuming it HAS a bound shape)
string $cluster = findRelatedSkinCluster$object;
}}}
Method B:
{{{
// query the shape of $object (useful if the object has multiple shape nodes): string$shape[] = listRelatives -s -ni $object; string$cluster = findRelatedSkinCluster $shape[0]; }}} Method C: {{{ // different approach: Use node connections to find it: string$shape[] = listRelatives -s -ni $object; string$cluster[] = listConnections -s 1 -d 0 -type "skinCluster" -scn 1 $shape[0]; }}} ---- {{{findRelatedSkinCluster}}} is a mel script located here: {{{C:\Program Files\Autodesk\Maya<VERSION>\scripts\others\findRelatedSkinCluster.mel}}}  Given a polygonal object that has different UV shells (it's been mapped different ways), how can you get what uv's are in those contiguous shells? Maybe there's a fancier way to do this, via some built-in command, but I couldn't find it. This tool will walk though each UV, expand the selection to the shell it lives in, and then compare the names of what is selected to a list of current shells (and a 'shell' is a sublist of uv's). If the current list finds no matching sublist, it adds itself in. {{{ import maya.cmds as mc import maya.mel as mm def uvShellsFromSelection(): sel = mc.ls(selection=True) shells = [] uvs = mc.ls(mc.polyListComponentConversion(sel, toUV=True), flatten=True) for uv in uvs: mc.select(uv) mm.eval('polySelectBorderShell 0') shell = mc.ls(selection=True) if shell not in shells: shells.append(shell) return shells }}} Presuming the surface is enclosed, you can use this script that comes with Maya: It acts on the current selection, returns a float, and, the mesh must be frozen if it has any transformations. {{{ C:/Program Files/Autodesk/Maya<version>/scripts/others/computePolysetVolume.mel }}} Note: sometimes this likes to return negative values, so use the {{{abs}}} command to correct for it. This could be a vertex, particle, CV, lattice point, etc. If querying a face (or other poly element), it will return the positions of each vertex associated with the face. {{{pointPosition}}} Here's some //Python// code that will return a list with the worldspace position for every component selected, based on using a //list comprehension//: {{{ import maya.cmds as mc pts = [mc.pointPosition(point, world=True) for point in mc.ls(selection=True, flatten=True)] }}} Given a default polygonal cube, if you selected its verts and executed the above you'd get: {{{ print pts [[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, -0.5, -0.5], [0.5, -0.5, -0.5]] }}} I ran into an instance where I had an asset with //many// shaders assigned, and I had to import in a new one and assign it. But when I imported in the new one (not knowing the name), I couldn't figure out which one it was. I wrote this code to find all //shadingEngines// (shaders) in the scene that weren't assigned to anything. //Actually// its the other way around: shadingEngines are sets, so you assign things //to them//. As well, //materials// (phong, lambert, etc) plug into shadingEngines, nothing is actually 'assigned to a material', or vice-versa: {{{ # Python code import maya.cmds as mc def findUnusedShaders(): shadingEngines = mc.ls(type='shadingEngine') unused = [] for se in shadingEngines: items = mc.sets(se, query=True) if items is None: unused.append(se) return unused }}} {{{ unused = findUnusedShaders() }}}  The mel {{{linstep}}} command. This command isn't available in Python. Make your own: {{{ def percentBetween(val, origMin, origMax): return float(val - origMin) / float(origMax - origMin) }}} {{{ print percentBetween(5, 0, 10) 0.5 }}} ---- For the opposite, see: *[[How can I find a value some percentage between two other values?]] *[[How can I find a 3d point some percentage between two other 3d points?]]  The [[unknownNode|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/unknownNode.html]] command: For example {{{ plugin = mc.unknownNode("someUnknownNodeName", query=True, plugin=True) print plugin # SomePluginName }}} Also lets you query the {{{realClassName}}} and {{{realClassTag}}}. In Maya 2016 they (in my opinion) broke the undo: They decoupled it from time. I'm told this was an intentional change based on user feedback: Some people don't like the fact when they hit undo they time changes (if a time change was performed). I'm guessing those people aren't animators. Here's an example of why it's bad: *Maya 2015 (good behavior) : **Go to frame 10, move node, set key manually. Make sure autokey is on. **Go to frame 100, move node, key is set. **Go to frame 1000, move node, key is set. ** Hit undo two times: The scene state reverts back to frame 100, time goes to frame 100. If you then move the node, it keyframes it at frame 100. * Maya 2016 (bad behavior) : **Go to frame 10, move node, set key manually. Make sure autokey is on. **Go to frame 100, move node, key is set. **Go to frame 1000, move node, key is set. ** Hit undo two times: The scene state reverts back to frame 100, but @@time stays at frame 1000@@. If you then move the node, it auto-keyframes it at frame 1000, even though the scene state is at frame 100. **This means every time an animator hits undo, they have to remember which frame they were at last, and scrub maya to that frame (or use ctrl+mmb+drag to set it on the timeline) before they set any keys. But who can keep track of every previous frame they were on? Otherwise, the end effect is that it looks like keys are set at random times all over their scenes. I've talked to quite a few people\lists about this: No one can convince me otherwise that this isn't a bug. Having Maya's scene state be on one frame, but the timeline be at another is //broken// (unless manually set that way by the user, via a ctrl+mm+drag on the timeline, for some special purpose they're //consciously aware of//). In Maya 2016 ~SP3 they fixed this, but it's a secret option. To put undo back the way it used to be in 2015: {{{ optionVar -iv timeChangeUndoable 1; }}} I understand this could be helpful in some circumstances. The issue is the developers just 'flipped this switch' for the worldwide 2016 user base, rather than adding it as an opt-in option in the prefs :(  !Quick notes: * Get some somewhat decent UV's put on it via whatever method you want. * UV Toolkit -> Unfold [options] !Docs: * [[Unfold UVs options|http://help.autodesk.com/view/MAYAUL/2019/ENU/index.html?guid=GUID-039FFDA5-3AC5-4CCF-A53E-546CCA6439FC]] * [[Unfolding a UV mesh|http://help.autodesk.com/view/MAYAUL/2019/ENU/index.html?guid=GUID-874CBD91-317F-4C7D-B293-EBBC93C2311A]] !Commands * [[unfold|http://help.autodesk.com/view/MAYAUL/2019/ENU/index.html?guid=__Commands_unfold_html]] No online docs? {{{ Synopsis: u3dOptimize [flags] [String...] Flags: -bi -borderintersection on|off -ite -iterations Int -ms -mapsize Int -pow -power Int -rs -roomspace Int -sa -surfangle Float -tf -triangleflip on|off Command Type: Command }}} Normal maps are finicky things: Often it seems that the green channel needs to be flipped to be viewed properly in Maya relative to how it should be in your game engine. I've written tools to do this, but a modeler just clued me in: Maya has this built in, it's just a bit buried: Select the shape node of your mesh, and in the Attribute Editor under the 'Tangent Space' frame, you can swap the 'Coordinate System' from 'Right Handed' to 'Left Handed', which effectively reverses the green channel of the normal map. Or, here is some ~PyMel to set the {{{tangentSpace}}} enum attr: {{{ # Make all "Tangent Space" Coordinate Systems "Right Handed" import pymel.core as pm allMesh = pm.ls(type='mesh') for mesh in allMesh: mesh.tangentSpace.set(2) }}} {{{ # Make all "Tangent Space" Coordinate Systems "Left Handed" import pymel.core as pm allMesh = pm.ls(type='mesh') for mesh in allMesh: mesh.tangentSpace.set(0) }}}  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?]]
{{{
import maya.cmds as mc
mc.file(renameToSave=True)
}}}
From [[this tip|https://groups.google.com/d/msg/maya_he3d/kiOOkpjbftk/u6oAVxWuAAAJ]] by Morgan Loomis.
Note, I have been unable to find something comparable in ~PyMel, so I end up doing this:
{{{
pm.mel.eval("file -renameToSave 1")
}}}
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 }}} Maya's File -> Open dialog never seems to open where I want it to. Ever. And normally I avoid hacking Maya's internal scripts, since you have to do it every new version that comes out. But currently this is worth it. It will change the behavior to open Maya's file dialog to the current directory of the current scene you have open. You need to edit this file: {{{ C:\Program Files\Autodesk\Maya<20XX>\scripts\others\projectViewer.mel }}} ! Maya 2018 In the global proc {{{pv_ActionSetup}}}, starting around line 153, you need to add this chunk of code: {{{ string$activeDir = "";
string $scene = file -q -sn; if(size($scene) > 0)
$activeDir = dirname($scene);
else
$activeDir =$workspace;
}}}
Then, below in that procedure in all the {{{switch/case}}} statements, for any line that has {{{$actionOptions[9]}}} in it, if you want this behavior, you need to comment out that line, and replace it with below. For example, the first is {{{case "Open":}}} : You'd make this edit: {{{ //$actionOptions[9] = setWorkingDirectory $workspace$readType "scene";
$actionOptions[9] =$activeDir;
}}}
And you'd continue to do this fore each other case you want the above behavior in.
! Up to Maya 2016
Around line 482 in:  (I was modifying Maya 2014-2016)
Comment out this line like so:
{{{
//$cmd += (" -startingDirectory \"" +$actionOptions[9] + "\"");
}}}
And replace the block around it with this mel:
{{{
if ("" != $actionOptions[9]){ //$cmd += (" -startingDirectory \"" + $actionOptions[9] + "\""); string$scene = file -q -sn;
if(size($scene) > 0){ string$dir = dirname($scene);$cmd += (" -startingDirectory \"" + $dir + "\""); } else$cmd += (" -startingDirectory \"" + $actionOptions[9] + "\""); } }}} Starting in Windows 10, whenever I'd try to access Maya's help, it would open it in Edge/Internet Explorer instead of Chrome, even though I'd set Chrome as my default browser. My buddy Conant Fong figure out you can edit the Window's registry to make this happen: {{{ # Change to use default browser to Chrome import _winreg _winreg.SetValue(_winreg.HKEY_CURRENT_USER, "Software\\Classes\\.htm", _winreg.REG_SZ, 'ChromeHTML') }}} 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. ''Notes:'' If you're using Maya's module system ([[Understanding Maya Modules and Pacakges]]), each module can have it's own userSetup.py in it. For these to execute properly on Maya startup, a common method inside userSetup.py is this: {{{ import maya.cmds as mc def main(): # put all your code in here mc.scriptJob(event=['DagObjectCreated', main], runOnce=True) }}} Maya's module system will auto-execute every userSetup.py it can find in every module it finds, and the scriptJob will wait for a dag object to be made before it runs it. This helps to get Maya fully initialized. It should also be noted that userSetup.py files added via modules may not be accessible on the path once Maya starts: Since Python can't have conflicting module names, my //guess// is the last module loaded is the one Maya sees. Even though I have my own unique userSetup.py, this is what I get when I print it: {{{ import userSetup print userSetup.__file__ C:\Program Files\Autodesk\Maya2018\plug-ins\MASH\scripts\userSetup.pyc }}} It prints the MASH userSetup.py, and my guess is it was simply the last one to load. !!!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. !!!Method #3 : Author a '{{{sitecustomize}}} and\or {{{usercustomize}}} modules': *See notes on my Python wiki [[here|http://pythonwiki.tiddlyspot.com/#%5B%5BUsing%20the%20sitecustomize%20%26%20usercustomize%20modules%5D%5D]]. *These are Python's equivalent to Maya's {{{userSetup.py}}} module. ---- Also see: *[[How can I update my Python Path?]] 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. With the invention of ~PyMel, this is now built in: The {{{pymel.core.language.melGlobals}}} is a dict of exactly that data. {{{ import pymel.core as pm for key in pm.language.melGlobals.keys(): val = pm.language.melGlobals[key] print key, val }}} ---- Old docs, for doing it in Python: mel has the command {{{env()}}} that returns back the name of every global variable in memory at the time. Via Python, this can be nicely wrappered in to a dictionary containing both the global var name, and its values. This really only falls down in regards to matrix (which are converted into lists of lists of floats) and vector data types (which are converted into 3-float tuples), since Python doesn't have those types (by default). But it still handles the conversion ok :) {{{ # Python code import maya.mel as mm env = {} allMelVars = mm.eval('env()') for v in allMelVars: # Convert mel into Python by assigning it to a dummy var the same # name as the original var, but with an underscore added to the name. # This lets us get around mel global scope naming conflicts if the tool # is ran twice: pv = mm.eval('%s_ = %s'%(v,v)) # strip off mel$ prefix:
env[str(v[1:])] = pv
}}}
Now you can use all of Pythons dictionary tools on the {{{env}}} dict.
If you want to print them all nicely:
{{{
# Cool Python printing module:
from pprint import pprint
pprint(env)
}}}
I like to make a lot of subsets while working, to help organize things.  While sets are {{{DG}}} nodes, and have no real hierarchy, by adding sets to one another, it sort of mirrors the child\parent {{{DAG}}} transform hierarchy, and the Outliner displays them this way.  However, since they aren't in a hierarchy, you can't use commands like {{{listRelatives}}} to get a list of all of their 'children'.  They have no 'children', since they aren't in a hierarchy.  But you still may want to get a list of their 'pseudo-chilld sets' anyway.

This code will recursively search a parental sets for all {{{objectSet}}}s connected too it, and all sets connected to them, etc.
{{{
# Python code
import maya.cmds as mc

sets = ["DZS_TORSO"]
for s in sets:
subSets = mc.listConnections(s, source=True, destination=False, type='objectSet')
if subSets is not None:
for ss in subSets:
sets.append(ss)
print sets
}}}
''Update'': This is a perfect example of overengineering ;)  I did all the below work before I realized there is a {{{listHistory}}} node, which does all this for you.  Check it out...
----
Say you have a list of Maya materials, and you want to find all the place2dTexture nodes connected to them.
{{{
# Python code
import maya.cmds as mc
def allIncomingConnections(nodes, nodeType=None):
"""
Based on the nodes, find all incoming connections, and return them.
This will recursively search through all incoming connections of all inputs.

nodes : string or list : of nodes to find all incoming connections on.

nodeType : None, string, list : Optional, will filter the result by these
types of nodes.

return : list : Nodes found
"""
ret = []

# Convert our args to lists if they were passed in as strings:
if type(nodes) == type(""):
nodes = [nodes]
if nodeType is not None:
if type(nodeType) == type(""):
nodeType = [nodeType]

for n in nodes:
incoming = mc.listConnections(n, source=True, destination=False)
if incoming is not None:
for i in incoming:
if nodeType is not None:
if mc.objectType(i) in nodeType:
ret.append(i)
else:
ret.append(i)
nodes.append(i)

# remove dupes:
ret = list(set(ret))
return ret
}}}
Example:
{{{
incoming = allIncomingConnections("myMaterial", "place2dTexture")
print incoming
# [u'place2dTexture643', u'place2dTexture642', u'place2dTexture641']
}}}
{{{
ls -as;
}}}
The {{{-as}}} stands for 'assemblies'.
I always forget how to do this for some reason... so here it is.  In STONE.
{{{
string $curve = "myCurve"; string$shape[] = listRelatives -shapes $curve; int$numCVs = getAttr -size ($shape[0] + ".controlPoints"); string$cvs[] = ls -fl ($shape[0] + ".cv[0:" +$numCVs + "]");
// myCurveShape.cv[0] myCurveShape.cv[1] myCurveShape.cv[2] etc...
}}}
If you take out the {{{-fl}}} flag from {{{ls}}}, it will collapse them into a single element that still works:
{{{
myCurveShape.cv[0:16] // presuming it had 17 cv's
}}}
I pulled this by digging into the script:
{{{
C:/Program Files/Autodesk/Maya<VER>/scripts/others/selectCurveCV.mel
}}}
Many attributes in in Maya are considered "multi":  They are considered "array" attributes in the API.  They can have many children attributes, and are often dynamically generated.  How to get a list of them?
{{{
import maya.cmds as mc
node = "pPlaneShape1"
attr = "uvSet"

multi = mc.listAttr('%s.%s'%(node,attr), multi=True)
for m in multi:
val = '<compound>'
try:
val = mc.getAttr('%s.%s'%(node,m))
except RuntimeError:
pass
print '%s%s'%(str(m).ljust(40,' '), str(val).rjust(20, ' '))
}}}
Will print something like this (presuming the plane only has four verts):
{{{
uvSet[0]                                          <compound>
uvSet[0].uvSetName                                      map1
uvSet[0].uvSetPoints[0]                         [(0.0, 0.0)]
uvSet[0].uvSetPoints[0].uvSetPointsU                     0.0
uvSet[0].uvSetPoints[0].uvSetPointsV                     0.0
uvSet[0].uvSetPoints[1]                         [(1.0, 0.0)]
uvSet[0].uvSetPoints[1].uvSetPointsU                     1.0
uvSet[0].uvSetPoints[1].uvSetPointsV                     0.0
uvSet[0].uvSetPoints[2]                         [(0.0, 1.0)]
uvSet[0].uvSetPoints[2].uvSetPointsU                     0.0
uvSet[0].uvSetPoints[2].uvSetPointsV                     1.0
uvSet[0].uvSetPoints[3]                         [(1.0, 1.0)]
uvSet[0].uvSetPoints[3].uvSetPointsU                     1.0
uvSet[0].uvSetPoints[3].uvSetPointsV                     1.0
uvSet[0].uvSetTweakLocation             [(0.086363710463047028, 0.047107480466365814)]
}}}
----
Also see:
*[[How can I add a multi attr to my node, query its connections?]]
*[[API: Find all child attributes under a compound attribute]]


{{{env}}}
This will return back textures, references, and presumably audio files:
{{{
> mayaBatch -archive c:\\directory\\with\\file.mb > logFile.txt
}}}
Note:  You ''must use double-backslash'' {{{\\}}} (on Windows at least) when providing the path:  Single backslash {{{\}}} or forwardslash {{{/}}} in the path will cause it to fail.

The resultant txt file collects //all// the startup text that Maya prints.  Finally, towards the bottom it has a line with {{{Result:}}} in it:  That's the line Maya prints when the file finally opens.  After that, there will be lines with paths to things (referenced files, textures) some ending with an asterisk {{{*}}} : These are the recorded dependencies.  If parsing that file, that's where you want to start looking.

Example snippet:
{{{
...
Result: c:/directory/with/file.mb
c:/directory/with/file.mb
c:/directory/with/otherReferenceFile.mb
c:/directory/with/someTexture.tga*
...
}}}
Not sure why it lists it's own filename twice (and without an c:/directory/with/file.mb), nor why some paths have an asterisk after them while others don't,  but such are things.
----
Here is a Python script that will do the same thing.  //Doesn't// need to be executed from within Maya, since it calls to it in batch mode.  Also, it seems far more flexible when accepting the path names, slash-wise, than the above code.
{{{
import subprocess
def getFileDependencies(filePath):
"""
Parameters:
filePath : string : Full path to .ma or .mb file to query.
Return : list : Full path to all dependencies in file.
"""
result = subprocess.Popen(["mayaBatch", "-archive", thefile], stdout=subprocess.PIPE).communicate()
foo = [f.strip() for f in result[0].split("\r\n") if f != ""]

dependencies = []
startSeach = False
for f in foo:
if f.startswith("Result: "):
startSeach = True
if not startSeach:
continue
if f.startswith('plugin: '):
continue
if startSeach and f.startswith("----"):
startSeach = False

if f.endswith("*"):
dependencies.append(f[:-1])
elif f.endswith(".ma") or f.endswith(".mb"):
if f != filePath and f not in dependencies:
dependencies.append(f)

return dependencies
}}}
{{{
runTimeCommand -q -ca;
}}}
When I originally created this post in Jan of 09', Maya had no built-in way for tracking component level selection order:  You'd pick a bunch of verts (for example), and the {{{ls}}} command would return them back in... probably some other order.

In later versions of Maya, {{{-ls}}} added a {{{-orderedSelection}}} flag that you could use //instead// of the {{{-selection}}} flag.  But this only worked if you first executed this command:
{{{
selectPref  -trackSelectionOrder 1;
}}}
Doing that, {{{ls}}} would now return component-level selections in the correct order.

Sometime in Maya 2015, maybe during a service-pack update, this functionality seemed to break though.  Long story short:  If you execute the above {{{slectPref}}} command twice in a row, {{{ls -orderedSelection}}} would return an empty list.  Turning it off, then back on would get {{{ls}}} to return items again, but now in the //wrong// order.
SO, if you have code that depends on this, you now need to add in something like this:
{{{
if (selectPref -q -trackSelectionOrder == 0){
selectPref  -trackSelectionOrder 1;
}
}}}
Basically, if it's off, turn it on, but if it's on, just leave it alone.  As of this posting (Maya 2016) I've bugged this with Autodesk.

If you want to prove this yourself, here's an example:
{{{
// make a cube, select some verts in a specific order:
polyCube -w 1 -h 1 -d 1 -sx 3 -sy 3 -sz 3 -ax 0 1 0 -cuv 4 -ch 1;
select -r pCube1.vtx[49] ;
select -tgl pCube1.vtx[1] ;
select -tgl pCube1.vtx[10] ;

// What state is our selection order tracking in?  Mine is always on by default:
int $trackState = selectPref -q -trackSelectionOrder; // 1 // Print the selection: ls -orderedSelection; // Result: pCube1.vtx[49] pCube1.vtx[1] pCube1.vtx[10] // THE CORRECT ORDER! // Turn selection tracking on. I'd exepct this to not change anything but... selectPref -trackSelectionOrder 1; ls -orderedSelection; // PRINTS NOTHING. What?!?! // Turn selection tracking OFF selectPref -trackSelectionOrder 0; ls -orderedSelection; // Result: pCube1.vtx[1] pCube1.vtx[10] pCube1.vtx[49] // THE WRONG ORDER (as expected...) // Turn selection tracking back ON selectPref -trackSelectionOrder 1; ls -orderedSelection; // Result: pCube1.vtx[1] pCube1.vtx[10] pCube1.vtx[49] // THE WRONG ORDER (not expected...) }}} Starting in Maya 2012, you can use [[melInfo|http://download.autodesk.com/global/docs/maya2012/en_us/CommandsPython/melInfo.html]] {{{ # Python code import maya.cmds as mc procs = mc.melInfo() for p in sorted(procs): print p }}} This will return back commands, runTimeCommands, and global procedures. As a test, I compared Solution A & B over a 1000 loops. Here's the results: *Solution A: .897 seconds. *Solution B: 40.704 seconds. API & algorithm FTW, 45.4x faster & ~PyMel & selection. !Solution A: Maya API & Python Algorithm: I came up with 'Solution B' first, but it bugged me I had to do it via selection. 'Solution A' is much faster, being based on the API and no selection is needed. I created the {{{combineSimilarElements}}} algorithm to merge the groups of faces together into their shells. Note that {{{getPolyShells}}} returns lists of ints: It's up to you to turn those back into valid faces, based on whatever method suits your needs (Mel\Python\~PyMel). {{{ import maya.OpenMaya as om def combineSimilarElements(data): """ Based on the passed in data (list of lists), combine them into the fewest possible number of items that have the same shared elements. For example, if these two lists were passed in: [[1,2,3], [3,4,5]] the tool would return the combined [1,2,3,4,5] : Since both lists share the int 3, they're combined. Parameters: data : list : List of sublists. The sublists will be compared to one another. Return : list : The combination of data. """ # Need to copy, otherwise we'll modify in-place: retData = data[:] intermediateData = [] curSize = len(retData) prevSize = -1 while curSize != prevSize: while len(retData): currentCheck = retData.pop(0) for data in retData: # Check for intersection/shared items: setData = set(data) setCurrentCheck = set(currentCheck) if setCurrentCheck & setData: currentCheck = list(setCurrentCheck.union(setData)) retData.remove(data) intermediateData.append(currentCheck[:]) prevSize = curSize retData = intermediateData intermediateData = [] curSize = len(retData) return retData def getPolyShells(mesh): """ For the provided mesh string name, return a list of sublists: Each sublist contains the int indicies contained in that shell. """ selList = om.MSelectionList() selList.add(mesh) mObject = om.MObject() selList.getDependNode(0, mObject) connected = [] iterMesh = om.MItMeshPolygon(mObject) while not iterMesh.isDone(): thisIndex = iterMesh.index() connectedIndices = om.MIntArray() iterMesh.getConnectedFaces(connectedIndices) connected.append([thisIndex]+connectedIndices) iterMesh.next() return combineSimilarElements(connected) }}} !Solution B: ~PyMel & Selection Normally I loath picking things in code, but the [[polySelect|http://download.autodesk.com/global/docs/maya2014/en_us/CommandsPython/polySelect.html]] command was too good to be {{{True}}}, and I couldn't pass up using it. Based on the passed in mesh, return a list of sublists. Each sublist is a collection of ~PyMel [[MeshFace|http://download.autodesk.com/global/docs/maya2014/en_us/PyMel/generated/classes/pymel.core.general/pymel.core.general.MeshFace.html]] indices that live in the given shell. {{{ import pymel.core as pm def getMeshShells(mesh): """ mesh : PyMel Mesh, or string name. return : list of lists of MeshFaces """ mesh = pm.PyNode(mesh) sel = pm.ls(selection=True) facePool = set([f for f in mesh.f]) shells = [] for i,f in enumerate(mesh.f): if f not in facePool: continue shell = pm.polySelect(mesh, extendToShell=i, replace=True) shellFaces = pm.ls(selection=True, flatten=True) facePool = facePool.difference(set(shellFaces)) shells.append(shellFaces) if len(sel): pm.select(sel) else: pm.select(clear=True) return shells }}} 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. I recently needed to find the sets shown in the Outliner. Problem is, commands like this: {{{ ls -type "objectSet"; ls -sets; listSets -type 2; }}} Return ALL {{{objectSet}}}s in the scene. By querying the contents of the {{{itemFilter}}} passed to the {{{setFilter}}} arg on the default {{{outlinerEditor}}} control, we're able to query its contents, which is //just the sets showing up in the Outliner//: {{{ # Python code import maya.cmds as mc # New empty scene, make some sets: mc.file(new=True, force=True) mc.sets(name='spam') mc.sets(name='eggs') }}} Now query the sets in the Outliner: {{{ myOutliner = "outlinerPanel1" # Name of the default Outliner setFilter = mc.outlinerEditor(myOutliner, query=True, setFilter=True) outlinerSets = mc.lsThroughFilter(setFilter, nodeArray=True) print outlinerSets }}} {{{ [u'spam', u'eggs'] }}} ---- This will list ALL the sets in the outliner, including any subsets. What if you want to get only the root most sets (since you can have subsets?). You can pass the above results into this function to get only sets that aren't members of any others: {{{ def getRootSets(setData): ret = [] for s in setData: outgoing = mc.listConnections('%s.message'%s, source=False, destination=True, type='objectSet') if not outgoing: ret.append(s) return ret }}} {{{ renderer -query -namesOfAvailableRenderers; }}} ...or for multiple objects: {{{ # Python code import maya.cmds as mc meshes = ['myMesh'] }}} This method requires the selection of objects, which isn't always desirable: {{{ mc.select(mc.polyListComponentConversion(meshes, toVertex=True)) verts = mc.ls(selection=True, flatten=True) }}} Non-selection method, but a bit more code: {{{ verts = [] for mesh in meshes: numVerts = mc.polyEvaluate(mesh, vertex=True) vtxList = ['%s.vtx[%s]'%(mesh, val) for val in range(numVerts)] verts += vtxList }}} {{{ print verts # [u'myMesh.vtx[0]', u'myMesh.vtx[1]', u'myMesh.vtx[2]', ... ] }}} {{{ lsUI -windows; }}} I had an issue where a tool would return a list of joints, but I wanted to list them in their hierarchical order. Below is the solution I came up with: By querying the long name of each joint, and then simply sorting that list, it will return them in a nice hierarchical order. I should note that this may not be the //exact// sibling order that Maya has them in since they've been alphabetized at the sibling level: For example, based on the below print, it's very possible that spineA is the first child of pelvis, rather than hip_L. But for my usage, it worked fine. {{{ # Python code import maya.cmds as mc # Define a list of joints in random order based on their actual hierarchy: joints = ['hip_L', 'pelvis', 'hipTwist_L', 'ball_L', 'spineA', 'spineC', 'spineB'] # Sort the joints by their full path. Generate the list of full paths via a # Python list comprehension: sorter = sorted([mc.ls(jntA, long=True)[0] for jntA in joints]) for s in sorter: print s }}} prints: {{{ |root|pelvis |root|pelvis|hip_L |root|pelvis|hip_L|hipTwist_L |root|pelvis|hip_L|knee_L|ankle_L|ball_L |root|pelvis|spineA |root|pelvis|spineA|spineB |root|pelvis|spineA|spineB|spineC }}} Should node that bad things will happen if there are duplicate named things in the scene. To do this with ~PyMel is a bit different: Since each node in the list is actually a ~PyNode, not a string with a long name, how to sort? Like so: {{{ import pymel.core as pm joints = [pm.PyNode(j) for j in ['hip_L', 'pelvis', 'hipTwist_L', 'ball_L', 'spineA', 'spineC', 'spineB']] sorter = sorted(joints , key=lambda x:x.longName()) }}} We can sort the ~PyNodes by their 'long name', (full path to node) via the {{{sorted}}} functions {{{key}}} arg. Credit Steve Kestell. This will return a list of all the items picked in the main {{{Outliner}}}: {{{ string$myOutliner = "outlinerPanel1";
string $selectionConnection = outlinerEditor -q -selectionConnection$myOutliner;
string $selectList[] = selectionConnection -q -obj$selectionConnection;
}}}
The {{{window}}} command has the flag {{{-rtf}}} (resize to fit), which will make it resize itself (bigger) to fit the child controls.  However, if the UI is dynamic, or its child controls change to be smaller, when the UI is regenerated, it'll keep its larger size.  How can you make it change size every time, to fit the child controls, even if the child controls shrink the overall UI size?

You need to add a call to 'remove the saved window size' at the top of the UI code:
{{{
if(windowPref -exists "myUI_name")
windowPref -remove "myUI_name";
}}}
In this example, you can change the button size however you’d like, and the UI will always resize correctly when reopened:
{{{
global proc tempWin_UI()
{
if(window -exists tempWin_UI)
deleteUI tempWin_UI;
if(windowPref -exists "tempWin_UI")
windowPref -remove "tempWin_UI";

window -t "Temp Window" -rtf 1 tempWin_UI;
columnLayout -adj 1 -co both 5;
button -w 200 -h 200;
showWindow;
}
tempWin_UI;
}}}

As of v8.0, Maya has no good debugger for scripting :(

The {{{trace}}} command (mel only, not in Python) 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). Presuming you have polygonal components picked, like faces: How can you get just the edges that live on the border of the mesh. For example, if you had a poly cube missing the top face, and you made a selection of all the faces in the cube, how could you convert that to just the edges around the top open edge? {{{ import maya.cmds as mc import maya.mel as mm def getBorderEdgesFromSelection(): mm.eval('ConvertSelectionToEdges;') mc.polySelectConstraint(mode=2,type=0x8000,where=1) borderEdges = mc.ls(selection=True, flatten=True) mc.polySelectConstraint(mode=0, disable=True) return borderEdges }}} This function both returns a list of the border edges, and selects them in the process. {{{ConvertSelectionToEdges}}} to a ~RunTimeCommand for '{{{PolySelectConvert 2}}}'. {{{PolySelectConvert}}} is a global mel proc living in this script: {{{ C:\Program Files\Autodesk\Maya20XX\scripts\others\PolySelectConvert.mel }}} Wrote this really quickly in ~PyMel, so there may be a speed hit on large polysets: It will find any non uv-mapped faces, and return that list. {{{ import pymel.core as pm def getUnmappedFaces(mesh): """ For the passed in mesh, return a list of any unmapped faces. Parameters: mesh : string\PyNode\list : Either the transform or shape level, string or PyNode instance of mesh to query. Return : list : PyNode MeshFace instances that have no UV mapping. """ if not isinstance(mesh, (list,tuple)): mesh = [mesh] # Convert to PyNode Mesh instances: _mesh = [] for m in mesh: mNode = pm.PyNode(m) if not isinstance(mNode, pm.nt.Mesh): meshShapes = mNode.getChildren(type='mesh') if meshShapes: _mesh.append(meshShapes[0]) else: _mesh.append(mNode) mesh = _mesh badFaces = [] pm.waitCursor(state=True) for m in mesh: for face in m.f: if not face.hasUVs(): badFaces.append(face) pm.waitCursor(state=False) return badFaces }}} [[MeshFace|http://download.autodesk.com/global/docs/maya2014/en_us/PyMel/generated/classes/pymel.core.general/pymel.core.general.MeshFace.html]] docs 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?]]
Starting late in Maya 2015, and documented in Maya 2016, there is a [[unknownPlugin|http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/unknownPlugin.html]] command that can be used to strip these out.

Here's an example of an error you may see on file open:
{{{
requires "depthOfFieldView" "1.0";
# Traceback (most recent call last):
#   File "<maya console>", line 1, in <module>
}}}
Removal code:
{{{
import pymel.core as pm
oldplugs= pm.unknownPlugin(query=True, list=True)
for op in oldplugs:
pm.unknownPlugin(op, remove=True)
print "Removed old, unknown plugin: %s"%op
}}}
Run the above code after opening a scene:  Re-save it, and the errors will go away on next open.
Had an issue where I was trying to use the 'set driven key' window to drive attributes on a {{{layeredTexture}}}:  Each 'layer' is an array compound {{{inputs}}} attr ({{{inputs[0]}}}, {{{inputs[1]}}}, etc), that has sub-attrs called things like {{{color}}} (which is itself a multi (float) ), {{{alpha}}} (float), {{{isVisible}}} (bool), etc.  When you select the {{{layeredTexture}}} node and add it as the 'driven' item in the SDK window, none of these array input attrs show up.

Looking into it, it ended up being a single line fix:
In this script:
{{{
C:\Program Files\Autodesk\Maya20XX\scripts\others\setDrivenKeyWindow.mel
}}}
In the local proc {{{genListAttrString}}}, change line 247 from this:
{{{
$cmd = "listAttr -scalarAndArray -visible -connectable -unlocked -multi -leaf "; }}} To this, by removing the {{{-leaf}}} at the end: {{{$cmd = "listAttr -scalarAndArray -visible -connectable -unlocked -multi ";
}}}
To refresh the SDK window in mel:
{{{
source setDrivenKeyWindow;
SetDrivenKeyOptions;
}}}
When reloading the driven nodes, all the compound {{{inputs}}} and their (float) children now show up, and can be keyed.
@@Be sure to set the SDK window to display the ''long'' names, or the data can be very confusing.@@
~PyMel makes acessing the bounding box data pretty easy actually.  Otherwise you'd have to call to the API.  From there, some simple Python {{{min}}}/{{{max}}} functions do the rest.
{{{
import pymel.core as pm

def intersectBBox(bboxA, bboxB):
"""
Return the bounding box intersection of one bounding box with another.

Parameters :
bboxA, bboxB : PyMel BoundingBox.

Return : None / PyMel BoundingBox : If the bounding boxes don't intersect,
None is returned.  Otherwise a new BoundingBox instance that is the
intersection of the two is returned.
"""
if not bboxA.intersects(bboxB):
return None

thisMin = bboxA.min().cartesian()
thisMax = bboxA.max().cartesian()
otherMin = bboxB.min().cartesian()
otherMax = bboxB.max().cartesian()
bboxMinPt = pm.dt.Point(max([thisMin[0], otherMin[0]]),
max([thisMin[1], otherMin[1]]),
max([thisMin[2], otherMin[2]]))
bboxMaxPt = pm.dt.Point(min([thisMax[0], otherMax[0]]),
min([thisMax[1], otherMax[1]]),
min([thisMax[2], otherMax[2]]))
return pm.dt.BoundingBox(bboxMinPt, bboxMaxPt)
}}}
Example using it with poly cubes, to prove out the values:
{{{
import pymel.core as pm

c1 = pm.PyNode("pCube1")
c2 = pm.PyNode("pCube2")
bb1 = c1.getBoundingBox(space='world')
bb2 = c2.getBoundingBox(space='world')

intersect = intersectBBox(bb1, bb2)
print intersect.min().cartesian()
print intersect.max().cartesian()
print intersect.width(), intersect.height(), intersect.depth()
}}}
~PyMel 2015 Docs for [[BoundingBox|http://help.autodesk.com/cloudhelp/2015/ENU/Maya-Tech-Docs/PyMel/generated/classes/pymel.core.datatypes/pymel.core.datatypes.BoundingBox.html]]
New to Maya 2010, and for use in //mel only//, and only on //procedures// (not built-in commands), is:
{{{getProcArguments}}}
Of course, you //can// wrapper this with Python:
{{{
import maya.mel as mm
args = mm.eval('getProcArguments stringArrayToString')
[u'string[]', u'string']
}}}
They use the term //argument//, which I feel is incorrect:  Arguments are what you pass into the parameters, and this tool queries the parameter //types// ({{{int}}}, {{{string[]}}}, etc), not the values passed in. 
I wanted a way to get a list of all of the parameters that make up the signature of a mel command.  Mel doesn't provide for a lot of introspection like Python does.  However, the mel {{{help}}} command will return back a bunch of info on a command, including the parameter list (when the command is authored properly via the API).

With a bit of string fiddling via Python, we can extract out just the parameter list.  For example, running the mel {{{help}}} on the {{{menu}}} command, it prints this:
{{{
// mel code

// Result:

Flags:
-e -edit
-q -query
-aob -allowOptionBoxes     on|off
-dai -deleteAllItems
-dt -defineTemplate       String
-dtg -docTag               String
-en -enable               on|off
-ex -exists
-fi -familyImage          String
-ia -itemArray
-l -label                String
-mn -mnemonic             String
-ni -numberOfItems
-p -parent               String
-to -tearOff              on|off
-ut -useTemplate          String
-vis -visible              on|off

Command Type: Command
//
}}}
We can intercept that with Python, and pull out just what we want:
{{{
# Python code
import maya.cmds as mc

def getParameterList(melCmd):
helper = [line.strip() for line in mc.help(melCmd ,syntaxOnly=True).splitlines()]
parameters = []
for h in helper:
if h.startswith('-'):
splitter = h.split(' ')
parameters.append(splitter[1][1:])
return parameters
}}}
{{{
[u'edit', u'query', u'allowOptionBoxes', u'deleteAllItems', u'defineTemplate', ...
}}}

{{{
import maya.cmds as mc
color = mc.grabColor(hsv=True)
print color
}}}
{{{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.

{{{
cmdFileOutput
}}}
It should be noted that this only works with calls via the {{{print}}} statement:  If a command prints stuff to the script editor, it won't be captured :(
[[This blog post|http://danostrov.com/2013/02/06/rerouting-the-maya-script-editor-to-a-terminal-and-other-places/]] presents a robust solution that should allow for it though.
----
Also see:
*[[How can I query the contents of the undo queue?]]
*Maya has the default hotkeys {{{ctrl+c}}} and {{{ctrl+v}}} like most software to copy and paste data.
*//But//, the hotkeys are //context sensitive//:  They do different things if the Channel Box, Graph Editor, or modeling panel (like, the perspective view) is active.
*In the Channel Box or Graph Editor, they copy and paste //keys//.  In a modeling panel, they //physically copy and past scene data//, based on what's currently selected.  It does this by exporting and importing data to\from a temp file on disk.
*This can wreck some major havoc when it comes to animators (most commonly) copying\pasting keys, and not realizing they //don't// have the Graph Editor\Channel Box actually active.  They'll get some temp file dumped into their scene, making the file dirty.  Then they yell.  It gets ugly.
*IMO, what Maya should do by default is //prompt the user// with a confirmDialog //before// the paste (scene import) happens.  But //not// prompt for the pasting of keyframes.  The below code does that.  I made a //new// 'user hotkey', and remapped it to {{{ctrl+v}}}
{{{
int $bad = 1; // get the current panel. If it's the Graph Editor, we're good. string$panel = getPanel -withFocus;
if(match "^graphEditor" $panel == "graphEditor")$bad = 0;

// is anything picked in the Channel Box?  If so, we're good.
string $selAttr[] = channelBox -q -sma "mainChannelBox"; if(size($selAttr))
$bad = 0; // if things are still 'bad', then fire up our confirmDialog, since we must be trying // to paste (import) our temp scene: if($bad)
{
string $yesNo = confirmDialog -title "Confirm File Paste" -message "Are you sure you want to paste (import)\nthe previously 'copied' (exported) Maya file?" -button "Yes" -button "No" -defaultButton "Yes" -cancelButton "No" -dismissString "No"; if($yesNo == "Yes")
cutCopyPaste "paste";
}
else
cutCopyPaste "paste";
}}}
There have been rare instances where I've needed to create mel procedures inside of Python modules.  For example, the {{{outlinerEditor}}} command has a {{{selectCommand}}} argument that expects a //mel procedure// passed in as a string.  If you're creating the {{{outlinerEditor}}} via Python (via {{{maya.cmds.outlinerEditor()}}}), and you want to access that argument, you can't call to another Python function... you //have to// call to a mel proc. :-(

It's pretty easy to author and execute mel from Python by simply making a multi-line string with the contents of the proc, and having the Python {{{maya.mel.eval()}}} function evaluate it:
{{{
# Python code:

import maya.mel as mm
mel ='''
global proc foo(){
print "foo is a go!\\n";
}
'''
mm.eval(mel)
}}}
Then to execute the newly created mel through Python:
{{{
# Python code:

mm.eval('foo()');
# foo is a go!
}}}
You can also confirm this in mel of course:
{{{
// mel code:

foo();
// foo is a go!
}}}
Continuing the example from above, your Python {{{outlinerEditor}}} code could look like this:
{{{
# Python code:

import maya.cmds as mc
mc.outlinerEditor(myOutliner, selectCommand='foo();')
}}}
@@Note:@@  Wing can interact with Maya in three different ways.  This is one of the three.  See an overview here on the whole system:  [[Interaction between Wing and Maya]]
The below examples I've gotten to work repeatedly on Windows and Mac.
----

I use [[Wing IDE|For Python: Maya 'Script Editor' style IDE]] when authoring Python and mel code for Maya, and have come up with a solution (presuming you have the 'Professional' version of Wing) to have the code (Python //or// mel) you @@highlight in Wing@@ //execute in Maya// (aka 'evaluate selection').  Also known as [[REPL|http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop]].  This lets me completely replace the Script Editor when authoring Python //or// mel.
>Note that you can have Wing recognize {{{.mel}}} files, with //some// (not full) context sensitive highlighting, correct indentation, scope hilighting, etc:  Under Wing 'Prefs -> Files -> File Types', 'insert' a new file type as {{{mel}}} and set its 'Mime Type' to be {{{C++ Source}}}.

There are a number of 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.)
!!!Important Starting Notes
When it comes to opening {{{commandPorts}}}, the port number is fairly arbitrary.  In the below examples I use {{{6000}}}, but you can use another number if you wish.  Just make sure to update it in both locations:  {{{mayaWingServer.py}}} //and// the function {{{send_to_maya()}}} in {{{wingHotkeys.py}}}.
!!!Overview of the below code:
*Maya launches, creates a listener-server waiting for input from Wing.
*In Wing, the user executes a hotkey that sends the highlighted text from Wing to a temp text file.  At the same time, over a {{{commandPort}}} it pings Maya and tells it to evaluate the text file.
*Maya's server receives the ping, processes the text file, and evaluates it in Maya, as if it was executed from the Script Editor
!!!#1 - userSetup.py & mayaWingServer.py
Starting with Maya 2013, the only success I've had getting Wing to talk to Maya is over a 'server' connection, rather than a more simplistic {{{commandPort}}} connection.

The module {{{mayaWingServer.py}}} should be authored and placed in the same directory as the user {{{userSetup.py}}} file.  When Maya starts up, it should be called to (via {{{userSetup.py}}}) thus launching a listener-server running in a separate thread that will wait for data incoming from Wing, and when it receives it, process the data.
{{{
"""
mayaWingServer.py
Author : Eric Pavey - 2012-10-23
"""
import sys
import socket

import maya.utils as mu
import maya.OpenMaya as om

import executeWingCode

#-----------------
PORT = 6000  # Needs to be the same value as authored in wingHotkeys.py below.
SIZE = 1024
BACKLOG = 5

def processDataInMaya(data):
"""
This function is designed to be passed as the 'processFunc' arg of
the mayaServer function.  It is mainly a try\except wrapper around the
executeWingCode.main() function.

data : string : The data passed from wing.  Currently this is 'python' or 'mel'.
"""
try:
# If we don't evaluate in maya.util.executeInMainThreadWithResult(),
# Maya can crash, and that's no good.
except Exception, e:
om.MGlobal.displayError("Encountered exception: %s"%e)

def server(processFunc=processDataInMaya, port=PORT, backlog=BACKLOG, size=SIZE):
"""
Create a server that will listen for incoming data from Wing, and process it.

Modified example taken from:
http://ilab.cs.byu.edu/python/socket/echoserver.html

The server will wait to recieve data from a single client.  When it receives
data, it will 'process' it via the processFunc function.

Parameters :
processFunc : function : A function object that will
process the data recieved by the client.  It should accept a single
string argument.
port : int : Default to global PORT.  The port to connect to.
backlog : int : Default to global BACKLOG.  The number of connections the
server can have waiting.
size : int : Default to global SIZE.  The size in bytes to recieve back from
the client.
"""
host = ''
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind((host,port))
except:
print "Tried to open port %s, but failed:  It's probably already open\n"%port
return

s.listen(backlog)
print "Starting Python server, listening on port %s...\n"%port
while True:
client, address = s.accept() # client is a socket object
data = client.recv(size)
if data:
processFunc(data)
client.close()

def startServer():
"""
When Maya starts up, execute this to start the Wing listener server:
"""
}}}
The {{{userSetup.py}}} file should import the above module, and call to {{{startSever()}}}:
{{{
# userSetup.py

import maya.cmds as mc
# Need to defer the execution, or on Mac it can fail...
mc.evalDeferred("import mayaWingServer; mayaWingServer.startServer()", lowestPriority=True)
}}}
If you don't have a {{{userSetup.py}}} file, you can make a new one and stick it here (PC):
{{{
}}}
Or here (Mac):
{{{
}}}
----
Older notes:

Up until Maya 2013, the below code was all that was needed to get a {{{commandPort}}} opened and let Wing talk to Maya.  But something happened in 2013 that broke all this, thus the above code was generated.
<<<
In Maya's {{{userSetup.mel}}} file, add this line to open a network socket.  This will later let Wing talk with Maya.
{{{
// userSetup.mel
commandPort -name ":6000" -echoOutput;
}}}
It appears, if you're running Maya on Vista \ 64bit, or maybe it's a Maya2010 thing, you need to call to {{{commandPort}}} twice to get this working, otherwise your socket will later be refused:
{{{
// userSetup.mel
commandPort -name "127.0.0.1:6000" -echoOutput;
commandPort -name ":6000" -echoOutput;
}}}
For whatever reason, having your {{{userSetup.py}}} execute these commands instead won't let the below process work.  This really confuses me, since Maya claims the port is open.  But Wing says the port is refused....
<<<
!!!#2 - wingHotkeys.py
The Wing Python module ({{{wingHotkeys.py}}}) and functions inside are authored to do a few things:
#Save the text selected in Wing as a temp file on disk.
#Ping Maya through the opened command port, and tell Maya to execute the contents of that file.  Depending on the type of data sent, either Python or mel, the tool will tell Maya how to intercept it, and execute it.
Again, this code is tailored to //Wing IDE's API//, and saved as part of that program's own "scripts" directory.
Default location (on winXP) of that dir is here:
{{{
C:\Documents and Settings\<userName>\Application Data\Wing IDE 3\scripts\wingHotkeys.py
}}}
On Windows 7:
{{{
}}}
On Mac:
{{{
}}}
<<<
Note for Mac:  The {{{/.wingide4}}} directory seems to be hidden in Finder.  To access it, open a Terminal, cd to that folder, and execute:
{{{
$open . }}} To launch a Finder to that folder. <<< Functions: *{{{send_to_maya()}}} : Function that does the heavy lifting, and calls to {{{executeWingCode.main()}}} (discussed below). *{{{python_to_maya()}}} : Wrapper to send the code as Python to Maya. I have this mapped to {{{ctrl+p}}}. (Wing hotkeys don't allow args, so you need to author wrapper functions) *{{{mel_to_maya()}}} : Wrapper to send the code as mel to Maya. I have this mapped to {{{ctrl+m}}}. The biggest hangup with this system is getting Maya to properly open a {{{commandPort}}}, getting a proper {{{socket.socket()}}} connection, and getting Maya to properly connect via {{{maya.connect()}}}. Based on the network settings of your machine, the below code may not work for you as-provided in all instances. Whenever I change machines I seem to have to modify one or more of these areas. I've left code-examples (commented out) for other alternatives that I've used on various machines to get these working, so if something fails, you can try using those examples. Otherwise you'll need to strap on your networking programmer hat and dig into the docs a bit. If you already have a {{{wingHotkeys.py}}} module for Wing, you can just add the below code to it: {{{ # wingHotkeys.py # Author: Eric Pavey - warpcat@sbcglobal.net import wingapi import socket import os import tempfile def getWingText(): """ Based on the Wing API, get the selected text, and return it """ editor = wingapi.gApplication.GetActiveEditor() if editor is None: return doc = editor.GetDocument() start, end = editor.GetSelection() txt = doc.GetCharRange(start, end) return txt def send_to_maya(language): """ Send the selected code to be executed in Maya language : string : either 'mel' or 'python' """ # The port the sever is listening on in mayaWingServer.py : commandPort = 6000 if language != "mel" and language != "python": raise ValueError("Expecting either 'mel' or 'python'") # Save the text to a temp file. If we're dealing with mel, make sure it # ends with a semicolon, or Maya could become angered! txt = getWingText() if language == 'mel': if not txt.endswith(';'): txt += ';' # Cross-platform way to get a temp dir: tempDir = tempfile.gettempdir() tempFile = os.path.join(tempDir, 'wingData.txt') f = open(tempFile, "w") f.write(txt) f.close() # Create the socket that will connect to Maya, Opening a socket can vary from # machine to machine, so if one way doesn't work, try another... :-S mSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # works in 2013... #mSocket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) # More generic code for socket creation thanks to Derek Crosby: #res = socket.getaddrinfo("localhost", commandPort, socket.AF_UNSPEC, socket.SOCK_STREAM) #af, socktype, proto, canonname, sa = res[0] #mSocket = socket.socket(af, socktype, proto) # Now ping Maya over the command-port try: # Make our socket-> Maya connection: There are different connection ways # which vary between machines, so sometimes you need to try different # solutions to get it to work... :-S #mSocket.connect(("127.0.0.1", commandPort)) # works in 2013... #mSocket.connect(("::1",commandPort)) mSocket.connect(("localhost", commandPort)) # 2016, mac # Send our code to Maya: # It is intercepted via the function processDataInMaya(), created via mayaWingServer.py mSocket.send(language) except Exception, e: print "Send to Maya fail:", e mSocket.close() def python_to_maya(): """Send the selected Python code to Maya""" send_to_maya('python') def mel_to_maya(): """Send the selected code to Maya as mel""" send_to_maya('mel') }}} !!!#3 - executeWingCode.py ''3.'' The Python module {{{executeWingCode.py}}} is the one Wing's {{{send_to_maya()}}} function (above, step 2) triggers via the Maya listener-sever. It is what physically evaluates the code executed in Wing, in Maya. Be sure to save this in a location seen by //Maya's// Python path (probably same dir as {{{userSetup.py}}}). {{{ """ executeWingCode.py Eric Pavey - 2011-03-23 Module that Maya calls to when Wing pings it through a socket, telling Maya to execute the commands in the temp file as either Python or mel code. """ import __main__ import os import tempfile import maya.OpenMaya as om def main(codeType): """ Evaluate the temp file on disk, made by Wing, in Maya. codeType : string : Supports either 'python' or 'mel' """ # Cross-platform way to get a temp dir: tempDir = tempfile.gettempdir() tempFile = os.path.join(tempDir, 'wingData.txt').replace("\\", "/") if os.access(tempFile , os.F_OK): # Print the lines from the file in Maya: with open(tempFile, "r") as f: for line in f.readlines(): print line.rstrip() print "\n", if codeType == "python": # execute the file contents in Maya: with open(tempFile , "r") as f: exec(f, __main__.__dict__, __main__.__dict__) elif codeType == "mel": melCmd = 'source "%s"'%tempFile # This causes the "// Result: " line to show up in the Script Editor: om.MGlobal.executeCommand(melCmd, True, True) else: print "No Wing-generated temp file exists: " + tempFile }}} The key here for the ''Python'' execution, 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. For ''mel'', calling to {{{OpenMaya.MGlobal.executeCommand}}} allows for the result of the execution to be printed to the Script Editor, just like you had originally executed it there. !!!#4 - Finish That's it: You can now highlight code in Wing (either Python or mel), and via a wing hotkey, have it execute directly in Maya. I use it __every day__... ---- !!!Older methods: *For Maya versions 8 and //earlier// (Maya 8.5 has Python embedded, yah!) *Thanks to Hamish ~McKenzie "//macaroniKazoo//" on highend3d.com *First, in Maya, create an INET domain on the local host at the command port: **Win2000 syntax is "{{{computerName:portNumber}}}", I've wondered why you don't have to specify the computerName here? {{{ commandPort -n ":5055"; }}} *Second, in Python: {{{ import socket maya = socket.socket(socket.AF_INET, socket.SOCK_STREAM) maya.connect(("127.0.0.1", 5055)) }}} *Then, to actually have Python talk to Maya: {{{ maya.send('print "I am typing in Python, but I see it in Maya!\\n";') }}} *Of course, you have to embed your //mel commands// in the Python syntax. *Python library docs on 'socket' [[HERE|http://docs.python.org/lib/module-socket.html]] *What's so special about '127.0.0.1'? Check Wikipedia [[HERE|http://en.wikipedia.org/wiki/127.0.0.1]] Say you have transformed a polygonal mesh off in space, so that it's local axis no longer matches up with what you think the overall axes of the mesh are. How can you make these axes match some other transform you have deemed "correct"? The below code accepts a mesh name, and the name of some transform that will be the 'new axis' : By placing and orienting this transform as you desire, the mesh will get re-parented to it, thus adopting it's transformations. {{{ import pymel.core as pm def main(mesh, axis): mesh = pm.PyNode(mesh) axis = pm.PyNode(axis) ret = None pm.undoInfo(openChunk=True) try: meshName = mesh.nodeName() kids = mesh.getChildren(noIntermediate=True) if kids: meshMtx = mesh.getMatrix() axisMtx = axis.getMatrix() axisMtxInv = axisMtx.inverse() # Transform both back to origin, but keep mesh relative to axis: pm.xform(mesh, matrix=meshMtx*axisMtxInv) pm.xform(axis, matrix=axisMtxInv*axisMtx) # Freeze the mesh, and parent shape nodes to axis: pm.makeIdentity(mesh, apply=True) kids = mesh.getChildren(noIntermediate=True) pm.parent(kids, axis, shape=True, relative=True) pm.delete(mesh) axis.rename(meshName) pm.xform(axis, matrix=axisMtx) ret = axis finally: pm.undoInfo(closeChunk=True) return ret }}} {{{ mesh = pm.PyNode("myMesh") axis = pm.PyNode("myTransform") main(mesh, axis) }}} !Worldspace Matrix If you're only interested in translation values, you can query a nodes {{{worldMatrix}}} attr to get this info directly from the node in question: {{{ string$node = "pSphere1";
float $offset = 1.0; float$t = currentTime -q;
float $worldMatrix[] = getAttr -t ($t- $offset) ($node+".worldMatrix");
vector $offsetTranslate = <<$worldMatrix[12], $worldMatrix[13],$worldMatrix[14] >>;
print $offsetTranslate; }}} !decomposeMatrix node Before I had a better understanding of nodes matrix attrs, I used to use this method: *The {{{decomposeMatrix}}} plugin wil help do the trick (got the info for this tip in a forum by Lutz Paelike) *I'm not a big fan of plugins in development, they can really cause a bottleneck, but since this plugin is distributed with Maya, I find it safer than usual. *What the decomposeMatrix node does, among other things, is let you pass in a source matrix, and it will spit out the worldspace trans, rot, scale, and shear for that matrix. From there, we can use {{{getAttr -t}}} to query those values in time. *Given this concept, regardless of your source objects hierarchical transformations, as long as your destination object lives in worldspace, it will follow along happily in time: First, load the "decomposeMatrix.mll" plugin: {{{ int$isLoaded = pluginInfo -q -l "decomposeMatrix.mll";
if (!$isLoaded) loadPlugin "decomposeMatrix.mll"; }}} Next create the decomposeMatrix node: {{{ string$dm = createNode decomposeMatrix;
}}}
given you have an object with it's name in ' string $obj = "myObj" ', connect: {{{ connectAttr -f ($obj + ".worldMatrix[0]") ($dm + ".inputMatrix"); }}} Now you can query the decomposeMatrix's output attrs to find the worldspace transformations. (or, you could connect them to another node, for a direct connection situation) And if we make an *expression* in like the link below, you can query them in time, to generate lag: {{{ float$t = currentTime -q;
followObj.translateX = getAttr -t ($t - 10) decomposeMatrix1.outputTranslateX; }}} Also see: *[[How can I compute distance traveled in an expression?]] *[[How can I make one object "follow" another, offset in time, but without using getAttr or setAttr?]] The {{{arrayMapper}}} node, and quite a bit of other work. The expression work below comes from the brain of Daniel Roizman: roizman@kolektiv.com *Steps, based on a nurbs curve softbody: **On your particle object there is a .goalPP attr. This allows the user to change the goal weights per particle in the component editor. However, it'd be a lot easier if you could use a ramp to do this instead. **On your particle object, in the Attribute editor, under "Add Dynamic Attributes", add a "General" attr that is a float "Per Particle (array)" attr, and name it uValuePP?. **Next make a "Runtime Before Dynamics Expression" on the new uValuePP? attr by RMB on it in the "Per Particle (Array) Attributes) section: {{{ myparticle.uValuePP = float(myparticle.particleId) / float(myparticle.count); }}} **This isn't super accurate, but it gets the job done. **RMB on the "goalPP" attr field and choose "Create Ramp (options)". **Set the "Input V" to uValuePP?, hit OK. This creates the arrayMapper, & associated ramp. **That's it. Animate your source curve and the default ramp will make it stick on one end, and be very floppy on the other. *You may want to have a script return back where it physically lives on disk. You can use this for file processing, tracking of assets... whatever. How to do? *The {{{whatIs}}} command is very handy at returning this info. *For example, you make a mel script called whatsit here: {{{C:/whatsit.mel}}} You save it, and then highlight-enter it in the script editor to define it: {{{ global proc string whatsit() { string$whatIs = whatIs whatsit;
if(match "^Mel procedure found in: " $whatIs == "Mel procedure found in: ")$whatIs = substitute "Mel procedure found in: " $whatIs ""; return$whatIs;
}
}}}
*If you then quit Maya, restarted it, and entered the command you'd get this:
{{{
whatsit;
// Result: C:/whatsit.mel //
}}}
*One tricky bit where the code can get hung up is if you’re //currently authoring// the code, and trying to get whatIs to work properly.
*Say you then highlighted the procedure, and defined it in the script editor by pressing Enter.  You //then// run whatsit, and you get:
{{{
whatsit;
// Result: Mel procedure entered interactively. //
}}}
*Hmm… not the full path (but true).  However, if you have it {{{rehash}}}, then {{{source}}} itself first, then it’s happy:
{{{
global proc string whatsit()
{
rehash;
source whatsit;
string $whatIs = whatIs whatsit; if(match "^Mel procedure found in: "$whatIs == "Mel procedure found in: ")
$whatIs = substitute "Mel procedure found in: "$whatIs "";
return $whatIs; } }}} *Now try executing the code after ''saving'' the script and 'highlighting-Enter' declaring it in the script editor **FYI saving it is very important: Since it's being rehashed and sourced, it will then look a the data ON DISK, not what you last interactively entered in the script editor. {{{ whatsit; // Result: C:/whatsit.mel // }}} *Again, this is only an issue if you’re in the habit of re-highlighting and re-defining the procedure //while// you’re working on it (which I am). If you restarted Maya, the first example above would work just fine. *Be warned: If you had mel commands outside of the whatsit //proc// but inside the whatsit.mel //script//, the {{{source}}} statement would executethem, so even then it’s not a best case solution. Python anyone? ;) ---- Also see *[[How can I query the location of a script?]] *[[How can I add images to my UI, without hardcoding their paths?]] I find two schools of though when it comes to switching constraint target weights: One is to blend the weights over time, providing (hopefully) smooth transitions between the targets. In this case, the constraint weights are controlled by some float attr, usually 0->1 or 0->10, with 0 being off, and 1/10 being on. The second is it do a hard switch, where the weights are either on or off (0 or 1), with no blending. This second method is usually accompanied with a 'switch tool' that will help preserve the position of the node being switched in world-space, so that no pop occurs, yet it now moves relative to a new parent. This discussion deals with the second method. If you had four constraint targets, you could create for boolean attrs for turning them on\off. But this is a lot of unnecessary keyed data to deal with when instead you can do it with a single enum attr that has four values (~enumNames). As long as the enum attr names are in the same order as the constraint weight targets, they'll pair up nicely. However, to actually get the enum attr to controll each of the constraint weights is a bit tricky: When enum 0 is active, you want the four weights to be set to 1,0,0,0. When enum 1 is active, you want the weights set 0,1,0,0, etc. This functionality can be provided via 'choice' nodes. Each choice node has an array 'input' attr, that can have values set for each index. Based on a connection to the choice's 'input' attr, it will decide what 'input' attr should be passed to the 'output'. The below function will create the choice node and setup this mapping: {{{ import maya.cmds as mc def switchConstraintConnect(switchNode, switchAttr, constraint): """ This tool is designed to allow a single enum attr to control the weight switching on a given parentConstraint. IMPORTANT: The passed in enum attr names and the constraint's weight attrs must be in the same order! Parameters: switchNode : string : Name of the node with the switch enum attr. switchAttr : string : Name of the enum attr used for the switching. constraint : string : Name of the constraint to control. """ enumAttrs = mc.attributeQuery(switchAttr, node=switchNode, listEnum=True)[0].split(':') conWeightAttrs = mc.parentConstraint(constraint, query=True, weightAliasList=True) num = len(enumAttrs) for i in range(num): choice = mc.createNode('choice', name="choice_%s_%s"%(switchNode, enumAttrs[i])) mc.connectAttr('%s.%s'%(switchNode, switchAttr), '%s.selector'%choice) for j in range(num): val = 1 if j == i else 0 mc.setAttr('%s.input[%s]'%(choice,j), val) mc.connectAttr('%s.output'%choice, '%s.%s'%(constraint,conWeightAttrs[i]) ) }}} I had a recent issue where I needed to have a skinned character, made out of many parts, deform a duplicate of itself, but all polycombined together. I couldn't copy the skin weights since I was going from a bunch of small skinned parts to one big one. I figured out you can have multiple wraps effect the same target mesh, so that was the route I went: The default behavior of the wrap command is to have one target mesh (the last picked) wrap all the mesh picked before it. Normally you use this to have a larger mesh wrapper up many smaller ones. But I needed the exact opposite behavior. Furthermore, I learned that the {{{createDeformer -type wrap}}} //doesn't// do the job: This just makes a wrap deoformer, doesn't do all the 'hooking up' required to make the system work. However, mel supplies you with a {{{CreateWrap}}} //~runTimeCommand// that calls to a bunch of other scripts\procs that do the heavy lifting. In the below example, the last node picked will be wrapped to all the mesh picked before it. Note, you first need to go into the wrap tool window, setup your options, and save the preferences. The runTimeCommand will use those preferences during the wrap creation. {{{ # Python code: import maya.cmds as mc import maya.mel as mm sel = mc.ls(selection=True) targets = sel[:-1] node = sel[-1] for t in targets: mc.select(node) mc.select(t, add=True) mm.eval('CreateWrap') }}} When you create a wrap through the Maya ui, this is the code it calls to (all procs, not actual commands): *{{{CreateWrap}}} runTimeCommand *{{{performCreateWrap false}}} : the code it executes. **That proc lives in: {{{C:\Program Files\Autodesk\Maya20XX\scripts\others\performCreateWrap.mel}}} *It calls to the local proc {{{assembleCmd()}}} *It calls to the global proc {{{doWrapArgList}}} **That proc lives in: {{{C:\Program Files\Autodesk\Maya20XX\scripts\others\doWrapArgList.mel}}} *Which calls to the local proc {{{createWrap}}} in the same script, that does the final, actual wrap work. {{{ # Python code import maya.cmds as mc huds = mc.headsUpDisplay(listHeadsUpDisplays=True) for hud in huds: mc.headsUpDisplay(hud, edit=True, visible=False) }}} Often, animators only want to work on a specific animation channels based on multiple selected objects. Say you have 10 objects picked, and only want to modify their tx and ry channels in the Graph Editor. You'd have to, by hand, scroll down through the Graph Editor Outliner, picking those channels. The Channel Box however has this functionality: With multiple objects picked, whatever channels are selected in the Channel Box will effect each of those objects. So the question is, how can you take what's selected in the Channel Box, and 'transfer' it to the Graph Editor? In a nutshell: *Find the {{{selectionConnection}}} being used by the Graph Editor Outliner. *Get a list of all the objects picked. *Query what attrs are picked in the Channel Box. *For each obj+attr combo, 'update' the {{{selectionConnection}}} in the Graph Editor Outliner to highlight these channels. Also need to "un-highlight" the picked //objects//. This will give the animator the visual filter they need to only work on those channels, in the Graph Editor. {{{ # Python code import maya.cmds as mc selectionConnection = mc.outlinerEditor("graphEditor1OutlineEd", query=True, selectionConnection=True) selObj = mc.ls(selection=True, long=True) selAttr = mc.channelBox("mainChannelBox", query=True, selectedMainAttributes=True) for so in selObj: mc.selectionConnection(selectionConnection, edit=True, deselect=so) for sa in selAttr: mc.selectionConnection(selectionConnection, edit=True, select=so+"."+sa) }}} It's often nice when there is a long operation that you wrapper that operation with a {{{waitCursor}}} command for your users, so they get the comfortable feeling that the machine is actually doing something important, rather than just being locked up. Via Python, there are two different implementations that allow you to 'wrapper' your code with other code: The 'context manager' and the 'decorator'. Context managers wrapper a block of code, while decorators wrapper functions. Context manager notes on my Python Wiki: *http://pythonwiki.tiddlyspot.com/#%5B%5Bwith%20statement%5D%5D Here, we build a context manager and a decorator for use: {{{ import maya.cmds as mc }}} {{{ class WaitCursor_cm(object): """ Our waitCursor context manager: """ def __enter__(self): mc.waitCursor(state=True) def __exit__(self, *args): mc.waitCursor(state=False) }}} {{{ class WaitCursor_d(object): """ Our waitCursor decorator: """ def __init__(self, f): self.f = f def __call__(self, *args, **kwargs): mc.waitCursor(state=True) try: self.f(*args, **kwargs) finally: mc.waitCursor(state=False) }}} ---- This shows an implementation using the context manager: {{{ def implementContextManager(): with WaitCursor_cm(): mc.pause(sec=5) implementContextManager() }}} And here an implementation using the decorator: {{{ @WaitCursor_d def implementDecorator(): mc.pause(sec=5) implementDecorator() }}} ---- In either case when the functions are executed, the pause command is wrappered, properly turning on and off the {{{waitCursor}}}. {{{ # Python code import maya.cmds as mc # Get a list of all the current references. They in turn # may reference other things that will be exposed once # they are imported: allrefs = mc.file(query=True, reference=True) for ref in allrefs: try: mc.file(ref, importReference=True) except RuntimeError: pass # See if there are any new references to import: newrefs = mc.file(query=True, reference=True) if len(newrefs): allrefs += newrefs }}} Or via ~PyMel: {{{ import pymel.core as pm allrefs = pm.getReferences(recursive=True) for ref in allrefs: try: allrefs[ref].importContents(removeNamespace=False) except RuntimeError: pass }}} Not the most elegant solution ( since this may not work in batch mode since it counts on the UI to run), but it works. {{{ string$weightName = "my_blendshape_target";

// Needed to access artBlendShapeSelectTarget:
source artAttrBlendShapeCallback;
// First, select the mesh with the blendshape.  Then:
// Open the 'Paint Blend Shape Weights Tool':
ArtPaintBlendShapeWeightsToolOptions;
// Select the Target weight in the window:
artBlendShapeSelectTarget artAttrCtx $weightName; // Import the saved weight onto it: artAttrCtx -e -importfileload "C:/path/to/my/weight.iff" "artAttrBlendShapeContext"; }}} I've also found this command that will select the given blendshape weight for import, but it doesn't update the UI, and the above command seems to run without it. But including if needed in the future. {{{ artAttrCtx -e -paintattrselected "blendShape.blendShapeName.weightName" artAttrBlendShapeContext; }}} The docs for the {{{paintattrselected}}} flag are: * An array of selected paintable attributes. Each element of the array is a string with the following information {{{NodeType.NodeName.AttributeName}}}. This is hackey, but it works {{{ # Select the mesh with the wrap and then: artAttrToolScript 4 "shrinkWrap"; artAttrInitPaintableAttr; artAttrCtx -e -importfileload "C:/path/to/my/mask.iff" "artAttrContext"; }}} Maya really seems to try to hid this stuff from the user. {{{artAttrToolScript}}} and {{{artAttrInitPaintableAttr}}} are both mel scripts that wrapper most of the heavy lifting. 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... [[Maya docs here|http://help.autodesk.com/view/MAYAUL/2019/ENU/index.html?guid=GUID-2380579F-F59C-4E7D-904C-B6B90E949D35]] * Select the node in question. * Run 'Rigging -> Control -> Tag As Controller' : This runs the {{{controller("nodeName")}}} command. * This will create a new {{{controller}}} node, via the [[controller|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/controller.html]] command. * The connection made is : {{{controller.message --> dagNode.controllerVisibility}}}. * On that {{{controller}}} node, set the {{{visibilityMode}}} attribute to {{{2}}} : 'Show on mouse proximity'. Note, that adding these nodes can have a significant performance impact on your scene during playback, up to %10 less framerate, even if you have that node's {{{hideOnPlayback}}} attr set on. ---- Also see: * [[Working with 'controller' types in Maya]] * [[Increasing animation playback performance]] ''Update #2'': You can use the {{{playblast}}} command, in either mel or Python, to query the active {{{modelPanel}}}. This has an advantage that it will always return back a {{{modelPanel}}}, even if some other panel (like say, the Outliner) was picked most recently: {{{ # Python code import maya.cmds as mc modelPanel = mc.playblast(activeEditor=True) }}} ---- ''Update #1'': Here is a Python solution: {{{ import maya.cmds as mc visiblePanels = mc.getPanel(visiblePanels=True) modelPanels = mc.getPanel(type='modelPanel') modelPanel = list(set.intersection(set(visiblePanels), set(modelPanels)))[0] }}} ---- 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?]]
*[[How do I query the name of the camera in the active panel?]]
Thanks to a tip from Mason Sheffield:
{{{
import maya.app.general.resourceBrowser as resourceBrowser
resBrowser = resourceBrowser.resourceBrowser()
print resBrowser.run()
}}}
Normally this modal dialog is accessed from the 'Maya Icon' in the Shelf Editor window.  But you can skip that and just run the above code.
*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);
}}}
This will open a Windows cmd shell from Maya, but not block Maya in the process:
{{{
import subprocess
subprocess.Popen("start cmd", shell=True)
}}}
The {{{launch}}} command.  Check it out.
----
Note, I haven't actually gotten this command to work for videos or web pages (haven't tried pdf's).
See [[How can I play a movie \ video?]]
----
For web pages, see: [[Launching web browsers]]


{{{
# Python code
import maya.cmds as mc

def getAllNamespaces(start=':'):
namespaces = [start]
mc.namespace(setNamespace=":")
for n in namespaces:
mc.namespace( setNamespace=n)
subNs = mc.namespaceInfo(listOnlyNamespaces=True)
if subNs is not None:
for sn in subNs:
if n is not ':':
namespaces.append(':%s'%(sn))
else:
namespaces.append('%s%s'%(n,sn))
mc.namespace(setNamespace=":")
return namespaces
}}}
{{{
namespaces = getAllNamespaces()

for n in namespaces:
print n
}}}
Prints (for example):
{{{
:
:UI
:myNamespace
:myNamespace:subNamespace
:etc...
}}}
Without the qualifying reference node, it'll list all edits in the scene.  Or you can pass in a reference node name, to limit it to just that reference
{{{
string $edits[] = referenceQuery -editStrings refNamespaceRN; // Result: setAttr refNamespace:myNode.rotate -type "double3" -21.50129 -10.55454 -9.645982 // }}} The {{{dgmodified}}} command will. ---- Also see: *[[How can I query if a scene has unsaved changes?]] I've found that when trying to use mel to __load__ a Python scripted plugin, you have to provide the //full path// to the scripted plugin, even if it's already in you 'maya plugin path'. However to __unload__ it, you only need the //name//: {{{ # Python code import os import maya.cmds as mc # Import our plugin, so we can extract the full path # from it. Let's pretend it lives in a package under \mayaPython: # c:\mayaPython\spDev\myScriptedPlugin.py import spDev.myScriptedPlugin # Pull the full path from it: fullPath = spDev.helloWorld2Cmd_sp.__file__ if fullPath.endswith('.pyc'): fullPath = fullPath [:-1] dirPath, plugin = os.path.split(fullPath) }}} {{{ # Load: mc.loadPlugin(fullPath, quiet=True) mc.pluginInfo(plugin, edit=True, autoload=True) }}} {{{ # Unload: mc.unloadPlugin(plugin, force=True) mc.pluginInfo(plugin, edit=True, autoload=False) }}} If you have the Plug-in Manager open while doing this, you won't always see the checkboxes check on and off. But if you close it and reopen it, they'll have updated correctly. ---- Based on the above code, it allows you to do something pretty nice: Author a function inside a scripted plugin module that will auto-load (or unload, to allow for iteration while working on the code) the scripted plugin. I usually stick this below the {{{initializePlugin()}}} and {{{uninitializePlugin()}}} functions. {{{ import os import maya.cmds as mc def pluginLoader(load): """ Load or Unload the plugin defined in this file. Parameters: load : bool : True to load the plugin, False to unload it. """ fullPath = __file__ if fullPath.endswith('.pyc'): fullPath = fullPath [:-1] dirPath, plugin = os.path.split(fullPath) if load: if not mc.pluginInfo(plugin, query=True, loaded=True): mc.loadPlugin(fullPath, quiet=True) mc.pluginInfo(plugin, edit=True, autoload=True) else: if mc.pluginInfo(plugin, query=True, loaded=True): mc.unloadPlugin(plugin, force=True) mc.pluginInfo(plugin, edit=True, autoload=False) }}} You no longer have to worry about about managing your scripted plugin load states as long as that function is called to before you ever call to your scripted plugin command\node\etc. 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. }}} ---- Also see: *[[How can I query\update Maya's script path?]] (also applies to plugins) Presuming your company has this level of support: * http://www.autodesk.com * 'log in' via the menubar on the top with an icon of a person * Via that same menu once logged in -> 'Manage Products & downloads' * There should be a '?' icon in the top right of that page : 'View My Support Cases' * Which should link you here: ** https://autodesk-communities.force.com/customer/apex/Case_CustomerPortalLandingPage ** Technically you should be able to go here directly, but if any problems happen, use the above route. 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.
This will generate a custom HUD that will update whenever a new scene is opened \ created.
{{{
# Python code
import os
import maya.cmds as mc

def getSceneName(*args):
"""
Returns the scene name.  Used by the hud call in sceneNameHud()
"""
sceneName = 'untitled scene'
fullSceneName = mc.file(query=True, sceneName=True)
if fullSceneName:
sceneName = os.path.split(fullSceneName)[-1]
return sceneName

def sceneNameHud():
"""
Create a HUD on the top middle of the screen that displays the scene name.
"""

command=getSceneName)
}}}
{{{rampColorPort}}}
{{{
import maya.cmds as mc

mc.colorEditor()
if mc.colorEditor(query=True, result=True):
hsv = mc.colorEditor(query=True, hsvValue=True)
rgb = mc.colorEditor(query=True, rgbValue=True)
print "HSV", [round(val, 2) for val in hsv]
print "RGB", [round(val, 2) for val in rgb]
}}}
{{{
HSV [235.95, 0.49, 0.6]
RGB [0.3, 0.32, 0.6]
}}}
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;
}
}
}}}
----
Also see:
*[[How can I add Python code as a new shelf button?]]
*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); }}} http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/CommandsPython/thumbnailCaptureComponent.html (haven't yet got this to work, but it looks like a thing.) ---- Also see: * [[API: How can I get a screen-shot of the active view?]] I usually like the main menu bar off, but occasionally I like it on, so I can tear off a menu. Rather than having to go through the steps: 'Marking Menu -> Hotbox Controls -> Window Options -> Show Main Menubar' I made this mel toggle: {{{ // Toggle the main menu-bar: global string$gMainWindow;
int $vis = window -q -mbv$gMainWindow;
int $newVis = abs($vis -1);
optionVar -iv mainWindowMenubarVis $newVis; window -e -mbv$newVis $gMainWindow; }}} ---- Also see: *[[How to toggle menubars]] Starting with the introduction of Qt in Maya 2012, you can make your own custom windows dockable in the Maya UI. The below examples will take a {{{columnLayout}}} (but any layout type will do) and turn it into a floating dock, that can then be dragged via the mouse and inserted into various parts of the Maya UI. Docs: [[dockControl|http://download.autodesk.com/global/docs/maya2012/en_us/CommandsPython/dockControl.html]] {{{ # Python code import maya.cmds as mc mc.setParent('MayaWindow') myLayout = mc.columnLayout() # Add everything that should go in the dock here... # Define the name for the docControl: dcName = 'myDC' # Just like you need to delete pre-existing windows before you make new ones, # you need to delete pre-existing dockControls: if mc.dockControl(dcName, query=True, exists=True): mc.deleteUI(dcName) # Create the dockControl that will house the window: mc.dockControl(dcName, allowedArea='right', area='right', floating=False, content=myLayout, label='Awesome Dock', visible=True) }}} Other stuff I've learned (as of Maya 2015) : *If the root most layout for a {{{dockControl}}}'s content //isn't// a {{{window}}} (like say, a {{{columnLayout}}}), you won't be able to query the undocked position via the {{{window}}} command. *If the root most layout isn't a {{{window}}}, you need to set the parent to the {{{MayaWindow}}} before creation (per the above example). *If the root most layout //is// a {{{window}}}, it mustn't be created with the Python {{{with}}} context manager, or it will complain when trying to be added to the {{{dockControl}}}. *If a dock's content was created as a {{{window}}}, then docked, the window appears to be destroyed. Undocking it seems to rebuild the window. *Even if the {{{dockControl}}}'s content was created as a window, and if it's undocked and floating when Maya is quit, Maya //won't// remember the position/size of it like a normal window when its rebuild :( ---- Also see: *[[Querying dockControl states]] *[[Understanding window docking and 'workspaceControls']] {{{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); }}} I've always wanted to add attrs to nodes that act like buttons: A user can change a value, a script executes, and the value resets. I've tried doing this via expressions, but have been unsuccessful getting them to execute correctly. But with scriptJobs, they work just fine. The idea is, a user changes an attr on a node: That triggers a 'run once' {{{scriptJob}}}, that in turn executes some code. When the scriptJob finishes, it kills itself, but before it sets the attr back to its default, and re-creates itself for another run. But how does that {{{scriptJob}}} get automatically created when the scene is opened? Via a {{{scriptNode}}}. The below code shows and example of this. It expects a few things: * There's already a node in the scene called {{{myNode}}} with a bool attr called {{{button}}}. * The code is also setup to work in namespaces, in case the scene is referenced. However, it only ever expects to find a single instance of the node in question: Multiple references of the same file will confuse it, and the code would need to be made more robust. {{{ import pymel.core as pm # The code to be added\executed on the scriptNode: scriptStr=''' import pymel.core as pm myNodeName = "myNode" myNodeAttr = "button" def myNodeAttrChanged(): """ The function executed when the attr is changed. """ node = pm.ls(myNodeName, recursive=True)[0] ### # Insert code that should be executed by the attr change here: print "Running Codez!" ### node.attr(myNodeAttr).set(0) makeSj() def makeNodeSj(): """ Creates the scriptJob that triggers on attr change. Only make the scriptJob if the node in question could be found. """ node = None nodes = pm.ls(myNodeName, recursive=True) if nodes: node = nodes[0] else: return pm.scriptJob(attributeChange=[node.attr(myNodeAttr), myNodeAttrChanged], runOnce=True, killWithScene=True) makeNodeSj() ''' # Create the scriptNode: scriptNode = pm.createNode('script', name='myNodeAttrChangedScript') # Set to Python source scriptNode.sourceType.set(1) # Set to execute when gui scene opens: scriptNode.scriptType.set(2) # Assign the above script to be executed scriptNode.before.set(scriptStr) # Connect from node to expression, just for safety, so you can always # follow the dependency from the Attribute Editor. Not doing this won't # cause any problems, I just like having the nodes connected directly for reference. pm.addAttr(node, longName='attrChangeScript', attributeType='message') scriptNode.message >> node.attr('attrChangeScript') # Finally execute the code in the current scene: pm.scriptNode(scriptNode, executeBefore=True) }}} The code should immediately start working. But to test, save the scene and reopen it: If you change the {{{button}}} attr on the {{{myNode}}} node to {{{on}}}, you should see the Script Editor print the above text, and the attr get automatically set back to {{{off}}}. Attr is button! Around Maya 2012, you can use the [[pointOnPolyConstraint|http://download.autodesk.com/global/docs/maya2014/en_us/CommandsPython/pointOnPolyConstraint.html]] command\node to do this. Note, it sticks it to a UV of the poly surface, and I've have questionable results using "maintain offset" with it. ---- Older methods: *This is a general outline, mel to follow *Particle way (credit Colin Penty): **Selct the vert you want to constrain to **"Particles -> Emit from Object [options] -> Emitter Type -> Surface -> Create **Delete the "particle1" node. You now have "emitter1" you can parent objects to, glued to the object. *"Hair Follicle" method: **Found example of this method here: http://www.creativecrash.com/maya/downloads/scripts-plugins/polygon/c/djrivet-mel--3 The [[textureWindow|http://download.autodesk.com/us/maya/2010help/CommandsPython/textureWindow.html]] command. However, the docs say you can only ever have one of these at a time, and the default one exists on Maya startup. So given that, it's probably better to work with the default one than make your own. Based on my tests, I've been unable to get a custom one to work. The {{{condition}}} command. ---- Also see: *[[How can I query if certain conditions exist in Maya?]] Really simple example, running Python: {{{ import maya.cmds as mc def myScriptEditor(): if mc.window('myPyScriptEditor', exists=True): mc.deleteUI('myPyScriptEditor') mc.window("myPyScriptEditor", title="My Python Script Editor") mc.columnLayout(adjustableColumn=True) mc.cmdScrollFieldReporter(height=200) mc.cmdScrollFieldExecuter(height=200, sourceType='python') mc.showWindow() myScriptEditor() }}} *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. {{{play -w;}}} *Example: I have a script that requires the current frame range is played through to define a dynamic simulation. But when I execute my script, the script doesn't wait for the range to finish playing. What to do? Use the -w flag, which is "wait". {{{ //script; play -w; // more script }}}  This... 'annoyance' was introduced around Maya 2008 (+- a version?): If you had objectB.tx connected to objectA.tx, and objectB.tx was keyframed, and you opened the Graph Editor for objectA, you'd see it's 'Translate X' channel expanded, with a pointer to 'objectB.Translate X' under it: ---- (pretend this is the Outliner in the Graph Editor) *objectA **//Translate X// ***@@//objectB.Translate X//@@ ---- @@This@@ is annoying enough on it's own, but presuming that there were many connections from B->A, A's Graph Editor could get full of all sorts of unwanted data. The fix, to turn this "functionality" off: {{{ # Python code import maya.cmds as mc # This is the name of the outlinerEditor in the Graph Editor: graphEd = 'graphEditor1OutlineEd' mc.outlinerEditor(graphEd, edit=True, expandConnections=True) }}} My //guess// is this has something to do with their implementation of animation layers, since the Graph Editor would need to show more than just standard ~animCurves now. Languages like Processing have [[functions|http://www.processing.org/reference/map_.html]] that will take a given value from one range and map it into another. I can't seem to find this in Python, but here's the code: {{{ # Python code def valueRangeMap(val, origMin, origMax, newMin, newMax): """ Will remap val from the original range into the new range. val : float : The value you are mapping. origMin : float : The original minimum value that val should be greater than. origMax : float : The original maximum value that val should be less than. newMin : float : The new minimum value that val will be mapped into. newMax : float : the new maximum value that val will be mapped into. return : float : The newly mapped value. """ # Find the percentage val is between origMin and origMax: percetVal = float(val - origMin) / float(origMax - origMin) # Map into the new range: mappedVal = (newMin+newMax)*percetVal return mappedVal print valueRangeMap(5, 0, 10, 10, 20) # 15.0 }}}  Mel examples first, Python (way at the bottom) second. !Mel Say you have a line with mixed-case characters in it, and you're trying to match a string in it. However, the string you're trying to match could in no-way be in the same case as the original string. In addition, you need to get the result back in the original case. For example: {{{ string$src = "My Mixed Case String";
string $matcher = "mixed case"; }}} What you want to get back is a string like this: {{{ "Mixed Case" }}} There is no "easy" way I've found to do this. If you don't care about having the return value come back case sensitive, you can use the {{{match}}} command combined with the {{{tolower}}} (or {{{toupper}}}) command to format all the string ahead of time. But if you want to maintain case sensivity as part of the return, you need to get into some regular expression voodoo. In a nutshell, the below code will take the {{{$matcher}}} string, and for each alphabetic character, turn it into a matching pair of upper and lower-case characters.  In addition to handling characters that are "special" to {{{match}}}, and need to be escaped when searching.  An example of what this crazy search string looks like is given at the very bottom.
{{{
global proc string matchNonCase(string $matcher, string$line)
{
// convert our $matcher string into a string array: string$array[];
for($i=0;$i<size($matcher);$i++)
$array[$i] = substring $matcher ($i+1) ($i+1); // define characters special to the match command that need to be escaped: string$spcl[] = {".", "*", "+", "^", "$", "\\", "[", "]", "(", ")"}; // generate our special match pattern string: string$matchPattern = "";
for($i=0;$i<size($array);$i++)
{
string $char = match "[a-z]*[A-Z]*"$array[$i]; if(size($char))
{
string $lower = tolower($char);
string $upper = toupper($char);
$matchPattern = ($matchPattern + "[" + $lower +$upper + "]");
}
else
{
int $special = 0; for($j=0;$j<size($spcl);$j++) { if($spcl[$j] ==$array[$i]) {$matchPattern = ($matchPattern + "\\"+$array[$i]);$special = 1;
break;
}
}
if($special == 0)$matchPattern = ($matchPattern +$array[$i]); } } // print$matchPattern;

// and finally, see if we have a match:
string $result = match$matchPattern $line; return$result;
}
}}}
{{{
string $line = "lineData -- \"c:/my/\\Dir$/is/MIXED/case\"  -- more Data";
string $matcher = "/my/\\diR$/is/mIxed/";
string $result = matchNonCase($matcher, $line); // Result: /my/\Dir$/is/MIXED/ //
}}}
If you're wondering, {{{$matchPattern}}} equals this given the above example: {{{ /[mM][yY]/\\[dD][iI][rR]\$/[iI][sS]/[mM][iI][xX][eE][dD]/
}}}
!Python
As ever, doing this in Python is way easier:
{{{
import re

search = "/my/dir/is/mixed"
line = "lineData -- C:/my/Dir/is/MIXED/case --moreData"
print re.findall(search, line, re.IGNORECASE)

# ['/my/Dir/is/MIXED']
}}}
However, embedding special chars will confuse it, so the mel is a bit more robust.  I'm currently checking to see if the special chars in Python can b handled as well.  I presume they can, just havn't found it yet...
As of Maya 2016, there is the {{{matchTransform}}} command.  But other solutions below:
----
We query the {{{.worldMatrix}}} of our source node, then via {{{xform}}} set our destination node to that worldspace transformation:
{{{
// mel
string $target= "pCube1"; string$node= "pCube2";

float $m[16] = getAttr ($target+".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] $node; }}} {{{ # Python import maya.cmds as mc target = "pCube1" node = "pCube2" mc.xform(node, worldSpace=True, matrix=mc.getAttr("%s.worldMatrix"%target) ) }}} {{{ #PyMel import pymel.core as pm target = pm.PyNode("pCube1") node = pm.PyNode("pCube2") pm.xform(node, worldSpace=True, matrix=target.worldMatrix.get() ) }}} 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?]] *[[Moving nodes in worldspace]]  This code will detect all the uv sets on a given mesh, and if it has more than one, merge all other uv sets with the base set, and delete the remainder. {{{ # Python code import maya.cmds as mc def combineUvs(mesh): uvSets = mc.polyUVSet(mesh, query=True, allUVSets=True) if len(uvSets) > 1: baseSet = uvSets.pop(0) for uvSet in uvSets: mc.polyCopyUV(mesh, uvSetNameInput=uvSet, uvSetName=baseSet) mc.polyUVSet(mesh, delete=True, uvSet=uvSet) }}} 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", }}} Some examples: {{{ // Change the value: artAttrSkinPaintCtx -edit -value .1 artAttrSkinContext; // Interactively pick a new value: artAttrSkinPaintCtx -edit -pickValue artAttrSkinContext; // Change the opacity: artAttrSkinPaintCtx -edit -opacity .5 artAttrSkinContext; // Change the 'Paint operation' to 'Replace': artAttrSkinPaintCtx -edit -selectedattroper absolute artAttrSkinContext; // Flood the current value/operation artAttrSkinPaintCtx -edit -clear artAttrSkinContext; }}} The script located here: {{{../MayaX.X/scripts/startup/generateChannelMenu.mel}}} This script has all the UI code which builds the marking menu, so edit for your needs. Of course, what would be safer would be to duplicate this script, rename it to something else (like say, {{{myCustomGenerateChannelMenu.mel}}}), and when Maya starts up, add this line to your {{{userSetup.mel}}} script: {{{ source myCustomGenerateChannelMenu; print "Custom generateChannelMenu.mel script sourced -- userSetup.mel\n"; }}} In the UI, Maya calls the term 'project' (as in File -> Project -> Set...). But the mel behind the scenes is {{{workspace}}}. Query the current workspace: {{{ string$proj = workspace -query -active;
}}}
Set the current workspace:
{{{
string $path = "c:/myProject"; if(filetest -d$path){
// This upates the optionVar used by the Preferences UI:
optionVar -sv ProjectsDir $path; // This (procedure, not command) sets the current path as the *default project*. // It also makes directories on disk in the process, so it's not always wanted. // setProject$path;

// This physically sets the project directory, which is also used by fileBrowserDialogs:
workspace -dir $path; // this is also needed to cement things for Maya's project settings, but isn't // needed if you're just trying to update fileBrowserDialogs: workspace -openWorkspace$path;
}
else
warning("Can't set project:  Directory '" + $path + "' doesn't exist."); }}} This sets the '@@current //workspace// directory@@', which also happens to be the path that opens by default when launching a {{{fileBrowser}}} (or a {{{fileBrowserDialog}}} when in //directory// mode), and is also used when searching for files. This is different from the [[current working directory]] (see those notes), that is used by the {{{pwd}}} and {{{chdir}}} commands. If you //don't// set the {{{optionVar}}}, things will still work, but if you look at the Maya Preferences UI, you could see a conflicting path. {{{setProject}}} does the real magic, and makes it persist between Maya sessions. {{{workspace -dir}}} also works, but doesn't seem to persist between Maya sessions. It should be noted that using this technique with a {{{fileBrowserDialog}}} doesn't seem to work, unless it is in 'directory mode', which is mode '4'. For other {{{fileBrowserDialog}}} techniques, see this subject: [[How can I define a start location for my fileBrowserDialog?]]  {{{move}}} \ {{{xform}}} *Example A: Given a transform: {{{ float$position[] = xform -q -ws -rp transformA;
move -a -ws -rpr $position[0]$position[1] $position[2] transformB; }}} *Example B: Given a joint: {{{ float$position[] = xform -q -ws -rp transformA;  // (or jointA, or whatever)
move -a -ws $position[0]$position[1] $position[2] jointB.scalePivot; move -a -ws$position[0] $position[1]$position[2] jointB.rotatePivot;
}}}
----
Also see:
*[[How can I match the worldspace transformations of two objects?]]
* [[Moving nodes in worldspace]]
{{{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]].

The function lets you filter out or include referenced animation data as well:
{{{
import pymel.core as pm

def offsetAllKeys(startFrame, offset, includeReferenced=False):
"""
startFrame : int : Where should the offset begin?
offset : int : How large should the offset be?
includeReferenced : bool : Default False : Should referenced animCurves be
included?
"""
animCurves = pm.ls(type='animCurve')
if not includeReferenced:
animCurves = [ac for ac in animCurves if not ac.isReferenced()]
pm.keyframe(animCurves, edit=True, includeUpperBound=False, animation="objects",
time=("%s:"%startFrame,), relative=True, option='over', timeChange=offset)
}}}
{{{
startFrame = 100
offset = 50
offsetAllKeys(startFrame, offset)
}}}
----
Also see:
*[[Offset animCurve data]]
{{{
file -prompt 0;
}}}
Also, at some point Maya started spamming you with a mental-ray nodes dialog.  If you want to turn that off via code:
{{{
optionVar -intValue "showMentalRayNotAvailableDialog" 0
}}}
BTW, that mental-ray code lives here:
{{{
c:\program files\Autodesk\Maya20XX\Python\Lib\site-packages\maya\app\general\mentalRayNotAvailableDialog.py
}}}
Mel solution, for Windows:
{{{
system("start explorer " + toNativePath(dirname(file -q -sn)));
}}}
Python, windows:
{{{
import os
import subprocess
import maya.cmds as mc
fil = mc.file(query=True, sceneName=True)
if fil:
dir = os.path.dirname(fil).replace('/', '\\')
subprocess.Popen(['explorer', dir])
}}}
----
Python solution, for Mac:
{{{
import os
import subprocess
import maya.cmds as mc

def main():
fn = mc.file(query=True, sceneName=True)
if fn:
pth, fil = os.path.split(fn)
# Open a shell:
#subprocess.Popen(['open', '-a', 'Terminal', pth])
# Open Finder:
subprocess.Popen(['open', '-a', 'Finder', pth])
main()
}}}
Note it provides a solution for opening a Terminal shell to the dir as well.
{{{file}}} \ {{{fileDialog}}}
*Example: I want to import a specific kind of file from a specific directory:
{{{
file -i fileDialog -dm \"Z:/rotk/objects/char/rig/scenes/myFile.*\";
}}}
Starting in Maya 2014, they added a {{{-directory}}} flag to the {{{launch}}} command:
{{{
// mel
launch -directory "c:/temp"
}}}
{{{
# Python
import maya.cmds as mc
mc.launch(directory="c:/temp")
}}}
Optionally you can do this in pure Python.  Note on Windows the paths need to be backslashed:
{{{
import os
import subprocess

os.startfile("c:/temp") # Windows only, supports forwardslash.
# or:
os.system('explorer c:\\temp') # must be backslash on windows
# or:
subprocess.Popen(['explorer', 'c:\\temp']) # must be backslash on windows
}}}
On Windows this should open Explorer, on Mac, Finder.
----
Very easily, you can write code that will open a window for the currently (saved) scene:
{{{
import os
import maya.cmds as mc

sceneName = mc.file(query=True, sceneName=True)
sceneDir = os.path.dirname(sceneName)
mc.launch(directory=sceneDir)
}}}
{{{
import maya.cmds as mc

def getSavedPath(ovName):
"""
Get an optionVar by the given name.  Return an empty string if either no
optionVar exists, or the path it defines doesn't exist.
"""
savedPath = mc.optionVar(query=ovName)
if savedPath == 0:
savedPath = ""
elif not os.path.isdir(savedPath):
savedPath = ""
return savedPath

def getSavePath(fileType):
"""
Create a fileDialog the user can use to specify a file to save, either new,
or pre-existing.  Will store the path to the file for future access.

returns the name of the file to save.
"""
try:
ovName = '%sSaveDir'%fileType
savedPath = getSavedPath(ovName)
# Open our file browser:
title="Save .%s data"%fileType)
# Append the extension to the file if the user didn't:
if f != "":
ext = os.path.splitext(f)
f = "%s.%s"%(ext[0], fileType)
else:
raise Exception

# Save our path for later browsing
browsedPath = os.path.split(f)
mc.optionVar(stringValue=[ovName, browsedPath[0]+"/"])

if os.access(f, os.F_OK) and not os.access(f, os.W_OK):
raise Exception("'%s' is read-only, unable to save."%f)
return f

except Exception:
print "Save canceled by user:",

"""
Create a fileDialog the user can use to specify a file to load.  Will store
the path to the file for future access.

returns the name of the file to load.
"""
try:
# Define the name of our optionVar
# Query for saved path data:
savedPath = getSavedPath(ovName)
# Open our file browser:
# Save our path for later browsing
if f != "":
browsedPath = os.path.split(f)
mc.optionVar(stringValue=[ovName, browsedPath[0]+"/"])
else:
raise Exception
return f

except Exception:
}}}
{{{
# C:/temp/file.ma
}}}
{{{
print getSavePath("ma")
# C:/temp/file.ma
}}}
There is a runTimeCommand called:
{{{
HardwareRenderBuffer
}}}
that will launch it, but it's just a wrapper for the procedure:
{{{
glRenderWin()
}}}
which lives in the script:
{{{
C:/Program Files/Autodesk/Maya<VER>/scripts/others/glRenderWin.mel
}}}
----
Also see:
*[[How can I render from the Hardware Render Buffer?]]
In this folder:
{{{
C:\Program Files\Autodesk\Maya20XX\scripts\others
}}}
Live many "marking menu" scripts, named "{{{context*ToolsMM.mel}}}".  For example:
{{{
contextNurbsCurveToolsMM.mel
contextPolyToolsDefaultMM.mel
contextPolyToolsEdgeMM.mel
contextPolyToolsFaceMM.mel
contextPolyToolsMM.mel
contextPolyToolsObjectMM.mel
contextPolyToolsVertexMM.mel
contextToolsMM.mel
etc...
}}}
The easy way to override them is copy them into a version-specific script dir, like
{{{
}}}
//Not// the version shared scripts dir here:
{{{
}}}
And then text-edit them to your hearts content ;)

I always seem to forget this:  To pain-select polygonal components, enter the component mode you wish to pain (vertex, edge, face), and:
* RMB on the mesh
* Paint -> Paint Select
Seems like this should be in a menu somewhere, but I can't find it :S
There are two runTimeCommands:
{{{
ArtPaintSelectTool;
ArtPaintSelectToolOptions;
}}}
The first starts the tool, the second starts the tool and opens the Tool Settings window.
They both in turn call to this script:
{{{
C:\Program Files\Autodesk\Maya2016\scripts\others\artSelectToolScript.mel
}}}
Executing either these two commands:
{{{
artSelectToolScript 4;  // execute cmd
artSelectToolScript 3;  // execute cmd, open tool settings
}}}
Which are in turn wrappers around the command:
{{{
setToolTo $gPaintSelect; }}} Which is: {{{ global string$gPaintSelect = "artSelectContext";
}}}
New way my buddy Te pointed out to me:
{{{
# Python code:
import maya.cmds as mc
}}}
I thought the code would //re-parent// the shape node.  But it doesn't, it makes it an instance.
----
Older method:
*First, you "instance" your shape node, which will give you a new transform as well.
*Second, you parent your new instanced shape node to it's new parental transform.
*Third, remove the 'no-longer-needed' transform
{{{
string $shapeNode = "myShape"; string$newParent = "someTransform";
string $instancedShape[] = instance$shapeNode;
parent -s -r ($instancedShape[0] + "|" +$shapeNode) $newParent; delete$instancedShape[0];
}}}
Also see:
[[How can I reparent the shape node of one object to another.  OR, how can I have a single transform with multiple shapes?]]
mayaAscii files are easy to parse:  Just open them in a text editor.  But how about a mayaBinary?
----
New version (from Autodesk) that works in 2020.2, and should work going forward:
{{{
import math
import struct

def processFileInfo(file):
file.seek(8,1)
size = int(math.ceil(data[0] / 8 + 1) * 8)
fileInfo = ['', '']
idx = 0
for i in range(size):
if idx < 2:
c = struct.unpack('c', pc)
if c[0] == '\x00':
idx += 1
else:
fileInfo[idx] += c[0]
return fileInfo

def parseMayaBinary(fileName):
try:
nthFINF = 0
file = open(fileName, mode='rb')
while True:
if data == 'FINF':
fileInfo = processFileInfo(file)
print fileInfo
nthFINF += 1
elif nthFINF > 0:
break
except:
print(fileName, 'is not a valid binary file')
}}}
----
This solution works great up until around Maya 2019.1:
https://github.com/mottosso/maya-scenefile-parser

----
https://sourceforge.net/p/cgkit/code/ci/master/tree/cgkit/mayabinary.py
- I have been unable to get this to work:  I can get it to read Maya iff (image) files, but not mayaBinary files.

----
https://github.com/westernx/mayatools

{{{
from mayatools.binary import Parser

def ExtractFileInfo(fPath):
RES = {}
f = open(fPath)
p = Parser(f)
Chunk = p.parse_next()
max_cnt = 100
fileInfoBlockFound = 0
while( Chunk and max_cnt ):
max_cnt -= 1
if(fileInfoBlockFound == 1 and Chunk.tag != "FINF"):
break
if(Chunk.tag == "FINF"):
fileInfoBlockFound = 1
bits = string.split( str(Chunk.string), '\x00')
RES[bits[0]] = string.join(bits[1:], ' ')
Chunk = p.parse_next()
return RES
}}}
----
Also see:
* [[How can I store and read metadata in Maya files, without having to open them?]]
*[[Info on the Maya Binary file format]]
If you have a polygonal asset with multiple materials assigned to multiple faces, and you pick a single face and try to find what material is assigned to it in the Hypershade, the Hypershade will list ALL the materials assigned to that mesh.

The below code will select the given material assigned to any single face:
{{{
string $sel[] = ls -fl -sl; if(size($sel)!= 1)
error "Please select a single face";
string $shaders[] = listSets -type 1 -object$sel[0];
string $mat[] = listConnections -s 1 -d 0 ($shaders[0] + ".surfaceShader");
select -r $mat[0]; }}} Presuming your selection is a //single face//, this code will find the associated {{{shadingEngine}}}, all items in that engine, and select them. {{{ string$sel[] = ls -fl -sl;
if(size($sel)!= 1) error "Please select a single face"; string$shaders[] = listSets -type 1 -object $sel[0]; string$items[] = sets -q $shaders[0]; select -r$items;
}}}
The above example will pick all faces assigned to that shader in the scene, spanning all meshes.  The code can be changed slightly:  The //below// example will do the same as the above, but it will only pick faces on the //same mesh// as the original selected face.
{{{
string $sel[] = ls -fl -sl; if(size($sel)!= 1)
error "Please select a single face";
string $shaders[] = listSets -type 1 -object$sel[0];
string $items[] = sets -q$shaders[0];
string $locMesh[]; tokenize$sel[0] "." $locMesh; select -cl; for($i=0;$i<size($items);$i++){ if(match$locMesh[0] $items[$i] == $locMesh[0]) select -add$items[$i]; } }}} I blogged about it here: http://www.akeric.com/blog/?p=1049 ---- And notes on the Python functions: http://docs.python.org/library/pickle.html http://docs.python.org/library/pickle.html#module-cPickle http://docs.python.org/library/stringio.html#module-StringIO If you're on Windows, the easiest way is to call out to {{{Windows Media Player}}} via the command line: {{{ string$app = "C:/Program Files/Windows Media Player/wmplayer.exe";
string $vid = "C:/temp/myMovie.avi"; system("start " +$app + " \"" + $vid + "\""); }}} This will of course load it in {{{Windows Media Player}}}. If you wanted to play a movie //in// a Maya UI, you'd need to know HTML programming, and tie that in with the {{{webBrowser}}} mel command (which however, will be removed sometime after Maya 2012). Note: You can find a list of the {{{Windows Media Player}}} command line options here: http://support.microsoft.com/KB/241422 ---- Python code: Here's a Python example using 'Windows Media Player' on my Win7 box: {{{ # Python code import os import subprocess player = os.path.join(os.getenv("ProgramFiles(x86)"), "Windows Media Player\wmplayer.exe") vid = "C:/videos/myVideo.avi" subprocess.Popen([player, vid]) }}} Simple overview: *Make sure your image sequence follows this naming convention : {{{imageName.#.iff}}} *Where the {{{#}}} is a //non-padded number//. Like {{{myImage.1.iff}}}, {{{myImage.2.iff}}}, etc. *Create a poly plane. *Create a {{{surfaceShader}}}, assign it to the plane. These ignore the scene lighting. *Create a {{{file}}} node connected to the {{{.outColor}}} attr of the {{{surfaceShader}}}. *Point the 'Image Name' to any image in your sequence. *Check on "Use Image Sequence". *If you want to offset the image sequence, you can enter a value in the 'Frame Offset' field. Note, this is opposite to the expected behavior: If you want to offset the image sequence by 30 frames into the future (to the right), you would enter -30. A value of positive 30 would offset it back in time by 30 frames (to the left). Make sure the viewport is in texture mode: The image sequence should scrub along with the time slider. Maya has no built-in sound playing to my knowledge. With Python (on windows), it's pretty easy actually: http://docs.python.org/library/winsound.html {{{ # Python code import winsound winsound.PlaySound( 'c:/temp/myWav.wav', winsound.SND_ALIAS) }}} ---- ~Pre-Python info: 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 ] ] ]}}}
https://knowledge.autodesk.com/support/maya/troubleshooting/caas/sfdcarticles/sfdcarticles/Restore-H-264-Codec-in-Maya.html
From that doc:

Start Maya, then go to: Windows > Setting and Preferences > Preferences

1. At the very bottom of the list on the left side of the Preferences window, click on Applications.
2. Under the Sequence Viewing Applications, click on the folder in the Image Sequence line and navigate to the quicktime player
3. Press open. The path should populate in the Image Sequence line.
4. Press save

H.264 should now be an option in your Playblast Options.
Check out 'Autodesk FBX Review':
https://www.autodesk.com/products/fbx/fbx-review
https://apps.autodesk.com/MAYA/en/Detail/Index?id=6979630331069053932&appLang=en&os=Win64
It installs here:
{{{
C:\Program Files\Autodesk\FBX Review\fbxreview.exe
}}}
FYI, you can open files directly via the commandprompt:
{{{
> fbxreview.exe "path/to/my/file.fbx"
}}}
The Output Window collects {{{stdout}}} and {{{stderr}}} streams from within Maya.  Below are vaious ways to interact with them
!!!Maya Python API:
{{{
import maya.OpenMaya as om
util = om.MStreamUtils()
stdout = util.stdOutStream()
stderr = util.stdErrorStream()
util.writeCharBuffer(stdout, "api stdout to the Output Window\r\n")
util.writeCharBuffer(stderr, "api stderr to the Output Window\r\n")
}}}
!!!Python:
{{{
import sys
sys.__stdout__.write("Python stdout to the Output Window\r\n")
sys.__stderr__.write("Python stderr to the Output Window\r\n")
}}}
!!!Mel:
The {{{trace}}} command (which prints to {{{stdout}}}).  For example:
{{{
trace "Mel stdout to the Output Window";
}}}
----
If you wanted to go the other way, and capture something from {{{stdin}}}, you have two options via Python:
A:  Via Maya's own {{{maya.app}}} package:
{{{
stream =  maya.app.baseUI.StandardInput()
print stdin
}}}
B:  Via python's own built-in {{{raw_input}}}:
{{{
stdin = raw_input()
}}}
If you were using Python external to Maya you could pass a string to {{{raw_input}}} that it would prompt the user with.  But Maya appears to hijack this functionality.

Found this awesome reference to {{{itertools}}} via the comments in this blog post:
http://tartley.com/?p=1081

http://docs.python.org/library/itertools.html#itertools.product

You need to be running Maya 2010 for {{{itertools.product}}} to work, since {{{product}}} was introduced in Python 2.6
{{{
# Python code
import maya.cmds as mc

import itertools
edge=1
verts=list(itertools.product(*([-edge/2,edge/2],)*3))
print verts
for v in verts:
mc.spaceLocator(position=v)
}}}
prints:
{{{
[(-1, -1, -1), (-1, -1, 0), (-1, 0, -1), (-1, 0, 0), (0, -1, -1), (0, -1, 0), (0, 0, -1), (0, 0, 0)]
}}}
I //always// forget how to do this, and I feel like Maya keeps moving this option around.
In Maya 2016+ at least, in the Hypershade -> Create -> 2D Textures -> Enable '2D Projection'.
It has three options:
# normal
# projection
# stencil
You can also change it by modifying the optionVar Maya uses to store it.  Valid string values are:
For example:
{{{
import pymel.core as pm
pm.optionVar['create2dTextureType'] = 'projection'
}}}
----
Also, Maya's node editor gives you the option to make all three, when you press 'tab', and enter in 'file'.


The ~OpenMayaUI API's [[M3dView class|http://docs.autodesk.com/MAYAUL/2013/ENU/Maya-API-Documentation/cpp_ref/class_m3d_view.html]] has several methods for doing this.  An example of projecting a 3d worldspace point to 2d screen-space can be found here:
*[[How can I convert a 3d point to 2d screen space?]]
Here's an overview of the pertinent methods:
*{{{viewToWorld}}} : Takes a point in port coordinates and returns a corresponding ray in world coordinates.  Takes a point in port coordinates and returns a point on the near and far clipping planes.
*{{{viewToObjectSpace}}} : Takes a point in port coordinates and returns a corresponding ray in object coordinates.
*{{{worldToView}}} : Converts a point in world space to port space.
*{{{projectionMatrix}}} : Returns the projection matrix currently being used by ~OpenGL in the current view.
*{{{modelViewMatrix}}} : Returns the modelview matrix currently being used by ~OpenGL in the current view.
It's often handy to have a UI that has a viewport with a custom perspective camera in it.  The below example shows a really easy way of doing it.  By default, it will create a window with the standard {{{persp}}} camera in it.  But it allows the user to pass in their own custom camera name as well.
{{{
# Python code
import maya.cmds as mc

class App(object):
def __init__(self, camera="persp"):
self.name = "customModelPanel"
if mc.window(self.name, exists=True):
mc.deleteUI(self.name)
mc.window(self.name, resizeToFitChildren=True)
mc.paneLayout(configuration='single')
mc.showWindow()
}}}
To execute:
{{{
App()
}}}
I often have geometry provided to me from the animation staff placed some distance away from the origin.  For rigging purposes, I need them centered about the origin, with their pivots at the origin.  How to get it moved back there quickly?
{{{
string $sel[] = ls -sl; for($i=0;$i<size($sel);$i++) { xform -cp$sel[$i]; vector$pos = xform -q -ws -rp $sel[$i];
move -a -ws (($pos.x)*-1) (($pos.y)*-1) (($pos.z)*-1); } // After this is done, you probably want to // freeze their transforms too: makeIdentity -a 1$sel;
}}}

Sometimes when working with a UI, I want to be able to query the command it executes.  Sometimes you can just turn on 'echo all commands' in the script editor, but not always.

If the button has a label on it, here is one method to extract the command information:

In this example, I track down the command executed from the "Flood" button in the Pain Smooth Skin Weights Tool.  (irony is you //can// find this out via 'echo all commands')
{{{
# Python code
import maya.cmds as mc

# Get every ui control in the scene:
allCtrls = mc.lsUI(controls=True)
# Filter for just buttons:
allBut = [item for item in allCtrls if mc.button(item, exists=True)]
# Filter buttons by label:
myBut = [b for b in allBut if mc.button(b, query=True, label=True) == 'Flood']
# Extract command:
command = mc.button(myBut[0], query=True, command=True)

print command
# artAttrSkinPaintCtx -e -clear currentCtx
}}}

{{{
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:
{{{
}}}
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.... Another dos example querying for a partial name of a file based on a specifically named subdir: {{{ DIR /s /b |FINDSTR /R "\\build\\.*_foo\.py$"
}}}
This says:  Look under the current dir for all subdirs named {{{\build}}}, and return a list of all files that end in {{{_foo.py}}}.
The {{{shaderfx}}} command doesn't seem to allow any easy way to make this happen.  And I've been unable to find anything else online as an example, so I reached out to Autodesk for help:  What they gave me worked, but was mel, so here's a pymel rewerite.

One weird thing:  Below, you'll see me do this:
{{{
getPropertyValue=(i+1, "texturepath")
}}}
In the //mel// versions, it looks like this:
{{{
-getPropertyValue $i "texturepath" }}} Not sure why in ~PyMel (or Python in general) you have to increase this number, and only in that one spot. Seems questionable... This is also a good general example of how one goes about querying internal data in a {{{ShaderfxShader}}} node... {{{ def getShaderFxTextures(): sfxTextures = ("Combine Normal Maps", "Derived Normal Z Map", "Flip Book", "LatLong UVs", "MatCapUVs", "Normal Map", "Offset Bump Map", "PNAEN Displacement Map", "Reflection Cube Map", "Refraction Cube Map", "Spherical Reflection UVs", "Texture Map", "UV Panner", "UV Rotator") textures = [] sfxnodes = pm.ls(type="ShaderfxShader") for sfxnode in sfxnodes: nodeCount = pm.shaderfx(sfxnode=sfxnode, getNodeCount=True) for i in range(nodeCount): try: propVal = pm.shaderfx(sfxnode=sfxnode, getPropertyValue=(i, "classname")) if propVal in sfxTextures: # Why is i+1 needed here? If I run this as pure mel, it is fine. # Seems sketchy... texPath = pm.shaderfx(sfxnode=sfxnode, getPropertyValue=(i+1, "texturepath")) if texPath not in textures: textures.append(texPath) except: # Not all ShaderfxShader subnodes can have getPropertyValue ran on them, # and will raise a 'Maya command error' when queried. pass return textures }}} 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. !Windows You can use {{{dxdiag}}} at the commandline: The 'Display 1' tab will tell you the Graphics card manufacturer and 'main driver' version. !In Maya You can call to the {{{ogs}}} Maya command to get info on your graphics card: {{{ ogs -deviceInformation; }}} Prints a long string with all sorts of info for your adapter, driver, api, gpu stuff, etc. http://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/Commands/ogs.html  The {{{listConnections}}} command has a '{{{type}}}' parameter that lets you filter the results by a given type. Unfortunately, you can only pass in a single string argument. Below is a little tip that let's you specify the connection types ahead of time. Via Python, we use a //list comprehension// to generate a list of all matching connection types for each of the ones we query. Then we use the {{{zip}}} Python command to zip our original connection type list together with our connection results list. Finally, we wrapper that in Python's {{{dict}}} command, which turns the result into an easily queryable dictionary: {{{ # Python code import maya.cmds as mc myNode = 'myNode' conType = ['bindPose', 'skinCluster'] con = dict(zip(conType, [mc.listConnections(myNode, type=ct) for ct in conType ])) print con # {'skinCluster': [u'skinCluster4', u'skinCluster4'], 'bindPose': None} }}} In Windows the {{{tasklist}}} command will return this info. Example if you have two Maya's open: {{{ > tasklist /FI "IMAGENAME eq maya.exe" Image Name PID Session Name Session# Mem Usage ========================= ======== ================ =========== ============ maya.exe 6064 Console 1 289,552 K maya.exe 1288 Console 1 289,480 K }}} In Maya, you can use Python to query its return: {{{ import subprocess def isMayaOpen(): """ Will return how many open maya.exe applications are running. """ mayaOpen = 0 output = subprocess.Popen(['tasklist', '/FI', 'IMAGENAME eq maya.exe'], stdout=subprocess.PIPE).stdout for line in output: if 'maya.exe' in line: mayaOpen = mayaOpen+1 return mayaOpen }}} {{{ print isMayaOpen() 2 }}} ---- On a related note, Maya can query its own //process identifier// number via the //mel// command (not in Python for some reason...), which coresponds to the above PID values: {{{ import maya.mel as mm pid = mm.eval('getpid'); print pid 1288 }}} (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"); } }}}  {{{filetest}}} Maybe there's an easier way, but this is one solution :) The only reason I don't like it is because you have to actually select stuff for the {{{polySelectConstraint}}} to work. This code simply checks to see if a mesh has border edges. Border edges only show up if there's a hole, that would create a border. {{{ import pymel.core as pm def selectBorderEdges(mesh): mesh = pm.PyNode(mesh) if len(mesh.e) == 0: raise Exception("") pm.select(mesh.e) pm.polySelectConstraint(mode=2, type=0x8000, where=True) pm.polySelectConstraint(mode=0, disable=True) def getBorderEdges(mesh): sel = pm.ls(selection=True) selectBorderEdges(mesh) borderEdges = pm.ls(selection=True, flatten=True) if sel: pm.select(sel) else: pm.select(clear=True) return borderEdges def hasHole(mesh): if getBorderEdges(mesh): return True else: return False }}} {{{ int$unloaded = file -query -deferReference $refFilePath; }}} Will return {{{1}}} if unloaded, {{{0}}} if loaded. {{{ import maya.cmds as mc needsSaving = mc.file(query=True, modified=True) }}} Have yet to find a API call for this: [[MFileIO|http://download.autodesk.com/us/maya/2011help/API/class_m_file_i_o.html]] (the obvious place for it) doesn't have a similar method. ---- Using that, you can wrapper the mel script / global procedure: {{{ C:\Program Files\Autodesk\Maya<VERSION>\scripts\others\saveChanges.mel }}} ...to trigger your own save dialog when needed: {{{ import maya.cmds as mc import maya.mel as mm def saveCheck(): """ return : int : 0 = save canceled. 1 = save not needed, scene saved, or user decided not to save. """ result = 1 if mc.file(query=True, modified=True): result = mm.eval('int$i_result = saveChanges("")')
return result
}}}
----
Also see:
*[[How can I list which nodes have been modifed since the last save?]]
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. ''However #1'', I've learned that if querying for the existence of an attr on a transform, it will also search the shape as well, which often' *isn't* wanted. Given that, you can use {{{attributeQuery}}}, or the {{{listAttr}}} command to get all the attrs on the node, then test for existence of your attr in that list. {{{ import maya.cmds exists = mc.attributeQuery('attrName', node='nodeName', exists=True) }}} ---- ''However #2'' : It's been reported to me (thanks Te) that {{{objExists}}} is up to 45x faster than {{{attributeExists}}}... so if you're doing a lot of looping, that could be a consideration. ---- There is also the [[attributeExists|http://download.autodesk.com/global/docs/maya2012/en_us/Commands/attributeExists.html]] mel //script// (not available in Python unless eval'd) I've been unable to find anything via mel or the API that returns whether an image has an alpha channel. The file //node// however, knows this: Presuming you've mapped some valid texture to a file node: {{{ int$hasAlpha = getAttr myFileNode.fileHasAlpha;
}}}

*Example: Select an object and:
{{{
string $sel[] = ls -sl; referenceQuery -inr$sel[0];
}}}
----
Via the API, you can query this via the {{{MFnDependencyNode}}} class:
{{{
import maya.OpenMaya as om
dgIterator = om.MItDependencyNodes()
while not dgIterator.isDone():
currentItem = dgIterator.thisNode() # MObject
currentItemFunc = om.MFnDependencyNode(currentItem)
if currentItemFunc.isFromReferencedFile():
print "I'm referenced:", currentItemFunc.name()
dgIterator.next()
}}}
----
Via ~PyMel:
{{{
import pymel.core as pm
node = pm.PyNode("myAwesomeNode")
print node.isReferenced()
}}}
{{{isTrue}}} is a command that you can use to query various condition and events in Maya.
What are those events and conditions?
The {{{scriptJob}}} command can be used to find them:
{{{
help -doc scriptJob;
}}}
Or you can have the events and conditions listed:
{{{
print scriptJob -listEvents;
print scriptJob -listConditions;
}}}
So for example, if you wanted to know if Maya was currently playing back animation:
{{{
int $playback = isTrue playingBack; }}} Or is something picked: {{{ int$selected = isTrue SomethingSelected;
}}}
There are many others.  Handing if while scripting, you need to have your script detect for a certain condition.
{{{
global proc int isKeyed(string $obj) { string$connections[] = listConnections -s 1 -d 0 $obj; for($i=0;$i<size($connections);$i++) { string$type = objectType($connections[$i]);
if(match "^animCurve" $type == "animCurve") return 1; } return 0; } string$sel[] = ls –l –sl;
int $keyed = isKeyed($sel[0]);
}}}
{{{
float $keys[] = keyframe -q -sl -vc; }}} changing the "-vc" with other flags will return different values Constraints (seemingly all of them) have a {{{-tl}}} (target list) flag that can be used to query a list of targets, and a {{{-wal}}} flag that can be used to query the coresponding list of 'weight attributes' on the constraint itself: Presuming it's a {{{parentConstraint}}} {{{ // mel string$targetObjs[] = parentConstraint -q -tl $constraintName; string$weightAttrs[] = parentConstraint -q -wal $constraintName; }}} Armed with this knowledge, I decided to write a Python function that would capture this data easily for the user. Since the mel command to deal with the a given constraint conveniently matches the objectType of the given node, it allows me to write one piece of code that can work on any constraint type. {{{ # Python code: import maya.cmds as mc def getConstraintTargetData(constName): """ Based on the given constraint, will return a list of data for each constraint weight. Specifically, each list index is a dict, with the keys "target" (target object) and "attribute" (corresponding attribute weight name on the constraint) """ constType = mc.objectType(constName) constraintData = [] targetList = eval('mc.%s("%s", query=True, targetList=True)'%(constType, constName)) weightList = eval('mc.%s("%s", query=True, weightAliasList=True)'%(constType, constName)) for i in range(len(targetList)): constraintData.append({"target":targetList[i], "attribute":weightList[i]}) return constraintData }}} To see it in action, parent constrain pSphere1 to pCube1 and pTorus1: {{{ targetData = getConstraintTargetData("pSphere1_parentConstraint1") print "Number of targets: %s"%len(targetData) for i,e in enumerate(targetData): print "Index %s: %s"%(i, e) print "Weight attribute for index 0: %s"%targetData[0]["attribute"] print "Target object for index 1: %s"%targetData[1]["target"] }}} prints: {{{ Number of targets: 2 Index 0: {'attribute': u'pTorus1W0', 'target': u'pTorus1'} Index 1: {'attribute': u'pCube1W1', 'target': u'pCube1'} Weight attribute for index 0: pTorus1W0 Target object for index 1: pCube1 }}} !!!~PyMel: In this example, we query values for vert 0 on our mesh: {{{ import pymel.core as pm skinCluster = pm.PyNode("someSkinClusterName") mesh = pm.PyNode("someMeshName") joint = pm.PyNode("someJointName") # Get a list of all the weight values on this vert: values = pm.skinPercent(skinCluster, mesh.vtx[0], query=True, value=True) # Get a list of all the transforms influencing this vert. Can't seem to get this command # to work in pure PyMel or Python, have to eval the mel: transforms = pm.mel.eval('skinPercent -query -transform "%s" "%s"'%(skinCluster.nodeName(), mesh.vtx[0])) # Get the float weight value for just this specific joint on this one vert: jointValue = pm.skinPercent(skinCluster, mesh.vtx[0], query=True, transform=joint) }}} !!!Mel 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 {{{ import maya.cmds as mc print mc.window("", query=True, frontWindow=True) # scriptEditorPanel1Window }}} Note you still need to pass an empty string into the {{{window}}} command to access this functionality. 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  It's easy enough to just query that part of the UI: {{{ string$char = textField -q -text characterField;
}}}
{{{
import pymel.core as pm

cam = None
mp = pm.playblast(activeEditor=True)
if mp:
# Doesn't like full path to panel, weird.
cam = pm.modelPanel(mp.split("|")[-1], query=True, camera=True)
print cam
# persp1
}}}
This prints the camera //transform// name, not shape.
With the 'Paint Skin Weights Tool' visible:
{{{
# Python code
import maya.cmds as mc
print mc.artAttrSkinPaintCtx('artAttrSkinContext', query=True, influence=True)
}}}
Update: Hmm... not sure why I thought this was hard in the past:
{{{
import pymel.core as pm
bsNode = pm.PyNode("blendShape1")
targets = pm.blendShape(bsNode, query=True, target=True)

print targets
}}}
----
Older code:
{{{
string $list[]= listAttr -m "<name of blendshapes>.w"; }}} *thanks to a post from David Coleman ---- Here's that in some ~PyMel: {{{ import pymel.core as pm bsNode = pm.PyNode("myBlendshapeNode") bsTargetNames = [pm.listAttr(bsNode.weight, multi=True)] bsTargetAttrs = [bsNode.attr(attr) for attr in pm.listAttr(bsNode.weight, multi=True)] }}} prints: {{{ [[u'bs_flattop', u'bs_ears', u'bs_chin']] [Attribute(u'myBlendshapeNode.weight[0]'), Attribute(u'myBlendshapeNode.weight[2]'), Attribute(u'myBlendshapeNode.weight[3]')] }}} ---- Optionally: {{{ import pymel.core as pm bsNode = pm.PyNode("myBlendshapeNode") aliases = pm.aliasAttr(bsNode, query=True) ret = {} for i in range(0, len(aliases), 2): ret[aliases[i+1]] = aliases[i] print ret {u'weight[3]': u'bs_chin', u'weight[2]': u'bs_ears', u'weight[0]': u'bs_flattop'} }}}  {{{colorAtPoint}}} *Example given a "ramp" node: {{{ colorAtPoint -o RGB -u .5 -v .5 ramp1; }}} I was told that if your UV's are tiling, UV 0 and 1 can return the same values. Querying .99999 is a safer bet. Should also be noted that this only samples the color for the given node, not the final combination of multiple nodes (if they exist). Haven't yet found an //easy// way to grab the final color of a given uv point based on a complex shading network. ---- Also see: *[[How can I find the color of a render node at a given vertex?]] The {{{undoInfo}}} command will let you print the contents of the undo queue. But it doesn't let you actually capture that data, which is a bit of a pain. The below method shows how you //can// capture that data, but turning on script editor history logging: {{{ # Python code import os import maya.cmds as mc # Define a temp file name, and delete if it exists, since the # history logging will append to it: tempfile = os.path.join(os.getenv("TMP"), "undoInfoTemp.txt") if os.access(tempfile, os.F_OK): os.remove(tempfile) # Set the Script Editor to write out all it's history to this file: mc.scriptEditorInfo(edit=True, historyFilename=tempfile, writeHistory=True) # print our undo queue: mc.undoInfo(query=True, printQueue=True) # Turn off history capture: mc.scriptEditorInfo(edit=True, writeHistory=False, historyFilename='') # Turn the file into a list we can use: undoQueue = [line.strip() for line in open(tempfile)] # And print the result: print "Stuff in queue:" for line in undoQueue: print "\t", line }}} {{{ Stuff in file: # 0: select -r foo # 1: select -cl # 2: select -r stuff }}} {{{ 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; }}} I needed a way to find all the inputs feeding into a node, and all their inputs. The whole 'input tree'. This snippet recurisvely searches through all the nodes that input into a given node, and all of their inputs, etc. {{{ import pymel.core as pm node = pm.PyNode("myNodeName") inputs = pm.listConnections(node, source=True, destination=False, skipConversionNodes=True) for item in inputs: newInputs = pm.listConnections(item, source=True, destination=False, skipConversionNodes=True) for ni in newInputs: if ni not in inputs: inputs.append(ni) }}} You don't want to query the //current// enumAttr value, you want to query //all// their names: ---- {{{ string$enumAttrs = addAttr -q -enumName "myNode.myEnumAttr";
}}}
This returns a single string of attr names separated by colons:
{{{
"enumA:enumB:enumEct"
}}}
----
Tip from my buddy Te:
{{{
string $enumAttrs[] = attributeQuery -node "myNode" -listEnum "myEnumAttr"; }}} This returns an array, with a single string value separated by colons: {{{ {"enumA:enumB:enumEct"} }}} ---- Or, via ~PyMel's [[Attribute.getEnums()|http://download.autodesk.com/global/docs/maya2014/en_us/PyMel/generated/classes/pymel.core.general/pymel.core.general.Attribute.html?highlight=attribute#pymel.core.general.Attribute.getEnums]] method: (Modified example from the ~PyMel docs): {{{ >>> from pymel.core.general import Attribute >>> addAttr( "persp", ln='numbers', at='enum', enumName="zero:one:two:thousand=1000:three") >>> numbers = Attribute('persp.numbers').getEnums() >>> sorted(numbers.items()) [(u'one', 1), (u'thousand', 1000), (u'three', 1001), (u'two', 2), (u'zero', 0)] >>> numbers[1] u'one' >>> numbers['thousand'] 1000 }}} Note, ~PyMel will raise a {{{TypeError}}} of the enum has no values. ---- Also see: * [[PyMel : enum attrs]] The active influence can be found in: {{{ global string$artSkinLastSelectedInfluence;
}}}
A list of the highlighted influences can be found in:
{{{
global string $gArtSkinOrderedInfluenceSelectionList[]; }}} These values are queried\modified inside this script: {{{ C:\Program Files\Autodesk\Maya20XX\scripts\others\artAttrSkinJointMenu.mel }}} ---- The actual control in Maya is a {{{treeView}}} named {{{theSkinClusterInflList}}}. So you can query the selected influences this way: {{{ string$infs[] = treeView -q -si "theSkinClusterInflList";
}}}
But this won't tell you the 'lead' influence:  Currently it seems you need the above global var to do it.
The ui is made via the proc {{{skinClusterInflBuildList}}} living in this script:
{{{
}}}
New in Maya 2010, is the //mel only// command {{{getLastError}}}
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.  Just the image name: {{{ string$name[] = renderSettings -firstImageName -leaveUnmatchedTokens;
}}}
Full path:
{{{
string $name[] = renderSettings -firstImageName -leaveUnmatchedTokens -fullPath; }}} *http://download.autodesk.com/global/docs/maya2014/en_us/Commands/renderSettings.html When you reference a scene, Maya creates a node of type {{{reference}}} to help manage the changes. Most {{{reference}}} nodes are the defined namespace plus "RN". So if you reference someting in with the "bob" namespace, the {{{reference}}} node created would be called {{{bobRN}}}. Presuming you know that reference node name (easily gathered by the '{{{ls -type "reference";}}}' command), how can you query the namespace that the referenced file currently resides in? {{{ string$refNode = "bobRN";
string $file = getAttr ($refNode +".fileNames[0]") ;
string $ns = file -q -rpr$file;
// bob
}}}
After a file has been referenced into a scene, it's possible that it's namespace could be different from what was specified due to namespace clashing.  How to query what the namespace actually is?
{{{
# Python code
import maya.cmds as mc
namespace = mc.file('c:/path/to/my/file.ma', query=True, namespace=True)
}}}
{{{
polyNormalPerVertex -q -xyz  pSphere1.vtx[41];
}}}
Note, this will shoot back multiple sets 3 floats for each "vertexNormal", one for each tri that the vert shares.

Also see:  [[How can I set the normal of a vertex?]]


{{{
string $name = setParent -q myElementName; }}} Thanks to Eric Vignola ;-) Say you have this transform hierarchy: {{{ |breakfast |spam |eggs }}} And you want to know what position eggs is in, relative to it's siblings. In this case, it would be the 2nd item. {{{ import pymel.core as pm node = pm.PyNode("eggs") parent = node.firstParent() position = parent.getChildren().index(node)+1 print position # 2 }}} Which is the 2nd item in the list. 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 }}} {{{ import pymel.core as pm tc = pm.language.melGlobals["gPlayBackSlider"] first,last = pm.timeControl(tc, rangeArray=True, query=True) }}} Mel: {{{ string$sceneName = file -query -sceneName;
}}}
Python:
{{{
import maya.cmds as mc
sceneName = mc.file(query=True, sceneName=True)
}}}
Via the API:
{{{
import maya.OpenMaya as om
sceneName = om.MFileIO.currentFile()
}}}
----
The main difference between the commands and the API is the return value when the current scene //isn't saved//:  The {{{file}}} command will return back an empty string.  But {{{MFileIO.currentFile()}}} will return the string {{{untitled}}}, with the current project path.  For example:
{{{
c/somePath/untitled
}}}
*Maya 5:
{{{
}}}
*Maya 6 and later:
{{{
getAttr -as object.attr\\ (as string)
}}}


This code works in 2013:  I'm not sure when {{{portWidth}}} and {{{portHeight}}} came online, but they're so easy to use I'm guessing they weren't available in earlier versions of Maya that the below code is based on.
{{{
import maya.OpenMayaUI as omui
activeView = omui.M3dView.active3dView()
width = activeView.portWidth()
height = activeView.portHeight()
}}}
----
No API, just mel.  Note, this returns a slightly larger value than the above and below examples (who's values match one another).
{{{
def getCurrentPanelSize():
# This returns the full name, and is filtered by modelPanel's only:
#mp = mc.playblast(activeEditor=True)
# This returns the leaf name, and can be any type of editor:
mp = mc.getPanel(withFocus=True)

width = mc.control(mp, query=True, width=True)
height = mc.control(mp, query=True, height=True)
return width,height
}}}
What's interesting is the {{{playblast}}} command limits its return to the last active 3D {{{modelPanel}}}, while the {{{getPanel}}} command will return back the last accessed panel or editor of any kind (Graph Editor, Outliner, etc).
----
Based on [[this post|http://www.rtrowbridge.com/blog/2009/05/render-selected-meshes-region/]] by Ryan Trowbridge, I found some code I slightly changed to spit out the width and height in pixels of the active view. The rest of [[the module he wrote|http://www.rtrowbridge.com/rtRenderSelectedMeshesRegion.zip]] to render selected meshes is pretty sweet too.
{{{
import maya.OpenMaya as om
import maya.OpenMayaUI as omui

def getViewportResolution():
activeView = omui.M3dView.active3dView()

xPtrInit = om.MScriptUtil()
yPtrInit = om.MScriptUtil()
widthPtrInit = om.MScriptUtil()
heightPtrInit = om.MScriptUtil()

xPtr = xPtrInit.asUintPtr()
yPtr = yPtrInit.asUintPtr()
widthPtr = widthPtrInit.asUintPtr()
heightPtr = heightPtrInit.asUintPtr()

activeView.viewport(xPtr, yPtr, widthPtr, heightPtr)
viewWidth = widthPtrInit.getUint( widthPtr )
viewHeight = heightPtrInit.getUint( heightPtr )

return (viewWidth, viewHeight)

widthHeight = getViewportResolution()
print widthHeight
#(477, 269)
}}}

{{{
# Python code
import maya.cmds as mc

animCurve = 'myNode_translateX'
time = mc.keyframe(animCurve, query=True, timeChange=True)
vals = mc.keyframe(animCurve, query=True, valueChange=True)
timeValues = zip(time,vals)
print timeValues
# [(0.0, 0.0), (1.0, -0.82123691729992565)]
}}}
----
Also see:
* [[How can I query which frames a node is keyframed on?]]
{{{
getAttr -type "persp.tx";
// doubleLinear //
getAttr -type "persp.rx";
// doubleAngle //
getAttr -type "persp.v";
// bool //
getAttr -type "persp.filmRollOrder";
// enum //
}}}
The mel to do this is:
{{{
optionVar -q blendShapeEditorTreeViewSelection;
}}}
but it returns a mess of stuff like:
{{{
// Result:     blendA.0/blendA.1/blendA.2/blendB.0/    blendA.0/blendA.1/blendA.2/blendA.0/ blendB.0/ 0 blendA.0/ //
}}}
It appears the first element always contains what you want though.  So here's some simple Python to get it:
{{{
import pymel.core as pm
selData = [item for item in pm.optionVar["blendShapeEditorTreeViewSelection"] if item][0].split("/")
bsIndices = []
for item in selData:
if not item:
continue
bsNode, index = item.split(".")
bsIndices.append((bsNode, int(index)))

print bsIndices
[(u'blendA', 0), (u'blendA', 1), (u'blendA', 2), (u'blendB', 0)]
}}}
Of course you now have to convert the indices to legible aliasAttr names, but it's a start!
{{{
string $selAttr[] = channelBox -q -sma "mainChannelBox"; }}} *Note, you can query the inputs and outputs as well, with different arugments. {{{file}}} *Example: Based on some given filename$filename:
{{{
file -q -rpl $filename; }}}  {{{ # Python code import maya.cmds as mc import maya.mel as mm # cast mel variable to Python: gPlayBackSlider = mm.eval("$g = $gPlayBackSlider") # Is anything highlighted? rangeHighlighted = mc.timeControl(gPlayBackSlider, query=True, rangeVisible=True) # What is the highlight range? highlightedRange = mc.timeControl(gPlayBackSlider, query=True, rangeArray=True) print rangeHighlighted, highlightedRange # 1 [9.0, 17.0] }}} or {{{ import pymel.core as pm tc = pm.language.melGlobals["gPlayBackSlider"] first,last = pm.timeControl(tc, rangeArray=True, query=True) }}} Given a selection, it's easy to change from one polygonal component type to another (via commands like {{{polyListComponentConversion}}}). But what if you just want to query what //type// of polygonal component is picked? I've not found any built-in commands that do this, so here is one solution: {{{ # Python code import maya.cmds as mc def getSelPolyComponentType(): """ Based on the selected polygonal components, return the type selected. If more than one type is selected, raise an exception. If no poly components are picked, return none. """ compTypes = [['.map', 'uv'], ['.vtx', 'vertex'], ['.e', 'edge'], ['.f', 'face'], ['.vtxFace', 'vertexFace']] sel = mc.ls(selection=True, flatten=True) compType = None for s in sel: for searchStr, result in compTypes: if searchStr in s: if not compType: compType = result else: if result != compType: raise Exception('More than one type of component selected') return compType }}} {{{ getPanel -vis; }}} Say the user has picked some anim curves in the Graph Editor... what are they? {{{ string$pickedKeys[] = keyframe -query -selected -name;
// Result: pCube2_rotateZ
}}}
{{{
import maya.cmds as mc

keyedFrames = []
node = "myNodeName"
animCurves = mc.listConnections(node, source=True, destination=False, type='animCurve')
for ac in animCurves:
frames =  mc.keyframe(ac, query=True, timeChange=True)
keyedFrames.extend(frames)
keyedFrames = list(set(keyedFrames))
print keyedFrames
[1.0, 45.0, 79.0]
}}}
----
Also see:
* [[How can I query time/value keyframe pairs on an animCurve?]]
{{{getModifiers}}}
{{{
int $mods = getModifiers; // note that it returns back "bit" values, so Shift: 1, CapsLock: 2, Ctrl: 4, and Alt: 8. }}} ---- Here's a modified version of what the docs have, in Python, showing how to grab multiple values at once via [[bitwise operators|http://wiki.python.org/moin/BitwiseOperators]] {{{ import maya.cmds as mc def PrintModifiers(): mods = mc.getModifiers() print 'Modifiers are:' if (mods & 1) > 0: print ' Shift' if (mods & 2) > 0: print ' CapsLock' if (mods & 4) > 0: print ' Ctrl' if (mods & 8) > 0: print ' Alt' print '\n' mc.window() mc.columnLayout() mc.button(label='Press Me', command=lambda *args:PrintModifiers() ) mc.showWindow() }}} {{{displayRGBColor}}} Basically, the stuff found in 'Window -> Settings/Preferences -> Color Settings' For example, to print a list of every possible UI element and their current values: {{{ displayRGBColor -list; }}} prints: {{{ template 0.47 0.47 0.47 preSelectHilite 1 0 0 userDefined1 0.63 0.41391 0.189 userDefined2 0.62118 0.63 0.189 userDefined3 0.4095 0.63 0.189 // etc... }}} To set the background color to black: {{{ displayRGBColor background 0.0 0.0 0.0; }}} It also lets you create new color presets as well.  The {{{upAxis}}} command. {{{ # Python code import maya.cmds as mc up = mc.upAxis(query=True, axis=True) # y }}} {{{MAYA_SCRIPT_PATH}}} is the environment variable in question. The below docs apply equaly well to {{{MAYA_PLUG_IN_PATH}}}. 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. Two examples below to add a new path to the current environment variable, //after// Maya has launched: !!!Python notes: The {{{maya.cmds}}} package doesn't include mel's {{{getenv}}} and {{{putenv}}}, however, Python's {{{os}}} module does contain similar functions ({{{os.getenv}}}, {{{os.putenv}}}). However, based on the below example, {{{os.putenv}}} doesn't actually do anything: It fails to update the path. However, by modifying the dictionary {{{os.environ}}} directly, we are able to make the change. {{{ import os newPath = r"c:\some\path\to\add" # Do this so we have a common base to compare against: comparePath = newPath.lower().replace("\\", '/') notInPath = True sysPath = os.getenv('MAYA_PLUG_IN_PATH') paths = sysPath.split(';') for path in paths: # Make the path we're comparing against the same format # as the one we're trying to add: compare = path.lower().replace("\\", '/').replace("//", '/') if comparePath == compare: notInPath = False break if notInPath: # Update our plugin path: newPath = '%s;%s'%(sysPath, newPath.replace("\\", "/")) #os.putenv('MAYA_PLUG_IN_PATH', newPath) # This doesn't work, interesting... os.environ['MAYA_PLUG_IN_PATH'] = newPath }}} !!!Mel notes: {{{ // 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; }}} Couple different ways: !!!A: *Pick a bunch of objects, with the target last. *Highlight a channel value and hit tab: It will copy that value to all the other selected objects. *Each time you hit tab, it will copy the next value. !!!B: *Pick a bunch of mesh *Pick the numeric values in the Channel Box *RMB on the values -> Duplicate Values There is any annoying feature in Maya that can cause the items highlited in the Graph editor to become de-highlighted... which means you loose all your curves from view. The below code will re-highlight all the objects in the outliner side of the Graph Editor, which will in-turn re-display all their curves: {{{ # Python code import maya.cmds as mc # Maya's standard graph editor outliner name: graphEdOutliner = 'graphEditor1OutlineEd' selectionConnection = mc.outlinerEditor(graphEdOutliner, query=True, selectionConnection=True) mainListConnection = mc.outlinerEditor(graphEdOutliner, query=True, mainListConnection=True) selectList = mc.selectionConnection(mainListConnection, query=True, object=True) if selectList: for so in selectList: mc.selectionConnection(selectionConnection, edit=True, select=so) }}} *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.
Sometimes my thumbnails go away and I just get a default shader ball.  This will fix it for all the scene.  Otherwise, in the Hypershade, you can RMB on any swatch and 'Refresh swatch'.
{{{
import maya.cmds as mc
import maya.mel as mm
for mat in mc.ls(materials=True):
mm.eval('updateFileNodeSwatch("%s");'%mat)
}}}
It should note that the {{{updateFileNodeSwatch}}} mel  procedure calls to the {{{renderThumbnailUpdate}}} command.
----
Also see:
* [[How can I refresh all the textures in the scene?]]
This implements what the "Reload" button on a file texture node does, but for all file nodes in the scene.
{{{
import maya.cmds as mc

print "\nRefreshing all textures in scene:"
files = mc.ls(type='file')
for f in files:
pth = mc.getAttr('%s.fileTextureName'%f)
mc.setAttr('%s.fileTextureName'%f, pth, type='string')
print "\tRefreshed:", f, " : ", pth
print "Texture refresh complete"
}}}
----
Also see:
* [[How can I refresh all material swatches in the Hypershade?]]
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
}}}
Update:
At a certain point Maya's {{{namespace}}} command made this super easy: (but this will delete all the nodes too...)
{{{
import maya.cmds as mc
mc.namespace(deleteNamespaceContent=True, removeNamespace="myAwesomeNamespace")
}}}
Old stuff:
----
{{{
# Python code
import maya.cmds as mc

nameSpace = "myNamespace:";
mc.namespace(force=True, moveNamespace=[nameSpace, ":"])
mc.namespace(set=":")
mc.namespace(removeNamespace=nameSpace)
}}}
This code takes all nodes in the given namespace, 'moves' (renames) them to the root namespace, then deletes the old, empty namespace.
Update:
At a certain point Maya's {{{namespace}}} command made this super easy:
{{{
import maya.cmds as mc
mc.namespace(deleteNamespaceContent=True, removeNamespace="myAwesomeNamespace")
}}}
Old Stuff:
----
Based on unclean scene files, I often need to 'clean out' junk namespaces.  But the junk namespaces often have many, many child-namespaces, which can all have nodes living in them.

The below code first recursively searches through the namspace hierarchy of a given namspace.  Once it has that list, child namespace first, it tries to delete all node in each namespace, then finally delete the namespace itself presuming there were no references or read-only nodes in the namespace.
{{{
# Python code
import maya.cmds as mc

def namespaceClense(namespace):
"""
For the given namespace, and all child namespaces, delete all nodes and namespaces.

namespace : string : Starting namespace.

return : list : Any namespaces that weren't able to be deleted, usually due
to references, or special Maya undeletable nodes.
"""
if namespace.endswith(':'):
namespace = namespace[:len(namespace)-1]

# Get a list of all namespaces to delete data from.
namespaces = [namespace]
for n in namespaces:
mc.namespace(setNamespace=":")
mc.namespace(setNamespace=n)
childNs = mc.namespaceInfo(listOnlyNamespaces=True)
if childNs:
namespaces.extend(childNs)
mc.namespace(setNamespace=":")

# Delete stuff in our child namespaces first, then delete the child namespace:
namespaces.sort()
namespaces.reverse()

undeletedNamespaces = []
for n in namespaces:
# Get a list of everything in the given namespace:
allInNs = mc.ls('%s:*'%n, recursive=True, long=True)
# Put in hierarchy order, and reverse.  Needed for happy deleting, don't
# want to delete parents before kids, or code gets all confused.  And
# after doing tests, they delete 25% faster than not being reversed.
allInNs.sort()
allInNs.reverse()
for node in allInNs:
try:
mc.lockNode(node, lock=False)
mc.delete(node)
except:
pass
# Remove namespace if empty (it should be...)
try:
mc.namespace(removeNamespace=n)
except:
undeletedNamespaces.append(n)

return undeletedNamespaces
}}}
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;
}}}
This works by selecting the actual {{{reference}}} type nodes in the Outliner:  Even if the file they're pointing to is missing on disk, this will remove them.  Maya prevents you from removing a missing reference from the Reference Editor for some reason :(
{{{
import maya.cmds as mc

selRef = mc.ls(selection=True, type='reference')
for ref in selRef:
fName = mc.referenceQuery(ref, filename=True)
mc.file(fName, removeReference=True, force=True)
print "For reference node '%s', removed reference to it's file: %s"%(ref, fName)
}}}

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");
}
}
}}}
{{{
import pymel.core as pm
refs = pm.getReferences()
for key in refs:
ref = refs[key]
if ref.isDeferred():
print "Removed reference:", ref
ref.remove()
}}}
http://download.autodesk.com/global/docs/maya2014/en_us/PyMel/generated/classes/pymel.core.system/pymel.core.system.FileReference.html
When you skin mesh to joints, there is an an option to 'colorize skeleton'.  But what if you want to turn this //off// after the initial bind?  Or if you want to change the individual colors after the fact?

In the UI, the colors are controlled by the 'Display->Wireframe Color...' UI.  In that UI, you can bonk the "Default" button to reset the color.
All this really does is call to the {{{color}}} command:
{{{
import maya.cmds as mc
joints = mc.ls(type='joint')
for j in joints:
mc.color(j)
}}}
Which with no args sets the default color on the selected objects.
{{{strip}}}
*Example:
{{{
string $word = "asdf\r\n"; int$size = size($word) \\ result: 6$word = strip($word);$size = size($word); \\ result: 4 }}} Pretty easily: {{{ import maya.cmds as mc mc.namespace(rename=["wasCalledThis", "nowCalledThis"], parent=":") }}} I'll often have a selection of nodes that I want to have renamed to have concequetive postfix numbers on the end. Say I have a list of nodes like: *foo *foo23 *foo16 *foo1 I want to rename it so that in the same node order their names are: *foo1 *foo2 *foo3 *foo4 ---- A version using ~PyMel, with padding as well: {{{ import pymel.core as pm def renamePadded(padding=4, startNum=1): """ Based on selection, rename all nodes to match the first, incrementing numbers on the end starting with startNum """ sel = pm.ls(selection=True) rootName = sel[0].stripNum().split("|")[-1] pm.undoInfo(openChunk=True) try: for j,item in enumerate(sel): i = j+startNum padStr = "%0"+str(padding)+"d" execStr = 'name = "%s'+padStr+'"%("'+rootName+'",'+str(i)+')' exec execStr item.rename(name) finally: pm.undoInfo(closeChunk=True) renamePadded() }}} ---- Python It's pretty easy using Python's {{{enumerate}}} class and {{{re}}} module: {{{ # Python code # Rename the selected objects to have incremented postfix values. # If any numric postfix exists, remove it and add the new one. import re import maya.cmds as mc startNum = 1 # Reverse our lists, so we rename things from child to parent, so that changing # a parents name won't confuse the childs name-path: sel = mc.ls(selection=True, long=True) sel.reverse() vals = range(startNum, len(sel)+startNum) vals.reverse() for i,s in enumerate(sel): leaf = s.split('|')[-1] try: # Find every occurrence of a number # starting at the end of the string: num = re.findall('[0-9]+$', leaf)[0]
crop = leaf[:-len(num)]
mc.rename(s, '%s%s'%(crop,vals[i]))
except IndexError:
mc.rename(s, '%s%s'%(leaf,vals[i]))
}}}
When it comes to rigging and tools, duplicate node names can be a real pain.  How can you easily rename all the node names in the scene that clash?
{{{
# Python code
import re
import maya.cmds as mc

newNames = []
fail = []

# Get two lists of all names in the scene that are duplicates.
# If there are any dupes, they'll have a pipe in their name:
dupes = [name for name in mc.ls() if "|" in name]
# Get another list with just the leaf names:
dupesLeaf = [name.split("|")[-1] for name in mc.ls() if "|" in name]

for i,e in enumerate(dupes):
# refresh our list of clashing names:
leafCompare = [name.split("|")[-1] for name in mc.ls() if "|" in name]
# if our current leaf node is in our clashing list, rename:
if dupesLeaf[i] in leafCompare:
# If nodes are referenced (etc) they can't be renamed
try:
endNum = re.findall('[0-9]+$', dupes[i]) try: name = dupesLeaf[i][:-len(endNum[0])]+"#" except IndexError: name = dupesLeaf[i]+"#" newName = mc.rename(dupes[i], name) newNames.append([dupes[i], newName]) except Exception, e: # Tell user the the node that was tried to be renamed, and why it failed. fail.append([dupes[i], str(e).strip()]) if len(fail): print "Some nodes couldn't be renamed:" for f in fail: print "\t"+" : ".join(f) if len(newNames): print "Nodes renamed:" for n in newNames: print "\t"+" : ".join(n) print "Rename complete: Renamed %s node(s), and had %s failures, see Script Editor for deatails." %(len(newNames), len(fail)), }}} To just print a list of all the dupe node names in the scene: {{{ # Python code import maya.cmds as mc dupeNodes = [name for name in mc.ls() if "|" in name] for d in dupeNodes: print d }}} See the command: {{{ glRender }}} For example, render the current sequence as defined by the Hardware Render Globals (presuming the Hardware Render Buffer Window is open): {{{ glRender -renderSequence hardwareRenderView; }}} '{{{hardwareRenderView}}}' is the default name of the {{{glRenderEditor}}} used to display the hardware view. Even though {{{glRenderEditor}}} appears to be a type of UI 'panel\editor' (like say, {{{modelEditor}}}), in the mel docs it's categorized under 'Rendering'. ---- Also see: *[[How can I open the Hardware Render Buffer Window?]] *[[How can I change my Render Settings?]] The.... [[reorder|http://download.autodesk.com/global/docs/maya2012/en_us/CommandsPython/reorder.html]] command.... Needed a way to sort all the child nodes under a given transform alphabetically. Below is one way to do it. I was making a temp group to store the nodes before an in-order reparent, but thanks to a tip from Roy Nieterau I switched to using the {{{reorder}}} command for an in-place sort (thanks Roy!). I use ~PyMel since it handles duplicate names in the scene, that dealing with the pure string-names would choke on. Also note that if any of these nodes are locked it will choke: You'll need to unlock them first. {{{ import pymel.core as pm def sortKids(grp): """ Reorder all the transforms under the provided group in alphabetical order. """ sortNodes = sorted(pm.PyNode(grp).getChildren(), key=lambda x: x.nodeName()) map(lambda x:pm.reorder(x, back=True), sortNodes) }}} Give this hierarchy: {{{ |group2 |foo5 |bob |asdf |foo4 |apple }}} Running this command: {{{ print sortKids("group2") }}} Will sort into this hierarchy as a result: {{{ |group2 |apple |asdf |bob |foo4 |foo5 }}}  {{{parent}}} {{{ // mel parent -s -r "shapeNode" "transformNode"; }}} Here's a function in Python that will do it, and clean up the leftover transform nodes: {{{ # Python code import maya.cmds as mc def parentShapes(): """ Select nodes in order: The last node picked will have all other shape nodes parented to it. Note: Should freeze transforms on all nodes before execution! """ sel = mc.ls(selection=True) if len(sel) < 2: raise Exception("Please select at least two objects with shape nodes") source = sel[-1] targets = sel[0:-1] for t in targets: shapes = mc.listRelatives(t, shapes=True) if shapes is not None: for s in shapes: mc.parent(s, source, shape=True, relative=True) mc.delete(t) mc.select(source) }}} ---- Also see: [[How can I parent the instance of a shape node from one object to another?]] For example, you've just created a sphere, and want to redo that action: {{{ repeatLast; }}} Would then execute: {{{ CreatePolygonSphere; polySphere -r 1 -sx 20 -sy 20 -ax 0 1 0 -tx 2 -ch 1; // Result: pSphere1 polySphere1 // }}} *Note, this appears to be an undocumented command, but can be accessed via: {{{ help repeatLast; }}} Also see: *[[How do I query what the last command entered was?]] {{{ // this is the name of the referenced scene you're replacing: string$refScene = "c:/temp/foo.ma";

// find the reference node for this file:
string $refNode = file -q -rfn$refScene;

// name of the scene to replace it with
string $newName = "c:/temp/goo.ma"; file -loadReference$refNode $newName; }}} If you're wondering, this was deduced by picking apart this script: {{{ ../MayaXXXX/scripts/others/referenceEditorPanel.mel }}} In particular, the procs {{{referenceEdRenameCB}}} and {{{findRefNodeToReplace}}}  * Maya has the built-in {{{substitute}}} command, but it only substitutes the //first// instance it encounters. Maya also has the {{{substituteAllString}}} //script//, but it never works like I'd expect. So because of that, we loop over the {{{substitute}}} command until our source no longer contains any of our matching elements. * For example, in our source data have all matching instances of "Happy" replaced with "Sad": {{{ string$source= "Happy Happy Joy Joy";
string $match = "Happy"; string$replace = "Sad";
while(match $match$source == $match)$source = substitute $match$source $replace; print ($source + "\n");
}}}
You can use a combination of this command and mel procedure:
{{{
ogs -reset;
hardwareRenderingGlobalsRevertToDefault;
}}}
Told the [[ogs|http://help.autodesk.com/cloudhelp/2019/ENU/Maya-Tech-Docs/Commands/ogs.html]] command can fix weird display bugs that show up.
The {{{hardwareRenderingGlobalsRevertToDefault}}} global proc lives in this script:
{{{
C:\Program Files\Autodesk\Maya2019\scripts\others\createViewport20OptionsUI.mel
}}}
And it will reset all the 'preset' data in the 'Hardware Renderer 2.0 Settings' menu (Found in the Panel's menu: 'Renderer -> Viewport 2.0').
`