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$
  • 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$ – WorldSEnder Jul 28 '15 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 Jul 28 '15 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 Jul 28 '15 at 8:00
  • 1
    $\begingroup$ related blender.stackexchange.com/questions/7358/… $\endgroup$ – zeffii Jul 28 '15 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 Jul 28 '15 at 12:53
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$
  • $\begingroup$ gist.github.com/anonymous/7799e5f41198e41d0825 $\endgroup$ – pink vertex Jul 28 '15 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 Jul 29 '15 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$ – WorldSEnder Jul 29 '15 at 19:04

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.