5
$\begingroup$

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))        
    else: 
        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)
bm.to_mesh(me)
bm.free
obj = bpy.data.objects.new(name,me)
scene = bpy.context.scene
scene.objects.link(obj)

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?

$\endgroup$
  • $\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 Oct 10 '15 at 3:41
  • $\begingroup$ Have you had a look at bpy.ops.mesh.convex_hull? $\endgroup$ – DrBwts Mar 3 '16 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$ – Peter Hilgers Mar 7 '16 at 9:54
2
$\begingroup$

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)
    bm.to_mesh(me)
    bm.free

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

    return obj

o = draw_base_tetra()
Tetrads = get_tetrads_from_some_source()

for Tetra in Tetrads:
    d = o.copy()
    bpy.context.scene.objects.link(d)

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

end = time.time()
print( end - start )
$\endgroup$
  • $\begingroup$ Yes I mean tetrahedron, but not regular ones. For the code I will look on monday $\endgroup$ – Peter Hilgers Oct 10 '15 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 Oct 10 '15 at 9:54
  • $\begingroup$ You could replace len(Tetrads) x 2 operator (dupe & select all) calls with o = o.copy() $\endgroup$ – batFINGER Oct 11 '15 at 18:16
  • $\begingroup$ Hmm, not really - that only copies the data, but does not create a new object in the scene $\endgroup$ – TLousky Oct 11 '15 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 Oct 12 '15 at 2:39
5
$\begingroup$

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())
        scene.objects.link(obj)
        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

        pass


# 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) )
$\endgroup$
  • $\begingroup$ The speed gain is amazing! Thank you so much $\endgroup$ – Peter Hilgers Oct 11 '15 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$ – Peter Hilgers Oct 12 '15 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 Oct 12 '15 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$ – Peter Hilgers Oct 14 '15 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$ – Peter Hilgers Oct 14 '15 at 5:26
2
$\begingroup$

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.

Method:

import bpy
import numpy as np

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

    # Create a particle system within the container
    bpy.ops.object.particle_system_add()
    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
    bpy.context.scene.frame_set(1) 

    # 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)

    bpy.ops.object.duplicates_make_real()

    # Cleanup
    bpy.data.meshes.remove(cube.data)
    bpy.data.objects.remove(cube)
    bpy.data.particles.remove(particle_sys)

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()))

>>1.9518235036226486

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).

$\endgroup$
  • $\begingroup$ Looks good, I will try it. $\endgroup$ – Peter Hilgers Jul 30 at 7:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.