Skip to content

Attributes

Attributes are used to store data that corresponds to geometry elements. Geometry element are items in one of the domains like points, curves, or faces (eAttrDomain). Attributes have generic data types like "Float" or "Integer" (CD_PROP_*), which means the type itself doesn’t convey how or when the attribute is used. User level documentation about attributes can be found in a dedicated page in the manual.

Attributes are meant to store original data. They aren’t used to cache data that can be recalculated from the geometry like “pointiness” or normals.

The benefits of the attribute system are:

  • Generic: Make geometry custom data implementations more generic, more consistent, and easier to use.
  • Common Interface: Attributes give a common abstraction for changing data on multiple geometry types, improving consistency and removing boilerplate.
  • Data Layout: The attribute system provides access to a high-performance struct-of-arrays layout.
  • Abstracted Storage Method: Using the virtual array system gives implementations flexibility on how data is stored, when that is necessary.
  • Const Correctness: With a const geometry, attributes cannot be modified; the for_write versions of the API are not available.

There are two APIs for retrieving attributes in Blender.

  1. BKE_attribute.h: The C API works on the ID structure. This approach is tied to the CustomData system commonly used to store attributes.
  2. BKE_attribute.hh: This C++ API is preferred, and is described below. Note that BMesh isn't supported yet.

Naming

Built-in attributes are given singular names, generally with full words. For example, position is used instead of positions and handle_type_left is used instead of handle_type_l, etc. Since these names are visible to users and in the Python API, consistency is important.

Besides built-in attributes, users can choose whatever name they want.

Internal attributes

Names that start with a period, like .hide_vert or .selection signify internal data that isn't user-accessible in a procedural context since internal changes might break user expectations. These attributes are hidden from the UI by default.

Unique Names

Unique names per attribute is a fundamental part of the generic attribute design. There are a few key benefits of keeping names unique across all attributes:

  • Attributes can be referred to with a simple focused and non-technical name
  • Attributes can be accessed with the same name no matter the domain or type, making those two properties much more flexible
    • Automatic domain and type interpolation when reading attributes add to the flexibility here, making it simple to choose the storage and usage of attributes separately.
  • As the line between attribute purposes blurs more and more, distinguishing them with these rigid rules makes less and less sense.
  • Geometry nodes builds on these unique name guarantees. As it becomes the main geometry processing mechanism in Blender, it's important that all geometry data follows this paradigm.

For example, an "eye_color" attribute could be stored on vertices, faces, or corners, and the access and interaction would be consistent. The domain or data type can be changed procedurally or in the original data without affecting any uses of the attribute.

However, some other systems like different file formats may not have the same uniqueness requirements as Blender. In this case, it's easier to handle duplicate names when adding data to the attribute system than it is to handle duplicate names everywhere attributes are used in Blender. In these cases, conflicting attribute names should be changed, and the new and old names should be reported to the user.

Accessing Attributes

The AttributeAccessor and MutableAttributeAccessor are used to read or modify a geometry's attributes.

Attributes are accessed by name. However, they can also be accessed with anonymous attribute IDs (AttributeIDRef) in geometry nodes to be passed as node sockets.

Functions to access existing attributes start with lookup. To retrieve write access, the for_write API functions can be used. To add attributes, lookup_or_add or add can be used.

  • Domain Interpolation: When retrieving an attribute, a domain (eAttrDomain) can be provided. If the attribute is stored on a different domain and conversion is possible, the API will return an attribute with values converted to the specified domain. These conversions are implemented for each geometry type with adapt_domain methods.
  • Type Conversion: Attributes can be read with any type. Conversions are lazy; they only happen when the values are actually accessed.

The attribute API uses the virtual array system (BLI_virtual_array.hh) to make type conversion and domain interpolation more convenient and to provide efficient access to default values. However, if contiguous data is necessary that can be retrieved with VArraySpan/GVArraySpan (reading) or API methods with span in the name (for writing).

Attribute Storage

Attributes can be stored in any format, as long as it can be presented as a virtual array when reading and writing data. In practice this usually means contiguous arrays for all geometry elements, but it leaves open the possibility of different formats (i.e. for optimized storage of sparse data). For example, vertex groups can be read and written (though not created yet).

Attributes are often tied to CustomData. However, since CustomData was created before attributes, it combines the legacy style of task-specific attribute types like CD_MLOOPUV with generic types like "Float".

Struct of Arrays vs. Array of Structs

One benefit of the attribute concept 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 curve control point:

struct ControlPoint {
  float3 position;
  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.