In my application I have to create up to some 1000 tetraders (objects with 4 vertices, 4 faces, 6 edges). As input I get only the absolute vertex coordinates for each tetraeder. From the mathematical background I know that all tetraeders are identical but have a different location and rotation which I do not know.

The following code works (Tetra is some class providing the vertex coordinates of an tetraeder and some other informations which is not relevant here):

# object creation
def draw_Tetra(Tetra, Faces): 

    bm = bmesh.new()
    V1 = bm.verts.new(Tetra.Vertices[0])
    V2 = bm.verts.new(Tetra.Vertices[1])
    V3 = bm.verts.new(Tetra.Vertices[2])
    V4 = bm.verts.new(Tetra.Vertices[3])

    if Faces == True: 
        bm.faces.new((V1, V2, V3))  
        bm.faces.new((V1, V2, V4))
        bm.faces.new((V1, V3, V4))
        bm.faces.new((V2, V3, V4))        
        bm.edges.new((V1, V2))
        bm.edges.new((V1, V3))
        bm.edges.new((V2, V3))
        bm.edges.new((V1, V4))
        bm.edges.new((V2, V4))
        bm.edges.new((V3, V4))  

name = "T_" + Tetra.Type + "_" + Tetra.pedigree
me = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name,me)
scene = bpy.context.scene

However the script is extremely slow, e.g. to create 5000 tetraeders takes many hours. I think that I have the same problem as discussed here:

Python performance with Blender operators

The problem is that I do not know how to use "low level code" in this special situation. Can someone give me a tip?

  • $\begingroup$ I think "tetrader" is the German(?) word for what in English is known as a "tetrahedron", or if plural "tetrahedra". It seems that you are referring to regular tetrahedra since you mentioned them being mathematically all identical in proportion. Just pointing this out for clarification. Sorry I don't have enough Python knowledge to help answer your question. $\endgroup$
    – Mentalist
    Commented Oct 10, 2015 at 3:41
  • $\begingroup$ Have you had a look at bpy.ops.mesh.convex_hull? $\endgroup$
    – DrBwts
    Commented Mar 3, 2016 at 16:54
  • $\begingroup$ No, I did not look at bpy.ops.mesh.convex_hull because the most important point is NOT to call bpy.context.scene.update for every new created object but only once. $\endgroup$ Commented Mar 7, 2016 at 9:54

3 Answers 3


Not sure how much faster this will be (hard to test your code since you only posted a fraction of it), but here's another approach that might be faster. It was quite fast in a test I ran here, 0.02 sec for 50 tetraders.

This approach is based on creating just one "base" tetrader object, and duplicating and updating its verts in every loop cycle:

import bpy
import time

start = time.time()

def draw_base_tetra(): 
    bm = bmesh.new()
    V1 = bm.verts.new((0.5,  0.5,  0.0))
    V2 = bm.verts.new((0.5, -0.50, 0.0))
    V3 = bm.verts.new((-0.5, 0.5,  0.0))
    V4 = bm.verts.new((0.0,  0.0,  1.0))

    bm.faces.new((V1, V2, V3))  
    bm.faces.new((V1, V2, V4))
    bm.faces.new((V1, V3, V4))
    bm.faces.new((V2, V3, V4))

    name = "tet"
    me   = bpy.data.meshes.new(name)

    obj   = bpy.data.objects.new(name,me)
    scene = bpy.context.scene

    return obj

o = draw_base_tetra()
Tetrads = get_tetrads_from_some_source()

for Tetra in Tetrads:
    d = o.copy()

    for i,v in enumerate( d.data.vertices ):
        v.co = Tetra.vertices[i]

end = time.time()
print( end - start )
  • $\begingroup$ Yes I mean tetrahedron, but not regular ones. For the code I will look on monday $\endgroup$ Commented Oct 10, 2015 at 9:36
  • $\begingroup$ You can change the vertex coordinates in each tetrahedron just like in your original script, only difference is using duplication to create objects instead of creating them from empty mesh objects. $\endgroup$
    – TLousky
    Commented Oct 10, 2015 at 9:54
  • $\begingroup$ You could replace len(Tetrads) x 2 operator (dupe & select all) calls with o = o.copy() $\endgroup$
    – batFINGER
    Commented Oct 11, 2015 at 18:16
  • $\begingroup$ Hmm, not really - that only copies the data, but does not create a new object in the scene $\endgroup$
    – TLousky
    Commented Oct 11, 2015 at 19:41
  • $\begingroup$ Add scene.objects.link(o) to link it to the scene. With that many operator calls you are going to encounter the performance hassles (link in OP). $\endgroup$
    – batFINGER
    Commented Oct 12, 2015 at 2:39

Could also look at using from_pydata ? The following runs in 11 seconds on my Athlon pussbox. Creating and freeing a bmesh for each new mesh seems a little overkill.

import bpy
import time

class Tetra():

    def object(self, scene):
        obj = bpy.data.objects.new("Tetra", self.mesh_from_pydata())
        return obj

    def mesh_from_pydata(self):
        me = bpy.data.meshes.new("some_name")
        me.from_pydata(self.Verts, [], self.Faces)
        return me

    def __init__(self, verts):
        self.Verts = verts
        faces = []
        for i in range(4):
            faces.append([j for j in range(4) if j != i])
        self.Faces = faces


# test drive
context = bpy.context
scene = context.scene
start = time.time()
for x in range(5000):
    if not x % 100:
       print("Creating Object", x)
    tet = Tetra([(0.5,  0.5,  0.0), (0.5, -0.50, 0.0), (-0.5, 0.5,  0.0), (0.0,  0.0,  1.0)])
    obj = tet.object(scene)

end = time.time()
print("Done in %d seconds" % (end - start) )
  • $\begingroup$ The speed gain is amazing! Thank you so much $\endgroup$ Commented Oct 11, 2015 at 18:03
  • $\begingroup$ when I run the second script (the one with from_pydata) on my computer I also observe the 11 seconds computing time. But when I select the objects in the viewport, delete them and run the script again time increases (41 seconds after third repetition). The outliner shows that there are more and more some_name.xxxx meshes which are not deleted. Is it possible to delete the objects AND the meshes in a script? $\endgroup$ Commented Oct 12, 2015 at 14:47
  • $\begingroup$ You can use the remove method bpy.data.objects.remove(ob)... meshes.remove(me)... The object needs to have no users. IMO saving and reopening is a quick and easy way to remove xs deleted objects. It may, and probably has, improved but often my scripts that perform many removes result in blender crashing. $\endgroup$
    – batFINGER
    Commented Oct 12, 2015 at 15:01
  • $\begingroup$ This works very fine. Remove does not cause problems in Blender 2.76 even when several thousand meshes are removed. $\endgroup$ Commented Oct 14, 2015 at 5:19
  • 1
    $\begingroup$ This works very fine. Remove does not cause problems in Blender 2.76 even when several thousand meshes are removed. However, I do not understand why there is such a big speed gain. In comparison to my code there is about the same number of object.new and object.link calls. In order to learn for other projects it would be helpful to gain more understanding. $\endgroup$ Commented Oct 14, 2015 at 5:26

In Blender, calls to bpy.data.objects.new() and .copy() become slow when there are many objects in the scene. So far, I've discovered that using a Particle system with Render->Object and Convert results in the fastest way to create many Blender objects. Using the code below I was able to create 5000 new objects in ~1.9s.

The particle system method also allows setting object location and rotations using .foreach_set() while the objects are still particles, which is must faster than looping over objects after they've been created.


import bpy
import numpy as np

def create_many_copies(target_obj, count):
    # Create a particle system container
    cube = bpy.context.object

    # Create a particle system within the container
    particle_sys = cube.particle_systems[0].settings

    # Set parameters so all particles show up at once
    particle_sys.frame_start = -1
    particle_sys.frame_end = 1

    particle_sys.count = count

    particle_sys.emit_from = 'VOLUME'

    # No random rotations
    particle_sys.use_rotations = True
    particle_sys.rotation_mode = 'NONE'

    # No physics
    particle_sys.physics_type = 'NO'

    # Duplicate the target object for each particle
    particle_sys.render_type = 'OBJECT'
    particle_sys.dupli_object = target_obj    
    particle_sys.particle_size = 1.0
    particle_sys.use_rotation_dupli = True
    particle_sys.use_scale_dupli = True

    # Make sure we're at the correct frame

    # Efficiently set the location of the objects (here all to 0,0,0)
    # must be a flat array/list with all locations pre-set
    # Otherwise the locations will be random and can be set by 
    # looping over objects (slower than foreach_set)
    # Rotations can be set in similar fashion
    locs = np.zeros(count*3) 
    cube.particle_systems[0].particles.foreach_set("location", locs)


    # Cleanup

Test code:

# Delete old dups
#for ob in bpy.data.objects:
#    if "." in ob.name:
#        bpy.data.objects.remove(ob)

# Tetrahedron is the mesh object to copy
target = bpy.data.objects['Tetrahedron']
create_many_copies(target, 20)

Performance test:

import timeit
print(timeit.timeit('create_many_copies(target, 5000)', number=1,globals=globals()))


This method is essentially letting the optimized particle convert operator do the work and avoid some of the overhead of creating objects with .new() and scene.link() calls.

Once pre-created, the new object properties can be set as desired without incurring the creation cost.

This method works for making copies of Meshes, Empties, and Curves (it might work with other types, but I haven't tested it).

  • $\begingroup$ Looks good, I will try it. $\endgroup$ Commented Jul 30, 2019 at 7:49

You must log in to answer this question.

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