how to draw a lot of cylinders, FAST?

I’m working on a plugin to use Blender to visualize some simulation results, to make a picture of atoms and bonds, a similar idea to this one . I’m drawing the atoms as dupliverts, and there’s no problem there, but the bonds - it’s prohibitively slow (I have 20,000-30,000 bonds), and I’m wondering if anyone has suggestions. I can’t use dupliverts like with atoms since each bond has a different length and rotation. (Unless there’s something I’m missing?)

Currently, I make one bond (BaseObj), and then copy it to duplicate it. BaseObj is a NURBS path with a Bezier circle as the bevel object, so it looks like an uncapped cylinder. Bonds is a Python array of bond locations and things.


for bond in bonds :
    
    bpy.ops.object.select_all(action = 'DESELECT')
    BaseObj.select = True
    
    # Duplicating shifts selection to the first duplicate rather than the most recent
    bpy.ops.object.duplicate(linked=True)
    NewObj = bpy.data.objects[BaseObj.name + ".001"]
    NewObj.name = "Bond"                  # I'm changing the name so that I can find the next duplicate
    
    NewObj.location = bond.location
    NewObj.scale = (1, 1, bond.length)
    bpy.ops.transform.rotate(value=bond.rot_angle, axis=bond.rot_axis)

I timed it, and 60-70% of the drawing time is spent on just duplicating.
I’m doing linked duplicates to make sure they all have the same material. Duplicating unlinked + assigning material after the fact seems to take longer.

I would appreciate suggestions or even small improvements. One thing I thought might be a tiny bit faster is using bpy.ops.object.duplicate_move(OBJECT_OT_duplicate=None, TRANSFORM_OT_translate=None) but I can’t figure out what’s expected in the arguments.

See:
Blender gets very slow to draw a scene having large number of plane?

That answer is for when all the objects are identical. I need cylinders with different length and different rotation, so I don’t see how dupliverts could be used.

Btw, I found that parenting all the cylinders to an empty cuts the time significantly, in my case from 100 sec to ~65.

If they all need to be different, then it’s gong to be slow. Not sure why an empty makes it faster, definately an interesting find.

Alright, this thread was exactly what I was looking for! I also found myself struggling to add large numbers of objects to the scene without everything getting enormously slow as the number of objects (bezier circles, in my case) increases.

My first attempt used the bpy.ops.curve.primitive_bezier_circle_add() function, and it took 416 seconds to add 4096 circles to the scene. When I use the method described above (using object.copy()), it takes about 27 seconds, which is about 15 times faster.

Even better: when I run my simulation without actually adding anything to scene, it takes about 26 seconds (pure simulation time). So this means that the overhead of adding 4096 circles to the scene has dropped from 390 (416 - 26) seconds to about 1 (27 - 26) second, which is about, err…, 390 times faster.

Which is a very nice result, indeed!

Not sure why an empty makes it faster, definately an interesting find.

It’s because of the outliner. I ran across this with my RE:Group AddOn which makes a lot of objects. If objects are parented to another object, in this case an Empty, only one refresh is needed in the outliner window.

Overall you will get better performance by not using bpy.ops. It is going to issue a scene update for every create.

I have attached a BLEND file with a script to generate a cylinder on-the-fly. This is Revolt Randy’s parametric beam code. So you could create cylinders with different side counts depending upon how far away from the camera they are. This code also supports thickness as well as your requirement for random height.

On my 2.5Ghz laptop, with 4Gb ram, I was able to generate 10,0000 cylinders in about 2.5 minutes.

I have separated the generation into two parts. First all meshes are generated and linked to new objects. After all that creation all the objects are linked to the scene.

Code snippet


# Start here.
scene = bpy.data.scenes[0]
parent_name = "mt_Parent"
mt_parent = bpy.data.objects.get(parent_name)
if mt_parent == None:
    mt_parent = createEmpty(scene,parent_name)


count = 1000
ob_name = "myCylinder"
ln = 8.01   # Height
sz = 3.11   # Radius
t = 0.2     # Thickness
s = 6       # Sides


# Create them all but do not link them or update the scene.
for n in range(count):
    v = (random.random()*4-1)  # Randomize height here.
    size = (1, ln+v, sz)
    ob_name = "%s.%s" % ("myCylinder", returnNameForNumber(n))
    print ("Creating %s." % ob_name)
    verts, faces = create_multi_side_box(size, t, s)
    me_new = returnMeshCreated("me_%s" % ob_name, verts, faces)
    ob = bpy.data.objects.new(ob_name, me_new)
    ob.parent = mt_parent


# Link them all to the scene.
for n in range(count):
    ob_name = "%s.%s" % ("myCylinder", returnNameForNumber(n))
    print("Linking %s." % ob_name)
    ob = bpy.data.objects.get(ob_name)
    if ob != None:
        try:
            scene.objects.link(ob)
        except:
            pass

Attachments

269_make_cylinders.blend (92.8 KB)