OpenMesh
Python Tutorial

This tutorial will introduce the basic concepts behind the OpenMesh Python Bindings.

We will cover the following topics:

  • How to build the Python Bindings
  • How to create an empty mesh
  • How to add vertices and faces to a mesh
  • How to navigate on a mesh using iterators and circulators
  • How to add and remove custom properties
  • How to read and write meshes from files

In addition, we will briefly discuss some of the differences between the Python Bindings and the original C++ implementation of OpenMesh.

Building the Python Bindings

The Python Bindings depend on the following libraries:

  • Python (2.7 or later)
  • Boost Python (1.54.0 or later)
Note
Make sure that your Boost Python and Python versions match, i.e. that Boost Python was linked against the correct Python version.

The Python Bindings are automatically built with OpenMesh. The generated files are written to the Build/python subdirectory of the build tree. For more information on how to build OpenMesh see Compiling OpenMesh.

If CMake does not find your Python installation (or finds the wrong one) you can explicitly specify an installation by setting the following variables:

PYTHON_LIBRARY         - Path to the python library
PYTHON_INCLUDE_DIR     - Path to where Python.h is found

Similarly, if CMake does not find your Boost Python installation, set the following variables:

BOOST_ROOT             - Preferred installation prefix
BOOST_INCLUDEDIR       - Preferred include directory e.g. <prefix>/include
BOOST_LIBRARYDIR       - Preferred library directory e.g. <prefix>/lib

Getting Started

To use the OpenMesh Python Bindings we first need to import the openmesh module:

from openmesh import *

The module provides two mesh classes: One for polygonal meshes (PolyMesh) and one for triangle meshes (TriMesh). You should use triangle meshes whenever possible, since they are usually more efficient. In addition, some algorithms are only implemented for triangle meshes while triangle meshes inherit the full functionality of polygonal meshes.

The following code creates a new triangle mesh:

mesh = TriMesh()

Adding Items to a Mesh

We can add a new vertex to the mesh by calling the add_vertex() member function. This function gets a coordinate and returns a handle to the newly inserted vertex.

vh0 = mesh.add_vertex(TriMesh.Point(0, 1, 0))
vh1 = mesh.add_vertex(TriMesh.Point(1, 0, 0))
vh2 = mesh.add_vertex(TriMesh.Point(2, 1, 0))
vh3 = mesh.add_vertex(TriMesh.Point(0,-1, 0))
vh4 = mesh.add_vertex(TriMesh.Point(2,-1, 0))

To add a new face to the mesh we have to call add_face(). This function gets the handles of the vertices that make up the new face and returns a handle to the newly inserted face:

fh0 = mesh.add_face(vh0, vh1, vh2)
fh1 = mesh.add_face(vh1, vh3, vh4)
fh2 = mesh.add_face(vh0, vh3, vh1)

We can also use a Python list to add a face to the mesh:

vh_list = [vh2, vh1, vh4]
fh3 = mesh.add_face(vh_list)

Iterators and Circulators

Now that we have added a couple of vertices to the mesh, we can iterate over them and print out their indices:

for vh in mesh.vertices():
print vh.idx()

We can also iterate over halfedges, edges and faces by calling mesh.halfedges(), mesh.edges() and mesh.faces() respectively:

# iterate over all halfedges
for heh in mesh.halfedges():
print heh.idx()
# iterate over all edges
for eh in mesh.edges():
print eh.idx()
# iterate over all faces
for fh in mesh.faces():
print fh.idx()

To iterate over the items adjacent to another item we can use one of the circulator functions. For example, to iterate over the vertices adjacent to another vertex we can call mesh.vv() and pass the handle of the center vertex:

for vh in mesh.vv(vh1):
print vh.idx()

We can also iterate over the adjacent halfedges, edges and faces:

# iterate over all incoming halfedges
for heh in mesh.vih(vh1):
print heh.idx()
# iterate over all outgoing halfedges
for heh in mesh.voh(vh1):
print heh.idx()
# iterate over all adjacent edges
for eh in mesh.ve(vh1):
print eh.idx()
# iterate over all adjacent faces
for fh in mesh.vf(vh1):
print fh.idx()

To iterate over the items adjacent to a face we can use the following functions:

# iterate over the face's vertices
for vh in mesh.fv(fh0):
print vh.idx()
# iterate over the face's halfedges
for heh in mesh.fh(fh0):
print heh.idx()
# iterate over the face's edges
for eh in mesh.fe(fh0):
print eh.idx()
# iterate over all edge-neighboring faces
for fh in mesh.ff(fh0):
print fh.idx()

Properties

OpenMesh allows us to dynamically add custom properties to a mesh. We can add properties to vertices, halfedges, edges, faces and the mesh itself. To add a property to a mesh (and later access its value) we have to use a property handle of the appropriate type:

  • VPropHandle (for vertex properties)
  • HPropHandle (for halfedge properties)
  • EPropHandle (for edge properties)
  • FPropHandle (for face properties)
  • MPropHandle (for mesh properties)

The following code shows how to add a vertex property to a mesh:

prop_handle = VPropHandle()
mesh.add_property(prop_handle, "cogs")

The second parameter of the function add_property() is optional. The parameter is used to specify a name for the new property. This name can later be used to retrieve a handle to the property using the get_property_handle() member function.

Now that we have added a vertex property to the mesh we can set and get its value. Here we will use the property to store the center of gravity of each vertex' neighborhood:

for vh in mesh.vertices():
cog = TriMesh.Point(0,0,0)
valence = 0
for neighbor in mesh.vv(vh):
cog += mesh.point(neighbor)
valence += 1
mesh.set_property(prop_handle, vh, cog / valence)

Properties use Python's type system. This means that we can use the same property to store values of different types (e.g. store both strings and integers using the same vertex property). Properties are initialized to the Python built-in constant None.

To remove a property we have to call remove_property() with the appropriate property handle:

mesh.remove_property(prop_handle)

Property Managers

Another way to add and remove a property is to use a property manager. A Property manager encapsulates a property and manages its lifecycle. A Property manager also provides a number of convenience functions to access the enclosed property.

There are four different types of property managers. One for each type of mesh item:

  • VPropertyManager (for vertex properties)
  • HPropertyManager (for halfedge properties)
  • EPropertyManager (for edge properties)
  • FPropertyManager (for face properties)

Property managers automatically add a new property to a mesh when they are initialized. Thus the following code not only creates a new vertex property manager, but also adds a new vertex property to the mesh:

prop_man = VPropertyManager(mesh, "cogs")

Property managers allow us to conveniently set the property value for an entire range of mesh items:

prop_man.set_range(mesh.vertices(), TriMesh.Point(0,0,0))

They also allow us to use the subscript operator to set and get property values. Here we will once again use a property to store the center of gravity of each vertex' neighborhood:

for vh in mesh.vertices():
valence = 0
for neighbor in mesh.vv(vh):
prop_man[vh] += mesh.point(neighbor)
valence += 1
prop_man[vh] /= valence

Properties that are encapsulated by a property manager are automatically removed from the mesh when the property manager goes out of scope (i.e. the property manager is garbage collected).

Read and write meshes from files

You can read and write meshes from files using the read_mesh() and write_mesh() functions:

mesh = TriMesh()
read_mesh(mesh, "bunny.obj")
# modify mesh ...
write_mesh(mesh, "bunny.obj")

The file type is automatically deduced from the file extension. OpenMesh currently supports four file types: .off, .obj, .stl and .om

The behaviour of the I/O functions can be controlled by passing an instance of the Options class to either read_mesh() or write_mesh(). The class controls the behaviour of the I/O functions by means of enabled/disabled bits in a bitset:

mesh = TriMesh()
mesh.request_halfedge_normals()
mesh.request_vertex_normals()
options = Options()
options += Options.VertexNormal
result = read_mesh(mesh, "bunny.obj", options)
if result:
print "everything worked"
else:
print "something went wrong"

Other available option bits include:

  1. mode bits - control binary reading/writing
    • Options.Binary
    • Options.MSB
    • Options.LSB
    • Options.Swap (MSB|LSB)
  2. property bits - controls which standard properties to read/write
    • Options.VertexNormal
    • Options.VertexTexCoord
    • Options.VertexColor
    • Options.FaceNormal
    • Options.FaceColor
    • Options.ColorAlpha
    • Options.ColorFloat
Note
You have to pass an instance of the Options class to the I/O functions, i.e. you cannot directly pass one of the option bits. For example, directly passing Options.Binary to either one of the functions will cause an error.

When reading a file the options are used as hints, i.e. depending on the format we can help the reader to interpret the data correctly.

Note
If you want to read a property from a file the property must have been requested prior to reading the file.

When writing the mesh the mode bits control whether to use the binary variant of the respective file format and the desired byte-ordering.

Additional Code Examples

You can use our unit tests to learn more about the OpenMesh Python Bindings. They are located in the src/Python/Unittests subdirectory.

Python and C++

The interface of the Python Bindings is to a large extent identical to the interface of the original C++ implementation of OpenMesh. You should therefore be able to use the C++ documentation as a reference for the Python Bindings. In particular, the classes KernelT, PolyMeshT and TriMeshT provide a good overview of the available mesh member functions. That being said, there are a number of small differences. For example, whenever the C++ implementation returns a reference to an object that is managed by OpenMesh, the Python Bindings will return a copy of that object. This is due to the fact that Python does not have a language feature that is analogous to C++ references. One example of such a function is the point() member function of the PolyMesh and TriMesh classes. Unlike its C++ counterpart, the function does not return a reference to the requested point. It instead returns a copy of the point. This implies that you have to use the set_point() member function to change the value of a point. The same applies to the following functions: normal(), color(), property(), status(), etc.


The complete source looks like this:

from openmesh import *
mesh = TriMesh()
# add a a couple of vertices to the mesh
vh0 = mesh.add_vertex(TriMesh.Point(0, 1, 0))
vh1 = mesh.add_vertex(TriMesh.Point(1, 0, 0))
vh2 = mesh.add_vertex(TriMesh.Point(2, 1, 0))
vh3 = mesh.add_vertex(TriMesh.Point(0,-1, 0))
vh4 = mesh.add_vertex(TriMesh.Point(2,-1, 0))
# add a couple of faces to the mesh
fh0 = mesh.add_face(vh0, vh1, vh2)
fh1 = mesh.add_face(vh1, vh3, vh4)
fh2 = mesh.add_face(vh0, vh3, vh1)
# add another face to the mesh, this time using a list
vh_list = [vh2, vh1, vh4]
fh3 = mesh.add_face(vh_list)
# 0 ==== 2
# |\ 0 /|
# | \ / |
# |2 1 3|
# | / \ |
# |/ 1 \|
# 3 ==== 4
# iterate over all vertices
for vh in mesh.vertices():
print vh.idx()
# iterate over all halfedges
for heh in mesh.halfedges():
print heh.idx()
# iterate over all edges
for eh in mesh.edges():
print eh.idx()
# iterate over all faces
for fh in mesh.faces():
print fh.idx()
# iterate over all neighboring vertices
for vh in mesh.vv(vh1):
print vh.idx()
# iterate over all incoming halfedges
for heh in mesh.vih(vh1):
print heh.idx()
# iterate over all outgoing halfedges
for heh in mesh.voh(vh1):
print heh.idx()
# iterate over all adjacent edges
for eh in mesh.ve(vh1):
print eh.idx()
# iterate over all adjacent faces
for fh in mesh.vf(vh1):
print fh.idx()
# iterate over the face's vertices
for vh in mesh.fv(fh0):
print vh.idx()
# iterate over the face's halfedges
for heh in mesh.fh(fh0):
print heh.idx()
# iterate over the face's edges
for eh in mesh.fe(fh0):
print eh.idx()
# iterate over all edge-neighboring faces
for fh in mesh.ff(fh0):
print fh.idx()
prop_handle = VPropHandle()
mesh.add_property(prop_handle, "cogs")
for vh in mesh.vertices():
cog = TriMesh.Point(0,0,0)
valence = 0
for neighbor in mesh.vv(vh):
cog += mesh.point(neighbor)
valence += 1
mesh.set_property(prop_handle, vh, cog / valence)
mesh.remove_property(prop_handle)
prop_man = VPropertyManager(mesh, "cogs")
prop_man.set_range(mesh.vertices(), TriMesh.Point(0,0,0))
for vh in mesh.vertices():
valence = 0
for neighbor in mesh.vv(vh):
prop_man[vh] += mesh.point(neighbor)
valence += 1
prop_man[vh] /= valence
mesh = TriMesh()
read_mesh(mesh, "bunny.obj")
# modify mesh ...
write_mesh(mesh, "bunny.obj")
mesh = TriMesh()
mesh.request_halfedge_normals()
mesh.request_vertex_normals()
options = Options()
options += Options.VertexNormal
result = read_mesh(mesh, "bunny.obj", options)
if result:
print "everything worked"
else:
print "something went wrong"

Project OpenMesh, ©  Computer Graphics Group, RWTH Aachen. Documentation generated using doxygen .