IAM

ARTICLE

Visualizing Triangular Meshes from .off Files using Python occmodel

This article discusses how to visualize triangular meshes available in Object File Format (.off) in Python using occmodel. Installation instructions for installing occmodel on Ubuntu are included.

occmodel is a Python library providing high-level access to OpenCASCADE, the C++ 3D modeling library powering FreeCAD. Personally, I mainly use occmodel to visualize and manipulate simple triangular meshes as for example provided by .off files. This article discusses the installation of occmodel on Ubuntu and gives an examples in Python of how to visualize .off files.

Installation

occmodel itself depends on:

where gltools and geotools have their own dependencies.

Unfortunately, the OpenCASCADE Community Edition (oce) available through the repositories is the wrong version. Therefore, oce is installed manually — the correct version to check out is oce-0.17.0:

# manually install oce, as sudo apt-get install liboce-* will install oce 0.17 or later
# which is not compatible with occmodel as it is
cd ~
git clone https://github.com/tpaviot/oce oce-0.17.0
cd oce-0.17.0
git checkout OCE-0.17.0 # make sure to checkout 0.17.0!
mkdir build
cd build
cmake ..
make
sudo make install

The above installation may take some time and assumes build-essentials and cmake to be installed. gltools additionally depends on GLFW. If libglfw3-dev is not available through the repositories (e.g. on Ubuntu 14.04), it can be installed following this issue:

echo "deb http://ppa.launchpad.net/keithw/glfw3/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list.d/fillwave_ext.list
echo "deb-src http://ppa.launchpad.net/keithw/glfw3/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list.d/fillwave_ext.list
sudo apt-get update
sudo apt-get install libglfw3 libglfw3-dev

gltools, geotools and occmodel additionally require Cython:

pip install cython

Then, geotools can be installed as follows:

cd ~
git clone https://github.com/tenko/geotools geotools-master
cd geotools-master
python setup.py build
sudo python setup.py install

And gltools is installed analogously:

cd ~
git clone https://github.com/tenko/gltools gltools-master
cd gltools-master/
python setup_build.py build
sudo python setup_build.py install

Both installations can be check using:

python
>>> import geotools
>>> import gltools

If the latter gives rise to undefined GLFW functions, this might be a clue that the install version is not GLFW3. To finally install occmodel, the corresponding setup_build.py needs to be adapted. For checking out occmodel:

cd ~
git clone https://github.com/tenko/occmodel occmodel-master

First, around line 47, the TKAdvTools library needs to be removed:

OCC = \
'''FWOSPlugin PTKernel TKBO TKBRep TKBinL TKBool TKCDF TKFeat TKFillet
TKG2d TKG3d TKGeomAlgo TKGeomBase TKHLR TKIGES TKLCAF TKMath TKMesh TKOffset
TKPLCAF TKPShape TKPrim TKSTEP TKSTEP209 TKSTEPAttr TKSTEPBase TKSTL TKShHealing
TKShapeSchema TKStdLSchema TKTObj TKTopAlgo TKXMesh TKXSBase TKXmlL TKernel
'''

Second, the correct include directory for oce needs to be set. For Ubuntu, this is around line 70:

else:
    SOURCES += glob.glob("occmodel/@src/*.cpp")
    OCCINCLUDE = '/usr/local/include/oce'
    OCCLIBS = OCC.split()
    COMPILE_ARGS.append("-fpermissive")

Now, occmodel can be built:

cd ~/occmodel-master
python setup_build.py build
sudo python setup_build.py install

The installation can again be checked by importing occmodel in Python.

Reading OFF Files

The Object File Format is a text-based format to save triangular meshes consisting of vertices and triangular faces. The file format looks as follows:

OFF
numVertices numFaces numEdges
x_1, y_1, z_1
x_2, y_2, z_2
...
n_1 v_11 v_12 ... v_1n_1
n_2 v_21 b_22 ... v_2n_2
...

The format gets clearer when looking at the following Python function to read .off files:

import os
import sys

def read_off(file):
    """
    Reads vertices and faces from an off file.

    :param file: path to file to read
    :type file: str
    :return: vertices and faces as lists of tuples
    :rtype: [(float)], [(int)]
    """

    assert os.path.exists(file)

    with open(file, 'r') as fp:
        lines = fp.readlines()
        lines = [line.strip() for line in lines]

        assert lines[0] == 'OFF'

        parts = lines[1].split(' ')
        assert len(parts) == 3

        num_vertices = int(parts[0])
        assert num_vertices > 0

        num_faces = int(parts[1])
        assert num_faces > 0

        vertices = []
        for i in range(num_vertices):
            vertex = lines[2 + i].split(' ')
            vertex = [float(point) for point in vertex]
            assert len(vertex) == 3

            vertices.append(vertex)

        faces = []
        for i in range(num_faces):
            face = lines[2 + num_vertices + i].split(' ')
            face = [int(index) for index in face]

            assert face[0] == len(face) - 1
            for index in face:
                assert index >= 0 and index < num_vertices

            assert len(face) > 1

            faces.append(face)

        return vertices, faces

Visualizing Meshes

Visualizing a triangular mesh given an .off file reduces to reading all the faces from the file and reconstructing the mesh face-by-face using Face().createPolygonal(real_face) where Face is provided by occmodel:

from occmodelviewer import viewer
from occmodel import Face, Solid, OCCError

import os
import sys

# read_off definition

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print('Usage: python view_off.py off_file')
        exit(1)

    off_file = sys.argv[1]
    assert os.path.exists(off_file)

    vertices, faces = read_off(off_file)

    real_faces = []
    for face in faces:

        points = []
        for i in range(1, len(face), 1):
            points.append(vertices[face[i]])

        real_faces.append(points)

    occfaces = []
    for real_face in real_faces:
        if len(real_face) == 3:
            try:
                occfaces.append(Face().createPolygonal(real_face))
            except OCCError as e:
                print(e, real_face)
        else:
            assert(False)

    viewer(occfaces)

Visualizations of this cuboid, this chair and this monitor (taken from ModelNet) are shown in Figure 1.

Figure 1 (click to enlarge): Visualizations of a cuboid, a chair and a monitor.

What is your opinion on this article? Let me know your thoughts on Twitter @davidstutz92 or LinkedIn in/davidstutz92.