OpenMesh::VPropHandleT<float> vprop_float; mesh.add_property(vprop_float, "vprop_float");
Here we registered a float property for the vertices at the mesh with name "vprop_float". The name of a property, that we want to make persistent, must follow a few rules
"v:"
, "h:"
, "e:"
, "f:"
and "m:"
are reserved.If we stick to this rules we are fine. Furthermore we have to consider, that the names are handled case-sensitive.
To actually make a custom property persistent we have to set the persistent flag in the property with
mesh.property(vprop_float).set_persistent(true);
Now we can use IO::mesh_write()
to write the mesh to a file on disk. The custom properties are added after the standard properties in the file, with the name and it's binary size. These two pieces of information are evaluated when reading the file again. To successfully restore the custom properties, the mesh must have registered named properties with equal names (case-sensitive compare). Additionally, when reading the data, the number of bytes read for a property must match the provided number in the file. If the OM reader did not find a suitable named property, it will simply skip it. If the number of byte do not match, the complete restore will be terminated and IO::read_mesh()
will return false
. And if the data cannot be restored, because the appropriate restore method is not available the exception std::logic_error() will be thrown.
Since we now know the behaviour, we need to know what kind of data can we store? Without any further effort, simply using named properties and setting the persistent flag, we can store following types
For further reading we call these types basic types. Apparently we cannot store non-basic types, which are
However there is a way to store custom types ( else we could not store std::string). Let's start with an more simple custom data. For instance we have a struct MyData
like this
struct MyData { int ival; double dval; bool bval; OpenMesh::Vec4f vec4fval; };
Here we keep an int, bool, double value and a vector of 4 floats, which are all basic types. Then we need to specialize the template struct OpenMesh::IO::binary<> within the namespace OpenMesh::IO
template <> struct binary<MyData>
Herein we have to implement the following set of static member variables and functions:
static const bool is_streamable = true; static size_t size_of(void) static size_t size_of(const value_type&) static size_t store(std::ostream& _os, const value_type& _v, bool _swap=false) static size_t restore( std::istream& _is, value_type& _v, bool _swap=false)
is_streamable
The flag is_streamable
has to be set to true
. Else the data cannot be stored at all.
size_of
methods
Since the size of the custom data can be static, which means we know the size at compile time, or the size of it is dynamic, which means me the size is known at runtime, we have to provide the two size_of()
methods.
The first declaration is for the static case, while the second for the dynamic case. Though the static case is more simple, it is not straight forward. We cannot simply use sizeof()
to determine the data size, because it will return the number ob bytes it needs in memory (possible 32bit alignment). Instead we need the binary size, hence we have to add up the single elements in the struct.
return sizeof(int)+sizeof(double)+sizeof(bool)+sizeof(OpenMesh::Vec4f);
Actually we would need to sum up the single elements of the vector, but in this case we know for sure the result (4 floats make 16 bytes, which is 32bit aligned therefore sizeof()
returns the wanted size). But keep in mind, that this a potential location for errors, when writing custom binary support.
The second declaration is for the dynamic case, where the custom data contains pointers or references. This static member must properly count the data, by disolving the pointers/references, if this data has to be stored as well. In the dynamic stetting the static variant cannot return the size, therefore it must return IO::UnknownSize
.
In this case the dynamic variant simply returns the size by calling the static variant, as the sizes are identical for both cases.
store
/ restore
For the dynamic case as for the static case, we have to make up a scheme how we would store the data. One option is to store the length of the data and then store the data itself. For instance the type std::string
is implemented this way. (We store first the length in a 16bit word (=> max. length 65536), then the characters follow. Hence size_of()
returns 2 bytes for the length plus the actual length of the value v
.) Since MyData
contains only basic types we can implement the necessary methods store
and restore
, by simply breaking up the data into the basic types using the pre-defined store/restore methods for them:
static size_t store(std::ostream& _os, const value_type& _v, bool _swap=false) { size_t bytes; bytes = IO::store( _os, _v.ival, _swap ); bytes += IO::store( _os, _v.dval, _swap ); bytes += IO::store( _os, _v.bval, _swap ); bytes += IO::store( _os, _v.vec4fval, _swap ); return _os.good() ? bytes : 0; } static size_t restore( std::istream& _is, value_type& _v, bool _swap=false) { size_t bytes; bytes = IO::restore( _is, _v.ival, _swap ); bytes += IO::restore( _is, _v.dval, _swap ); bytes += IO::restore( _is, _v.bval, _swap ); bytes += IO::restore( _is, _v.vec4fval, _swap ); return _is.good() ? bytes : 0; }
It's very important, that the store/restore methods count the written/read bytes correctly and return the value. On error both functions must return 0.
A more complex situation is given with the following property
typedef std::map< std::string, size_t > MyMap; OpenMesh::MPropHandleT<MyMap> mprop_map;
In this case the data contains a container, a map from strings to integer numbers. If we want to store this as well, me need to make up a scheme how the map will be store in a sequential layout. First we store the number of elements in the map. Then, since the map has an iterator, we simply iterate over all elements and store each pair (key/value). This procedure is equal for the size_of()
, store()
, and restore()
methods. For example the size_of()
methods look like this
static size_t size_of(void) { return UnknownSize; } static size_t size_of(const value_type& _v) { if (_v.empty()) return sizeof(size_t); value_type::const_iterator it = _v.begin(); size_t N = _v.size(); size_t bytes = IO::size_of(N); for(;it!=_v.end(); ++it) { bytes += IO::size_of( it->first ); bytes += IO::size_of( it->second ); } return bytes; }
The implementation of store()
and restore()
follow a similar pattern.
The given example program does the following steps
Since the example is a little bit longer than usual the source is in several files. The main program is in persistence.cc
, the cube generator in generate_cube.hh
, stats.hh
provides little tools to display information about the mesh and the properties, the file fill_props.hh
providing the test data, and int2roman.hh/
.cc, which is used in fill_props.hh. All necessary parts are in persistence.cc
, which displayed in full length below. For the other files please have a look in the directory OpenMesh/Doc/Tutorial/09-persistence/
.
#include <iostream> #include <string> #include <map> // -------------------- OpenMesh #include <OpenMesh/Core/IO/MeshIO.hh> #include <OpenMesh/Core/Mesh/Types/TriMesh_ArrayKernelT.hh> #include <OpenMesh/Core/Mesh/Types/PolyMesh_ArrayKernelT.hh> // -------------------- little helper #include "generate_cube.hh" #include "stats.hh" #include "fill_props.hh" // ---------------------------------------------------------------------------- // Set to 1 to use an PolyMesh type. #define UsePolyMesh 1 // ---------------------------------------------------------------------------- using namespace OpenMesh; // ---------------------------------------------------------------------------- typedef TriMesh_ArrayKernelT<> TriMesh; typedef PolyMesh_ArrayKernelT<> PolyMesh; #if UsePolyMesh typedef PolyMesh Mesh; #else typedef TriMesh Mesh; #endif // ---------------------------------------------------------------------------- #ifndef DOXY_IGNORE_THIS struct MyData { int ival; double dval; bool bval; OpenMesh::Vec4f vec4fval; MyData() : ival(0), dval(0.0), bval(false) { } MyData( const MyData& _cpy ) : ival(_cpy.ival), dval(_cpy.dval), bval(_cpy.bval), vec4fval(_cpy.vec4fval) { } // ---------- assignment MyData& operator = (const MyData& _rhs) { ival = _rhs.ival; dval = _rhs.dval; bval = _rhs.bval; vec4fval = _rhs.vec4fval; return *this; } MyData& operator = (int _rhs) { ival = _rhs; return *this; } MyData& operator = (double _rhs) { dval = _rhs; return *this; } MyData& operator = (bool _rhs) { bval = _rhs; return *this; } MyData& operator = (const OpenMesh::Vec4f& _rhs) { vec4fval = _rhs; return *this; } // ---------- comparison bool operator == (const MyData& _rhs) const { return ival == _rhs.ival && dval == _rhs.dval && bval == _rhs.bval && vec4fval == _rhs.vec4fval; } bool operator != (const MyData& _rhs) const { return !(*this == _rhs); } }; #endif // ---------------------------------------------------------------------------- typedef std::map< std::string, size_t > MyMap; // ---------------------------------------------------------------------------- #ifndef DOXY_IGNORE_THIS namespace OpenMesh { namespace IO { // support persistence for struct MyData template <> struct binary<MyData> { typedef MyData value_type; static const bool is_streamable = true; // return binary size of the value static size_t size_of(void) { return sizeof(int)+sizeof(double)+sizeof(bool)+sizeof(OpenMesh::Vec4f); } static size_t size_of(const value_type&) { return size_of(); } static size_t store(std::ostream& _os, const value_type& _v, bool _swap=false) { size_t bytes; bytes = IO::store( _os, _v.ival, _swap ); bytes += IO::store( _os, _v.dval, _swap ); bytes += IO::store( _os, _v.bval, _swap ); bytes += IO::store( _os, _v.vec4fval, _swap ); return _os.good() ? bytes : 0; } static size_t restore( std::istream& _is, value_type& _v, bool _swap=false) { size_t bytes; bytes = IO::restore( _is, _v.ival, _swap ); bytes += IO::restore( _is, _v.dval, _swap ); bytes += IO::restore( _is, _v.bval, _swap ); bytes += IO::restore( _is, _v.vec4fval, _swap ); return _is.good() ? bytes : 0; } }; template <> struct binary< MyMap > { typedef MyMap value_type; static const bool is_streamable = true; // return generic binary size of self, if known static size_t size_of(void) { return UnknownSize; } // return binary size of the value static size_t size_of(const value_type& _v) { if (_v.empty()) return sizeof(size_t); value_type::const_iterator it = _v.begin(); size_t N = _v.size(); size_t bytes = IO::size_of(N); for(;it!=_v.end(); ++it) { bytes += IO::size_of( it->first ); bytes += IO::size_of( it->second ); } return bytes; } static size_t store(std::ostream& _os, const value_type& _v, bool _swap=false) { size_t bytes = 0; size_t N = _v.size(); value_type::const_iterator it = _v.begin(); bytes += IO::store( _os, N, _swap ); for (; it != _v.end() && _os.good(); ++it) { bytes += IO::store( _os, it->first, _swap ); bytes += IO::store( _os, it->second, _swap ); } return _os.good() ? bytes : 0; } static size_t restore( std::istream& _is, value_type& _v, bool _swap=false) { size_t bytes = 0; size_t N = 0; _v.clear(); bytes += IO::restore( _is, N, _swap ); value_type::iterator it = _v.begin(); std::string key; size_t val; for (size_t i=0; i<N && _is.good(); ++i) { bytes += IO::restore( _is, key, _swap ); bytes += IO::restore( _is, val, _swap ); _v[key] = val; } return _is.good() ? bytes : 0; } }; } } #endif // ---------------------------------------------------------------------------- int main(void) { // Mesh mesh; // generate a geometry generate_cube<Mesh>(mesh); // should display 8 vertices, 18/12 edges, 12/6 faces (Tri/Poly) mesh_stats(mesh); // print out information about properties mesh_property_stats(mesh); std::cout << "Define some custom properties..\n"; OpenMesh::VPropHandleT<float> vprop_float; OpenMesh::EPropHandleT<bool> eprop_bool; OpenMesh::FPropHandleT<std::string> fprop_string; OpenMesh::HPropHandleT<MyData> hprop_mydata; OpenMesh::MPropHandleT<MyMap> mprop_map; std::cout << ".. and registrate them at the mesh object.\n"; mesh.add_property(vprop_float, "vprop_float"); mesh.add_property(eprop_bool, "eprop_bool"); mesh.add_property(fprop_string, "fprop_string"); mesh.add_property(hprop_mydata, "hprop_mydata"); mesh.add_property(mprop_map, "mprop_map"); mesh_property_stats(mesh); std::cout << "Now let's fill the props..\n"; fill_props(mesh, vprop_float); fill_props(mesh, eprop_bool); fill_props(mesh, fprop_string); fill_props(mesh, hprop_mydata); fill_props(mesh, mprop_map); std::cout << "Check props..\n"; #define CHK_PROP( PH ) \ std::cout << " " << #PH << " " \ << (fill_props(mesh, PH, true)?"ok\n":"error\n") CHK_PROP(vprop_float); CHK_PROP(eprop_bool); CHK_PROP(fprop_string); CHK_PROP(hprop_mydata); CHK_PROP(mprop_map); #undef CHK_PROP std::cout << "Set persistent flag..\n"; #define SET_PERS( PH ) \ mesh.property(PH).set_persistent(true); \ std::cout << " " << #PH << " " \ << (mesh.property(PH).persistent()?"ok\n":"failed!\n") mesh.property(vprop_float).set_persistent(true); std::cout << " vprop_float " << (mesh.property(vprop_float).persistent()?"ok\n":"failed!\n"); SET_PERS( eprop_bool ); SET_PERS( fprop_string ); SET_PERS( hprop_mydata ); mesh.mproperty(mprop_map).set_persistent(true); std::cout << " mprop_map " << (mesh.mproperty(mprop_map).persistent()?"ok\n":"failed!\n"); std::cout << "Write mesh.."; if (IO::write_mesh( mesh, "persistence-check.om" )) std::cout << " ok\n"; else { std::cout << " failed\n"; return 1; } std::cout << "Clear mesh\n"; mesh.clear(); mesh_stats(mesh, " "); std::cout << "Read back mesh.."; try { if (IO::read_mesh( mesh, "persistence-check.om" )) std::cout << " ok\n"; else { std::cout << " failed!\n"; return 1; } mesh_stats(mesh, " "); } catch( std::exception &x ) { std::cerr << x.what() << std::endl; return 1; } std::cout << "Check props..\n"; #define CHK_PROP( PH ) \ std::cout << " " << #PH << " " \ << (fill_props(mesh, PH, true)?"ok\n":"error\n") CHK_PROP(vprop_float); CHK_PROP(eprop_bool); CHK_PROP(fprop_string); CHK_PROP(hprop_mydata); CHK_PROP(mprop_map); #undef CHK_PROP return 0; } // end of file // ============================================================================