RandomSel Script

From The Foundry MODO SDK wiki
Revision as of 21:16, 30 March 2012 by Jangell (Talk | contribs) (Querying the User Value)

Jump to: navigation, search

This example shows how to use Perl scripts to read the state of the model through ScriptQuery interfaces and execute commands, as well as using user values to create a simple user interface with Forms. The script RandomSel allows you to randomly select points, polygons and edges in a model. What percentage of the model is selected is governed by a user value with the name RandomSel.Coverage.

The script uses the select.typeFrom command to determine the current selection mode. It then checks the value of RandomSel.Coverage using the user.value command. The query command is then used to walk the list of selected points, polygons and vertices via the layerservice ScriptQuery interface, and chances the selection using the select.element command.

The Script

Here is the entire random selection script. The leading line numbers are for clarity only. Blank lines are so the line numbers will match between scripts. Note that the Python implementation could use the lx.Service method to obtain a Service object instead of using the query command.


Now a line by line breakdown of the script:


All scripts must start with a language-specific header. Some languages, such as Perl, often included a path as well, such as #/user/bin/perl, but that isn't required here. The Perl example also turns on the Perl’s strict and warnings flags. In Python, we import the random module.


Using lxout() for Debugging

Throughout the script there are commented-out calls to lxout (Perl/Lua) or lx.out (Python), which were originally used for debugging purposes. Removing the comment character(s) will cause these lines to show up in the Event Log Viewport, such as this one stating that the script has started executing.


Querying the User Value

Line 13 marks the first call to the lxq (Perl/Lua) or lx.eval (Python) function. In this case, the user.value command is being queried for the value of RandomSel.coverage, the creation of which will be discussed later. The value is stored in the variable chance. RandomSel.coverage is defined as a percent with a minimum of 0% and a maximum of 100%. This means that the chance variable will be between 0.0 and 1.0. There is also another commented-out call to lxout to report the chance of selection by using the 0% to 100% value of chance as stored in chancePercent.


Next, the script defines two new variables. The selType variable is the mode argument for the select.element command, which will be used later to select vertices, polygons or edges. The attrType variable is used when querying the layerservice ScriptQuery interface.


Checking the Selection Mode

The next section checks to see what the current selection mode is via the Select.typeFrom command. This command takes a semicolon-delimited list of selection types to test against. If the first type in the list has been more recently selected than any of the others, the query will be true. These tests are used to check the vertex, polygon and edge selections, respectively, to see which is the current selection.

{{TabbedArea |Perl|

  1.  if( lxq( "select.typeFrom typelist:\"vertex;polygon;edge;item;ptag\" ?" ) ) {
  2.      $selType  = "vertex";
  3.      $attrType = "vert";
  4.  #   lxout( "- RandomSel.pl - Vertex Selection Mode" );
  6.  } elsif( lxq( "select.typeFrom typelist:\"polygon;vertex;edge;item\" ?" ) ) {
  7.      $selType = "polygon";
  8.      $attrType = "poly";
  9.  #   lxout( "- RandomSel.pl - Polygon Selection Mode" );
  11.  } elsif( lxq( "select.typeFrom typelist:\"edge;vertex;polygon;item\" ?" ) ) {
  12.      $selType = "edge";
  13.      $attrType = "edge";
  14.  #   lxout( "- RandomSel.pl - Edge Selection Mode" );

|Lua| TODO

  1.  if lxq( "select.typeFrom typelist:vertex;polygon;edge;item;ptag ?" )[1] then
  2.      selType  = "vertex"
  3.      attrType = "vert"
  4.  --   lxout( "- RandomSel.lua - Vertex Selection Mode" )
  6.  elseif lxq( "select.typeFrom typelist:polygon;vertex;edge;item ?" )[1] then
  7.      selType = "polygon"
  8.      attrType = "poly"
  9.  --   lxout( "- RandomSel.lua - Polygon Selection Mode" )
  11.  elseif lxq( "select.typeFrom typelist:edge;vertex;polygon;item ?" )[1] then
  12.      $selType = "edge"
  13.      $attrType = "edge"
  14.  --   lxout( "- RandomSel.lua - Edge Selection Mode" )

|Python| TODO

  1.  if lx.eval1( "select.typeFrom typelist:vertex;polygon;edge;item;ptag ?" ):
  2.      selType  = "vertex"
  3.      attrType = "vert"
  4.      #lx.out( "- RandomSel.py - Vertex Selection Mode" )
  6.  elif( lx.eval1( "select.typeFrom typelist:polygon;vertex;edge;item ?" ):
  7.      selType = "polygon"
  8.      attrType = "poly"
  9.      #lx.out( "- RandomSel.py - Polygon Selection Mode" )
  11.  elif( lx.eval1( "select.typeFrom typelist:edge;vertex;polygon;item ?" ):
  12.      selType = "edge"
  13.      attrType = "edge"
  14.      #lx.out( "- RandomSel.py - Edge Selection Mode" )
  15.  }} 
  17. Notice that these calls also test the item selection. Together with vertices, polygons and edges, these define the four most common geometry selection types that can directly selected by the user (although more have since been added to ''modo''). Since the script does not support items, it checks for them and uses the language's own functions to exit if they represent the current selection.
  19. {{TabbedArea
  20. |Perl|
  21. <syntaxhighlight lang="perl" line start="45">
  22.  } else {
  23.      # This only fails if none of the three supported selection
  24.      # modes have yet been used since the program started, or
  25.      # if "item" or "ptag" (ie: materials) is the current
  26.      # selection mode.
  27.      die( "Must be in vertex, edge or polygon selection mode." );
  28.  }


  1.  else
  2.      -- This only fails if none of the three supported selection
  3.      -- modes have yet been used since the program started, or
  4.      -- if "item" or "ptag" (ie: materials) is the current
  5.      -- selection mode.
  6.      error "Must be in vertex, edge or polygon selection mode."
  7.  end


  1.  else:
  2.      # This only fails if none of the three supported selection
  3.      # modes have yet been used since the program started, or
  4.      # if "item" or "ptag" (ie: materials) is the current
  5.      # selection mode.
  6.      sys.exit( "LXe_FAILED:Must be in vertex, edge or polygon selection mode." )


Querying the Foreground Layer List

Now that the current selection mode is known, the script needs to get a list of foreground layers to operate on. It could operate on only the main or primary layer, but instead it chooses to operate on the entire foreground layer selection.

To get the list of foreground layers, the query command is used with the layerservice ScriptQuery interface. The layers attribute is used with the fg selector to get the foreground layer list, storing it in the fgLayers variable.


The count variable is used for debugging, and keeps track of the number of elements that have been selected.


Scanning the Foreground Layers

Line 59 marks the beginning of the main loop. The outer loop runs through the list of foreground layers.


A list of all elements is obtained with another call to layerservice through the query command. This time, the attribute is stored the one in attrType, which was set in the if/else blocks from lines 30 through 43. For polygons, attrType contains the string poly, but the attribute that gets a list of polygons is called ploys, and so an ‘s’ is added to the end. The selector all is used to get a list of all elements.


Scanning the Layer’s Elements

The inner loop scans the layer’s element list. The current element index is stored in the variable e.


Next, the script picks a random number using the language's random number function, comparing it against chance to decide if this element should be selected.

If the test is successful, the Select.element command is called to perform the selection. The command takes an index into the layer’s element list. To ensure the indices match, the script queries layerservice yet again, this time for the {attrType}.index attribute. For polygons, this will resolve to poly.index. The selector in this case is the index of the element in the list returned by the {attrType}s query made earlier.


Selecting an Element

Finally, the script executes Select.element using the lx (Perl/Lua) or lx.command (Python) function. The select.element command takes the layer number, which was stored in layer at line 59 when the outer loop was started. It also takes an element type, which was determined at lines 30 through 43. The mode argument is set to add to add these elements to the current selection. Finally, the index argument is set to the contents of index to select that specific element.


Finishing Up

The counter is incremented before the loop closes, for debugging purposes, and a final commented-out call to lxout or lx.out is made:


Running the Script

That’s it -- the script is done. Try running modo, creating a unit sphere, and executing the script with a line similar to this:


modifying that as necessary to match the path to the script. Don’t forget to use curly braces around the path to the script if there is a space in it. When you run it, you’ll see that the script does nothing. This is because it relies on a user value, RandomSel.coverage, which hasn’t been defined yet.

Defining RandomSel.coverage with user.defNew

To define RandomSel.coverage, you’ll use the user.defNew command. Open a Command History Viewport and enter the following command into the edit field:

user.defNew RandomSel.coverage percent

The first argument is the name of the variable to create, in this case RandomSel.coverage. Note that user value names are case sensitive. The second argument is the datatype of the variable. Since we want value in the range of 0% to 100%, we entered percent.

The default value of a percentage is 0%, so running the script still wouldn’t appear to do anything. You can assign a value to RandomSel.coverage using the user.value command, the same command that the script uses to get the state of RandomSel.coverage:

user.value RandomSel.coverage 0.4

If you would prefer to use units, you can enter the value using the square bracket syntax:

user.value RandomSel.coverage [40%]

Now you can run the script with @d:\randomsel.pl as described above. If all goes well, you’ll see that about forty percent of the elements in the current selection type are randomly selected.

User Interface

While this is interesting, RandomSel.coverage seems a bit unnecessary. The script could just take an argument, or you could edit the script whenever you want to change the coverage. But what if you could put a control on the interface to adjust the coverage, automatically executing the script every time it changed? This is where real power of the user.value comes into play.

Creating a Form

First, you’ll need to create a new form for the user.value command to reside in. The Form Editor is beyond the scope of this article, but here are some quick steps to help you along:

  1. Open the Form Editor from the System menu
  2. Right click on the (new sheet) entry at the bottom of the list, and select Add Form from the menu. Name the form Random Selection.
  3. Click in the X column of the new form to export it. This allows the form to be selected for display in a Forms Viewport.
  4. Click the right-facing triangle to show the controls in the form. Currently there are none, just the (new control) entry.
  5. Right click (new control) and select Add Command from the menu. Enter user.value RandomSel.coverage ? as the command
  6. Use the Label edit field in the properties viewport to the right of the tree to change the label to Coverage.
  7. Create a new Forms Viewport, or find an existing one.
  8. Right click in the Forms Viewport’s header to see the list of exported forms, and select Random Selection.

You should now have a Forms Viewport showing a single percent control labeled Coverage. If you change the value of this control, nothing happens, but the next time you run the script, you’ll see that the new percentage of elements are selected.

Running the Script when RandomSel.coverage Changes

What you really want is for the script to run automatically whenever the value changes. This can be done by assigning an action to RandomSel.coverage using user.def:


Now whenever the value of RandomSel.coverage changes, the script will automatically run. Go ahead and give it a try: as you drag the Coverage minislider, you’ll notice that the retiring feature is taking effect, with the previous change being reverted before the new one is applied as long as you are dragging. The script is undoable because it calls the undoable command select.element.

Limiting the Control Range

There’s still a problem: it is very easy to go over 100% or under 0%. To fix this, we’ll use user.def’s min and max attributes.

user.def RandomSel.coverage min 0.0
user.def RandomSel.coverage max 1.0

Now RandomSel.coverage can never go outside the range of 0% to 100%.

Creating RandomSel.coverage Preset Buttons

You can also add a button to your form that will execute the script directly by using the same command you used to execute it from the Command History:


You can also make buttons that execute with a specific coverage through user.value, such as this example that sets the value to 50%. Since we have associated an action with the user value, the button will execute the script after changing the value.

user.value RandomSel.coverage 0.5 

Creating RandomSel.coverage Value Presets

An alternative to adding preset buttons to the form is to use Value Presets. This adds a popup to the right of the edit field that allows users to create and choose preset values for edit field style controls.

Value Presets are defined through the use of a cookie. Simply setting the cookie on the user value will cause the popup to be created, with any presets automatically being loaded and saved in the config. The cookie can be any string, and multiple clients can share presets by using the same cookie.

user.def RandomSel.coverage valuepresetcookie randomSelCoverage

Improving the Script

There is quite a bit more that you can improve in this script. For example, the current random selection isn’t really selecting an exact coverage amount, but rather deciding, on an element by element basis, if the element should be selected by chance, resulting in more or less the coverage amount chosen, but not exactly. You can modify the script to ensure that the exact coverage amount is selected.

The script does not support item selections. You can use the layerservice ScriptQuery interface to add this support as well.

Another enhancement might be to add a deselect toggle via another user value, causing the script to deselect elements instead of selecting them, or an option to limit the selection to only currently selected or deselected elements, rather than all elements matching the current selection type. The script could be changed to clear the current selection before randomly selecting; currently, the new selection is added to the existing one. There are surely many more variations that you can experiment with.

More Information