Developing a Scripted Utility

Hello everyone, this tutorial will guide you to develop a scripted utility in 3DS MAX 9. This tutorial is for users with basic knowledge in max script or any other object oriented language. Here I explained everything step by step. Actually this coding is a part in my plug-in. Due to coding complexity, I cut short some and added only a few things. If time permits I will explain the rest.

It covers:

  • How to develop a tool (with rollouts, buttons, check boxes etc) in 3ds Max.
  • How to position an object over another objects vertex or polygon.
  • Calculating local normals of vertex and polygons in an object and positioning another object according to it.
  • By using basic coding, making copies of an object according to user selection (may be Instance, Copy or references).

What is Scripted Utility?

Max Script allows us to create our own Tools for specific purpose and embed them into existing max interface. The Scripted Utilities are tools appear in Utility panel > Max Script Utility. Scripted utility is an easiest way of developing tools, since they are special kind of rollouts that saves the developer some UI management work.

Creating and running Scripted Utility

To create new script go to MaxScript menu->New (or)
Open Utility panel -> MaxScript->New script.

To run a script, go to MaxScript menu->Run Script (or) Utility panel ->
MaxScript-> Run Script and choose script to be run.

When we put the Script file into \Scripts\Startup folder then it will automatically executed when 3DMax starts.

After execution our utility is available at Utilities panel -> maxScript -> Utility dropdown list (See Fig01). When we select Object Placer the rollout will appear below dropdown list as shown below.

Fig. 01 - OpeningTheScript

Fig. 01 - OpeningTheScript

Purpose of our utility - Object Placer

Object placer allow us to place copies of source object into the destination object's vertices and/or polygon's centre, Also aligned with local normal.

Fig.02 shows the final object placer utility. The contents are:

About rollout with Labels. Parameter rollout with following:
- Two Pick buttons for Source and Destination object selection.
- Two Check boxes for selecting Vertex and /or Polygon.
- A Group named Copy option with three Radio buttons for selecting copy type.

Fig. 02 - ObjectPlacerUtility

Fig. 02 - ObjectPlacerUtility

Fig.03 shows, source object Cone copied and aligned to Geosphere's polygons and another object box copied and aligned with vertices of Geosphere.

Fig. 03 - ObjectWithConeandBox

Fig. 03 - ObjectWithConeandBox

Before using this utility

Actually this tool is not the final one, as I said earlier I added only basic coding. But here I gave you technique of developing the utility and manipulating objects through script. You can extend this script at your own way (At your own risk).

1. Object positioning is based on local normal of a polygon or vertex in destination object and world up axis. So u needs to adjust rotation manually in some places.
2. Scale Source object in Sub-object selection mode (in polygon, edge or vertex).Better use vertex mode for similar result as normal scaling.
3. Source must be geometry object. Before selecting destination object, collapse it into Editable Poly.
4. Modify Source objects pivot for different type of arrangement.
5. When you use Vertex positioning, command panels may blink because it jumps between create and modify panels.

Normal

Surface normal or normal is a vector perpendicular to the surface (Fig04).

Fig. 04 - NormalExample1

Fig. 04 - NormalExample1

Script

Utility ObjPlacer "Object Palcer"                     
 (
       Local copyState=1,sourceObj,destinationObj

fn Align souCopy desNormal pos =      
(                                                                           
   worldUpVector = [0,0,1]                         

   rightVector = normalize (cross worldUpVector desNormal)
   upVector = normalize ( cross rightVector desNormal)        
   theMatrix = matrix3 rightVector upVector desNormal Pos

   souCopy.transform = theMatrix                                                 

)     

fn CopySource numCopies=                                      
    (
         case copyState of                                         
          (
           1:                                                       
                         souCpy = for i = 1 to numCopies  collect(copy sourceObj)                
           2:                                                       
                      souCpy = for i = 1 to numCopies  collect(instance sourceObj)    
           default:                                       
                   souCpy = for i = 1 to numCopies  collect(reference sourceObj)            
          )

              return souCpy    
            )     
rollout Abt "About"                
  (
    Label lab1 "Object Placer"         
    Label lab2 "By Sathish"   
    Label lab3 "Mail:Sathish101@gmail.com"
  )        
rollout Param "Parameters"                                                          
  (

       pickButton sourcePikBtn "Source" width:75 autoDisplay:true 
       pickButton destnationPikBtn "Destination" width:75                   

       checkBox vertexChkBox "Vertex" checked:true                                              
    checkBox polygonChkBox "Polygon"                                               

       group "Copy option"                                                                      
      (

         radioButtons copyOption labels:#("Copy", "Instance", "Reference") align:#left default:1
              )
on sourcePikBtn picked sObj do    
    (
      if  sObj != undefined then 
                sourceObj=sObj                   
            )
on destnationPikBtn picked dObj do                                                                   
    (

         if(sourceObj != undefined) and (not isDeleted sourceObj) then           
         (
         if(vertexChkBox.state == true or polygonChkBox.state == true) then   
         (   
         if dObj!= undefined then      
       (
               destinationObj=dObj      
               If ((classOf destinationObj) == Editable_poly) then      
                     (

                      if (polygonChkBox.state == true) then                           
                        (
                         numPolygon = destinationObj.getnumfaces()              

                            Source =CopySource numPolygon                            

                            for i = 1 to numPolygon do                                       
                              (
                              faceCentre= polyOp.getFaceCenter destinationObj i          
                              faceNormal = polyOp.getFaceNormal destinationObj i         

                              Align source[i] faceNormal faceCentre 
                              )   
                            )                          
                  if (vertexChkBox.state == true) then                          
                        (
                         numVertex = destinationObj.getNumVertices()    

                            source =CopySource numVertex
                   editNormalsModifier=edit_Normals()                          

                          addModifier destinationObj editNormalsModifier

                      setCommandPanelTaskMode #modify 
                             destinationVertex = #{}           
                             destinationNormalIds = #{}            

                             for i = 1 to numVertex do               
                              (
                                   select destinationObj         
                                   destinationVertex = #{i}     

                                   destinationObj.edit_Normals.convertVertexSelection &destinationVertex; &destinationNormalIds;   

                                normalArray = destinationNormalIds as array

                                   firstNormalValue = destinationObj.edit_Normals.getNormal normalArray[1]
                                   vertexPos= polyOp.getVert destinationObj i     

                                   Align source[i]  firstNormalValue vertexPos    
                       )

                            deleteModifier destinationObj(editNormalsModifier)        
                            )     

               )
                      else
                            messageBox "Select only Editable poly object" title:"Error"
              )       
         ) 
               else
                            messageBox "Select Vertex or/and Polygon" title:"Error"
           ) 
               else
                            messageBox "Select Source Object" title:"Error"    
            )
on copyOption changed State do   
  (
   copyState=State                           
  )

 )    
 on ObjPlacer open do    
(
 addRollout Abt                
 addRollout Param           
 )
 on ObjPlacer  close do      
 (
  removeRollout Abt           
  removeRollout Param      
 )
)

Script Explanation

utility ObjPlacer "Object Placer"

A Utility is created using a constructor utility with name ObjPlacer and caption Object Placer.

Local copyState=1,sourceObj,destinationObj

Local variables are alive untill we close the utility is closed.

NOTE: Align and CopyOption functions are explained below.

About rollout

rollout Abt "About"

A rollout named Abt with caption "About" is defined.

Label lab1 "Object Placer"
Label lab2 "By Sathish"
Label lab3 "Mail:Sathish101@gmail.com"

Labels are used to display information and it could not be altered. (If u need syntax details refer MaxScript reference).

Parameter rollout

pickButton sourcePikBtn "Source" width:75 autoDisplay:true
pickButton destnationPikBtn "Destination" width:75


Two pick buttons with caption Source and Destination is created with width =75. In Source pick button autoDisplay is enabled to display selected objects name as button caption.

checkBox vertexChkBox "Vertex" checked:true
checkBox polygonChkBox "Polygon"


Two checkboxes for Vertex and Polygon is created. Initially Vertex check box is enabled.

group "Copy option"
(
radioButtons copyOption labels:#("Copy", "Instance", "Reference") align:#left default:1
)


A group with three radio buttons for selecting copy type such as Copy, References or Instance is created. Radio buttons are aligned left in group box and Copy is set as default.

on sourcePikBtn picked sObj do
(
if sObj != undefined then
sourceObj=sObj
)


This function will be called when source pick button picked. It checks whether object selected or the operation cancelled, after clicking pick button.

If selected then assign it to a global variable.

on destnationPikBtn picked dObj do
(
if(sourceObj != undefined) and (not isDeleted sourceObj) then
(
if(vertexChkBox.state == true or polygonChkBox.state == true) then
(
if dObj!= undefined then
(
destinationObj=dObj
If ((classOf destinationObj) == Editable_poly) then
(
..............................
..............................
..............................
)
else
messageBox "Select only Editable poly object" title:"Error"
)
)
else
messageBox "Select Vertex or/and Polygon" title:"Error"
)
else messageBox "Select Source Object" title:"Error"


Following operation will took place when destination pick button is picked.

Check whether Source object already selected and not deleted else display message " Select Source Object".

Check whether vertex and/or polygon checkbox checked else display message "Select Vertex or/and Polygon"

Check whether destination object is picked after clicking destination button or the operation is cancelled.

Check whether selected object is Editable poly, else displays message "Select only Editable Poly object".

If polygon checkbox is checked then following operation will took place.

numPolygon = destinationObj.getnumfaces()
Source =CopySource numPolygon


Get number polygons in destination object.

Get copies of source object according to number of polygons in destination object and store it into an array.

for i = 1 to numPolygon do
(
faceCentre= polyOp.getFaceCenter destinationObj i
faceNormal = polyOp.getFaceNormal destinationObj i
Align source[i] faceNormal faceCentre
)


Iterate from 1 to numPolygon.

Get face centre and face normal of i th polygon in destination object.

Call Align function by passing i th copy of source object, face center and face normal of i th polygon in destination object.

End of iteration.

The following operation will took place when vertex check box checked

numVertex = destinationObj.getNumVertices()
source =CopySource numVertex
editNormalsModifier=edit_Normals()
addModifier destinationObj editNormalsModifier setCommandPanelTaskMode #modify
destinationVertex = #{}
destinationNormalIds = #{}

For getting normal of vertex, we need to do some more work

Get number of vertices in destination object.

Make copies of source object equal to number of vertices, by calling CopySource function.

Add Edit Normals modifier to Destination object.

Set modify panel to be the active panel in the view port. We can get normal value only when modify panel is active and destination object is selected.

Declare two bit array for representing destination vertex and its normals.

for i = 1 to numVertex do
(
select destinationObj
destinationVertex = #{i}
destinationObj.edit_Normals.convertVertexSelection &destinationVertex; &destinationNormalIds;
normalArray = destinationNormalIds as array
firstNormalValue = destinationObj.edit_Normals.getNormal normalArray[1]
vertexPos= polyOp.getVert destinationObj i
Align source[i] firstNormalValue vertexPos
)


Iterate from 1 to number of vertex

Select destination object. As I said already we can get normal value only when modify panel is active and destination object is selected.

Make the vertex bit array to represent i th vertex.

Get normas(normal Id's) available in vertex i. Single vertex may contain more than one normal See fig below (vertex 7 contains 3 normals with Id 8, 19, 24.)

Store array of destinationNormals to normalArray, because destinationNormals is bit array. In bit array the corresponding bit value is set to true and all other values are set to false, but in this case we need normal Id. For example consider figure below, for 7 th vertex the bit values 8 ,18, 24 are set to true. If we convert bit array to an array we can get Id value 8 at normalArray[1].

firstNorml retrieves actual vector value of the first normal in the vertex. In below fig firstNormal =[0,0,1] for normal 8.

Get Position of i th vertex.

Call function Align by passing i th copy of Source object, normal and position of destination objects i th vertex (Fig05).

Fig. 05 - NormalExample2

Fig. 05 - NormalExample2

Other functions in Utility

on copyOption changed State do
(
copyState=State
)

This function will be called when copy option radio buttons are changed.

If changed then store current state to a variable copyState.

on ObjPlacer open do
(
addRollout Abt
addRollout Param
)

This function will be called when object placer utility opened. Add About and Parameter rollout to the utility panel.

on ObjPlacer close do
(
removeRollout Abt
removeRollout Param
)

This function called when object placer utility closed.

Remove About and Parameter rollout in the utility panel.

Align function

Align function calculates the transformation value of source object from a polygon or vertex of destination object. This technique is also explained in MaxScript user reference "How do I align the UVMap modifier to the selected face?" (Fig06)

Fig. 06 - NormalCalculation

Fig. 06 - NormalCalculation

fn Align souCopy desNormal pos =
(
worldUpVector = [0,0,1]
rightVector = normalize (cross worldUpVector desNormal)
upVector = normalize ( cross rightVector desNormal)
theMatrix = matrix3 rightVector upVector desNormal Pos
souCopy.transform = theMatrix
)


Align function with three arguments. A copy of source object, destination objects normal and position.

The default value of world up vector is [0, 0, 1]

Calculate right vector from world up vector and normal value

Calculate local up vector from right vector and normal value.

Store right vector, up vector normal and position to theMatrix as a matrix3 value

Set Source object transformation to theMatrix

Copy Option Function

The copy option function make copies of source object according to user's selection.

fn CopySource numCopies=
(
case copyState of
(
1:
souCpy = for i = 1 to numCopies collect(copy sourceObj)
2:
souCpy = for i = 1 to numCopies collect(instance sourceObj)
default:
souCpy = for i = 1 to numCopies collect(reference sourceObj)
)
return souCpy


If copy option function called

Check the state of copy option radio buttons.

According to copy type make copy of object and store it to an array souCpy .

Return souCopy.

Script Link: Objectplacer.ms

I hope this tutorial is helpful. If u have any questions or suggestions, just mail me sathish101@gmail.com.

Enter content...

Fetching comments...

Post a comment