Reducing View Layer Updates To Speed Up Blender Python Scripts

The Problem: Frequent Viewport Refreshing Slows Down Scripts

A common bottleneck that slows down Blender Python scripts is the frequent refreshing of the 3D viewport required to show changes to the scene. Each time an object is added, moved, rotated or otherwise modified, Blender must re-draw the viewport to match the updated state of the scene. For simple scripts with only a few objects, this redrawing happens very quickly. But for complex scenes with thousands of objects, frequent viewport refreshes can start to lag behind script execution, drastically slowing down apparent script performance.

This article examines how viewport updates work in Blender and provides both high-level strategies and practical examples to optimize scene management in Python scripts. By understanding how to minimize viewport updates, you can speed up scripts by orders of magnitude even with very large and complex Blender projects.

How Viewport Updates Work in Blender

Before looking at techniques to optimize viewport performance, it helps to understand what causes Blender to redraw the 3D view. There are three main triggers that force an update:

  1. Modifying the visibility of an object or collection
  2. Changing a property that affects an object’s visual representation
  3. Calling bpy.context.view_layer.update() directly from Python

Examples of property changes that trigger updates include transforming an object (moving, rotating, scaling), changing materials or textures, modifying the vertex locations of meshes, changing particle settings, and adjusting curves or surface vertices, among others.

Essentially any change that modifies the final rendered appearance of the scene will require the viewport to refresh to match. This presents an optimization challenge – how can necessary changes be made to scene contents without incurring the performance cost of visually updating the viewport each time?

Minimizing Viewport Updates

The key strategy to minimize viewport lag is to avoid unnecessary updates by controlling what is visible in the view layer as changes occur. This involves leveraging two core Blender features:

  • Layers
  • Collections

Using Layers to Control Visibility

Blender has 20 visible layers that can be used to organize objects and control visibility. By placing objects on separate layers and only making one layer visible at a time, changes can be made “invisibly” with the viewport showing another layer.

For example, say you have a script that generates a series of objects onto the default layer. Instead of updating the viewport for each new object, the active layer could be switched to a separate empty layer just before running the generation script. After completing object creation, the layer can be switched back to show all new objects at once with only two view updates instead of hundreds.

Organizing Objects into Collections

Layers provide one approach to managing visibility, but they apply on a per-scene basis globally. For larger projects, organizing content into Collections provides more fine-grained control. Collections allow you group objects and control visibility on a per-group basis.

Following the previous example, instead of using layers, the generation script could place all newly created objects into a temp collection named “GenerateCollection”. By disabling viewport visibility on just this collection, the script can run to completion without any view updates at all. After finishing, the collection could be enabled to show all content at once.

Disabling Viewport Visibility Per Collection

The key to leveraging Collections is to control viewport visibility separately from render visibility. Disabling render visibility will completely hide a collection and its contents from final renders. But by leaving render enabled while disabling viewport visibility, objects can remain present in the scene but invisible in the 3D view.

In Python this is achieved with the Collection property:

myCollection.hide_viewport = True

This hides myCollection in the viewport only while keeping it present for rendering. With strategic use of this property and separation of objects into Collections, rapid unseen changes become possible in scripts.

Examples

With an understanding of minimizing updates in place, here are some practical examples applying the principles.

Toggling Visibility of Multiple Collections

A common need is to quickly show or hide multiple collection groups at once. This script defines a simple ShowHideCollections class to wrap this behavior:

import bpy

class ShowHideCollections:
    def __init__(self, collections):
        self.collections = collections

    def show(self):
        for col in self.collections:
            col.hide_viewport = False
    
    def hide(self):
        for col in self.collections:
            col.hide_viewport = True

# Create wrapper to control two sets of collections 
collections_1 = [bpy.data.collections["Col_1"], bpy.data.collections["Col_2"]] 
collections_2 = [bpy.data.collections["Col_3"], bpy.data.collections["Col_4"]]

set_1_collections = ShowHideCollections(collections_1)
set_2_collections = ShowHideCollections(collections_2) 

# Show only first set
set_1_collections.show()  
set_2_collections.hide()

# Now switch visibility
set_1_collections.hide() 
set_2_collections.show()

By toggling entire collection sets together, view updates are minimized rather than changing visibility object-by-object.

Temporarily Disabling a Collection

Another common need is to make temporary unseen changes to a collection. This example disables a collection, makes changes, and re-enables it when done:

bot_collection = bpy.data.collections["Bots"] 

# Disable viewport visibility
bot_collection.hide_viewport = True  

# Make updates to objects in collection
for ob in bot_collection.objects:
    # Move, scale, change materials etc
    ob.location.z += 1
    ob.scale *= 2

# Re-enable collection to show all changes 
bot_collection.hide_viewport = False

Without hiding the collection first, each per-object change would have updated the viewport. By disabling first, updates are condensed into a single refresh at the end after making all changes.

Optimizing Complex Scenes

For typical projects with under 10,000 objects, following these principles will provide significant speed ups. But extremely complex scenes like game levels or architectural visualizations can contain over 100,000 entities. Additional steps may be needed to maintain performance.

Identifying Update Bottlenecks

With massive scenes, it helps to analyze with a profiler to understand where updates are actually occurring. bpy.app.handlers provides callbacks to allow custom profiling of different events.

For example, this snippet times view layer updates:

import bpy
import time

update_times = []
start_time = None

def handle_view_update(scene):
    global start_time
    if not start_time:
        start_time = time.time()
        return
        
    end_time = time.time()
    elapsed = end_time - start_time
    
    update_times.append(elapsed)
    
    print("View updated in", elapsed)
    
    start_time = None
    
bpy.app.handlers.depsgraph_update_post.append(handle_view_update)  

Running a script with this handler enabled will print profile timings for each view refresh, identifying spikes where optimization is needed.

Updating Only When Necessary

In some cases, not all changes require the viewport to actually update – for example, modifying mesh vertices does not visibly change the scene until shading or normals are recalculated. Using depsgraph.updates_flush_all() and view_layer.update() explicitly only when needed can further minimize lag.

# Modify thousands of mesh vertices 

# ....

# Recalculate normals to reflect changes
bpy.context.view_layer.update() 

# Now update viewport
bpy.context.scene.depsgraph.updates_flush_all() 

Used judiciously, this can keep view rendering ahead of changes and avoid pauses to “catch up” with visible updates.

By applying the principles and techniques explored here – utilizing layers, collections and selective updates – the responsiveness and interactivity of Blender Python scripts can scale to handle even extremely demanding large scenes.

Leave a Reply

Your email address will not be published. Required fields are marked *