1
$\begingroup$

I'm relatively new to blender and scripting in blender so I am missing some basic concepts. I'm working with Blender 3.0.

I'm trying to import some position and radius information for many particles (~18k). After a bit of stumbling around I've found that importing a vertex cloud and creating sphere instances on the vertices is a very fast way to load my geometry.

My problem is that I can't figure out how to scale the instances - I know from playing around with geometry nodes in the gui that a 'random value' node can output per-instance scaling vectors. However I want to input a list of scaling vectors to my InstanceOnPoints geometry node.

When I naively try to do this by giving my node's input['Scale'] a numpy array, it throws an error (seemingly it wants just a length 3 list to turn into a single vector).

I'm having a lot of trouble searching for this information in the docs or elsewhere, so any help is appreciated.

My script:

import bpy
import bmesh
import numpy as np
import mathutils

# load some data with each row [x,y,z,radius,type]
fdir = '/Users/bryan/Downloads/frame_0.npy'
pdata = np.load(fdir)
scale = 0.1
box = np.array([200, 200, 600])*scale

# for each particle type
for typ in [0,1]:
    # select some subset of particles
    cond = (pdata[:,2]<10)*(pdata[:,4]==typ)
    pos = pdata[cond,0:3]*scale + np.array([[0,0,box[2]/2]])

    # these are the scale vectors I want to apply
    scales = pdata[cond,3].reshape(-1,1)*scale*np.ones((len(pos),3))
    
    # make vertex cloud
    obj_name = "points_t{t}".format(t=typ)
    mesh_data = bpy.data.meshes.new(obj_name+"_data")
    pos_obj = bpy.data.objects.new(obj_name, mesh_data)
    bpy.context.collection.objects.link(pos_obj)
    mesh_data.from_pydata(pos, [], [])

    # add the geometry nodes
    gm = pos_obj.modifiers.new('GN', 'NODES')
    geom_in = gm.node_group.nodes.get('Group Input')
    geom_out = gm.node_group.nodes.get('Group Output')
    points = gm.node_group.nodes.new('GeometryNodeMeshToPoints')
    sphere = gm.node_group.nodes.new('GeometryNodeMeshIcoSphere')
    instances = gm.node_group.nodes.new('GeometryNodeInstanceOnPoints')
    
    sphere.inputs['Subdivisions'].default_value = 2
     
    gm.node_group.links.new(geom_in.outputs['Geometry'], points.inputs['Mesh'])
    gm.node_group.links.new(points.outputs['Points'], instances.inputs['Points'])
    gm.node_group.links.new(sphere.outputs['Mesh'], instances.inputs['Instance'])
    gm.node_group.links.new(instances.outputs['Instances'], geom_out.inputs['Geometry'])
    
    instances.inputs['Scale'].default_value = scales # this causes an error
$\endgroup$
3
  • $\begingroup$ What does print(scales) print? As you noted yourself, the default value of the Scale input expects a vector, or something it's able to convert to the vector (an iterable of 3 numbers). $\endgroup$ Jan 2, 2022 at 23:02
  • $\begingroup$ @MarkusvonBroady scales is an Nx3 numpy array, where N is the number of points in the object pos_obj. The intent is to apply a length-3 scale vector to each instance generated by InstanceOnPoints (in a manner analogous to how linking a random value node into the Scale input of the instances on points geometry node in the gui yields per-instance scaling) $\endgroup$ Jan 2, 2022 at 23:10
  • 1
    $\begingroup$ That's not how nodes work, a default value is a value that is the same for the entire evaluation of the node tree. To have different values for different points, you need to link another node to this input that is evaluated multiple times, for example an image texture node (where the 3 values would be pixels, and the coordinates on the image texture could be calculated based on a point coordinate or index... $\endgroup$ Jan 2, 2022 at 23:21

1 Answer 1

1
$\begingroup$

Per-instance scaling on a point cloud can be accomplished by setting the radius attribute of the point cloud, and adding a InputRadius node. The following lines need to be added to the original script:

pos_obj.data.attributes.new(name='radius', type='FLOAT', domain='POINT')
pos_obj.data.attributes['radius'].data.foreach_set('value', radii) # where radii is a list of radii of the same length as the number of points

rnode = gm.node_group.nodes.new('GeometryNodeInputRadius')
    
gm.node_group.links.new(rnode.outputs['Radius'], instances.inputs['Scale'])
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .