MeVisLab Scripting Reference
Open Inventor Wrappers

Introduction

MeVisLab contains Python wrappers for the whole Open Inventor toolkit. This allows to:

The wrappers are inside of the Inventor Python package and are organized as follows:

  • Inventor
    • actions
    • base
    • details
    • draggers
    • elements
    • engines
    • events
    • fields
    • manips
    • nodekits
    • nodes
    • projectors
    • sensors

For a description of the defined classes and the interface of these classes, please see the C++ Open Inventor Reference. Most of the interface will be replicated in the Python wrappers.

Inventor.base

The Inventor.base module contains all the linear algebra classes and some base classes like SoType and SoBase. This is different to the C++ Inventor, where these classes are in the root directory and the the misc directory.

The following shows an example of doing a plane/line intersection:

from Inventor import base
plane = base.SbPlane(base.SbVec3f(0,0,1), 45)
line = base.SbLine(base.SbVec3f(0,0,1), base.SbVec3f(0,0,100))
result = plane.intersect(line)

Linear algebra is available in single and double precision, denoted by a "f" postfix for float and a "d" postfix for double. Some single precision classes do not have a "f" postfix, e.g. SbPlane, SbRotation, SbMatrix, while the double precision versions always have a "d" postfix.

The MLABField classes accept both single and double precision on setValue(), but always return double precision versions, since they use double precision internally.

Each linear algebra class has a conversion method to convert to the other precision, e.g. SbVec3f.toVec3d(), SbVec3d.toVec3f(), SbMatrix.toMatrixd(), SbMatrixd.toMatrix(), ...

Inventor.nodes

The Inventor.base module contains all basic Inventor nodes and can be used to construct scene graphs in Python. The fields of Inventor nodes and engines can be accessed by their name. Use getValue()/setValue() to read/write a field's value. A scene graph can be stored in a MLABSoNodeField, so you can create a macro module that internally builds a scene graph and outputs the scene via an MLABSoNodeField.

from Inventor import base
from Inventor import nodes
def createScene():
group = nodes.SoGroup()
cam = nodes.SoOrthographicCamera()
group.addChild(cam)
cube = nodes.SoCube()
cube.width.setValue(2)
cube.height.setValue(4)
cube.depth.setValue(6)
group.addChild(cube)
cube2 = nodes.SoCube()
cube2.width.setValue(2)
cube2.height.setValue(4)
cube2.depth.setValue(6)
group.addChild(cube2)
# store the group in the "output" MLABSoNodeField of a macro module:
ctx.field("output").setObject(group)

Inventor.actions

The Inventor.actions module contains all Inventor actions. You can use it to e.g. get the bounding box of a scene or to search for a node in the scene.

from Inventor import base
from Inventor import nodes
from Inventor import actions
action = actions.SoGetBoundingBoxAction(base.SbViewportRegion(256,256))
group = nodes.SoGroup()
cube = nodes.SoCube()
cube.width.setValue(2)
cube.height.setValue(4)
cube.depth.setValue(6)
group.addChild(cube)
action.apply(group)
box = action.getBoundingBox()

Inventor.fields

The Inventor.fields defines all core Inventor fields. Fields are exposed as properties of the nodes/engines they are contained in. Field values can be set/get via setValue()/setValues() and getValue()/getValues(). Fields with multiple values (e.g. SoMFFloat, SoMFVec3f, ...) support NumPy arrays in their setValues() methods and return a read-only NumPy array in getValues(). To efficiently set values, you can either create a NumPy array and set it via setValues() (this causes an extra copy), or you can use the setNum() and startEditing()/finishEditing() methods to directly work on the field data using a NumPy array that contains a direct pointer to the field data.

The MLABField offers the MLABField::inventorField() method to access the underlying SoField of an MLABField that wraps an Inventor field. This allows to e.g. use the SoMFVec3f API to set multiple vector values on an MLABField that wraps a SoMFVec3f, without the need to use setStringValue().

from Inventor import base
from Inventor import nodes
import numpy
prop = nodes.SoVertexProperty()
# Using an extra NumPy array:
array = numpy.ndarray([10,3],dtype='f')
for i in range(10):
array[i] = (i,i+1,i+2)
# Set the vertex field from a numpy array
prop.vertex.setValues(0, array)
# Alternative: using a NumPy array that uses the direct memory of the field
# (This is the fastest way of setting the field's value)
# Set number of needed elements
prop.vertex.setNum(5)
# Get the numpy array using startEditing()
array = prop.vertex.startEditing()
for i in range(5):
array[i] = (i,i-1,i-2)
# Finish the editing.
prop.vertex.finishEditing()
# NOTE: Do not access the array, nor keep a reference to it AFTER finishEditing()!
array = None

Inventor.sensors

The Inventor.sensors module contains all Inventor sensors. You can use it to listen to fields and nodes, or to create a timer.

cube = None
sensor = None
def fieldChanged(sensor):
print("field changed")
def createFieldSensor():
# note that it is important to retain a reference to the sensor, otherwise it will be removed again
global cube, sensor
cube = nodes.SoCube()
sensor = sensors.SoFieldSensor(fieldChanged)
sensor.attach(cube.width)
cube.width = 12

NOTE: Inventor sensors are triggered via a delayed queue, so you will not get an immediate notification if a field changes, you will get it later on when the Inventor queue is processed. This can be circumvented by calling setPriority(0) on the sensor, but note that this might lead to the sensor being triggered more often.

Deriving your own Inventor SoNode

The Inventor wrappers offer to derive from a SoNode (or any derived class) from within Python. It is possible to implement the GLRender method and to use PyOpenGL (currently not included with MeVisLab) to do the actual rendering. Using the Inventor.elements and the SoState of an action, it is possible to read/write to all available Inventor elements in the state.

Here is a simple example of how to override event handling in such a class:

# These imports are needed to get the full interface for, e.g., handleEventAction!
from Inventor import nodes, actions, events
class MySoNode(nodes.SoNode):
# override handleEvent method:
def handleEvent(self, handleEventAction):
soevent = handleEventAction.getEvent()
if soevent.isOfType(events.SoLocation2Event.getClassTypeId()):
pos = soevent.getPosition()
print(f"Position: {pos[0]}, {pos[1]}")
myNode = MySoNode()

Callbacks

Various places in Open Inventor take static C callback functions. Most places have been overloaded with methods that take a Python callable instead. Please note that these callables are not reference counted by the callback setter function, so you need to keep a reference of the function around in Python. Normal member functions will be ref-counted anyways, so this is mainly a problem when you want to use local functions or lambdas as callback functions, in which case you need to store them in a Python variable that lives longer than the C++ object on which you are setting the callback.