OpenMesh: Read and write meshes from files

| OpenMesh

Read and write meshes from files

This section explains the methods used to read a mesh from a file or write it to a file.

The corresponding functions are defined in the namespace OpenMesh::MeshIO. This section is divided into three steps. Step one will give a short example on how to use the OpenMesh IOManager, step two will give some background information on how IOManager works and finally step three will show you how to add your own modules to IOManager.

A tutorial with more information regarding file IO can be found here: Using IO::Options

# Step 1 - IOManager quick start

For a quick start you can copy the following code directly to your project.

Note
  - If you link statically against OpenMesh, you have to add the define OM_STATIC_BUILD to your application. This will ensure that readers and writers get initialized correctly.
  - IOManager uses the filename extension to determine which reader/writer to use. I.e. if passing "inputmesh.obj" as filename parameter, the OBJ-File reader/writer will be used to parse/write the file.

#include <OpenMesh/Core/IO/MeshIO.hh>
MyMesh mesh;
if (!OpenMesh::IO::read_mesh(mesh, "some input file"))
{
std::cerr << "read error\n";
exit(1);
}
// do something with your mesh ...
if (!OpenMesh::IO::write_mesh(mesh, "some output file"))
{
std::cerr << "write error\n";
exit(1);
}

# Step 2 - The theory behind IOManager

Usually mesh reader and writer routines are written directly against the data structure and the respective file format they support. This approach has the main disadvantage that targeting a different data structure or adding another file format leads to duplication of code.

IOManager acts as an interface between persistent data on one side and an arbitrary data structure on the other side by means of reader/writer and importer/exporter modules. This is illustrated by the following diagramm :

Persistent data of arbitrary format is first interpreted by a reader module. The data is then passed - by means of a specified interface - to an importer module for the target data structure. The process for writing data is analogous. The IOManager controls the entire process. Reader/Writer modules are invisible to the user. Importer/Exporter however have to be specified explicitely as they are specific to a data structure.

The complete separation of data structure and persistent data makes it especially easy to maintain existing code and to extend funtionality at both ends as will be shown in step three.

See also
  OpenMesh::IO::IOManager

# Step 3 - How to extend IOManager

## Adding support for a new file format

Adding support for a new file format involves adding a reader and writer module. Reader modules are classes derived from OpenMesh::IO::BaseReader. The part of the interface that you usually have to define is shown below.

class BaseReader
{
public:
virtual std::string get_description() const = 0;
virtual std::string get_extensions() const = 0;
virtual std::string get_magic() const { return std::string(""); }
virtual bool read(std::istream& _is, BaseImporter& _bi) const = 0;
virtual bool read(const std::string& _filename, BaseImporter& _bi) const = 0;
...
};

Based on the file extension or the header information the IOManager decides which reader module to use. The reader then parses the format and the information will be passed to the target data structure be means of a class derived from OpenMesh::IO::BaseImporter.

Writer modules are derived from OpenMesh::IO::BaseWriter and work the same way as reader modules.

## Adding support for a new data structure

As we have already seen, Importers receive information from the reader modules. Reader modules pass information through a specified interface :

class BaseImporter
{
public:
virtual void add_vertex (const OpenMesh::Vec3f&) {};
virtual void add_normal (const OpenMesh::Vec3f&) {};
virtual void add_texture (const OpenMesh::Vec2f&) {};
virtual void add_face (const FaceType&) {};
};

The Importer is then responsible for filling the target data structure. Exporting information from a data structure is a little bit more involved than importing data to it. The writer modules must be able to iterate over all vectors/texcoords/faces. Therefore an exporter has to provide these iterators :

class BaseExporter
{
public:
virtual void update() = 0;
virtual PVertexIter const_vertices_begin() = 0;
virtual PVertexIter const_vertices_end() = 0;
virtual PTexCoordIter const_texcoords_begin() = 0;
virtual PTexCoordIter const_texcoords_end() = 0;
virtual PIdxFaceIter const_idx_faces_begin() = 0;
virtual PIdxFaceIter const_idx_faces_end() = 0;
virtual PFaceIter const_set_faces_begin() = 0;
virtual PFaceIter const_set_faces_end() = 0;
virtual unsigned int n_faces() = 0;
virtual unsigned int n_vertices() = 0;
virtual unsigned int n_texcoords() = 0;
};

There might be the need for the exporter to cache data from the structure it refers to. The update() function should be called at the beginning of each BaseWriter::save() method and it should make sure that cached information is up to date.

For further information you are encouraged to take a look at the modules provided by OpenMesh which can be found in the IO subdirectory.