RandomSel Script

This example shows how to use Perl]] scripts to read the state of the model through [[ScriptQuery Overview 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 ScriptQuery Overview#The query Command 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 Python#lx.Service method to obtain a Service object instead of using the query command.

Perl

#perl
# Randomly select elements of the current selection type.

#use strict
#use warnings

#lxout( "RandomSel.pl - Started" );

# Query user.value for the value of RandomSel.coverage, which
# we'll use to decide how likely an element is be selected.
# We only care about the first value, so we store it as a
# scalar instead of a list.
my $chance = lxq( "user.value RandomSel.coverage ?" );

my $chancePercent = $chance * 100.0;
#lxout( "- RandomSel.pl - ${chancePercent} percent chance of selection (chance: ${chance})" );

# Figure out the current selection type.  We test against the
# vertex, polygon and edge types to see which one was most
# recently active by using select.typeFrom.  We also test
# the item type in case materials are the active selection,
# in which case we fail.

# Used with select.element to select polygons, edges or vertices.
my $selType;

# Used to query layerservice for the list of polygons, edges or vertices.
my $attrType;

if( lxq( "select.typeFrom typelist:\"vertex;polygon;edge;item;ptag\" ?" ) ) {
    $selType  = "vertex";
    $attrType = "vert";
#   lxout( "- RandomSel.pl - Vertex Selection Mode" );

} elsif( lxq( "select.typeFrom typelist:\"polygon;vertex;edge;item\" ?" ) ) {
    $selType = "polygon";
    $attrType = "poly";
#   lxout( "- RandomSel.pl - Polygon Selection Mode" );

} elsif( lxq( "select.typeFrom typelist:\"edge;vertex;polygon;item\" ?" ) ) {
    $selType = "edge";
    $attrType = "edge";
#   lxout( "- RandomSel.pl - Edge Selection Mode" );

} else {
    # This only fails if none of the three supported selection
    # modes have yet been used since the program started, or
    # if "item" or "ptag" (ie: materials) is the current
    # selection mode.
    die( "Must be in vertex, edge or polygon selection mode." );
}

# Get the list of foreground layers by querying the layerservice "layers" attribute
my @fgLayers = lxq( "query layerservice layers ? \"fg\"" );

# Loop through the foreground layers
my $count = 0;

foreach my $layer (@fgLayers) {
    # Loop through all the elements in the layer of the
    # current selection type, using the "polys", "verts"
    # or "edges" attributes.
    my @elems = lxq( "query layerservice ${attrType}s ? \"all\"" );

#   lxout( "- RandomSel.pl - $#elems ${selType}s total in layer ${layer}." );

    foreach my $e (@elems) {
        # Pick a random number; if that is less
        # than $chance, we select the element.
        if( rand(1.0) < $chance ) {
            # We're going to select it; get the index of the element
            my $index = lxq( "query layerservice ${attrType}.index ? \"$e\"" );

            # Add this element to the selection
            lx( "select.element layer:$layer type:$selType mode:add index:$index" );

            $count++;
        }
    }
}
#lxout( "- RandomSel.pl - $count ${selType}s selected." );

# And we're done.

Lua

-- Randomly select elements of the current selection type.
-- lxout( "RandomSel.lua - Started" )
-- Query user.value for the value of RandomSel.coverage, which
-- we'll use to decide how likely an element is be selected.
-- We only care about the first value, so we extract it from the
-- table and store it.

chance = lxq( "user.value RandomSel.coverage ?" )[1]
chancePercent = chance * 100.0

-- lxout( "- RandomSel.lua - ${chancePercent} percent chance of selection (chance: ${chance})" )
-- Figure out the current selection type.  We test against the
-- vertex, polygon and edge types to see which one was most
-- recently active by using select.typeFrom.  We also test
-- the item type in case materials are the active selection,
-- in which case we fail.

-- Used with select.element to select polygons, edges or vertices.
selType = ""
-- Used to query layerservice for the list of polygons, edges or vertices.
attrType = ""

if lxq( "select.typeFrom typelist:vertex;polygon;edge;item;ptag ?" )[1] then
    selType  = "vertex"
    attrType = "vert"
--   lxout( "- RandomSel.lua - Vertex Selection Mode" )

elseif lxq( "select.typeFrom typelist:polygon;vertex;edge;item ?" )[1] then
    selType = "polygon"
    attrType = "poly"
--   lxout( "- RandomSel.lua - Polygon Selection Mode" )

elseif lxq( "select.typeFrom typelist:edge;vertex;polygon;item ?" )[1] then
    $selType = "edge"
    $attrType = "edge"
--   lxout( "- RandomSel.lua - Edge Selection Mode" )

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

-- Get the list of foreground layers by querying the layerservice "layers" attribute
fgLayers = lxq( "query layerservice layers ? fg" )

-- Loop through the foreground layers
count = 0

for i,layer in ipairs(fgLayers) do
    -- Loop through all the elements in the layer of the
    -- current selection type, using the "polys", "verts"
    -- or "edges" attributes.
    elems = lxq( "query layerservice "..attrType.."s ? all" )

--   lxout( "- RandomSel.lua - "..# elems.." "..selType.."s total in layer ..layer.."." )

    for j,e in ipairs(elems) do
        -- Pick a random number; if that is less
        -- than chance, we select the element.
        if math.random() < chance then
            -- We're going to select it; get the index of the element
            index = lxq( "query layerservice "..attrType..".index ? "..e.. )[i]

            -- Add this element to the selection
            lx( "select.element layer:"..layer.." type:"..selType.." mode:add index:"..index )

            count  = count + 1
        end
    end
end
-- lxout( "- RandomSel.lua - $count ${selType}s selected." )

-- And we're done.

Python

# python
# Randomly select elements of the current selection type.

import random


#lx.out( "RandomSel.py - Started" )

# Query user.value for the value of RandomSel.coverage, which
# we'll use to decide how likely an element is be selected.
# We only care about the first value, so we store it as a
# scalar instead of a list.
chance = lx.eval1( "user.value RandomSel.coverage ?" )

chancePercent = chance * 100.0
#lx.out( "- RandomSel.py - ", chancePercent, " percent chance of selection (chance: ", chance, ")" )

# Figure out the current selection type.  We test against the
# vertex, polygon and edge types to see which one was most
# recently active by using select.typeFrom.  We also test
# the item type in case materials are the active selection,
# in which case we fail.

# Used with select.element to select polygons, edges or vertices.
selType = ""

# Used to query layerservice for the list of polygons, edges or vertices.
attrType = ""

if lx.eval1( "select.typeFrom typelist:vertex;polygon;edge;item;ptag ?" ):
    selType  = "vertex"
    attrType = "vert"
    #lx.out( "- RandomSel.py - Vertex Selection Mode" )

elif lx.eval1( "select.typeFrom typelist:polygon;vertex;edge;item ?" ):
    selType = "polygon"
    attrType = "poly"
    #lx.out( "- RandomSel.py - Polygon Selection Mode" )

elif lx.eval1( "select.typeFrom typelist:edge;vertex;polygon;item ?" ):
    selType = "edge"
    attrType = "edge"
    #lx.out( "- RandomSel.py - Edge Selection Mode" )

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


# Get the list of foreground layers by querying the layerservice "layers" attribute
fgLayers = lx.evalN( "query layerservice layers ? fg" )

# Loop through the foreground layers
count = 0

for layer in fgLayers:
    # Loop through all the elements in the layer of the
    # current selection type, using the "polys", "verts"
    # or "edges" attributes.
    elems = lx.evalN( "query layerservice " + attrType + "s ? all" )

    #lx.out( "- RandomSel.py - ", len(elems), " ", selType, "s total in layer ", layer, "." )

    for e in elems:
        # Pick a random number; if that is less
        # than chance, we select the element.
        if random.random() < chance:
            # We're going to select it; get the index of the element
            index = lx.eval1( "query layerservice " + attrType + ".index ? " + str(e) )

            # Add this element to the selection
            lx.command( "select.element",  layer=layer, type=selType, mode="add", index=index )

            count += 1



#lx.out( "- RandomSel.py - ", count, " ", selType, "s selected." )

# And we're done.

Now a line by line breakdown of the script:

Using lxout() for Debugging

Throughout the script there are commented-out calls to lxout (Perl#lxout.28.29, such as this one stating that the script has started executing.

Perl

#lxout( "RandomSel.pl - Started" );

Lua

--lxout( "RandomSel.lua - Started" );

Python

#lx.out( "RandomSel.py - Started" );

Querying the User Value

Line 13 marks the first call to the lxq (Perl#lxq.28.29 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’’.

Perl

my $chance = lxq( "user.value RandomSel.coverage ?" );

my $chancePercent = $chance * 100.0;
#lxout( "- RandomSel.pl - ${chancePercent} percent chance of selection (chance: ${chance})" );

Lua

chance = lxq( "user.value RandomSel.coverage ?" )[1]

chancePercent = chance * 100.0
-- lxout( "- RandomSel.lua - ${chancePercent} percent chance of selection (chance: ${chance})" )

Python

chance = lx.eval1( "user.value RandomSel.coverage ?" )

chancePercent = chance * 100.0
#lx.out( "- RandomSel.py - ", chancePercent, " percent chance of selection (chance: ", chance, ")" )

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.

Perl

# Used to with select.element to select polygons, edges or vertices.
my $selType;

# Used to query layerservice for the list of polygons, edges or vertices.
my $attrType;

Lua

-- Used with select.element to select polygons, edges or vertices.
selType = ""

-- Used to query layerservice for the list of polygons, edges or vertices.
attrType = ""

Python

# Used with select.element to select polygons, edges or vertices.
selType = ""

# Used to query layerservice for the list of polygons, edges or vertices.
attrType = ""

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.

Perl

if( lxq( "select.typeFrom typelist:\"vertex;polygon;edge;item;ptag\" ?" ) ) {
    $selType  = "vertex";
    $attrType = "vert";
#   lxout( "- RandomSel.pl - Vertex Selection Mode" );

} elsif( lxq( "select.typeFrom typelist:\"polygon;vertex;edge;item\" ?" ) ) {
    $selType = "polygon";
    $attrType = "poly";
#   lxout( "- RandomSel.pl - Polygon Selection Mode" );

} elsif( lxq( "select.typeFrom typelist:\"edge;vertex;polygon;item\" ?" ) ) {
    $selType = "edge";
    $attrType = "edge";
#   lxout( "- RandomSel.pl - Edge Selection Mode" );

Lua

if lxq( "select.typeFrom typelist:vertex;polygon;edge;item;ptag ?" )[1] then
    selType  = "vertex"
    attrType = "vert"
--   lxout( "- RandomSel.lua - Vertex Selection Mode" )

elseif lxq( "select.typeFrom typelist:polygon;vertex;edge;item ?" )[1] then
    selType = "polygon"
    attrType = "poly"
--   lxout( "- RandomSel.lua - Polygon Selection Mode" )

elseif lxq( "select.typeFrom typelist:edge;vertex;polygon;item ?" )[1] then
    $selType = "edge"
    $attrType = "edge"
--   lxout( "- RandomSel.lua - Edge Selection Mode" )

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.

Perl

} else {
    # This only fails if none of the three supported selection
    # modes have yet been used since the program started, or
    # if "item" or "ptag" (ie: materials) is the current
    # selection mode.
    die( "Must be in vertex, edge or polygon selection mode." );
}

Lua

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

Python

else:
    # This only fails if none of the three supported selection
    # modes have yet been used since the program started, or
    # if "item" or "ptag" (ie: materials) is the current
    # selection mode.
    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 ScriptQuery Overview#Selectors to get the foreground layer list, storing it in the fgLayers variable.

Perl

# Get the list of foreground layers by querying the layerservice "layers" attribute
my @fgLayers = lxq( "query layerservice layers ? \"fg\"" );

Lua

-- Get the list of foreground layers by querying the layerservice "layers" attribute
 fgLayers = lxq( "query layerservice layers ? fg" )

Python

# Get the list of foreground layers by querying the layerservice "layers" attribute
 fgLayers = lx.evalN( "query layerservice layers ? fg" )

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

Perl

# Loop through the foreground layers
my $count = 0;

Lua

-- Loop through the foreground layers
count = 0

Python

# Loop through the foreground layers
count = 0

Scanning the Foreground Layers

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

Perl

foreach my $layer (@fgLayers) {
    # Loop through all the elements in the layer of the
    # current selection type, using the "polys", "verts"
    # or "edges" attributes.

Lua

for i,layer in ipairs(fgLayers) do
    -- Loop through all the elements in the layer of the
    -- current selection type, using the "polys", "verts"
    -- or "edges" attributes.
    elems = lxq( "query layerservice "..attrType.."s ? all" )

Python

for layer in fgLayers:
    # Loop through all the elements in the layer of the
    # current selection type, using the "polys", "verts"
    # or "edges" attributes.

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.

Perl

    my @elems = lxq( "query layerservice ${attrType}s ? \"all\"" );

#   lxout( "- RandomSel.pl - $#elems ${selType}s total in layer ${layer}." );

Lua

    elems = lxq( "query layerservice "..attrType.."s ? all" )

--   lxout( "- RandomSel.lua - "..# elems.." "..selType.."s total in layer ..layer.."." )

Python

elems = lx.evalN( "query layerservice " + attrType + "s ? all" )

#lx.out( "- RandomSel.py - ", len(elems), " ", selType, "s total in layer ", layer, "." )

Scanning the Layer?s Elements

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

Perl

foreach my $e (@elems) {

Lua

for j,e in ipairs(elems) do

Python

for e in elems:

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.

Perl

# Pick a random number; if that is less
# than $chance, we select the element.
if( rand(1.0) < $chance ) {
    # We're going to select it; get the index of the element
    my $index = lxq( "query layerservice ${attrType}.index ? \"$e\"" );

Lua

-- Pick a random number; if that is less
-- than chance, we select the element.
if math.random() < chance then
    -- We're going to select it; get the index of the element
    index = lxq( "query layerservice "..attrType..".index ? "..e.. )[i]

Python

# Pick a random number; if that is less
# than chance, we select the element.
if random.random() < chance:
    # We're going to select it; get the index of the element
    index = lx.eval1( "query layerservice " + attrType + ".index ? " + str(e) )

Selecting an Element

Finally, the script executes select.element]] using the lx ([[Perl#lxq.28.29) 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.

Perl

 # Add this element to the selection
lx( "select.element layer:$layer type:$selType mode:add index:$index" );

Lua

-- Add this element to the selection
lx( "select.element layer:"..layer.." type:"..selType.." mode:add index:"..index )

Python

# Add this element to the selection
lx.command( "select.element",  layer=layer, type=selType, mode="add", index=index )

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:

Perl

            $count++;
        }
    }
}
 #lxout( "- RandomSel.pl - $count ${selType}s selected." );

# And we're done.

Lua

            count  = count + 1
        end
    end
end
 -- lxout( "- RandomSel.lua - $count ${selType}s selected." )

-- And we're done.

Python

count += 1

#lx.out( “- RandomSel.py - “, count, ” “, selType, “s selected.” )

# And we’re done.

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:

Perl

@d:\randomsel.pl

Lua

@d:\randomsel.lua

Python

@d:\randomsel.py

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 Values#user.defNew:

1
 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 Common Datatypes 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 Values#user.value command, the same command that the script uses to get the state of ‘’RandomSel.coverage’’:

1
 user.value RandomSel.coverage 0.4

If you would prefer to use units, you can enter the value using the Command System: Executing Commands#Values with Units: Using Square Braces syntax:

1
 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:

# Open the Form Editor]] from the [[System menu # 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’’. # 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. # Click the right-facing triangle to show the controls in the form. Currently there are none, just the ‘’(new control)’’ entry. # Right click ‘’(new control)’’ and select ‘’Add Command’’ from the menu. Enter ‘’user.value RandomSel.coverage ?’’ as the command # Use the Label edit field in the properties viewport to the right of the tree to change the label to ‘’Coverage’’. # Create a new ‘’Forms Viewport’’, or find an existing one. # 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 User Values#Responding to Value Changes:

Perl

user.def RandomSel.coverage action {@d:\randomsel.pl}

Lua

user.def RandomSel.coverage action {@d:\randomsel.lua}

Python

user.def RandomSel.coverage action {@d:\randomsel.py}

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 Command System: Querying#Refiring 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 User Values#Fixed Ranges attributes.

1
2
 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’’:

1
 {d:\randomsel.pl}

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.

1
 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 Files. The cookie can be any string, and multiple clients can share presets by using the same cookie.

1
 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.