databpy 0.11.0
  • Home
  • Attributes
  • API
  • Changelog

On this page

  • The attribute system
    • Attributes and domains
    • The three-level API
    • Attribute types
    • Attribute domains
    • Common workflows
    • Errors
    • Best practices
    • See also

Working with Attributes

The attribute system

Attributes and domains

In Blender, data on geometry is stored as attributes. An attribute is a named array with one value for each element of a domain, such as the vertices or faces of a mesh. Vertex positions, edge vertices, UV coordinates and vertex colors are all attributes.

For example: - Vertex positions are stored as a FLOAT_VECTOR attribute named "position" on the POINT domain - Face materials are stored as an INT attribute on the FACE domain - UV maps are stored as FLOAT2 attributes on the CORNER domain

databpy reads and writes these attributes as NumPy arrays.

The three-level API

databpy offers three levels of abstraction for working with attributes, each suited to different use cases:

flowchart TD
    A[BlenderObject] --> B[AttributeArray]
    B --> C[Attribute]
    C --> D[bpy.types.Attribute]

    A -.->|"Auto-sync, convenience methods"| E[User Level]
    B -.->|"NumPy operations with auto-sync"| E
    C -.->|"Manual control, one-shot operations"| F[Advanced Use]
    D -.->|"Raw Blender API"| G[Low Level]

    style A fill:#90EE90
    style B fill:#87CEEB
    style C fill:#FFB6C1
    style D fill:#FFE4B5

1. BlenderObject: highest level

The BlenderObject class (nicknamed “bob”) wraps an object, with dictionary-style access to its attributes and properties such as bob.position.

import databpy as db
import numpy as np

# Create a simple object
bob = db.create_bob(np.random.rand(10, 3))

# Access attributes like a dictionary - returns AttributeArray
positions = bob["position"]
positions[:, 2] += 1.0  # Automatically syncs to Blender

# Or use the convenience property
bob.position[:, 2] += 1.0  # Same thing

# Store new attributes
bob["my_values"] = np.random.rand(10)

When to use: Interactive work, scripting, when you want automatic syncing.

2. AttributeArray: NumPy arrays that sync

AttributeArray is a numpy.ndarray subclass holding a copy of an attribute’s values, which writes changes back to Blender as they are made.

import databpy as db
import bpy

obj = bpy.data.objects["Cube"]

# Create an AttributeArray
pos = db.AttributeArray(obj, "position")

# All NumPy operations work and auto-sync
pos[:, 2] += 1.0      # In-place addition
pos *= 2.0            # Broadcasting
pos[0] = [0, 0, 0]    # Item assignment

# Changes are immediately reflected in Blender
NotePerformance

Creating an AttributeArray (e.g. bob.position) reads the entire attribute into the array. Each modification then writes only the values it changed, directly into the attribute’s memory: pos[:10, 2] = 1.0 writes 10 values however large the mesh is. Values changed in Blender since the array was read are left alone.

STRING attributes are written in full instead, one value at a time, and an attribute that was removed is created again.

Indexing that returns a view (slices like pos[:, 2] or pos[2:5]) stays linked to Blender, so modifying the view syncs. Indexing that returns a copy (pos[mask], pos[[0, 3]], pos + 1, pos.copy()) gives a detached array that doesn’t sync:

pos = db.AttributeArray(obj, "position")

column = pos[:, 2]
column += 1.0             # a view - syncs

high = pos[pos[:, 2] > 1]
high += 1.0               # a copy - doesn't sync

pos[pos[:, 2] > 1] += 1.0  # assigning back through the original array syncs

Read the array again after changing the geometry (e.g. adding vertices). Writing to an array whose attribute no longer matches raises a NamedAttributeError, and one whose object was removed raises a LinkedObjectError.

When to use: When you need NumPy-like operations with automatic syncing, working with position/color/custom data interactively.

3. Attribute: manual reads and writes

The Attribute class provides direct, stateless access with explicit control over when data is read or written.

import databpy as db
import numpy as np
import bpy

obj = bpy.data.objects["Cube"]

# Get the attribute wrapper
attr = db.Attribute(obj.data.attributes["position"])

# Manually read data
positions = attr.as_array()

# Perform operations (no auto-sync)
positions[:, 2] += 1.0
positions *= 2.0

# Manually write back (single write operation)
attr.from_array(positions)

When to use: - One-shot read or write operations - Performance-critical code where you want control over sync timing - Batch processing where you make many whole-array changes before writing back - When you need to inspect attribute metadata (atype, domain, shape, storage_type) without reading data

Attribute types

Blender supports various attribute data types. databpy works with all of them through the AttributeTypes enum:

Float types

import databpy as db
import numpy as np

bob = db.create_bob(np.random.rand(10, 3))

# FLOAT - Single float per element
temperatures = np.random.rand(10).astype(np.float32)
bob["temperature"] = temperatures

# FLOAT2 - 2D vectors
uv_coords = np.random.rand(10, 2).astype(np.float32)
db.store_named_attribute(bob.object, uv_coords, "uv", atype="FLOAT2")

# FLOAT_VECTOR - 3D vectors (most common)
velocities = np.random.rand(10, 3).astype(np.float32)
bob["velocity"] = velocities

# FLOAT4 - generic 4D vectors (requires Blender 5.2)
if db.attribute.HAS_FLOAT4:
    values = np.random.rand(10, 4).astype(np.float32)
    db.store_named_attribute(bob.object, values, "values", atype="FLOAT4")

# FLOAT_COLOR - RGBA colors (4 components)
colors = np.random.rand(10, 4).astype(np.float32)
db.store_named_attribute(bob.object, colors, "color", atype="FLOAT_COLOR")

# QUATERNION - Rotations (4 components: w, x, y, z)
rotations = np.random.rand(10, 4).astype(np.float32)
db.store_named_attribute(bob.object, rotations, "rotation", atype="QUATERNION")

# FLOAT4X4 - 4x4 transformation matrices
matrices = np.random.rand(10, 4, 4).astype(np.float32)
db.store_named_attribute(bob.object, matrices, "transform", atype="FLOAT4X4")
bpy.data.meshes['NewObject.001'].attributes["transform"]

Integer types

# INT - 32-bit signed integers
ids = np.arange(10, dtype=np.int32)
bob["id"] = ids

# INT8 - 8-bit signed integers (memory efficient)
small_values = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype=np.int8)
db.store_named_attribute(bob.object, small_values, "category", atype="INT8")

# INT16_2D - 2D 16-bit integer vectors
offsets = np.random.randint(-100, 100, size=(10, 2)).astype(np.int16)
db.store_named_attribute(bob.object, offsets, "offsets", atype="INT16_2D")

# INT32_2D - 2D integer vectors
pairs = np.random.randint(0, 100, size=(10, 2), dtype=np.int32)
db.store_named_attribute(bob.object, pairs, "edge_ids", atype="INT32_2D")
bpy.data.meshes['NewObject.001'].attributes["edge_ids"]

Color types

# BYTE_COLOR - RGBA as unsigned 8-bit (0-255), read back as uint8
byte_colors = np.random.randint(0, 255, size=(10, 4), dtype=np.uint8)
db.store_named_attribute(bob.object, byte_colors, "vertex_color", atype="BYTE_COLOR")
bpy.data.meshes['NewObject.001'].attributes["vertex_color"]

Boolean type

# BOOLEAN - True/False flags
selection = np.random.rand(10) > 0.5
bob["selected"] = selection

String type

# STRING - text values, read and written one value at a time
names = np.array([f"point_{i}" for i in range(10)])
bob["name"] = names
/tmp/ipykernel_2632/2168651459.py:3: UserWarning: String attributes can be read and written through the Python API but are not yet properly supported within Geometry Nodes. Support may change in future Blender / databpy versions.
  bob["name"] = names
WarningExperimental

STRING attributes can be read and written through the Python API but aren’t yet properly supported in Geometry Nodes, so databpy warns when they are used. They are also much slower than numeric attributes, as Blender can’t transfer them in bulk.

TipType inference

If you don’t specify an atype, databpy will infer it from your array’s dtype and shape using guess_atype_from_array():

Array Inferred type
(n,) float / int / int8 / bool / str FLOAT / INT / INT8 / BOOLEAN / STRING
(n, 2) float / int16 / other int FLOAT2 / INT16_2D / INT32_2D
(n, 3) numeric FLOAT_VECTOR
(n, 4) uint8 BYTE_COLOR
(n, 4) other numeric FLOAT4 (FLOAT_COLOR before Blender 5.2)
(n, 4, 4) numeric FLOAT4X4

FLOAT_COLOR and QUATERNION are never inferred, so pass them as atype.

Attribute domains

Attributes exist on different domains of the geometry, specified by the AttributeDomains enum:

Domain Description Example Use Cases
POINT Vertices, curve control points, point cloud points Positions, vertex colors, temperature
EDGE Mesh edges Edge weights, crease values
FACE Mesh faces/polygons Material indices, face areas
CORNER Face corners (vertex-face pairs) UV coordinates, split normals
CURVE Individual curve splines Per-spline properties
INSTANCE Geometry instances Instance transforms, IDs
LAYER Grease Pencil layers Per-layer properties
import databpy as db
import numpy as np
import bpy

obj = bpy.data.objects["Cube"]

# POINT domain (default) - one value per vertex
vertex_data = np.random.rand(len(obj.data.vertices), 3)
db.store_named_attribute(obj, vertex_data, "vertex_attr", domain="POINT")

# FACE domain - one value per face
face_data = np.random.rand(len(obj.data.polygons))
db.store_named_attribute(obj, face_data, "face_attr", domain="FACE")

# EDGE domain - one value per edge
edge_data = np.random.rand(len(obj.data.edges))
db.store_named_attribute(obj, edge_data, "edge_attr", domain="EDGE")
bpy.data.meshes['Cube'].attributes["edge_attr"]
ImportantDomain sizes

The length of your data array must match the number of elements in the target domain. A cube has 8 vertices, 12 edges, and 6 faces, so your arrays must have corresponding lengths.

Common workflows

Reading attributes

import databpy as db
import bpy

obj = bpy.data.objects["Cube"]

# Method 1: Simple function call (returns regular numpy array)
positions = db.named_attribute(obj, "position")  # see named_attribute()

# Method 2: Via BlenderObject (returns AttributeArray)
bob = db.BlenderObject(obj)  # see BlenderObject
positions = bob["position"]  # or bob.position

# Method 3: List available attributes
attrs = db.list_attributes(obj)  # see list_attributes()
print(attrs)

# Method 4: With modifier evaluation
evaluated_positions = db.named_attribute(obj, "position", evaluate=True)
['.corner_edge', '.corner_vert', '.edge_verts', '.select_edge', '.select_poly', '.select_vert', '.uv_select_edge', '.uv_select_face', '.uv_select_vert', 'UVMap', 'edge_attr', 'face_attr', 'position', 'sharp_face', 'vertex_attr']

Writing attributes

import databpy as db
import numpy as np

bob = db.create_bob(np.random.rand(10, 3))

# Method 1: Dictionary-style (most convenient)
bob["my_data"] = np.random.rand(10, 3)

# Method 2: Function call (more options)
db.store_named_attribute(
    bob.object,
    data=np.random.rand(10),
    name="custom_attr",
    atype="FLOAT",
    domain="POINT",
    overwrite=True
)

# Method 3: Via BlenderObject method
bob.store_named_attribute(
    np.random.rand(10, 3),
    name="another_attr",
    domain="POINT"
)
bpy.data.meshes['NewObject.002'].attributes["another_attr"]

Modifying attributes in place

import databpy as db
import numpy as np

bob = db.create_bob(np.random.rand(100, 3))

# Using AttributeArray for interactive modifications
pos = bob.position

# Simple operations
pos[:, 2] += 1.0          # Move all points up
pos *= 2.0                # Scale positions
pos[pos < 0] = 0          # Clamp negative values

# Boolean indexing
selection = pos[:, 2] > 0.5
pos[selection, 2] = 1.0

# Column operations
pos[:, 0] = np.linspace(0, 1, len(pos))  # Linear ramp on X

Batch processing

For large datasets, use the Attribute class to batch operations:

import databpy as db
import numpy as np
import bpy

obj = db.create_object(np.random.rand(int(1e5), 3))
attr = db.Attribute(obj.data.attributes["position"])

# Single read
positions = attr.as_array()

# Many operations without syncing
positions[:, 2] += 1.0
positions *= 2.0
positions = np.clip(positions, -10, 10)

# Single write
attr.from_array(positions)
TipAttributeArray compared to Attribute
  • AttributeArray: 1 read, then each operation writes the values it changed
  • Attribute: 1 read + 1 write for any number of operations (manual control)

Operations on a few values are cheap with AttributeArray, but each whole-array operation (pos *= 2.0) writes the whole array again. For many whole-array operations, Attribute is faster as it only writes once.

Integer attributes

import databpy as db
import numpy as np

bob = db.create_bob(np.random.rand(10, 3))

# Store integer IDs
ids = np.arange(10, dtype=np.int32)
bob["id"] = ids

# Retrieve as AttributeArray
id_array = bob["id"]

# Modify (automatically maintains int32 dtype)
id_array += 100

# Verify it's still integers
print(id_array.dtype)  # int32
int32

Boolean attributes

import databpy as db
import numpy as np

bob = db.create_bob(np.random.rand(20, 3))

# Create selection based on position
selection = bob.position[:, 2] > 0.5
bob["selected"] = selection

# Use boolean attribute for filtering
selected_mask = bob["selected"]
bob.position[selected_mask, 2] = 1.0

Errors

databpy uses a consistent exception hierarchy for attribute-related errors:

Exception hierarchy

  • db.DatabpyError - base class for all databpy errors
    • db.NamedAttributeError - raised when attribute operations fail
      • Used for: invalid names, domain size mismatches, type or domain conflicts with an existing attribute
      • db.AttributeNotFoundError (also a KeyError)
        • Raised when an attribute doesn’t exist, including bob["missing"]
      • db.AttributeMismatchError
        • Raised when data doesn’t match attribute expectations
        • Used for: shape mismatches, values that don’t fit the attribute’s type
    • db.LinkedObjectError - raised when an AttributeArray can’t sync because its object was removed or a file was loaded

NamedAttributeError currently also inherits from AttributeError, which will be removed in databpy 0.13.0 as hasattr() and getattr() silently swallow AttributeError. Catch NamedAttributeError or DatabpyError instead.

Common errors

import databpy as db
import numpy as np
import bpy

obj = bpy.data.objects["Cube"]

try:
    # Trying to access non-existent attribute
    data = db.named_attribute(obj, "nonexistent")
except db.AttributeNotFoundError as e:
    print(f"Attribute not found: {e}")

try:
    # Wrong data size for domain
    db.store_named_attribute(obj, np.random.rand(100, 3), "test")
except db.NamedAttributeError as e:
    print(f"Size mismatch: {e}")

try:
    # Shape mismatch when using Attribute class
    attr = db.Attribute(obj.data.attributes["position"])
    wrong_shape = np.random.rand(8, 4)  # Should be (8, 3)
    attr.from_array(wrong_shape)
except db.AttributeMismatchError as e:
    print(f"Shape error: {e}")
Attribute not found: The selected attribute 'nonexistent' does not exist on the object. Available attributes: ['.corner_edge', '.corner_vert', '.edge_verts', '.select_edge', '.select_poly', '.select_vert', '.uv_select_edge', '.uv_select_face', '.uv_select_vert', 'UVMap', 'edge_attr', 'face_attr', 'position', 'sharp_face', 'vertex_attr']
Size mismatch: Data size 300 (shape (100, 3)) does not match the required size 24 for domain `POINT` with 8 elements and dimensions (3,)
Shape error: Array size 32 does not match attribute size 24. Array shape (8, 4) cannot be reshaped to attribute shape (8, 3)
TipCatching all attribute errors

Since AttributeNotFoundError and AttributeMismatchError inherit from NamedAttributeError, you can catch all attribute-related errors with a single except db.NamedAttributeError: clause, or every databpy error with except db.DatabpyError:.

Best practices

1. Choose the level of the API

  • Interactive work, scripting: Use BlenderObject and AttributeArray
  • Performance-critical code: Use Attribute with manual read/write
  • Quick one-off reads: Use named_attribute() function

2. Match Blender’s data types

# Good: Explicit dtype matching Blender's storage
data = np.random.rand(10, 3).astype(np.float32)

# Less good: float64 will be converted to float32 anyway
data = np.random.rand(10, 3)  # defaults to float64

3. Vectorize operations

# Not great: a Python loop with a sync per iteration
pos = bob.position
for i in range(len(pos)):
    pos[i, 2] += 1.0  # writes a single value, but syncs every iteration

# Better: Vectorized operation (single sync)
pos[:, 2] += 1.0

Each sync only writes the changed values, so occasional single-value writes are cheap, but the per-sync overhead adds up in a loop over every element.

4. Domains and types as enums or strings

Domains and types can be given as enums or as strings such as "POINT". Both are checked by type checkers, as the string parameters only accept the valid names.

from databpy import AttributeDomains, AttributeTypes

# equivalent to atype="FLOAT_VECTOR", domain="POINT"
db.store_named_attribute(
    obj,
    np.random.rand(len(obj.data.vertices), 3),
    "my_attr",
    atype=AttributeTypes.FLOAT_VECTOR,
    domain=AttributeDomains.POINT
)
db.named_attribute(obj, "my_attr")
array([[0.53907025, 0.29898933, 0.37800607],
       [0.9290844 , 0.32685682, 0.8981513 ],
       [0.56450593, 0.7567582 , 0.4331677 ],
       [0.01150827, 0.7891584 , 0.9120895 ],
       [0.03559079, 0.04118609, 0.6577564 ],
       [0.40975833, 0.50005865, 0.1216043 ],
       [0.5727442 , 0.08088566, 0.37693074],
       [0.69431007, 0.10804583, 0.03235256]], dtype=float32)

5. Remove temporary attributes

# Remove attributes you no longer need
db.remove_named_attribute(obj, "my_attr")
try:
    db.named_attribute(obj, "my_attr")
except db.NamedAttributeError as e:
    print(e)
The selected attribute 'my_attr' does not exist on the object. Available attributes: ['.corner_edge', '.corner_vert', '.edge_verts', '.select_edge', '.select_poly', '.select_vert', '.uv_select_edge', '.uv_select_face', '.uv_select_vert', 'UVMap', 'edge_attr', 'face_attr', 'position', 'sharp_face', 'test', 'vertex_attr']

See also

Classes

  • BlenderObject - High-level object wrapper with convenience methods
  • AttributeArray - Auto-syncing NumPy array subclass
  • Attribute - Low-level attribute wrapper with manual control

Functions

  • named_attribute() - Read attribute data as NumPy array
  • store_named_attribute() - Write attribute data to object
  • remove_named_attribute() - Delete an attribute
  • list_attributes() - List all attributes on an object
  • create_bob() - Create a new BlenderObject
  • create_object() - Create a new Blender object

Enums

  • AttributeTypes - Enum of all available attribute data types
  • AttributeDomains - Enum of all available geometry domains

Exceptions

  • DatabpyError - Base exception for all databpy errors
  • NamedAttributeError - Base exception for attribute operations
  • AttributeNotFoundError - Exception for missing attributes
  • AttributeMismatchError - Exception for data/type mismatches

External references

  • numpy.ndarray - NumPy array documentation
  • bpy.types.Object - Blender Object documentation
  • bpy.types.Attribute - Blender Attribute documentation