Is there a faster way to join several thousand cylinder meshes? .join() is too slow

Hello!
As in the Title, I have thousands (up to 50-100k) of cylinders and spheres that I need to ‘Join’ into a single object. Using bpy.ops.object.join() on the selected objects works, but is far too slow. I am joining ~10000, 24 side cylinders and it is taking about 10 min or even more! (Blender 3.4) … ouch. Plus, I ultimately want to join up to 100k elements…
I tried a lot of ideas using bmesh but nothing works, partly because operators that I used to use years ago ( e.g. bmesh.ops.join() ) , are no longer available in 3.4?

I can program in python no problem. Any help, kind of desperately needed.

Much thanks for any help or suggestions.

Have you considered using geometry nodes to create individual instances of those objects. If you need a single mesh at the end, you may use a realize instances node.

No idea whether this works for your use case though or if it is fast enough.

Yes, I’ve tried some things with Geo nodes, but am novice at that. I might have done something wrong but it was also even slower. Right now I am simply copying a cylinder primitive multiple times, to avoid having to make new instances in any way, then joining all the instances, which works.

basically like this snippet:

    #this is just a generic loop meaning I first create a lot of cylinder copies...
    for i = range(manycylinders)    
        cylinder = cylinder.copy()
        cylinder.name = 'Metajoin_{}'.format(i)

    bpy.ops.object.select_all(action='DESELECT')
    obj_cap_fixed.select_set(True)
    #obj_cap_loaded.select_set(True)
    #obj_cap_shaft.select_set(True)  # <<---- add other connecting objects here
    for obj in bpy.data.objects:
        if obj.name.startswith('Metajoin'):
            obj.select_set(True)
    
    bpy.context.view_layer.objects.active = obj_cap_fixed
    bpy.ops.object.join()
    objcaps = bpy.context.object

I’m not familiar with a bmesh.ops.join() ever having been part of the API. That said- you don’t need an operator to join one bmesh to another, as bmesh objects are by their very nature additive.

This will take the selected objects and join them together into a single object- it doesn’t handle materials, or any other data like UVs or vertex colors. It’s easy enough to add, I’ll leave that up to you.


import bpy
import bmesh

selected_objects = bpy.context.selected_objects

# Create a new empty mesh and object to join the selected objects into
new_mesh = bpy.data.meshes.new("new_mesh")
new_object = bpy.data.objects.new("new_object", new_mesh)

# Join the selected objects into the new object's mesh using bmesh
bm = bmesh.new()
for obj in selected_objects:
    if obj.type == 'MESH':
        # Get the object's mesh and transform matrix
        mesh = obj.data
        matrix = obj.matrix_world

        # Apply the object's transform matrix to the mesh using a copy. this is just one way to do this,
        # you could also use bm.transform, etc.
        mesh_copy = mesh.copy()
        mesh_copy.transform(matrix)

        # Add the transformed mesh to the bmesh
        bm.from_mesh(mesh_copy)

# Convert the bmesh to the new mesh
bm.to_mesh(new_mesh)

# Link the new object to the scene and select it
bpy.context.scene.collection.objects.link(new_object)
bpy.context.view_layer.objects.active = new_object
new_object.select_set(True)

# Delete the original selected objects
for obj in selected_objects:
    bpy.data.objects.remove(obj, do_unlink=True)

1 Like

Thanks much! I will try this and report back.

Here is a quick example using geometry nodes.

It uses the vertices of the icosphere as positions to create instances of the cylinders. At the end, there is a realize instance if you actually need a single mesh at the end.
Instantiate Cylinders with Geometry Nodes.blend (837.8 KB)

Edit: I guess the other suggestion makes more sense for you. Only saw it after posting :slight_smile:

Hey thanks much anyway. I’m going to try working with Geo nodes and this will give me a great head start. Best!

Hi testure,
Well thanks it works, but actually took more than 2x longer than the ‘bpy.ops.object.join()’ on appx 10000 cylinders, about 155 seconds for this bmesh method, versus 75 sec for regular .join() …

I guess I don’t know what to do from here, any other suggestions are very welcome.

what are you trying to do? maybe there’s another way to accomplish what you’re after that doesn’t involve joining that much geometry. If you manually select 10,000 cylinders and hit CTRL+J to join them using the native operator, how fast is it (removing time for looping through objects, etc)? I don’t know how dense your cylinders are, but a conservative estimate puts your total vertex count nearly at 500,000 which Blender is going to have a hard time with even on the best of days.

Hi, I’m making stress adapative meta-structures for high performance components, such as this. Note that this is not a surface, but a ‘solid’ hypermesh structure, so there are many more tapered cylinders inside.
I’m not hitting ctrl-J (that’s even slower), but using python and bpy.ops.object.join(). Each cylinder has 40 vertices, so 40 * ~50k = ~2 M vertices, which is not a problem. I regularly go up to 20M vertices for larger meta-structures. Everything else I do (adapting, sizing, etc.) is quite fast. It’s just the join() operation that is the last remaining ‘slow’ part. This smaller part with 5k cylinders took about 70 sec to “Join”. I feel like there should be some way to speed this up, but maybe not. Hmm. I have to join them to enable NanoVDB to voxel remesh in the final step. Maybe there is another way other than Joining? I haven’t found another way.

The native join operator also uses bmesh under the hood- basically all mesh operations do at this point. Bmesh makes geometry much easier to work with, but it’s not the fastest data structure.

One possible idea to experiment would be to use numpy and read the geometry directly from the objects mesh data, store it into a buffer, and then use numpy to create the mesh.

If the initialization time isn’t too high, the time spent looping through geometry with numpy is going to be orders of magnitude faster.

an older thread, but still relevant: https://devtalk.blender.org/t/alternative-in-2-80-to-create-meshes-from-python-using-the-tessfaces-api/7445

The problem is not the number of vertices, but the number of objects. If all your cylinder objects had only 1 or 2 verts it would also take an ungodly amount of time. You can’t join objects with 0 geometry, but if you could it would also take an insane amount of time.

Blender handles several million vertices and triangles pretty well, but only if they’re displayed in an optimised way.

But it doesn’t handle several thousand different objects well, unfortunately. The devs are aware of this and they sometimes issue patches to fix it. See https://projects.blender.org/blender/blender/commit/68589a31eb
You should definitely try out geometry nodes, it seems like your script might benefit from it. protip : don’t instance cylinders, instance curve lines and transform them to mesh using a circle curve.

If you really want to instantiate cylinder meshes the old fashioned way, you should do it inside a single object in edit mode using bpy.ops.mesh.primitive_cylinder_add instead of in object mode, that way you don’t need to join objects afterwards.

1 Like

Thanks. I am looking at the single object in edit mode as you described. It looks totally doable this way, but I’m not sure if it will increase or slow down speed (so far it’s slower, but I’m still working on it). I’ll update here if/when I get something working. Perhaps will be helpful to others as well.
Again, thanks for your input. Much appreciated.

you can also programmatically create your geometry using vertex locations and face indices, and update the mesh only once with mesh.from_pydata(verts, edges, faces) which is extremely fast