Archive for the ‘MEL / Maya’ Category

Drag ‘n’ Drop UI in Maya

Monday, June 14th, 2010

Creating Drag/Droppable UI in Maya is a really nice way of making your UIs either more user-friendly or adding a lot more functionality without adding any more visible UI controls. It is also very easy to set up and manage once you know how.

Nearly all UI controls (of all types; buttons, sliders, fields, layouts, etc..) support being a drag zone, drop zone or both. If you add a drag callback flag to your UI control it will be dragable, if you add a drop callback it will be a drop zone. Add both callbacks and your UI control can be dragged and dropped to!

To make a piece of UI drag or droppable you simply add the appropriate flag and it’s function to the control command. For example:

button -l "Create Cube" -c "polyCube" -dgc "dragButton" -dpc "dropButton";

This creates a normal button that, when pressed creates a cube. When MMB-dragged, the button will execute the function dragButton and if anything is dropped onto the button, the function dropButton will be executed.

Every UI control uses these same two flags to support drag/drop (-dgc and -dpc) and when their functions are executed Maya automatically passes certain parameters into them.

Of course we have to write these callback functions, and every callback you write must always contain the same correct input parameters:

// the drag function:
proc string[] dragProc(string $dragCtrl,int $x, int $y, int $mods)
{ ... }
// and the drop function:
proc dropProc(string $dragCtrl, string $dropCtrl, string $msgs[], int $x, int $y, int $typ)
{ ... }

Don’t worry about what all those params do just yet, we’ll get to that later, for now lets just create some test UI:

{
string $win = "dragDropTestWindow";
if (`window -exists $win`)	deleteUI -window $win;
window -title "Drag/Drop Window" $win;
columnLayout -adj 1 -rs 5;
button -h 50 -l "Drag Me!" -dgc "dragCallBack";
button -h 50 -l "Drop Here!" -dpc "dropCallBack";
button -h 50 -l "Drag/Drop Here!" -dgc "dragCallBack" -dpc "dropCallBack";
separator -h 20;
button -h 50 -l "MMB-Click me!" -dgc "dragCallBack" -dpc "dropCallBack";
setParent ..;
window -e -wh 200 300 $win;
showWindow $win;
}

Now if you run that MEL script it should display a test window with four large buttons. Of course they won’t work yet as we’ve not defined our callback functions, but lets do that now…

global proc string[] dragCallBack(string $dragCtrl,int $x, int $y, int $mods)
{
	print ("dragCallBack - Drag control: "+$dragCtrl+"\n");
	return {"A","B","C"};
}

This is the DRAG callback. You can call it anything you like but it must have four input parameters (but again, as long as they are a string and three ints, you can call them whatever you like). This callback function is fired as soon as you MMB-drag on any UI control with it specified in its -dgc flag. The drag callback function MUST return a string array otherwise it will NOT register the drag/drop functionality at all!. If you’ve ever tried to get drag/drop working and been unsuccessful, chances are this was the reason!

Even if you don’t want your drag callback to return any data or messages it still has to return a string array, although returning an empty array is fine if you really don’t want any actual data.

The primary input parameter is: $dragCtrl. When the callback is executed this parameter contains the (full) name of the UI control you dragged on. To be honest I’ve never really needed this data as you also get it inside the drop callback, but it’s here anyway if you do need it for something. The $x and $y params pass in the screen x and y positions you clicked on and $mods holds any keyModifier values, so if you want to check the user is holding down SHIFT or CTRL or whatever you can. (see Maya’s getModifiers command for more details on modifiers).

You may find that in your production UI you have several different drop callbacks, but you can often recycle the same drag callback for all the UI controls. In all my UIs so far the drag callback does practically nothing, it simply exists to ensure that the dropping works, as you cannot have one without the other!

global proc dropCallBack(string $dragCtrl, string $dropCtrl, string $msgs[], 
			int $x, int $y, int $type)
{
	string $text = "dropCallBack:\n";
	$text += ("    Drag control: "+$dragCtrl+"\n");
	$text += ("    Drop control: "+$dropCtrl+"\n");
	$text += ("    messages: ");
	$text += `stringArrayToString $msgs ", "`;
	confirmDialog -m $text;
}

This is the DROP callback and it’s the important one that does all the work. Basically, dragging something usually has no effect in itself right? it’s only once you let go and drop it that something happens! As with the drag function, you may call this anything you like, but make sure your function always has these same six parameters.

The first two are strings which contain the (full) names of the drag control and the drop control. These are important because you will always want to know what you dragged and what you dropped it on to determine what action to take. The third parameter is the message array. This simply contains whatever the drag callback returned. While this is often unused it is a way to pass your own data from the drag to the drop zone if you need to. The $x and $y parameters are the screen position of the drop and $type is supposed to somehow indicate move, copy and link, but I’ve never found any details of how to utilise this parameter. I’ve never needed to use any of these three last parameters and chances are you won’t either, but don’t forget they still have to be declared as function parameters.

Once you’ve input the UI code above and defined both drag and drop callbacks you should have a fully working Drag/Drop UI to play with.

You can legally drag/drop controls from any drag/drop UI to any other drag/drop UI, even between different windows and even when they have different callback functions. The only way to restrict what is permitted to be dropped on what is by making your drop callbacks check some attribute or property of the drag control and return if not “compatible”.

Unfortunately you cannot interact with the existing drag/drop controls inside Maya. For example you cannot drop a shelf button or a hypershade swatch onto a custom UI drop zone or drop your custom drag objects onto a maya drop zone (i.e. viewport, outliner, hypershade, etc..)

Protected: Labyrinth for Maya

Saturday, June 5th, 2010

This content is password protected. To view it please enter your password below:

Maya Scripting – Working with vectors in Python

Tuesday, May 4th, 2010

I avoided writing anything model-related in python for ages because I thought it didn’t have a native vector data type like MEL does. It doesn’t as it happens, but OpenMaya comes with one that is really simple to use and understand called: MVector.

Ryan Trowbridge has already gone over the basics of this on his blog, but I thought I’d cover it in a bit more detail, especially with some real-world examples.

The first thing you have to do is import the MVector class from the OpenMaya module:

from maya.OpenMaya import MVector

Once this is done you can start creating vectors like so:

X = MVector(1.0, 0.2, 0.3)
Y = MVector(0, 1, 0)

The MVector simply takes 3 floats which represent the x, y and z components of the vector. You can’t, unfortunately, print out the MVector values by refering directly to it:

print X
<maya.OpenMaya.MVector; proxy of <Swig Object of type 'MVector *' at 0x18b95ad0> >

but you can refer to the individual components:

print X.x
# 1.0
print X.y
# 0.2
print X.z
# 0.3
print X.x, X.y, X.z    # in one line
# 1.0 0.2 0.3
mc.setAttr('pCube1.tx', X.x)    # use as you would ordinary float variables

You can also do cool and useful stuff with the vectors very easily:

mag = X.length()    # gets the vector magnitude(length)
print mag
# 1.06301458127
X = X.normal()    # normalises the Vector

Z = X^Y    # the ^ character is a shortcut for the CrossProduct function
d = X*Y    # the * returns the dotProduct if both items are vectors
Yb = Y*5.0    # but if one item is a single number it simply multiplies the vector
print Yb.x,Yb.y,Yb.z
# 0.0 5.0 0.0

If you want to capture command output into an MVector it’s just as easy as with MEL, you just have to add one minor addition, the * character:

V = MVector(*mc.pointPosition('pCube1.vtx[0]'))

The * character, in front of a list in python is kind of like shorthand for “unpack”. Because MVector expects 3 individual floats (doubles actually, but that’s splitting hairs!) you cannot legally pass it a list, even if the list is 3 elements long. The * tells the list to “unpack” itself into it’s constituent elements, so the MVector accepts it. This also holds true for anywhere in python where you need to pass a list in as individual params.

There’s a lot more you can do with MVectors, but I hope this basic stuff is enough to help you replicate what you can do with vectors in MEL. If you want to check out the more complex MVector methods and uses have a look in the Maya API – MVector docs

Code Syntax Highlighting – part II

Saturday, April 17th, 2010

Seeing as how this is supposed to be (partially at least) a MEL and Python code blog I thought it only proper that I set up some kind of code syntax highlighting.
However, if I’d known what utter hell it was going to be I may have reconsidered. It’s taken me basically the whole day to get this much working and that’s only due to the help of a WordPress/PHP savvy mate (cheers John).

The first thing I tried was Code-Snippet, which seems pretty good and allowed me to easily write and impliment a new MEL syntax file. But I couldn’t get the colours to change from the defaults, it used the wrong kind of quotes (not good for MEL/py code!) and I couldn’t get word-wrapping to turn off either..! so I ditched that and tried SuperHighlighter Evolved.

SuperHighlighter Evolved seemed great but I couldn’t make that work like the site said it would either. Additionally, creating a new colour theme or Syntax file required a degree in scripting this kind of crap as well as a full understanding of WordPress’s underlying PHP gobbledegook. Goodbye SuperHighlighter Evolved…
 
Finally I plumped for Syntax Highlighter and Code Prettifier (by Vijesh Mehta) which seems to almost work so far.. once I’ve hacked around with the settings a bit! 😛

like so:

MEL Script :

// this proc returns the id number(s) of the scriptjob(s) using the given command (if existing)
// this can be used not only to simply query the scriptjob Id so you can edit or kill it, but
// also as a true/false exists check as it returns false if the scriptjob is not found.
global proc int[] getScriptJobID(string $command)
{
	string $sjs[] = `scriptJob -lj`;	// list ALL jobs
	int $jobs[] = {};
	for ($sj in $sjs)
	{	// loop through each job description looking for our command string
		if (`gmatch $sj ("*"+$command+"*")`)
		{
			string $token[];
			tokenize $sj ":" $token;
			$jobs[size($jobs)] = $token[0];
		}
	}
	return $jobs;
}

Python Script:

def typecastString(inputStr):
	"""Given an input STRING this returns it cast into the correct type;
	int, float, or string depending on what it contains."""

	inputStr = str(inputStr)	# just in case the input is not a string

	output = inputStr

	if inputStr.isdigit():
		output = int(inputStr)
	elif inputStr.replace('.','',1).isdigit():
		output = float(inputStr)

	return output

Syntax Testing

Thursday, February 5th, 2009
//
//	removeInvalid: takes an input string and returns a valid DAG name string (alpha-numerics only)
//
global proc string removeInvalid(string $input, int $mode)	// 0=strip , 1=replace_
{
	string $output = "";
	for ($n = 1 ; $n < size($input)+1 ; $n++)
	{
		string $char = `substring $input $n $n`;
		if ($n == 1 && `match "[0-9]" $char` != "")
			$output += ("_"+$char);
		else	if (`match "[a-zA-Z0-9_]" $char` != "")
			$output += $char;
		else	if ($mode)
			$output += "_";
	}
	return $output;
}

Breaking infinite loops in MEL

Friday, August 22nd, 2008

Quite often, especially when you are writing and testing a new MEL script, you’ll inadvertently create a for or while loop that never exits. Usually due to a testing condition that never becomes true, 99% of times it’s probably due to a dumb oversight that is (hopefully) picked up during initial testing/debugging, but infinite loops can still sometimes occur in production scripts. Even if you haven’t got stuck in an infinite loop you may suddenly realise that you’ve started a script with the wrong type of, or too many, components selected and the script will be churning away for a good 2-3 hours if not interrupted!

Once a bit of MEL enters such a loop there is no way of breaking out of it other than killing Maya’s process from the Windows Task Manager. Obviously this is a bit of a tedious job at the best of times, especially if it’s a script that needs thorough testing, but what if your loop-locked script was running on a scene you’d not saved for a couple of hours?

Here’s an excellent and, so far, bulletproof (touch wood) method of protecting yourself and your scripts. Because Maya has locked up at this point we can’t issue any kind of command or process from Maya. We can however issue a command from outside Maya, say from DOS…

If you create a batch file somewhere on your PC with the following content:

@echo off
echo This file breaks infinite loops in MEL scripts > c:\breakMel
pause Maya halted - Press any key to continue
del c:\breakMel

call it HALTMAYA.bat or STOPLOOP.bat or whatever name works best for you so you don’t forget what it’s for. If you have no idea what a batch file is, simply copy and paste the above code into a text file and save it as one of the names suggested above (make sure you don’t have file extensions hidden in windows!).

Now, when you run this batch file it should open a DOS shell window and prompt you to press a key to continue. Pressing a key dismisses the DOS box…

So? what exactly did this do and how does it help us stop an infinite loop??

Well if we take a look at the batch code it should be relatively self explanatory, even if you don’t know any DOS batch commands. The first line simply hides the echoing of commands (kinda like Maya script Editor “Echo All Commands” set to “off”). The second line echos the specified sentence, but the important bit is the > at the end. This > symbol tells the echo commad to redirect the echoed words into a file instead of onto the screen. That’s why you never see these words printed in the DOS window, they are sitting in a file called “breakMel” on the root of your C: drive. Of course you can change these words and the name of the file, as they are entirely irrelevant and no-one will ever see either of them. All that’s important here is that a detectable file has been created. The third line simply pauses DOS and prompts you to press a key. This is vitally important because we’ve no idea how long the rogue MEL script will take to get once through the loop it’s stuck in, and we need to ensure that it detects the breakMel file we’ve just created. The pause allows us to sit and wait for the loop to detect the file before we proceed to the fourth and final line which deletes it. If we don’t delete the break file your MEL script will find it again next time it is run and halt immediately!

So, the final piece of the puzzle.. how does your MEL script detect the creation of this file? It’s quite straightforward; you just put a single line inside the loop in your MEL script:

if (`filetest -f "c:/breakMel"`) break;

Every iteration through your loop will look for the existence of a file called “c:\breakMel” and if it exists it will break out of the loop. If you have more than one nested loop though, you will either need to put the same line in each nested level of the loop or change the break command into an error, which is a lot simpler:

if (`filetest -f "c:/breakMel"`) error "User broke out of infinite loop!";

Believe it or not, adding this command doesn’t appear to have any noticeable effect on the performance speed of a script. I initially thought checking for a file would take MEL ages but surprisingly it doesn’t, at least not in this context. I’ve run timing tests with and without this line and there’s no significant difference, so you don’t need to worry about adding this feature to all your scripts. Of course, once you’re convinced your script is fully robust enough that it can’t get stuck in an infinite loop you can always remove this line, but it won’t hurt if you do decide to leave it in.

The other good thing about this trick is that it has no dependencies, so you can safely distribute the script to anyone without worrying that the line will cause any kind of problems at their end, as it’s unlikely anyone will ever have a file called “breakMel” on their C:\ drive!

You can do a similar thing in Python:

import os
if os.path.exists("c:/break"): break

MEL Scripts

Monday, August 11th, 2008

Here’s a link to my old MEL web site:

Naughty’s MEL Scripts – 404

Eventually I’ll be updating all these old scripts and putting them on here, but until then you can access the older version here.

[EDIT]
Someone’s pointed out that this old site is now defunct and returns a 404 error (thanks Daniel Lee!). so, until I get my arse in gear and make a new page here, you should be able to grab the scripts from this web directory:

Naughty’s MEL Scripts