编写用于“绘制脚本工具”(Paint Scripts Tool)的 MEL 脚本

本部分假定了解 MEL 脚本和编程的知识。

有关 MEL 的详细信息,请参见面向程序员的 MEL

“绘制脚本工具”(Paint Scripts Tool)的依据是在 NURBS 或多边形曲面上叠加的二维数值数组的概念。该数组由曲面的顶点位置定义或由均匀分布于曲面参数空间的任意 2D 栅格定义。将该数值数组想象成 2D 灰度图像,其中各像素对应一个数组位置,而灰度值对应与该数组位置相关联的数值。与“绘制脚本工具”(Paint Scripts Tool)相关联的脚本确定 Artisan 解释数值数组的方式。需要知道指定给如上所述其中之一数组位置的数值时,Artisan 调用脚本。更改指定给其中一个数组位置的数值时,Artisan 也可以调用脚本。

脚本绘制 MEL 程序

“绘制脚本工具”(Paint Scripts Tool)由一组 MEL 程序定义。MEL 程序的名称在“工具设置”(Tool Settings)编辑器的“设置”(Setup)区域显示。您还可以使用 MEL 命令 artUserPaintCtx 设置 MEL 程序。本章介绍了以下 Artisan MEL 程序:

在下列描述中,用于设置命令的 artUserPaintCtx 标志括在括号内。

“工具设置命令”(Tool Setup Command) (-tsc "ToolSetupCommand")

如果已定义,一选定(通过使用修改 > 绘制脚本工具(Modify > Paint Scripts Tool)菜单项或从“工具箱”(Tool Box)或工具架中选择)“绘制脚本工具”(Paint Scripts Tool)之后,即调用 ToolSetupCommand。该程序常用于设置所有其他的 Artisan MEL 程序。该程序定义如下:

global proc ToolSetupCommand (
	string $toolContextName
)
{
	// This is an example artUserPaintCtx command that would setup
	// the following Artisan procedures. This is not a realistic
	// example because you would never have all the procedures
	// defined.
	//
	artUserPaintCtx -e
		-tcc "ToolCleanupCommand"
		-gac "GetArrayAttributeCommand"
		-ic "InitializeCommand" -fc "FinalizeCommand"
		-svc "SetValueCommand" -gvc "GetValueCommand"
		-gsc "GetSurfaceCommand"
		$toolContextName;
}

其中,$toolContextName 是选定工具上下文的名称。旨在将其用作 artUserPaintCtx 命令的最终参数。

“工具清理命令”(Tool Cleanup Command) (-tcc "ToolCleanupCommand")

如果已定义,在即将退出“绘制脚本工具”(Paint Scripts Tool)之前,调用 ToolCleanupCommand。该程序定义如下:

global proc ToolCleanupCommand (
	string $toolContextName
)
{
	...
}

其中,$toolContextName 是已退出工具上下文的名称。

“获取数组属性命令”(Get Array Attribute Command) (-gac "GetArrayAttributeCommand")

如果定义该命令,可忽略对初始化命令、完成命令、设置值命令以及获取值命令定义的任何命令。

如果已定义,一旦选定用于绘制的各个曲面,即调用 GetArrayAttributeCommand。该程序返回字符串,其解释为指称某些依存关系节点上双数组属性的名称列表。该程序定义如下:

global proc string GetArrayAttributeCommand (
	string $surfaceName
)
{
	string $arrayAttributes
	// determine array attributes that correspond to
	// $surfaceName
	...
	return $arrayAttributes;
}

其中,$surfacename 是曲面的名称,其与返回的数组属性相关联。如果返回多个数组属性,Artisan 需要一个值时将读取第一个属性,但是 Artisan 会将所有属性都写入。

“初始化命令”(Initialize Command) (-ic "InitializeCommand")

如果已定义,开始每个笔划前会调用一次 InitializeCommand。该程序定义如下:

global proc string InitializeCommand(
	int $surfaceName
)
{
	string $flags;
	// build up $flags
	//
	...
	return $flags;
}

其中,$surfacename 是曲面名称。有关示例,请参见以下目录中的 spherePaint.mel 和 geometryPaint.mel:

  • (Windows) Drive:\Program Files\Autodesk\Maya<版本>\scripts\others
  • (Linux) mayapath/scripts/others
  • (Mac OS X) /Applications/Autodesk/Maya<版本>/Maya.app/Contents/scripts/others

该程序应:

  • 为该曲面确定唯一的整数 曲面 ID。该 ID 将被传递到与该曲面相关联的 SetValueCommand、GetValueCommand 和 FinalizeCommand 的未来调用。如果脚本保持每个曲面的信息,将该 ID 变成数组索引是极容易的,索引可用于轻松访问每个曲面的信息。
  • 确定未来调用与该曲面相关联的 SetValueCommand 哪种类型的信息。
  • 确定是在曲面顶点绘制,还是在曲面参数空间中等间距分布的栅格上绘制。

所有这类信息通过程序传递回的 $flags 变量传递回 Artisan。该字符串包含一系列标志和参数,这些标志和参数与能够传递到标准 MEL 程序中的标志和参数非常类似。下面介绍了“绘制脚本工具”(Paint Scripts Tool)能够识别的标志。每个标志都有一个短版本和一个长版本(按下面的方式即为:-短/长)以及一些参数:

  • -id/identifier int

    该标志表示即将用于该曲面的整数曲面 ID。如果该标志未指定,该曲面的曲面 ID 为 -1。

  • -uv/uvlong string

    该标志指示是否应该将 (U,V) 位置发送到 SetValueCommand。对于此标志,可能的值为 none、surface 或 normalized。默认值为 none。

  • -p/position string

    该标志指示是否应该将 (x,y,z) 位置发送到 SetValueCommand。对于此标志,可能的值为 none、local 或 world。默认值为 none。

  • -n/normal string

    该标志指示是否应该将 (nx,ny,nz) 法线发送到 SetValueCommand。对于此标志,可能的值为 none、local 或 world。默认值为 none。

  • -g/grid int1 int2

    该标志指示应在曲面 U 方向大小为 int1 且曲面 V 方向大小为 int2 的参数空间内等间距分布的栅格上进行绘制。如果未指定该标志,将在曲面顶点定义的数组上进行绘制。

  • -j/jitter boolean

    该标志仅与 -grid 标志配合使用时才有效。如果为 true,那么在将其传递到 SetValueCommand 之前,栅格位置发生抖动。默认值为 false。

  • -d/dither boolean

    该标志仅与 -grid 标志配合使用时才有效。如果为 true,Artisan 使用 16x16 抖动矩阵,以确定是否为其调用 SetValueCommand。

  • -dt/directionType string

    该标志指示 (U,V,W) 笔划方向是否应发送到 SetValueCommand。对于此标志,可能的值为 none、screenV(屏幕空间中的向量)或 worldV(世界空间中的向量)。默认值为 none。对于投影绘制,screenV 和 worldV 均返回屏幕空间中的向量。方向选项不支持反射绘制。如果请求的方向为屏幕向量,那么第三个值 (W) 将无效。

  • -sp/stampPosition string

    该标志指示 (spX,spY,spZ) 图章位置是否应发送到 SetValueCommand。对于此标志,可能的值为 none、local 或 world。默认值为 none。

  • -pa/path boolean

    此标志指示应将曲面路径发送到 SetValueCommand/GetValueCommand。默认值为 false。

“完成命令”(Finalize Command) (-fc "FinalizeCommand")

如果已定义,每个笔划完成后,各可绘制曲面调用一次 FinalizeCommand。该程序定义如下:

global proc FinalizeCommand(
	int $surfaceID
)
{
	...
}

其中,$surfaceID 是由该曲面相应的 InitializeCommand 指定给曲面的曲面标识符。

“设置值命令”(Set Value Command) (-svc "SetValueCommand")

如果已定义,Artisan 设置了与曲面的特定“位置”相关联的值后,调用 SetValueCommand。每个位置调用该程序一次。因此,如果某个 Artisan 绘制操作在曲面上的 10 处位置更改值,那么调用 SetArrayAttributeCommand 10 次。该程序定义如下:

global proc SetValueCommand(
	int $surfaceID,
	int $index,
	float $value,
	//
	// The following arguments are only passed if requested by
	// InitializeCommand. However this is the order that they
	// will be passed in.
	//
	float $u,			// ($u,$v) is UV location on surface
	float $v,				
	float $px,			// ($px,$py,$pz) is position on surface
	float $py,
	float $pz,
	float $nx,			// ($nx,$ny,$nz) is surface normal
	float $ny,
	float $nz,
	string $path			// the full path to the associated surface
)
{
	...
}

其中:

  • $surfaceID 是由该曲面相应的 InitializeCommand 指定给曲面的曲面标识符
  • $index 是顶点或栅格索引
  • $value 是即将指定给传递索引的值

传递到该程序的附加参数取决于曲面 InitializeCommand 的返回。

“获取值命令”(Get Value Command) (-gvc "GetValueCommand")

如果已定义,Artisan 想要与曲面的特定“位置”相关联的值时,调用 GetValueCommand。该程序定义如下:

global proc float GetValueCommand(
        int $surfaceID,
        int $valueIndex,
        //
        // The following arguments are only passed if requested by
        // InitializeCommand. However, this is the order that they
        // will be passed in.
        //
        string $path
)
{
	float $value
	// determine the value associated with $valueIndex location
	// on surface $surfaceID
	//
	...
	return $value;
}

其中:

  • $surfaceID 是由该曲面相应的 InitializeCommand 指定给曲面的曲面标识符
  • $valueIndex 是 Artisan 请求其值的某“位置”的索引

“获取曲面命令”(Get Surface Command) (-gsc "GetSurfaceCommand")

很少对该命令进行定义。如果已定义,Artisan 每次处理选择列表时,选择列表上各依存关系节点调用一次 GetSurfaceCommand。任何时候选定“绘制脚本工具”(Paint Scripts Tool),通常都会出现这种情况。该程序定义如下:

global proc string GetSurfaceCommand (
	string $selectedDependencyNode
)
{
	string $surface;
	// Set $surface based on $selectedDependencyNode.
	// Set $surface to NULL string (""), if no surface 
	// is to be painted because of $selectedDependencyNode.
	//
	...
	
	return $surface;
}

该程序覆盖 Artisan 对选择列表的默认处理。

示例脚本绘制脚本

本部分介绍总体脚本布局并给出注释脚本示例。

总体脚本布局

用于“绘制脚本工具”(Paint Scripts Tool)的脚本可以以多种方式排列。该示例脚本布局用于采样 Artisan 脚本,可见于:

  • (Windows) Drive:\Program Files\Autodesk\Maya<版本>\scripts\others
  • (Linux) mayapath/scripts/others
  • (Mac OS X) /Applications/Autodesk/Maya<版本>/Maya.app/Contents/scripts/others

布局在一个文件中保留所有 MEL 程序,所有的脚本,并仅允许填充“工具设置”(Tool Settings)窗口“设置”(Setup)区域中的“工具设置命令”(Tool Setup Cmd)框的方式定义所有脚本。该程序通过使用 artUserPaintCtx 命令设置所有其他 MEL 程序。对此的详细介绍如下:

// Define global variables used throughout the script
//
global ...;
// Define the Tool Setup Cmd procedure. This procedure will
// setup all the other MEL procedures used by the Paint //Scripts tool.
//
// "spherePaint" would be entered into the Tool Setup Cmd field in
// the Setup section of the tool settings window.
//
global proc spherePaint( string $context )
{
	artUserPaintCtx -e
		-ic "initSpherePaint"
		-fc "finishSpherePaint"
		-svc "setSpherePaintValue"
		-gvc "getSpherePaintValue"
		-gsc ""
		-gac ""
		-tcc ""
		$context;
}
// Define various procedures that were mentioned in the Tool
//Setup Cmd procedure above. All the procedures defined by
//the artUserPaintCtx command have to be global.
//
global proc string initSpherePaint( string $surfaceName )
{
	...
}
...

spherePaint.mel

这是一个注释版本的 spherePaint.mel 文件,提供位置如下:

  • (Windows) Drive:\Program Files\Autodesk\Maya<版本>\scripts\others
  • (Linux) mayapath/scripts/others
  • (Mac OS X) /Applications/Autodesk/Maya<版本>/Maya.app/Contents/scripts/others

该脚本仅适用于 NURBS 曲面。

//
// This is a simple example script for the Artisan Paint 
//Scripts tool. It will paint spheres onto the selected
//surfaces. The size of the spheres are controlled by the
//painted values.
//
// Usage:
// 1) Place this script into your scripts directory (usually
//the Maya/scripts directory in your home directory
// 2) Select the Paint Scripts Tool (Modify > Paint Scripts
// Tool) and bring up the Tool Settings window
// 3) Go to the Setup section and enter "spherePaint" into 
// the "Tool Setup Cmd" field and hit enter
// 4) Paint Geometry
//
// Tips:
// Once you have the Geometry Paint Tool setup you may want
// to it from the toolbar to the shelf so that it is always
// accessible
//

这些全局变量用于确定正在绘制的曲面数量并确定曲面 ID。$sphereNamePrefix 是一个字符串数组,每个活动曲面一个字符串。如果条目为空字符串,则表示可获取其数组索引,并将其用作曲面 ID。如果该条目非空,那么创建球体时将该字符串用作前缀。$spherePaintFreeSlot 是 $sphereNamePrefix 的第一个可用条目,$spherePaintSlots 是 $sphereNamePrefix 的当前大小。曲面 ID 使用数组索引来存储每个曲面信息,这种方法经常在一些其他的采样 Artisan 脚本中频繁使用。

// These are global variables used to keep track of multiple
// surfaces and the name prefixes used for the spheres on each
// surface
//
global string $sphereNamePrefix[];
global int $spherePaintFreeSlot = 0;
global int $spherePaintSlots = 0;

spherePaint 是初始化“绘制脚本工具”(Paint Scripts Tool)的程序,方法是告知其在各种情况下应调用哪些程序。

// This procedure should be set as the "Tool Setup Cmd" in the 
// Setup section of the Maya Artisan Paint Scripts Tool’s tool settings
// window. The tool context is supplied as an argument.
//
global proc spherePaint( string $context )
{
	// initialize all the other commands in this scriptable 
	// paint tool context.
	// 
	artUserPaintCtx -e
		-ic "initSpherePaint"
		-fc "finishSpherePaint"
		-svc "setSpherePaintValue"
		-gvc "getSpherePaintValue"
		-gsc ""
		-cc ""
		-tcc ""
		-gac ""
		$context;
}
// This is the "Initialize Cmd". This procedure is called once
// for every selected surface when an initial click is received
// on any surface. The argument is the name of the surface. This
// procedure returns a string which indicates to the scriptable
// tool how to behave for the duration of the stroke. 
//
global proc string initSpherePaint( string $name )
{
	global string $sphereNamePrefix[];
	global int $spherePaintFreeSlot;
	global int $spherePaintSlots;
	int $slot;

首先要确定与该曲面相关联的曲面 ID。这可以通过查阅 $sphereNamePrefix 数组,查找第一个可用条目完成。一旦找到条目,该条目索引将被用作曲面 ID ($slot)。

	// find a free slot for this surface in the global arrays
	//
	for ( $slot = $spherePaintFreeSlot; $slot < $spherePaintSlots; $slot++ )
	{
		if ( $sphereNamePrefix[$slot] == "" ) {
			break;
		}
	} 
	if ( $slot == $spherePaintSlots ) {
		$spherePaintSlots++;
		$spherePaintFreeSlot = $spherePaintSlots;
	}

下一步是确定传入的 $name 是否对应于 NURBS 曲面。如果对应,那么生成已绘制球体的前缀,并将其存储到 $sphereNamePrefix 数组中相应的条目。

	if ( ‘nodeType $name‘ == "nurbsSurface" ) {
		// save the name of the parent of this shape as well
		// as a prefix to use when creating the spheres
		//
		string $parent[] = `listRelatives -p $name`;
		$sphereNamePrefix[$slot] = $parent[0] + "Sphere";
	}

初始化程序的最终功能是返回一个字符串,该字符串指示 Artisan 如何处理该曲面。该字符串是特殊标志及其参数组成的一个序列,上文已有描述。在本例中,字符串包含的标志可指示 Artisan:

  • 应为该曲面使用哪个曲面 ID
  • 绘制应在曲面的参数空间上均匀分布的 20x20 栅格上进行
  • 任何时候一旦调用,即将世界空间的位置传递到 setSpherePaintValue。
	// Return an argument string which:
	// - tells the tool what surface ID to use for this surface
	// - indicates that values should be distributed on a 20x20
	// grid on the surface
	// - indicate that the associated world space position
	// should also be passed to the "Set Value Cmd".
	//
	return ( "-id " + $slot
		 + " -grid 20 20"
		 + " -position world");
}
// This is the "Finalize Cmd". This procedure is called at the
// end of the stroke. It is passed the surface ID, that was
// generated by the "Initialize Cmd".
//
global proc finishSpherePaint( int $slot )
{

在笔划结束时调用。使用 $slot 清除该曲面在 $sphereNamePrefix 中的相应条目。如果数组中已有 $slot,也可以用 $slot 更新 $spherePaintFreeSlot。

	
	global string $sphereNamePrefix[];
	global int $spherePaintFreeSlot;
	// clear out the slot that was used for this surface
	//
	$sphereNamePrefix[$slot] = "";
	if ( $slot < $spherePaintFreeSlot ) {
		$spherePaintFreeSlot = $slot;
	}
}
// This is the "Set Value Cmd". It is called everytime a value
// on the surface is changed. A surface ID, a grid index
// on the surface and the value associated with that grid index
// is passed. There can be additional arguments depending on the
// options generated by the return value of the "Initialize Cmd".
// In this case the (x,y,z) surface position for this grid point
// is also passed.
// 
global proc setSpherePaintValue(
	int $slot,
	int $index,
	float $val,
	float $x,
	float $y,
	float $z
)
{
	global string $sphereNamePrefix[];

确定 $slot 是否是有效的曲面 ID,方法是检查 $sphereNamePrefix 中的相应条目是否并非空字符串。

	if ( $sphereNamePrefix[$slot] != "" ) {

已绘制的所有球体都具有唯一的名称,该名称是曲面的 $sphereNamePrefix 和栅格索引的组合。这正是该脚本确定在此位置是否已进行绘制的方式。该方法的一个缺点是它对 initSpherePaint 中指定的栅格大小较为敏感。例如,索引 44 将对应于 10x10 栅格上的栅格位置 (4,4)。但是,如果栅格大小已更改为 20x20,索引 44 现在将对应于栅格位置 (2,4)。

		// determine the name of the sphere associated with this
		// grid location 
		//
		string $objname = $sphereNamePrefix[$slot] + $index;
		if ( ‘objExists $objname‘ ) {

在本例中,球体已经存在。因此,传递的值 $val 将用作球体的总体比例因子。作为一种特殊情况,如果值小于或等于 0,应移除对应的球体。

			// if the sphere already exists, use the value to
			// adjust the size of the sphere. If the value is
			// 0, the sphere is deleted
			//
			if ( $val > 0 ) {
				scale $val $val $val $objname;
			} else {
				delete $objname;
			}
		} else if ( $val > 0 ) {

在本例中,对应于该栅格位置没有已绘制的球体。使用球体 MEL 命令创建一个球体,并使用 -name 选项为新创建的球体给出所需名称。球体创建之后,均匀地更改球体的比例因子,直至达到传入的值 ($val)。此时球体也将定位,通过将球体从原点(默认情况下在此处创建球体)移动到传入该程序的世界空间位置 ($x,$y,$z),可以使球体出现在正确的位置。

			
			// the sphere doesn’t exist
			//
			string $sname[];
			// create a sphere with the proper name, scale it by
			// the passed value and parent the sphere to the same
			// parent as the surface we are painting on
			//
			$sname=‘sphere -ch off -name $objname‘;
			if ( $sname[0] != $objname ) {
				print ("SPHERE NAME FAILED: wanted "
					+ $objname + " got " + $sname[0] + "\n");
			}
			scale $val $val $val;
			move $x $y $z;
		}
	}
}
// This is the "Get Value Cmd". It is called everytime a value
// on the surface is needed by the scriptable paint tool. A
// surface ID and a grid index is passed in. This procedure should
// return the value for this grid location on the specified surface.
// 
global proc float getSpherePaintValue( int $slot, int $index )
{
	global string $sphereNamePrefix[];
	if ( $sphereNamePrefix[$slot] != "" ) {
		// if this slot is valid, generate the name for the
		// sphere at this grid index
		//
		string $objname = $sphereNamePrefix[$slot] + $index;

任何时候 Artisan 需要一个对应于特定栅格位置的值时,便会调用该程序。在该脚本中,值是球体的比例因子。

		
		if ( ‘objExists $objname‘ ) {

如果在 $index 引用的位置上绘制了球体,将返回球体的 X 轴比例因子。X 轴比例因子也没有什么神奇奥妙之处。可能很容易拥有 Y 轴或 Z 轴比例因子,或者是所有与此相关的比例因子的平均值。这完全取决于脚本。

			
			// if the sphere exists, return the X scale factor
			// as the value for this grid location
			//
			return ‘getAttr ($objname + ".sx")‘;
		} else {

如果在该位置没有球体,那么仅返回 0.0 作为该位置的值。

			// the sphere doesn’t exist, therefore return 0 as
			// the value for this grid location
			//
			return 0.0;
		}
	} else {
		return 0.0;
	}
}

以下是几个试验,可以尝试使用该脚本,以便要了解其工作方式。

更改栅格大小

更改第 89 行自:

		 + " -grid 20 20"

为:

		 + " -grid 40 30"
		

绘制圆锥体而不是球体

更改第 154 行自:

			$sname=‘sphere -ch off -name $objname‘;

为:

			$sname=‘cone -ch off -name $objname‘;

抖动栅格

添加行:

		 + " -jitter true"

于第 90 行之前。