MorphoGraphX  2.0-1-227
Attribute system

MorphoGraphX attribute system is a way to store data inside the project.

Arbitrary data can be saved and loaded together with the project file, and accessed by all processes.

Description

MorphoGraphX attribute system is defined in Attributes.hpp.

Unit tests, useful as documentation, are placed in tests/attributes.cpp.

Currently, every attribute (and data within it) is assigned to a particular mesh.

A mesh can have multiple attribute maps. Attribute maps are accessed by names, which must be unique within a mesh.

Each map stores user-defined keys and values. It is possible to use any key for accessing - keys which are not present will return the default value.

Accessing attributes

Attribute maps are accessible via a Mesh. If one isn't present, it will be created on access. If it's present, but the type doesn't match, it will be destroyed (see also Attributes::getAttrMap. If it's present and with matching type, it will be returned.

Because AttrMap data are owned by the Mesh, it's possible to use references to the stored data for as long as the Mesh exists.

The following example (taken from unit tests) will try to access the map called name by assuming that it's a map of keys unsigned to values vector<int>.

const std::vector<int>& getAttribute(mgx::Mesh &mesh, const QString &name, unsigned idx)
{
// If the map called "name" is not present, it will be created
const mgx::AttrMap<unsigned, std::vector<int> >& myAttributeMap = mesh.attributes().attrMap<unsigned, std::vector<int> >(name);
// This is also guaranteed to work - if key "idx" doesn't exist, default constructor of vector<int> will be used.
return myAttributeMap[idx];
}

Saving values

Saving values is similar to reading them. Because each AttrMap is a plain object in memory, operations done directly on it update it.

The following example (taken from unit tests) takes an existing vector and places it (by copying) inside the attribute map.

void setAttribute(mgx::Mesh &mesh, const QString &name, unsigned idx, vector<int>& input)
{
// If the map called "name" is not present, it will be created
mgx::AttrMap<unsigned, std::vector<int> > myAttributeMap = mesh.attributes().attrMap<unsigned, std::vector<int> >(name);
// This is also guaranteed to work - if key "idx" doesn't exist, default constructor of vector<int> will be used.
myAttributeMap[idx] = input;
}

Note: Don't forget that return values are references! Writing to a copy will not update the source.

Stored data

MorphoGraphX supports using simple structures (std::is_trivially_constructible) as AttMap values out of the box.

WARNING: Trivial structures will be saved as a simple memory dump - no endianness preserved.

Because data inside attribute maps is supposed to be persistent between runs of MorphoGraphX, there are some restrictions on what kinds of data can be used.

  • all data placed inside an AttrMap must be owned by it - data can be freed only when it's no longer used by the AttMap
  • pointers should not be used - if two pointers are pointing to the same data, there is no obvious (or quick) way to save (and later load) the data as one unit

Customizing stored data

To use data used with AttrMaps, the following functions must be defined: mgx::writeAttr and mgx::WriteAttr. Place them in a header where your Attrmap<Foo, Bar> declarations can see.

namespace mgx {
// serialize data into QByteArray
template<>
inline bool writeAttr(const MyType &m, QByteArray &ba) { ... }
// deserialize data from QByteArray, starting at position pos
template<>
inline bool readAttr(MyType &data, const QByteArray &ba, size_t &pos) { ... }
}

These methods MUST be reverse of each other - object passed to serialization must be recovered after deserialization. MyType must have a default constructor in order to be passed into readAttr.

Note: The compiler will not tell you directly that one of these is missing - it will complain about templates not found. The errors with GCC look like this:

error: no matching function for call to ‘readPOD(cfm::CFMMesh&, const QByteArray&, size_t&)’
return readPOD<T>(data, ba, pos);
~~~~~~~~~~^~~~~~~~~~~~~~~

or like this:

error: no matching function for call to ‘writePOD(const cfm::CFMMesh&, QByteArray&)’
return writePOD<T>(data, ba);
~~~~~~~~~~~^~~~~~~~~~

Note: It's possible to use existing readAttr and writeAttr from Attributes.hpp to serialize sub-fields of your data.