How to set mesh vector attributes in a python script?

I’m trying to write a script that sets some attribute data on a mesh that I hope to later use in a geometry nodes setup. Right now, I’m just trying to write dummy data into the attributes I need. While the int and float data seems to be set, the script is failing when I try to set the vector data.

Traceback (most recent call last):
  File "C:\Users\kitfox\AppData\Roaming\Blender Foundation\Blender\4.2\scripts\addons\BlenderGIS-228\__init__.py", line 95, in _excepthook
    if 'BlenderGIS' in exc_traceback.tb_frame.f_code.co_filename:
                       ^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'tb_frame'

Original exception was:
TypeError: must be real number, not Vector
Traceback (most recent call last):
  File "C:\Users\kitfox\AppData\Roaming\Blender Foundation\Blender\4.2\scripts\addons\WearCreate\roblox_operators.py", line 724, in execute
    attribute_bind.data.foreach_set("vector", pos_values)
TypeError: couldn't access the py sequence

I found some code that used the foreach_set method to set the attribute data, and while this works for float and int data, it is failing for Vector data. I thought the issue might be that FloatVectorAttributeValue uses vector instead of value for this parameter, but using vector instead just gave me a different error.

How can I fix this code so that I can set the data for the "bind_pos" attribute?

Edit:

I’ve also just realized that this isn’t actually setting any attribute data. While it does add the attribute group, when I examine it in the Geometry Nodes spread sheet, only default values are stored, not the dummy data I’m writing here.

        cage_mesh = context.active_object.data
        
        for mesh_obj in context.selected_objects:
            if mesh_obj.type != 'MESH' or mesh_obj == cage_mesh:
                continue
        
            if "bind_pos" in mesh_obj.data.attributes:
                mesh_obj.data.attributes.remove("bind_pos")
            if "index" in mesh_obj.data.attributes:
                mesh_obj.data.attributes.remove("index")
            if "weight" in mesh_obj.data.attributes:
                mesh_obj.data.attributes.remove("weight")
                
            attribute_bind = mesh_obj.data.attributes.new(name="bind_pos", type="FLOAT_VECTOR", domain="POINT")
            attribute_index = mesh_obj.data.attributes.new(name="index", type="INT", domain="POINT")
            attribute_weight = mesh_obj.data.attributes.new(name="weight", type="FLOAT", domain="POINT")
            
            weight_values = [6.0 for i in range(len(mesh_obj.data.vertices))]
            attribute_weight.data.foreach_set("value", weight_values)
            
            index_values = [5 for i in range(len(mesh_obj.data.vertices))]
            attribute_index.data.foreach_set("value", index_values)
            
            pos_values = [mathutils.Vector([1, 2, 3]) for i in range(len(mesh_obj.data.vertices))]
            attribute_bind.data.foreach_set("vector", pos_values)

Hey,

as said here:
https://docs.blender.org/api/current/bpy.types.bpy_prop_collection.html#bpy.types.bpy_prop_collection.foreach_set

it only works with bool, int and float, so you can’t use that function to set them all at once. you can still set all coordinates of the vector to the same value by putting a float as value.

for different values, you need to make the loop yourself.

Zelfior

1 Like

A simple way to pass vectors to the foreach_set, is to use numpy…

import numpy as np

#.....

        pos_values = np.array([(1.0, 2.0, 3.0) for i in range(len(mesh_obj.data.vertices))])
        attribute_bind.data.foreach_set("vector", pos_values.flatten())

1 Like

Note using a list comprehension defeats the purpose of foreach_set wich IMO is its lightning fast computation speed. You might as well go

for i in range(len(mesh_obj.data.vertices)):
    # I assume you want to pass different values here otherwise define the vector once before the loop
    attribute_bind.data[i] = mathutils.Vector([1, 2, 3])  

You won’t notice it if you have a light mesh but if you have millions of vertices the hiccup will be bad. If you want to fill an array you can use numpy.full :

eg

import numpy as np 
...
weight_values = np.full(len(mesh_obj.data.vertices), 6.0)
attribute_weight.data.foreach_set("value", weight_values)

Note foreach_set takes a flattened array, which means the vectors need to be flattened eg [(1,2,3), (1,2,3)] becomes [1,2,3,1,2,3]

import numpy as np 
...
vectors = np.full((len(mesh_obj.data.vertices), 3), (1, 2, 3))
flattened_array = array.flatten()
attribute_bind.data.foreach_set("vector", flattened_array)

You can time the difference but it should be magnitudes faster than using a list comprehension. Cheers