A SimpleObject is a kind of geometry primitive that is defined by a tri-mesh such as, a box, sphere, cone, and so on. In other words, a simpleObject plugin is similar to all the Standard and Extended primitives in 3ds Max.
In scripting a SimpleObject plug-in, you need to supply a handler for building this mesh and 3ds Max automatically handles the scene display, hit-testing, ray intersection, rendering, modifier applicability, and others.
A scripted SimpleObject plug-in is declared by specifying the <superclass>
as simpleObject
.
Scripted SimpleObject plug-ins require a create
tool.
A SimpleObject plug-in must not perform any scene related actions such as, creating scene nodes or building modifier stacks. It must only adjust its parameters in the mouse tool handlers and construct a mesh in the buildMesh
handler. Attempting to create scene nodes or applying modifiers in a SimpleObject plug-in will cause all kinds off strange interactions with the in-progress scene node creation.
A SimpleObject plug-in has a predefined local variable mesh
. This variable contains the underlying mesh of the object being created. The mesh
local variable is accessible in any of the plug-in's handlers, but is typically only built in the buildMesh
handler. You can either modify the existing TriMesh value in place using the TriMesh methods, or construct a new TriMesh value and assign it to the mesh
plug-in local variable. The TriMesh methods and properties are described in Editable Mesh.
SCRIPT:
plugin simpleObject tower_plugin_def name:"Tower" classID:#(145345,543211) category:"Scripted Primitives" ( parameters main rollout:params ( height type:#worldUnits ui:height default:0 width type:#worldUnits ui:width default:0 depth type:#worldUnits ui:depth default:0 ) rollout params "Two Faces Parameters" ( spinner height "Height" type:#worldunits range:[-1000,1000,0] spinner width "Width" type:#worldunits range:[-1000,1000,0] spinner depth "Depth" type:#worldunits range:[-1000,1000,0] ) on buildMesh do ( setMesh mesh \ verts:#([0,0,0],[width,0,0],[width,depth,0],[0,depth,0]) \ faces:#([3,2,1], [1,4,3]) extrudeFace mesh #(1,2) (height * 0.5) 40 dir:#common extrudeFace mesh #(1,2) (height * 0.5) 50 dir:#common ) tool create ( on mousePoint click do case click of ( 1: nodeTM.translation = gridPoint 3: #stop ) on mouseMove click do case click of ( 2: (width = gridDist.x; depth = gridDist.y) 3: height = gridDist.z ) ) )
In this script a new primitive called Tower is defined. It constructs a very simple tower form; the first drag sets the base and the second drag sets the height. It contains three animatable rollout parameters: height
, width
, and depth
, which set the object's basic bounds. The key components here are the create
tool and the on buildMesh
handler, and they work together in a simple way. The create
tool sets the values of the parameters and the on buildMesh
handler constructs a mesh based on the parameter values.
The create
tool can also access and set the position of the node that is being created to contain the new object through the Matrix3 value in the pre-defined mouse tool local variable nodeTM
. In this case, the position portion of the node's TM is set to the initial mouse down world position. The on mouseMove
handler sets the width and depth during click 2 and the height during click 3.
The on buildMesh
handler constructs the desired mesh in the TriMesh value in the pre-defined plug-in local variable mesh
. Typically, it does this by building the mesh from scratch each time the handler is called. In the above example, the mesh is initially set to a simple two-face rectangular plane (using setMesh()
), and then the two faces are extruded up and scaled-down twice to get the simple tower.
The mesh
plug-in local variable is accessible in any of the plug-in's handlers, but is typically built only in the on buildMesh
handler. You can either modify the existing TriMesh value in place using the TriMesh methods, or construct a new TriMesh value and assign it to the mesh
plug-in local variable. The TriMesh created must be valid or a 3ds Max crash might occur. All face, vertex, material ID, and texture vertex arrays allocated must be filled in the handler.
There are three additional event handlers that might be implemented for a scripted SimpleObject:
on OKtoDisplay do <boolean_expr>
If the OKtoDisplay
event handler is implemented, <boolean_expr>
must return true
or false
depending on whether it is OK to display the current mesh. This is useful in situations where a mesh might be in some degenerate state for certain parameter settings and must not be displayed in the viewports. The default implementation is true
. Note that empty meshes are OK to display.
on hasUVW do <boolean_expr>
If the hasUVW
event handler is implemented, <boolean_expr>
must return true
or false
depending on whether UVW coordinates are present on the mesh. In many primitive objects, a Generate Mapping Coords checkbox is provided for the user to control UVW coordinate generation and the hasUVW
handler expression returns the state of this checkbox. The default implementation returns true
or false
depending on whether the mesh has texture vertices or not. At present, only a single map channel is supported.
on setGenUVW <onOff> do <expr>
The setGenUVW
event handler is called when 3ds Max wants the plug-in to automatically generate mapping coordinates. This happens when you first render an object that has a material applied, but not mapping coordinates. It is called with a switch argument <onOff>
, which is true
or false
to turn on or turn off the mapping coordinates. If your plug-in is managing mapping coordinates, it must implement this handler and generate mapping coordinates when called with a true
argument.
EXAMPLE
plugin simpleObject TexturePlane name:"TexturePlane" classID:#(0x19a75166, 0x83e3a1c7) category:"MAXScript Examples" ( parameters main rollout:params ( Size type:#worldUnits ui:size default:0 genMapCoords type:#boolean ui:gen_Map_Coords default:false animatable:false u_tile type:#float default:1.0 ui:u_step v_tile type:#float default:1.0 ui:v_step ) rollout params "TexturePlane" ( group "Geometry" ( spinner size "Size:" type:#worldunits range:[0,1000,1] ) group "Mapping" ( spinner u_step "U Tiling" scale:0.01 spinner v_step "V Tiling" scale:0.01 checkbox gen_Map_Coords "Generate Mapping Coords." ) ) --end roll --When Max wants to know whether the object has UV coords, --return the value stored in genMapCoords to inform it of --the current UV sate of the plug-in. on hasUVW do genMapCoords --When 3ds Max tells the plug-in to turn on UV coordinates --(for example when applying a texture --with "Show Map In Viewport" checked), --this handler will automagically check the checkbutton --connected to our parameter called genMapCoords. --Also, when the user changes the checkbox, --the variable will tell the builMesh to generate coordinates. --Note that the handler will override the manual settings, --for example if a texture has "Show Map In Viewport" enabled, --you cannot actually uncheck the "Generate Mapping Coords." --checkbox with the mouse! on setGenUVW bool do genMapCoords = bool on buildMesh do ( --Generate a simple Quad local vertex_array = #() append vertex_array [-size/2,-size/2,0] append vertex_array [size/2,-size/2,0] append vertex_array [size/2,size/2,0] append vertex_array [-size/2,size/2,0] face_array = #( [1,2,3], [1,3,4] ) setMesh mesh verts:vertex_array faces:face_array --If the checkbox was enabled and we want --coordinates to be generated... if genMapCoords then ( --define the texture vertices local uvw_array = #() append uvw_array [0.0,0.0,0.0] append uvw_array [u_tile ,0.0,0.0] append uvw_array [u_tile ,v_tile ,0.0] append uvw_array [0.0,v_tile ,0.0] --set the number of vertices setNumTVerts mesh uvw_array.count --set all vertices for v = 1 to uvw_array.count do setTVert mesh v uvw_array[v] --build texture faces buildTVFaces mesh false --set all texture faces (in this case using the mesh faces --since we happen to have a one-to-one correspondence --between vertices and texture vertices.) --This is not always the case though... for f = 1 to face_array.count do setTVFace mesh f face_array[f] ) )--end on buildMesh tool create ( on mousePoint click do case click of ( 1: ( nodeTM.translation = gridPoint ) 2: ( #stop) ) on mouseMove click do case click of ( 2: (Size= abs gridDist.x) ) ) )--end plugin
Here is another example that creates a mesh by first building other temporary objects and using mesh booleans to build the final mesh:
SCRIPT
plugin simpleObject squareTube name:"SquareTube" classID:#(63445,55332) category:"Scripted Primitives" ( local box1, box2 parameters main rollout:params ( length type:#worldUnits ui:length default:1E-3 width type:#worldUnits ui:width default:1E-3 height type:#worldUnits ui:height default:1E-3 ) rollout params "SquareTube" ( spinner height "Height" type:#worldUnits range:[1E-3,1E9,1E-3] spinner width "Width" type:#worldUnits range:[1E-3,1E9,1E-3] spinner length "Length" type:#worldUnits range:[-1E9,1E9,1E-3] ) on buildMesh do ( if box1 == undefined do (box1 = createInstance box; box2 = createInstance box) box1.height = height; box2.height = height box1.width = width; box2.width = width * 2 box1.length = length; box2.length = length * 2 mesh = box2.mesh - box1.mesh ) tool create ( on mousePoint click do case click of ( 1: nodeTM.translation = gridPoint 3: #stop ) on mouseMove click do case click of ( 2: (width = abs gridDist.x; length = abs gridDist.y) 3: height = gridDist.z ) ) )
In this example, the parameters and rollouts are handled in a similar manner to the first example, but the buildMesh
handler generates the mesh in an indirect way. The final object is in the form of a rectangular pipe, a box with a box hole through the middle. Two plug-in locals ( box1
and box2
) are made to contain Box base objects whose size parameters are set according to the plug-in's parameters ( box2
is the outer box and box1
is the hole). The final mesh is made by boolean subtraction of box1
from box2
. In this case, a separate new mesh is created and assigned to the mesh
plug-in local variable, in contrast to the first example that modified the object's original mesh directly. Either method is okay.
The createInstance()
method is used to directly create the box base objects. This method creates the geometry associated with the specified object, but does not create a node. This method is used since SimpleObject plug-in must not perform any scene related actions such as, creating scene nodes or building modifier stacks.