5
$\begingroup$

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

time to make a thousand cubes

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)
$\endgroup$
10
  • 2
    $\begingroup$ I think your suspicion is correct. It compares against all other object's names to rule out duplicates. I wish I could give you credible source from the source code, maybe later I'll dig into it. EDIT: going against the mentioned answer, it could be faster - if one edits the blender source code - it could have a runtime of O(n*log(n)) instead of O(n^2), a significant improvement. Might want to open a bugreport $\endgroup$ Commented Jul 28, 2015 at 6:55
  • $\begingroup$ It would be great to have a facility to switch off collision checks (for new non-primitive objects - which allow you to specify an object name), in the same way one can switch off undo for parts of the script. $\endgroup$
    – zeffii
    Commented Jul 28, 2015 at 7:46
  • 1
    $\begingroup$ Also note that you are doing 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 using data.objects.new(object_name, object_mesh) in a loop like that is most likely faster but still noticably slow $\endgroup$
    – zeffii
    Commented Jul 28, 2015 at 8:00
  • 1
    $\begingroup$ related blender.stackexchange.com/questions/7358/… $\endgroup$
    – zeffii
    Commented Jul 28, 2015 at 9:18
  • 2
    $\begingroup$ As I found in the tests I done dupliverts are much faster than creating one cube at a time. $\endgroup$
    – sambler
    Commented Jul 28, 2015 at 12:53

1 Answer 1

5
$\begingroup$

I followed the code from bpy.data.objects.new and will put this here.

Call Hierarchy

rna_Main_objects_new               /* called by python - bpy.data.objects.new */
    BKE_object_add_only_object     /* set default values for the object */
        BKE_libblock_alloc         /* allocate memory for the object */
            new_id                 /* name validation - fallback, not \0, utf8 */
                check_for_dupid    /* check for collisions and find new name*/
                    is_dupid       /* linear search for duplicate name */
                id_sort_by_name    /* sort list */

Links

Condensed body of new_id

bool new_id(ListBase *lb, ID *id, const char *tname)
{
    bool result;
    char name[MAX_ID_NAME - 2];

    BLI_strncpy(name, tname, sizeof(name));

    result = check_for_dupid(lb, id, name);
    strcpy(id->name + 2, name);

    /* however all data in blender should be sorted, not just duplicate names
     * sorting should not hurt, but noting just incase it alters the way other
     * functions work, so sort every time */

    id_sort_by_name(lb, id);

    return result;
}

Body of is_dupid called in check_for_dupid

static ID *is_dupid(ListBase *lb, ID *id, const char *name)
{
    ID *idtest = NULL;

    for (idtest = lb->first; idtest; idtest = idtest->next) {
        /* if idtest is not a lib */ 
        if (id != idtest && idtest->lib == NULL) {
            /* do not test alphabetic! */
            /* optimized */
            if (idtest->name[2] == name[0]) {
                /* STREQ - macro for strcmp */
                if (STREQ(name, idtest->name + 2)) break;
            }
        }
    }

    return idtest;
}

Body of id_sort_by_name

void id_sort_by_name(ListBase *lb, ID *id)
{
    ID *idtest;

    /* insert alphabetically */
    if (lb->first != lb->last) {
        BLI_remlink(lb, id);

        idtest = lb->first;
        while (idtest) {
            if (BLI_strcasecmp(idtest->name, id->name) > 0 || 
               (idtest->lib && !id->lib)) {
                BLI_insertlinkbefore(lb, idtest, id);
                break;
            }
            idtest = idtest->next;
        }
        /* as last */
        if (idtest == NULL) {
            BLI_addtail(lb, id);
        }
    }
}

So indeed blender checks for duplicates and furthermore removes the id from the list again and inserts it alphabetically.

You can't even trick it by choosing names in the right order C, B, A because is_dupid will search the full list regardless.

Though if you specify a non colliding name it exits check_for_dupid early and does not have to find a name on its own.

Additionally as others have mentioned you should not use operators unless you have to and only need to create the mesh once.

$\endgroup$
3
  • $\begingroup$ gist.github.com/anonymous/7799e5f41198e41d0825 $\endgroup$ Commented Jul 28, 2015 at 23:13
  • 1
    $\begingroup$ This is a model answer @pink-vertex, full of teaching and a direct answer to all aspects of the question. Thanks for taking the time to dig in and finding out what's really going on! OK I have my homework now. $\endgroup$
    – uhoh
    Commented Jul 29, 2015 at 0:31
  • $\begingroup$ I wonder who had to bright idea to sort all names but not perform a binary/heap search on the names. On the other hand you can't blame the voluntary coders for being lazy, can you - they should have left a TODO comment, nevertheless $\endgroup$ Commented Jul 29, 2015 at 19:04

You must log in to answer this question.

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