Getting data from a stored name attribute back to python

Following my previous question on how to generate textures without rendering, using the python module as a standalone:

Someone suggested that I actually use geometry nodes! I can create a grid and then store the texture values for each vertex and access it from there without any need to render. This sounds like a great idea - I just want to get texture value, no need for lighting etc - but I can’t find how to access data in “stored name attribute” and bring it back to python.
Here’s the code I have:

import bpy

def create_node(node_tree, type_name, node_x_location, node_location_step_x=0):
    """Creates a node of a given type, and sets/updates the location of the node on the X axis.
    Returning the node object and the next location on the X axis for the next node.
    """
    node_obj = node_tree.nodes.new(type=type_name)
    node_obj.location.x = node_x_location
    node_x_location += node_location_step_x

    return node_obj, node_x_location

bpy.ops.mesh.primitive_plane_add()

bpy.ops.node.new_geometry_nodes_modifier()
node_tree = bpy.data.node_groups["Geometry Nodes"]

out_node = node_tree.nodes["Group Output"]
in_node = node_tree.nodes["Group Input"]

node_x_location = 0
node_location_step_x = 300

grid, node_x_location = create_node(node_tree, "GeometryNodeMeshGrid", node_x_location, node_location_step_x)
grid.inputs[2].default_value = 512
grid.inputs[3].default_value = 512
voronoi, node_x_location = create_node(node_tree, "ShaderNodeTexVoronoi", node_x_location, node_location_step_x)
store_attr, node_x_location = create_node(node_tree, "GeometryNodeStoreNamedAttribute", node_x_location, node_location_step_x)
store_attr.inputs[2].default_value = 'out'
out_node.location.x = node_x_location

node_tree.links.new(grid.outputs[0], store_attr.inputs["Geometry"])
node_tree.links.new(store_attr.outputs["Geometry"], out_node.inputs["Geometry"])
node_tree.links.new(voronoi.outputs["Distance"], store_attr.inputs["Value"])

Any ideas how can I get the data back to python now?
(The goal of this whole task is to be able to generate lots of random textures - randomly generating node trees with different combinations of textures, then generating their corresponding image)

Because you are creating a new mesh in GN, you need to evaluate your object first, and then get the attributes from the evaluated object.

dpg = bpy.context.view_layer.depsgraph
ev_obj = bpy.data.objects['Your Object'].evaluated_get(dpg)
attributes = ev_obj.data.attributes['out'].data
1 Like

Now I have this:

import bpy

def create_node(node_tree, type_name, node_x_location, node_location_step_x=0):
    """Creates a node of a given type, and sets/updates the location of the node on the X axis.
    Returning the node object and the next location on the X axis for the next node.
    """
    node_obj = node_tree.nodes.new(type=type_name)
    node_obj.location.x = node_x_location
    node_x_location += node_location_step_x

    return node_obj, node_x_location

bpy.ops.node.new_geometry_nodes_modifier()
node_tree = bpy.data.node_groups["Geometry Nodes"]

out_node = node_tree.nodes["Group Output"]
in_node = node_tree.nodes["Group Input"]

node_x_location = 0
node_location_step_x = 300

grid, node_x_location = create_node(node_tree, "GeometryNodeMeshGrid", node_x_location, node_location_step_x)
grid.inputs[2].default_value = 512
grid.inputs[3].default_value = 512
voronoi, node_x_location = create_node(node_tree, "ShaderNodeTexVoronoi", node_x_location, node_location_step_x)
store_attr, node_x_location = create_node(node_tree, "GeometryNodeStoreNamedAttribute", node_x_location, node_location_step_x)
store_attr.inputs[2].default_value = 'out'
out_node.location.x = node_x_location

node_tree.links.new(grid.outputs[0], store_attr.inputs["Geometry"])
node_tree.links.new(store_attr.outputs["Geometry"], out_node.inputs["Geometry"])
node_tree.links.new(voronoi.outputs["Distance"], store_attr.inputs["Value"])

dpg = bpy.context.view_layer.depsgraph
ev_obj = bpy.data.objects['Cube'].evaluated_get(dpg)
attributes = ev_obj.data.attributes['out'].data

And I’m getting
“KeyError: ‘bpy_prop_collection[key]: key “out” not found’”

Did I miss something?

You need to wait for the nodetree to be evaluated (after a context update).

If you run this after (in separated actions) your initial script has ran:

import bpy

dpg = bpy.context.view_layer.depsgraph
ev_obj = bpy.data.objects['Cube'].evaluated_get(dpg)
attributes = ev_obj.data.attributes['out'].data
#just check the first 20 entries, as there are 262144 of them.
slice = attributes[:20] 
for i, atr in enumerate(slice):
    print(i, atr.value)

you’ll get this:

0 0.43779173493385315
1 0.43665820360183716
2 0.43574145436286926
3 0.43504294753074646
4 0.43456369638442993
5 0.434304416179657
6 0.43426549434661865
7 0.4344469904899597
8 0.4348486363887787
9 0.4354698359966278
10 0.4363096356391907
11 0.437366783618927
12 0.43863970041275024
13 0.44012650847435
14 0.44182509183883667
15 0.44373294711112976
16 0.44584742188453674
17 0.4481655955314636
18 0.45068439841270447
19 0.4534003436565399
....
1 Like

Amazing, thank!
a few more questions though…

  1. How do I get the nodetree evaluated? Sure, when I manually run this separately it works but the goal is to automate the whole process so I don’t have to manually separate it.
  2. Is there a faster method to access the data and put it in a np.array? iterating with a for loop is very slow
  3. Sadly this method is still somehow slower than rendering a full image! Which is insane because I really just need to access the evaluations of the textures, ignoring lights, camera etc. When I profiled the code, almost the entire runtime went to _view_layer_update. I don’t know what that is, is there any way to remove or optimize it?
  1. Just call bpy.context.view_layer.update() between the two parts

  2. You need to create a np.array with length equal to the attributes (same dtype), and then you can use .foreach_get

import numpy as np
nparray = np.zeros(len(attributes), dtype=np.float32)
attributes.foreach_get('value', nparray)
  1. The view_layer.update is quite fast… Of course, doing it in the GPU will be way faster.
1 Like

I wonder how geometry nodes are updated. It surely is optimized somehow, but might it be faster to work with premade geometry?.. Another thing is - geometry has a lot of other stuff: edges, faces, normals… I wonder if that has anything to do with how fast it updates. Maybe using only points or only vertices might be faster.

some months ago, Iliya told me in the chat how all gets evaluated (just an overview)… it has some complex branched stuff, where some nodes require different steps… I cannot clarify it better than this… :slight_smile:

I think if Oren_Matar is after speed it might be worth testing.

Well… for speed, the best is to use the gpu module…
A 512*512 buffer there, would render in a fraction of a millisecond, and the only perk would be transfering the data from the GPU to the disk…

If it’s just to use a Voronoi texture, it’s rather a simple solution; but if one wants to use the complete shader nodetree, it will require that all nodes have their function written in glsl (which can be copied from the source), and a compiler for the nodetree. it’s doable, but it requires a good knowledge of glsl, and quite some code

Amazing! it seems to work, but still takes 2-3x the runtime of a full render…
Now, how can I make the view layer update use the GPU? I tried switching to cycles and use GPU device but that has no effect.

Now my code is:

import bpy
import numpy as np

def create_node(node_tree, type_name, node_x_location, node_location_step_x=0):
    """Creates a node of a given type, and sets/updates the location of the node on the X axis.
    Returning the node object and the next location on the X axis for the next node.
    """
    node_obj = node_tree.nodes.new(type=type_name)
    node_obj.location.x = node_x_location
    node_x_location += node_location_step_x

    return node_obj, node_x_location

bpy.ops.node.new_geometry_nodes_modifier()
node_tree = bpy.data.node_groups["Geometry Nodes"]

out_node = node_tree.nodes["Group Output"]
in_node = node_tree.nodes["Group Input"]

node_x_location = 0
node_location_step_x = 300

grid, node_x_location = create_node(node_tree, "GeometryNodeMeshGrid", node_x_location, node_location_step_x)
grid.inputs[2].default_value = 512
grid.inputs[3].default_value = 512
voronoi, node_x_location = create_node(node_tree, "ShaderNodeTexVoronoi", node_x_location, node_location_step_x)
store_attr, node_x_location = create_node(node_tree, "GeometryNodeStoreNamedAttribute", node_x_location, node_location_step_x)
store_attr.inputs[2].default_value = 'out'
out_node.location.x = node_x_location

node_tree.links.new(grid.outputs[0], store_attr.inputs["Geometry"])
node_tree.links.new(store_attr.outputs["Geometry"], out_node.inputs["Geometry"])
node_tree.links.new(voronoi.outputs["Distance"], store_attr.inputs["Value"])

bpy.context.view_layer.update()

dpg = bpy.context.view_layer.depsgraph
ev_obj = bpy.data.objects['Cube'].evaluated_get(dpg)
attributes = ev_obj.data.attributes['out'].data

nparray = np.zeros(len(attributes), dtype=np.float32)
attributes.foreach_get('value', nparray)
print(nparray)

You can’t!
All your code requires to run in CPU only. View_layers and GN don’t work in GPU, so you’re doomed to use the CPU.

When I mentioned the GPU above, was in the same direction as I explained in the other thread…

For example, these are the vert and frag shaders for a simple voronoi (taken from 2.79).
The frag shader, includes most nodes that existed at that time (thought the execution only calls the node_tex_voronoi function).
mat_Material.001.vert (2.3 KB)
mat_Material.001.frag (100.0 KB)

With a some work, one might be able to update this to 4.0+… but it’s enough to get the idea.

1 Like

Right… that seems above my capabilities and effort I’m willing to put in. I guess I’ll have to work with the full render.
Thanks!