Attributes are groups of data stored on elements of a geometry that have a generic type. A generic type means "Float" or "Integer" etc, rather than a specific type used for a specific purpose. For example, mesh data like the definition of a polygon (MPoly) is stored with attributes, but it is does not have a generic type and is not really an attribute itself.

User level documentation about attributes can be found in a dedicated page in the manual

Attributes are often tied to CustomData, which is a system to store "layers" of data in a data structure like a mesh, point cloud, etc. However, since CustomData was added to Blender before attributes were conceptualized, it combines the "legacy" style of task-specific attribute types with generic types like "Float".

There are two APIs for retrieving attributes in Blender.

  1. Defined in BKE_attribute.h, the first API works on the ID structure. This approach is tied to the CustomData system commonly used to store attributes.
  2. Defined in BKE_geometry_set.hh, the C++ attribute API works on geometry components. Using this is generally preferred, since it has a few more capabilities, which are described below.

Attribute API

Attributes are retrieved from geometry components by providing a "id" (AttributeIDRef). This is most commonly just an attribute name. The attribute API in BKE_geometry_set.hh has some more advanced capabilities.

  1. Read Access vs. Write Access: One important factor is whether the attribute can be modified after retrieving it. With a const geometry component, an attribute on the geometry cannot be modified. This means that the for_write and for_output versions of the API are not available. This is extremely important for writing coherent bug-free code. When an attribute is retrieved with write access, via WriteAttributeLookup or OutputAttribute, the geometry component must often be tagged to clear caches that depend on the changed data.
  2. Domain Interpolation: When retrieving an attribute, a domain (AttributeDomain) can be provided, optionally. If the attribute is stored on the provided domain, nothing extra will happen, but if is stored on a separate domain and conversion is possible, the API will return an attribute with values converted to the specified domain. These conversions are implemented in each GeometryComponent with the attribute_try_adapt_domain method.
  3. Implicit Type Conversion: In addition to interpolating domains, attribute types can be converted. The virtual array system (BLI_virtual_array.hh) can make the converted type available, but only actually convert the necessary indices as they are retrieved.
  4. Anonymous Attributes: The "id" used to look up an attribute can also be an anonymous attribute reference. Currently anonymous attributes are only used in geometry nodes.
  5. Abstracted Storage Method: Since the data returned from the API is usually a virtual array, it doesn't have to be stored contiguously (even though that is generally preferred). This allows accessing "legacy" attributes like material_index, which is stored in MPoly.

Struct of Arrays vs. Array of Structs

One benefit of the idea of attributes is storing each attribute contiguously, rather than trying to imagine all necessary data and storing it in a specialized structure for each element. For example, here is a structure one might conceive of to store everything necessary for a Bezier control point:

struct ControlPoint {
  float3 position;
  float3 handle_right;
  float3 handle_left;
  int8 handle_type_right;
  int8 handle_Type_left;
  float radius;
  float tilt;

However, there are many problems with this approach:

  1. To access just all the positions, all other data has to be loaded from memory, which has a large performance cost.
  2. The positions (or any other attribute) cannot be accessed simply by generic code that is meant to handle any data rather than just control points.
  3. Even if radius or tilt don't need to be used, they must still be stored.
  4. If support for user-defined attributes is required, they will have to be stored separately, and handled separately, duplicating all code that deals with the data structure.

In contrast, code that stores each field as a separate named data layer in generic attribute storage like CustomData is much more flexible, faster, and easier to code.