0
$\begingroup$

Since the Python API does not support events when objects are removed I had to go a pretty "uncomfortable" way to visualize contents of a game level to which I write an addon.

The game places different stuff in a level, and in Blender, I represent that stuff with Empty's. Each Empty has children mesh objects attached to it holding the model(s) to visualize that stuff (e.g. an item box, coin, banana with sunglasses or whatever crazy ideas the lead designers get).

Now, whenever someone deletes the Empty, I also want to delete all the children. Since Blender does not delete children automatically when the parent is killed, I check for "lose" children in scene_update_post. (I also do it the other way round: If an Empty has no children, I add the models to it).

This worked fine on a i7 6700K fullblown atomic power plant until I tested it on a 5 year old PC with an AMD Athlon 6000+ CPU. Blender uses one whole core all the time, so I cut down the code I do in scene_update_post to see what's causing this performance rape. Already these few lines make Blender use 24% of the core, when there are like 100 objects in the scene:

@bpy.app.handlers.persistent
def scene_update_post(scene):
    for ob in scene.objects:
        hl3 = ob.halflife3_props  # Yes, this name is made up

That's quite horrible. But I don't know any other way to check for objects which just lost their parent. It would be even more horrible to tell the level designers (which are not the biggest Blender boys) to select all children first and then delete them with the parent.

Are there better solutions to solve this problem? I thought about overriding the deletion behavior for objects of a specific type string I stored in my custom properties, but I have no clue if that's even possible.

$\endgroup$
  • 3
    $\begingroup$ You are running python code inside not well optimized software on outdated hardware. It is to be expected. The only thing you can do is to design the code in a smart way - for example only loop through the objects when the size of scene.objects array changes, etc. $\endgroup$ – Jaroslav Jerryno Novotny Jul 26 '16 at 13:46
  • 2
    $\begingroup$ further to Jerryno .. a simple print or timer in a scene_update : its run a phenomenal amount of times per second. A modal timer would be a less heavy duty approach.. Adding a custom property to the model objects with the empty parent's name? would me my approach. $\endgroup$ – batFINGER Jul 26 '16 at 15:12
  • $\begingroup$ The idea with checking the object array length, additionally only in a timer sounds like the best idea. @batFINGER I don't quite understand what the parent name attached to the child models would help though; I still have to check wether they have such name, then I could also just check if their parent isn't None? =3 $\endgroup$ – Ray Jul 26 '16 at 21:20
  • $\begingroup$ ... and you'll have a situation where if you add a new mesh without a parent it will be deleted immediately. Surely some kind of test other than just having no parent would be more workable. $\endgroup$ – batFINGER Jul 27 '16 at 5:53
  • $\begingroup$ @batFINGER Ah that's right. I always create the parents first at the moment, so that doesn't happen yet. $\endgroup$ – Ray Jul 28 '16 at 12:41
2
$\begingroup$

The scene_update handlers are called on every update, which is basically the Blender's refresh rate. On empty scene I measured 200 updates/sec. On every cycle Blender has to update the whole scene and run the python code on top of that - that leaves a very small processing time for the python part (cca 17ms is ok which translates into 60 updates/sec).

Thus any code that is being run inside scene_update handlers is extremely performance sensitive and should be optimized.

Optimization tips:

  • Before updating or processing anything, check if it has to be updated or processed. In this case run the code only when number of objects changes, check the length of the scene.objects list first:

    import bpy
    
    num_objects = 0
    
    def handler(scene):
        global num_objects
        if len(scene.objects) == num_objects:
            # Nothing to do
            return
        num_objects = len(scene.objects) 
        # Your code here
        return
    
    bpy.app.handlers.scene_update_post.append(handler)
    

    You can use global variables, custom properties, properties of objects or binary flags to memorize when code needs to be run.

  • Update or process your stuff in an efficient way:

    • pre-sort arrays where it will increase performance
    • use data-structures optimized for the task (like dictionaries, numpy arrays, etc.) and access them in an efficient way
    • use list comprehension for cycling through list elements
    • use local variables wherever possible
    • profile your code to know where the bottlenecks are: How can you profile a Python script?
  • If it needs to be really fast, write it in Fortran, C, C++ or Assembly, then call it from Python.

Python is not C, Java or Haskell. Knowledge of how other languages perform to Python isn't very well transferable, your python code will pick up speed and performance with your experience in the language. The next example illustrates it well (from: wiki.python.org - Performance Tips):

% timeit.py -s 'x = 47' 'x * 2'
loops, best of 3: 0.574 usec per loop
% timeit.py -s 'x = 47' 'x << 1'
loops, best of 3: 0.524 usec per loop
% timeit.py -s 'x = 47' 'x + x'
loops, best of 3: 0.382 usec per loop

Note that there is a significant advantage in Python to adding a number to itself instead of multiplying it by two or shifting it left by one bit. In C on all modern computer architectures, each of the three arithmetic operations are translated into a single machine instruction which executes in one cycle, so it doesn't really matter which one you choose.

Other resource for performance tips: Optimizing Python Code

$\endgroup$

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.