Creating a Selection Set

From The Foundry MODO SDK wiki
Jump to: navigation, search

Localizing a Mesh for Editing

When you localize a mesh in the API, you need to specify what you're going to be doing with the mesh in advance. It may not seem like it, but creating a new vertex map (in this case a selection set) is editing a mesh. That means we need to localize a mesh object specifically for editing. This can be done through a Layer Scan Object. So the first step is to create a layer scan object.

  layerService = lx.service.Layer()
  layerScanObject = layerService.ScanAllocate(lx.symbol.f_LAYERSCAN_EDIT)

Notice that the flag we use when we allocate a layer scan object is 'lx.symbol.f_LAYERSCAN_EDIT'. This creates a layerscan object that is capable of making edits, as opposed to some other flags which are specifically used for reading data. If we take a look at the declarations for layerscan objects (meaning the flags we give when we allocate the object) we can see the flags we can give in the left hand column, and any flags that are implicitly included with it on the right. Notice that all of the EDIT flags include a WRITEMESH flag and an ACTIVE flag. The WRITEMESH flag is what gives us the ability to make edits to a mesh, while the ACTIVE flag means that the layerscan object will be capable of localizing all active mesh items. So now we need to localize an editable mesh. If we do a quick dir() on our Layer Scan Object, we can see the different methods for localizing meshes:

  dir(layerScanObject)
  -Apply
  -Count
  -GetState
  -MeshAction
  -MeshBase
  -MeshEdit
  -MeshInstance
  -MeshItem
  -SetMeshChange

The MeshAction(), MeshBase(), and MeshEdit() methods will give us a localized mesh in action, base, or editable mode. The MeshInstance() method will allow us to localize a specific instance of a mesh based on index, and the MeshItem() method will return a generic item object lx.object.Item. It's important to note that because we're implicitly using the lx.object.f_LAYERSCAN_ACTIVE flag, the layer scan object may have access to more than one mesh. For our purposes we'll just use the first available one (the index 0 mesh) but it would probably be better to localize each available mesh (you can check how many are there with the Count() method) and make sure it's the mesh you want. We want an editable mesh, so we'll use the MeshEdit() method to localize our mesh.

NB: It is worth noting that inactive ("background") mesh layers, cannot be edited and MeshEdit will fail if you attempt to call it on inactive mesh layers. However, you can safely use MeshAction or MeshBase to create read-only access to those meshes.

  meshObject = layerScanObject.MeshEdit(0)

Creating a Selection Set

What you might not know is that internally, selection sets are vertex maps just like UV Maps, Weight Maps, or Morph Maps. Because of this, the logical place to look for creating a new vertex map might be in a Mesh Map Accessor. By doing a quick dir() on our localized mesh, we can see that we can create a Mesh Map Accessor by using the MeshMapAccessor() call. If we then look at the dir() of the Mesh Map Accessor, we can find a method called New(). So what does it take to create a new mesh map?

  mapAccessor = meshObject.MeshMapAccessor()
  mapAccessor.New.__doc__
  - id mapID = New(integer type,string name)

So the New() method takes an integer which specifies the type of map, and a string for its name. It will return this new maps ID. In dir(lx.symbol) we can find among the integers (symbols starting with lx.symbol.i) symbols for vmaps, and specifically a symbol for pick maps, which is another name for a selection set: lx.symbol.i_VMAP_PICK

  mapID = mapAccessor.New(lx.symbol.i_VMAP_PICK, "Selection Set")

Now we have a new map created, and the ID for that map stored as mapID. The last thing we need is to tell it which points to select.

Adding Points to the Selection Set

In order to assign points to a pick map, we need to have access to the points. So let's create a Point Accessor.

  pointAccessor = meshObject.PointAccessor()

In the dir(pointAccessor), we can see a method for setting a points map value, so let's see what it needs to work.

  pointAccessor.SetMapValue.__doc__
  - SetMapValue(id map,float[] value)

So we need the id of the map, which we already have, and a float storage object which contains the value we want to set. When you see [] in the requirements of a method, it denotes a storage object set to the datatype next to it. Since there's no point ID in the requirements for this method, it's safe to assume that we need to use our Point Accessor to select a point before we run the SetMapValue() call. So let's create a float storage object, select a point with our accessor, and then run the SetMapValue() method.

  storageObject = lx.object.storage('f',0)
  pointAccessor.SelectByIndex(0)
  pointAccessor.SetMapValue(mapID, storageObject)

As discussed in the Map Types page, pick maps are dimensionless. That means that our storage object didn't need any dimensions (hence the 0 when it was created), and we don't need to set a value or values for it (using the set() method). Just feeding it in to our Point Accessor's SetMapValue() method while a point is selected is enough to add it to the selection set.

  • Note that when we talk about a point being selected by the Point Accessor, it just means it currently holds the focus of the API, not that the element is selected in modo's UI.

Finishing up and Making this more useful

Since we've edited attributes of our mesh, we need to let modo know when we're finished in a couple of ways. Since our mesh was localized as an editable mesh (remember our LayerScanObject.MeshEdit(0) call) we need to use the Layer Scan Object's SetMeshChange() method to tell modo what we did. We also need to tell our Layer Scan Object that we're finished editing, and to Apply changes by using the Apply() method. The Layer Scan Object's SetMeshChange() method requires the indice of the layer we've edited and a flag to tell it what we've edited, and we can find these again in the dir(lx.symbol) starting with lx.symbol.f_MESHEDIT. For this situation, we're going to use MESHEDIT_MAP_OTHER because there isn't a specific flag for editing pick maps. We'll also call the Apply() method on our Layer Scan Object.

  layerScanObject.SetMeshChange(0, lx.symbol.f_MESHEDIT_MAP_OTHER)
  layerScanObject.Apply()

So we now have a selection set which has a single vertex in it. That's probably not too useful, so how about we clean this up a little into a function that we can feed a list of elements, a name for the selection set, and it will create a selection set with those elements in it? Here's a commented script that does just that. Almost all of it we have just been through, with the exception of a 'for' loop and defined function.

#python
import lx
 
# We'll define a function called 'createVertSet' that takes two arguments.  
# A list of element indices to make into a selection set, and a string name for the selection set.
def createVertSet(elements, setName):
 
    # Localize an editable mesh and create map and point accessors
    layerScan = lx.service.Layer().ScanAllocate(lx.symbol.f_LAYERSCAN_EDIT)
    mesh = layerScan.MeshEdit(0)
    mapAccessor = mesh.MeshMapAccessor()
    pointAccessor = mesh.PointAccessor()
 
    # Create a storage object and a new pick map, then loop through our elements and add them
    storageBuffer = lx.object.storage('f',0)
    newMap = mapAccessor.New(lx.symbol.i_VMAP_PICK,setName)
    for i in elements:
        pointAccessor.SelectByIndex(i)
        pointAccessor.SetMapValue(newMap, storageBuffer)
 
    # Apply our changes in the layerscan object and tell it which layer we made changes to, as well as what changes.
    layerScan.SetMeshChange(0, lx.symbol.f_MESHEDIT_MAP_OTHER)
    layerScan.Apply()