Creating 1000's of objects is slower than I thought it should be. Based on a suggestion here it could be related to name collision checking so I thought I would look into it. Here is some data, and the script I used. As a reference, I also did a simple name comparison between all pairs of objects using an extremely sloowwwww loop in python. That is almost 100 times faster than the creation (which is happening in C, right?) So something else is going on.
Both of these times are clearly proportional to n^2, so it does seem to be checking the new shape against all the others. The times don't change much when I use icospheres (80 faces) instead of cubes(6 faces), so it's based on objects, not faces. That rules out some kinds of checking.
My question is, first, why (actually) is it so slow, and second, how can I create thousands of objects (e.g. cubes) in a script, without waiting minutes each time I tweak some parameter, while still seeing all of them once it's done. I'm using 2.74.
NOTE: I did some spot checking by running the loop only once, or over and over with the same nxy, and there does not appear to be contamination of the measurements due to memory issues (see my comments about garbage collection in the script).
import bpy
import numpy as np
import time
wxy = 15.0
sx, sy, sz = 0.9, 0.9, 0.2
t_create_list, t_check_list = [], []
n_create_list, n_check_list, n_same_list = [], [], []
nxy_list = [10, 15, 20, 25, 30, 35, 40]
listlen = len(nxy_list)
for ii, nxy in enumerate(nxy_list):
rcube = 0.5 * wxy / float(nxy)
q = 0.5 * wxy * (1. - 1. / float(nxy))
xyc = np.linspace(-q, q, nxy)
XC, YC = np.meshgrid(xyc, xyc)
ZC = np.zeros_like(XC)
centers = zip(XC.flatten(), YC.flatten(), ZC.flatten())
start = time.clock()
for ic, center in enumerate(centers):
ok = bpy.ops.mesh.primitive_cube_add(radius=rcube, location=center)
ao = bpy.context.active_object
ao.scale = (sx, sy, sz)
stop = time.clock()
time_to_create = stop - start
so = bpy.context.selectable_objects
number = len(so)
same, count = 0, 0
start = time.clock()
for i in range(number): #very sloowwwww way to compare names
for j in range(i, number):
count += 1
if so[i].name == so[j].name:
same += 1
stop = time.clock()
time_to_check = stop - start
t_create_list.append(time_to_create)
t_check_list.append(time_to_check)
n_create_list.append(number)
n_check_list.append(count)
n_same_list.append(same)
for thing in so:
thing.select = True
bpy.ops.object.delete(use_global=True)
# but can I invoke garbage collection inside this loop?
# for example, can I somehow do a save .blend which should cause garbage collection?
print("number ", ii+1, " of ", listlen)
t_create_list = [round(thing,4) for thing in t_create_list]
t_check_list = [round(thing,4) for thing in t_check_list]
lists = [n_create_list, t_create_list, n_check_list,
t_check_list, n_same_list]
save = np.array(lists[0],dtype='float')
for thing in lists[1:]:
save = np.vstack((save, thing))
np.save("check_it_out", save)
O(n*log(n))
instead ofO(n^2)
, a significant improvement. Might want to open a bugreport $\endgroup$bpy.ops.mesh.primitive_cube_add
which is an.ops
, really meant to be triggered by UI only it does additional scene updates too (besides name collision checking). The pure creation of an object usingdata.objects.new(object_name, object_mesh)
in a loop like that is most likely faster but still noticably slow $\endgroup$