The <tool_body>
of a mouse tool definition is made up of a sequence of tool clauses as follows:
<tool_body> ::= { <tool_clause> }+
Tool clauses define the components of a tool and can be one of two basic things:
Local variables, functions or structure definitions are variables, functions, and structures whose scope is the tool. The <tool_body>
is compiled in its own local scope and the locals are only visible inside the scope of the tool. Tool locals are heap-based locals, which are slightly different from normal (stack-based) locals. Normal locals are visible in the current scope and have a lifetime of one execution of that scope. Heap-based locals are also visible only in the current scope, but have a lifetime equal to the lifetime of the top-level expression where they are defined. A tool’s locals are created the first time you execute the tool and are kept permanently in the heap (unless and until you redefine the tool). This means things like rollouts created in a tool that live beyond the simple execution of the tool can refer to these locals and the locals will still exist. Each time you execute a tool, its local variables are initialized to a value of undefined
, or to their specified initialization value. For example, you can create a rollout in a tool, and the rollout’s event handlers can access the locals defined in the tool because the rollout is executing within the scope of the tool. You cannot access the tool’s locals from another utility or from Listener, because they are not executing within the scope of the tool. See Scope of Variables for more information.
Event handlers are special kinds of functions associated with particular events. Event handlers specify the processing you want to occur when a user clicks or moves a mouse, right-clicks, or starts or stops the tool. These actions generate named events and the optional event handler you supply for that event is called when the action occurs.
Formally, the syntax of a <tool_clause>
is defined as follows:
<tool_clause> ::= <local_variable_decl> | <local_function_decl> | <local_struct_decl> | <event_handler>
Locals
A <local_variable_decl>
, <local_function_decl>
, and <local_struct_decl>
are exactly the same as local variable, function, and structure definitions in MAXScript:
<local_variable_decl> ::= local <decl> { , <decl> }
<decl> ::= <name> [ = <expr> ] -- optional initial value
<local_function_decl> ::= [ mapped ](function | fn ) <name> { <argument> } = <expr>
<local_struct_decl> ::+ struct <name> ( <member> { , <member> } )
<member> ::= ( <name> [ = <expr> ] | <local_function_decl> )
Global variables cannot be declared as a tool clause, however they can be declared within event handler scripts. If you need to ensure a variable name references a global variable, declare the variable name as global immediately before defining the tool in your script.
When writing scripts, it is good programming practice to explicitly declare your local and global variables. Implicit declaration is provided as a short-hand, typically used when working in the Listener interactively or developing short scripts. When developing extended scripts, explicitly declaring variables can reduce errors and improve readability of the code. It is also recommend that you declare as local all variables unless you really want them to be global variables. The reasons for this are described in Scope of Variables.
Within the functions and event handlers of a tool, 13 pre-defined locals variables are always accessible. Each one holds a useful value relating to the mouse action that just occurred:
viewPoint Point2
The current mouse position in viewport pixel coordinates.
worldPoint Point3
The current mouse position projected on the active grid in world coordinates.
worldDist Point3
The distance in X, Y, and Z from the previous click point to the current click point in world coordinates.
worldAngle Point3
The angles around the world X, Y, and Z axes from the previous click point to the current click point in world coordinates.
gridPoint Point3
The current mouse position projected on the active grid in the coordinate system of the active grid. If the active grid is the home grid, the coordinate system is the home grid plane active in the current viewport (that is, the construction plane). See the discussion of grid versus world values below.
gridDist Point3
The distance in X, Y, and Z from the previous click point to the current click point in the coordinate system of the active grid. If the active grid is the home grid, the coordinate system is the home grid plane active in the current viewport (that is, the construction plane). See the discussion of grid versus world values below.
gridAngle Point3
The angles around the world X, Y, and Z axes from the previous click point to the current click point in the coordinate system of the active grid. If the active grid is the home grid, the coordinate system is the home grid plane active in the current viewport (that is, the construction plane). See the discussion of grid versus world values below.
shiftKey Boolean
true
if SHIFT key was down.
ctrlKey Boolean
true
if Ctrl key was down.
altKey Boolean
true
if Alt key was down.
lButton Boolean
true
if left mouse button was down.
mButton Boolean
true
if middle mouse button was down.
rButton Boolean
true
if right mouse button was down.
When a create mouse tool is used in a level 5 scripted plug-in, an additional local variable is accessible:
nodeTM Matrix3
Provides read/write access to the transform of the node currently being created. This value is in the current grid coordinate system.
See Scripted Plug-ins for more information.
When writing SimpleObject scripted plug-ins, you should always use the gridPoint
, gridDist
, and gridAngle
values rather than corresponding world values, as object-creation in SimpleObject scripted plug-ins is managed in the active grid coordinate system.
For gridDist
, the .X and .Y components are the delta X and Y between the previous clicked point and the current mouse point in the plane of the current grid. The .Z component is a projection from the current Y screen coordinates onto a Z-vector (in the grid coordinate system) based at the last point clicked on the grid, such that the .Z value is the projected height up the Z-vector. For non-orthogonal viewports this is as expected, but for orthogonal viewports (in which you are always dragging in the XY plane of the grid), this is always the same as gridDist.y
. If you are using the gridDist value to build the portion of an object on the grid, or determine the distance on the current grid from the last point, you want to use only the .X and .Y components.
FOR EXAMPLE
side1Len = gridDist.x
side2Len = gridDist.y
dist=length [gridDist.x,gridDist.y]
If you are specifying the height off of the current grid, you would typically use the .Z component.
FOR EXAMPLE
height = gridDist.z
For worldDist
, the behavior is similar to that for gridDist
, however the projected component is dependent on the construction plane of the viewport. For the Top, Bottom, and non-orthogonal viewports, the projected height is contained in the .Z component. For Left and Right viewports, the projected height is contained in the .X component. For Front and Back viewports, the projected height is contained in the .Y component.
When you create a node in the 3ds Max user-interface, the local Z axis of the node is perpendicular to the construction plane. When you create a node using MAXScript, by default the local Z axis of the node points along the direction of the world Z axis. If you create a node in a tool (other than in a SimpleObject scripted plug-in), you must take into account the construction plane orientation if you want to duplicate 3ds Max’s behavior when creating a node. The easiest way to do this is to create the node in a coordsys grid
context and specify the position of the node during creation in grid coordinate space. An example of this is shown in the following script.
POINT CREATOR
tool PointCreator
(
local p, createpoint
-- define a function to perform actual node creation. Setting coordsys to
-- ’r;grid’ in order for the alignment of the node’s local Z axis to be
-- perpendicular to the construction grid
fn createpoint = in coordsys grid p=point pos:gridPoint
--
-- set up so that a node is created on a mouse button down, move node
-- drag, release node at mouse button up.
--
-- if clickno == 1, then we are at first mouse click, which is a mouse
-- button down. If clickno != 1, at following mouse button up.
on mousePoint clickno do
(
if clickno == 1
then createPoint()
-- if p == undefined, then clicked twice without mouse movement
-- (double clicked). No point object present, so just ignore this click.
else if p != undefined do (p.pos=worldPoint;p=undefined)
)
-- if p != undefined, we are moving a previously created node
-- if p == undefined, and left mouse button is down, create a node
on mouseMove clickno do
(
if p != undefined
then p.pos=worldPoint
else if lbutton do createPoint()
)
)
-- start the tool. No exit condition defined, so right-click to exit.
startTool PointCreator
Event Handlers
An <event_handler>
is a special function definition local to a tool that you provide to handle the processing you want to occur when a user clicks in a viewport, right-clicks, or starts or stops the tool. Each user action generates a named event and any event handler you supply for that event is called when the action occurs. The available event handlers are:
on start do <expr>
Called when the tool starts.
on stop do <expr>
Called when the tool ends.
on freeMove do <expr>
Called when the mouse moves prior to the first click.
on mousePoint <arg> do <expr>
Called for each mouse click, parameter arg
contains the number of the click.
on mouseMove <arg> do <expr>
Called when the mouse moves anytime after the first click.
on mouseAbort <arg> do <expr>
Called when the user right-clicks to cancel or presses the ESC key.
The <arg>
parameter on each of the mouse event handlers lets you know which mouse click you are in. Mouse clicks are actually only the mouse click release, except for the initial mouse click. If you do a mouse click, drag, release, move, click, drag, and release, the on mousePoint
event handler is called three times: the first click, the first release, and the second release, with <arg>
values of 1, 2, and 3, respectively. The mouse click counter is incremented after processing the on mousePoint
event handler, so the <arg>
parameter for the mouseMove
and mouseAbort
event handlers specify the click count for the next mouse click. In the previous example, when you drag between the first click and first release, the <arg>
value for a mouseMove
event handler will have a value of 2. During the following move, click, and drag it will have a value of 3.
In 3ds Max 6 and higher, while running a mouse tool, if you click in a viewport where the construction plane is on edge, the tool will exit with a return value of #abort
The following script simply demonstrates when the various event handlers are called. Simply run this script and drag in the viewports. Right-click or press the ESC key to stop the tool’s execution.
EXAMPLE
tool foo
(
on freeMove do print "Free Moving"
on mousePoint clickno do format "Point: %\n" clickno
on mouseAbort clickno do format "Abort: %\n" clickno
on mouseMove clickno do format "Moving: %\n" clickno
on start do print "Starting"
)
startTool foo prompt:"Hello!"
All of the mouse action event handlers may return the special value #stop
which causes the tool to stop. In the following mousePoint
event handler example, the first click creates the box and the release (second click) terminates the tool.
EXAMPLE
on mousePoint clickno do
if clickno == 1
then b = box pos:worldPoint
else #stop
The following script implements the following functions: on mouse click three spot lights are created at the mouse point. While holding the mouse button down, drag the mouse to position the classical key, back and fill light positions. During this drag, holding down the SHIFT key will flip the fill light side. Release the mouse button and move the mouse to change the elevation of the lights. Click the mouse button when the lights are at the correct height. The back light is raised 1.5 times the key light’s height, and the fill light is raised 0.75 times the key light’s height.
TO START THIS TOOL, YOU WOULD SAY
startTool three_lights
and then drag in one of the viewports.
EXAMPLE:
tool three_lights
(
local key, fill, back, targ
on mousePoint click do coordsys grid
(
if click == 1 then -- create key, back & fill lights at mousedown
(
targ = targetobject pos:gridPoint
key = targetspot pos:gridPoint name:"key"target:targ
back = targetspot pos:gridPoint name:"back"target:targ
fill = targetspot pos:gridPoint name:"fill"target:targ
)
if click == 3 then #stop
)
on mouseMove click do
(
if click == 2 then -- drag out & round on x-y plane
(
coordsys grid key.pos = gridPoint
coordsys targ back.pos = - key.pos
local p = if shiftKey then 90 else -90
coordsys targ fill.pos = key.pos * ((eulerangles 0 0 p) as quat)
)
else if click == 3 then -- drag up to elevate lights
(
in coordsys targ
(
local Z = gridDist.z
key.pos.z = Z
back.pos.z = Z * 1.5
fill.pos.z = Z * 0.5
)
)
)
)