How To ... Develop A Face Area XView Checker - Part 1

How To > Develop A Face Area xView Checker - Part 1

The following tutorial demonstrates the creation of a custom xView Checker using MAXScript functions.

The xView Checker is available in 3ds Max 2010 and higher.

Related topics:

xViewChecker Access

Interface:xViewChecker

NATURAL LANGUAGE

Create a global struct with properties and functions

Define an xView geometry checker function

Define a supported function

Register xView Checker.

SCRIPT:

(
global FaceAreaChecker
struct FaceAreaCheckerStruct
(
FaceAreaThresholdMin = 1,
FaceAreaThresholdMax = 10,
faceAreaDialog = undefined,
 
fn geomCheck theTime theNode theResults =
(
  local theCount = case classof theNode of
  (
    Editable_Poly: getNumFaces theNode
    Editable_Mesh: theNode.numfaces
  )
  local theAreaMethod = case classof theNode of
  (
    Editable_Poly: polyOp.getFaceArea
    Editable_Mesh: meshOp.getFaceArea
  )
  
  for f = 1 to theCount do
  (
    local theArea = theAreaMethod theNode f
    if theArea >= FaceAreaChecker.FaceAreaThresholdMin and theArea <= FaceAreaChecker.FaceAreaThresholdMax do append theResults f
  ) 
3
),
 
fn supportedCheck theNode =
(
  classof theNode == Editable_Mesh or classof theNode == Editable_Poly
),
 
fn configDlg =
(
  try(destroyDialog FaceAreaChecker.faceAreaDialog)catch()
  rollout faceAreaDialog "Face Area Checker"
  (
    spinner spn_FaceAreaThresholdMin "Min. Threshold:" range:[0,1000000,FaceAreaChecker.FaceAreaThresholdMin] offset:[7,-3] type:#worldUnits
    spinner spn_FaceAreaThresholdMax "Max. Threshold:" range:[0,1000000,FaceAreaChecker.FaceAreaThresholdMax] offset:[7,-3] type:#worldUnits
   
    fn updateDisplay =
    (
      XViewChecker.runCheck CurrentTime
      max views redraw
    )
    on spn_FaceAreaThresholdMin changed val do
    (
      FaceAreaChecker.FaceAreaThresholdMin = val
      updateDisplay()
    ) 
    on spn_FaceAreaThresholdMax changed val do
    (
      FaceAreaChecker.FaceAreaThresholdMax = val
      updateDisplay()
    )   
  )--end rollout
  
  createDialog faceAreaDialog
  setDialogPos faceAreaDialog mouse.screenpos
  FaceAreaChecker.faceAreaDialog = faceAreaDialog
)
)--end struct
 
try(destroyDialog FaceAreaChecker.faceAreaDialog)catch()
FaceAreaChecker = FaceAreaCheckerStruct()
 
XViewChecker.unRegisterChecker "Face Area Checker"
XViewChecker.registerChecker FaceAreaChecker.geomCheck FaceAreaChecker.supportedCheck #Faces "Face Area Checker" FaceAreaChecker.configDlg undefined undefined
)--end script

Step-By-Step

(

The complete script is in a local scope.

global FaceAreaChecker

Only the instance of the struct containing the xView Checker properties and functions will be exposed to the global scope.

struct FaceAreaCheckerStruct (

We start a struct to encapsulate all properties and functions. It will be local to the current scope.

FaceAreaThresholdMin = 1,

This property will contain the minimum face area to check for.

FaceAreaThresholdMax = 10,

This property will contain the maximum face area to check for.

faceAreaDialog = undefined,

Our xView Checker will include a settings dialog. To be able to close it between sessions, we will store the rollout of the dialog in this property. This will give us global access to the dialog without directly storing it in a global variable.

fn geomCheck theTime theNode theResults =
(

This is the main xViewChecker function.

An xView Checker function must expect three arguments which are passed to it by the system - the time at which the check is performed, the node being checked, and an array to put the results from the check into.

local theCount = case classof theNode of
(
Editable_Poly: getNumFaces theNode
Editable_Mesh: theNode.numfaces
)

Since we want to be able to check both Editable Poly objects and Editable Meshes, we check the class of the node passed to the function and get the face or polygon count using the correct method or property.

local theAreaMethod = case classof theNode of
(
Editable_Poly: polyOp.getFaceArea
Editable_Mesh: meshOp.getFaceArea
)

Again, based on the class of the node, we store the method to get the polygon or face area in a user variable. This will both speed up the access to the method and allow us to use the same expression to get the face area in the following loop.

for f = 1 to theCount do
(

We loop through all the faces or polygons of the node

local theArea = theAreaMethod theNode f

We call the cached method for polygon resp. face area and pass the node and the face index to it.

if theArea >= FaceAreaChecker.FaceAreaThresholdMin and theArea <= FaceAreaChecker.FaceAreaThresholdMax do append theResults f

Then we can compare the value to the min. and max. threshold and if the area is between the two thresholds, we append the face index to the results array.

) 
3

The xViewCheck function must return an integer specifying the result type.

We return 3 because we stored the indices of the faces in the results array.

The other options are 2 for edges, 1 for vertices and 0 if the check failed.

This will help the rest of the xView Checker system figure out how to use the data in the results array to display in the viewport or convert the results to a sub-object selection.

),

fn supportedCheck theNode =
(
classof theNode == Editable_Mesh or classof theNode == Editable_Poly
),

The supported check function is called by the xView Checker system to determine whether a node supports the Checker or not. In our case, we want to allow the checking of Editable Meshes and Editable Poly objects, so we check the class of the node and compare to these classes. Any other objects will be skipped.

fn configDlg =
(

This function will be called by the xView Checker system whenever the user clicks on the "Click Here To Configure" button in the Viewport. If this function was not registered with the checker (but passed as undefined value instead), that viewport button would not be displayed and the system would assume the Checker does not support a configuration dialog.

try (destroyDialog FaceAreaChecker.faceAreaDialog)catch()

We attempt to close any dialog that might have been opened previously. We use the property of the struct instance where we will assign the rollout used by the dialog.

If the dialog is not open, nothing will happen.

rollout faceAreaDialog "Face Area Checker"
(

Now we define a rollout to display as a configuration dialog.

spinner spn_FaceAreaThresholdMin "Min. Threshold:" range:[0,1000000,FaceAreaChecker.FaceAreaThresholdMin] offset:[7,-3] type:#worldUnits
spinner spn_FaceAreaThresholdMax "Max. Threshold:" range:[0,1000000,FaceAreaChecker.FaceAreaThresholdMax] offset:[7,-3] type:#worldUnits 

We define two spinners to control the min. and max. threshold values used by the test. Both spinners are set to default to the current properties of the struct instance and display world units. Of course, we will be checking for SQUARE units, but the spinners will show just regular linear units. So if the spinner shows 10", we will interpret the value as 10 square inches.

fn updateDisplay =
(
XViewChecker.runCheck CurrentTime
max views redraw
)

This function will be called when User Interface elements (the spinners) change their values. The function calls the method to run the current xView check at the current time, resulting in updated checker data. To force the display of the new data, we have to do a full redraw of all viewports.

on spn_FaceAreaThresholdMin changed val do
(
FaceAreaChecker.FaceAreaThresholdMin = val
updateDisplay()
) 

Whenever the min. threshold spinner is changed, we store the new value in the struct instance's property and call the update function to refresh the viewports.

on spn_FaceAreaThresholdMax changed val do
(
FaceAreaChecker.FaceAreaThresholdMax = val
updateDisplay()
)   

We do the same with the max. threshold spinner.

)--end rollout 

createDialog faceAreaDialog

At this point, we can create a dialog from the rollout. The size of the dialog will be set automatically based on the size of the controls in the rollout.

setDialogPos faceAreaDialog mouse.screenpos

Then we reposition the dialog to the mouse position, so when the user clicks the "Click Here To Configure" button in the viewport, the dialog will appear next to the mouse pointer.

FaceAreaChecker.faceAreaDialog = faceAreaDialog

Finally, we store the rollout definition in the property of the struct instance. This way, the attempt to close the dialog before the rollout definition will access the correct value.

) )--end struct

try (destroyDialog FaceAreaChecker.faceAreaDialog)catch()

This attempt to close the dialog is useful when developing the script and re-evaluating the source again and again - each time the script is run, a previous open dialog will be closed. Otherwise, orphaned dialogs could accumulate during testing.

FaceAreaChecker = FaceAreaCheckerStruct()

Here we create the instance of the local struct and assign to the global variable. This way, any other script or the Listener can access the properties and functions inside the struct instance, which simplifies both development and debugging.

XViewChecker.unRegisterChecker "Face Area Checker"

We unregister the XView Checker by name. If the same code is run multiple times, we ensure the previous version is removed before registering it again.

XViewChecker.registerChecker FaceAreaChecker.geomCheck FaceAreaChecker.supportedCheck #Faces "Face Area Checker" FaceAreaChecker.configDlg undefined undefined

Finally, we register our Checker with the xViewChecker system.

The first argument is the function to call to check scene objects.

The second argument is the function to call to determine if a scene object can be checked.

The third argument defines the type of the checker - whether it operates on Faces, Edges or Vertices. This is necessary for the Selection conversion to interpret the content of the results array as the right type of Sub-Object indices.

The fourth argument is the name of the checker. It is used both to display the checker in the list and the viewport and to access the checker by name when unregistering it.

The fifth argument is the function to call when the "Click Here To Configure" button is clicked. It can be passed as undefined if no dialog is necessary.

The sixth argument is the function to call to override the text in the viewport next to the Checker's name. We pass undefined since we don't want a text override at this point. We will implement such a function in the next part of the tutorial.

The last argument is the function to call to override the default display functionality. We pass again undefined because we want to use the default display feature at this point. We will implement such a function in the next part of the tutorial.

)--end script

Using the xView Checker

At this point, we can evaluate the code by pressing Ctrl+E and use the xView checker, but it will not be visible in the Viewport Menus yet.

To see the new checker, enable any other xView checker like for example Face Orientation, then click on the [Face Orientation] label at the bottom of the viewport to display the list of registered checkers. The Face Area Checker should appear there.

Select it from the list to enable it.

The label [ Face Area Checker ] will be displayed at the bottom of the screen and if a valid object (Editable Mesh or Editable Poly) is selected, the faces/polygons will be tested and shaded in the viewport.

To configure the Min. and Max. Thresholds, click on the [ Click Here To Configure ] label.

To make the checker available in future 3ds Max sessions without the need to evaluate the script manually, copy the script to the \Startup folder or any folder registered as a plugin path OR its sub-folders.

We will take a look at how to add a custom xView Checker to the Viewport Menus in Part 4 of this tutorial.

Next

How To ... Develop A Face Area XView Checker - Part 2

See Also