In my script, I have for loop over many cube objects (~1000) and the treatment is very slow. Looking more in details, I notice that in the same amount of loops:

  1. if I use python operation or simple blender operation like

    obj = bpy.data.objects[obj_name]
    

    or

    obj.select = True
    

    it takes less than 0.08s (for all 1000 objects)

  2. but as soon as I start using blender operators like:

    bpy.ops.object.select_pattern() # or
    bpy.ops.object.duplicate() # or
    bpy.ops.object.location_clear() # or
    bpy.ops.object.transform_apply() # etc.
    

    then, the performance drops down deeply with more than 6s for the same amount of objects.

And, a last information, if I reduce the amount of objects from 1000 to 50, the same set of operations with bpy.ops is 0.03s - so extrapolating, it would be 0.6s for the 1000 objects and not 6s. It is as is with 1000 objects, we loose a factor 10 in speed in comparison than with 50 objects.

I tried reducing the complexity of my mesh changing the cubes in planes but it had no effect at whole on performance.

Is there something particular to know to improve these performance or a way to use the bpy.ops methods with better performances? Obviously, I am missing something important probably.

up vote 31 down vote accepted

Most operators cause implicit scene updates. It means that every object in the scene is checked and updated if necessary. If you add e.g. mesh primitives using bpy.ops.mesh.primitive_cube_add() in a loop, every iteration creates a new cube, starts a scene update and Blender iterates over all objects in the scene and updates objects if it has to.

If you start with 0 objects, there will be 1 in the first iteration and 1 object needs to be checked in the scene update. In the second iteration, there will be 2 objects and 2 be checked. The first object was checked in the first iteration already (thus, 3 object updates in total). In the third iteration, there will be 3 objects and 3 + 2 + 1 = 6 objects checked in total. In iteration 1000, there will be 1000 objects and 500,500 checks have been carried out. Here's the formula where n is the number of objects:

Non-linear runtime formula

As you see, the runtime isn't linear and could only be if there was only one update for every object after all have been added. You need to use the "low-level" API - RNA methods and attributes - instead of operators to achieve better runtimes. A scene update needs to be called manually like bpy.context.scene.update() with this approach.

Many, but not all operator calls can be somehow replaced by "low-level" code. You can duplicate objects very efficiently like:

import bpy
from mathutils import Vector

ob = bpy.context.object
obs = []
sce = bpy.context.scene

for i in range(-48, 48, 3):
    for j in range(-48, 48, 3):
        copy = ob.copy()
        copy.location += Vector((i, j, 0))
        copy.data = copy.data.copy() # also duplicate mesh, remove for linked duplicate
        obs.append(copy)

for ob in obs:
    sce.objects.link(ob)

sce.update() # don't place this in either of the above loops!

A good comparison between 4 different ways to do the same thing:

  1. bpy.ops.anim.keyframe_insert_menu() - don't ever use this in a script, it is solely to show a menu for the user

  2. bpy.ops.anim.keyframe_insert() - this is supposed to be used via the UI, not in script. Use operator calls only if there is no lower-level API!

  3. Object.keyframe_insert() - RNA method that can be called on an object, better

  4. The low-level way - add F-Curves and keyframe_points manually, fastest but you need to do alot yourself and consider several conditions (like object not having animation_data or an animation_data.action)

Related (also examples included):

  • According to Blender Python API docs bpy.context.scene.objects.link runs scene.update every time it is invoked, so the last line in the code is superfluous and the whole situation is not very happy in that way. – user2683246 Oct 15 '14 at 12:55
  • The docs state "Link object to scene, run scene.update() after", so it's telling you that you are supposed to call scene.update() - did you read "runs..." by chance? Even if it did cause an update, performance is still great compared to operator calls. – CoDEmanX Oct 15 '14 at 15:55
  • Yes, I did read both "link..." and "run..." as "links..." and "runs..." in the docs. Am I right that the second part of the phrase is ambiguous? I'm not an English speaker, but suppose my reading to be a valid and more consistent one. Anyway I was to correct the last part of my comment. Yes, if objects are linked to a scene in a separate loop all at once, performance is really great. I've changed my script so that all the lamps are generated without bpy.ops and the execution time has reduced – user2683246 Oct 17 '14 at 7:22
  • one half from 30s to 15s. And there is more room for improvement as I still use bpy.ops for generating meshes. The other observations are also mostly consistent with what you says. Linking objects in the same loop where they are being created makes performance slightly even worse than when using bpy.ops. And scene.update() can be really omitted. Seems like when scene.objects.link(o) invocations are bunched together scene.update() runs only once at the end. – iKlsR Oct 17 '14 at 11:40
  • Have you considered adding this to the Blender Manual? – dr. Sybren Apr 30 at 12:20

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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