Blender | PY

Boolean Plugin

Example add-on with live preview and boolean operations.

We provide a complete example add-on in python/examples/bpy-plugin/ demonstrating how to build Blender plugins with trueform. This page walks through the key patterns.

See Installation for pre-built downloads and building from source.

Live Preview with Depsgraph

The most powerful pattern is live preview—showing intersection curves that update as objects move. This requires three components working together.

Scene Registration

First, register trueform's scene handlers when your add-on loads:

from trueform import bpy as tbpy

def register():
    # Register trueform scene handlers for mesh caching
    tbpy.register()

def unregister():
    tbpy.unregister()

This enables tbpy.scene.get() to return cached meshes with automatic dirty tracking.

Preview Update Function

The preview function computes intersection curves and creates/updates a curves object:

import trueform as tf
from trueform import bpy as tbpy
import bpy

_PREVIEW_CURVES_NAME = None

def _update_preview(context):
    props = context.scene.my_addon
    obj_a, obj_b = props.target_a, props.target_b

    if not obj_a or not obj_b:
        _remove_preview_curves()
        return

    # Get cached meshes with world transforms
    mesh_a = tbpy.scene.get(obj_a)
    mesh_b = tbpy.scene.get(obj_b)

    # Compute intersection curves
    paths, points = tf.intersection_curves(mesh_a, mesh_b)

    global _PREVIEW_CURVES_NAME

    if len(paths) > 0:
        # Create or update curves object
        existing = bpy.data.objects.get(_PREVIEW_CURVES_NAME)

        if not existing:
            curves_obj = tbpy.convert.to_blender_curves(paths, points, "Preview_Curves")
            _PREVIEW_CURVES_NAME = curves_obj.name
        else:
            # Reuse existing object, just update curve data
            old_data = existing.data
            existing.data = tbpy.convert.make_curves(paths, points, "Preview_Curves")
            bpy.data.curves.remove(old_data)

        # Style the curves
        curves_obj.data.bevel_depth = 0.02
    else:
        _remove_preview_curves()

The key insight: reuse the existing curves object when possible, only replacing its data. This avoids flickering and keeps the object in the same position in the outliner.

Depsgraph Handler

To update the preview when objects change, register a depsgraph handler:

def _on_depsgraph_update(scene, depsgraph):
    props = scene.my_addon
    targets = {props.target_a, props.target_b}

    # Check if any target object changed
    for upd in depsgraph.updates:
        if upd.id.original in targets:
            _update_preview(bpy.context)
            break

# Register handler when preview is enabled
def _on_preview_toggle(self, context):
    if self.interactive_preview:
        if _on_depsgraph_update not in bpy.app.handlers.depsgraph_update_post:
            bpy.app.handlers.depsgraph_update_post.append(_on_depsgraph_update)
        _update_preview(context)
    else:
        if _on_depsgraph_update in bpy.app.handlers.depsgraph_update_post:
            bpy.app.handlers.depsgraph_update_post.remove(_on_depsgraph_update)
        _remove_preview_curves()

The handler checks if any update affects our target objects, then triggers a preview refresh. This is efficient—we only recompute when relevant objects change.

Boolean Operator

The operator applies the selected boolean operation using the high-level scene functions:

class MESH_OT_boolean(bpy.types.Operator):
    bl_idname = "mesh.my_boolean"
    bl_label = "Apply Boolean"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        props = context.scene.my_addon

        if not props.target_a or not props.target_b:
            self.report({'WARNING'}, "Select two objects")
            return {'CANCELLED'}

        # Remove preview curves before applying
        _remove_preview_curves()

        # Apply boolean operation
        op_map = {
            'DIFFERENCE': tbpy.scene.boolean_difference,
            'UNION': tbpy.scene.boolean_union,
            'INTERSECTION': tbpy.scene.boolean_intersection
        }

        result = op_map[props.operation](
            props.target_a,
            props.target_b,
            name=f"Boolean_{props.target_a.name}"
        )

        # Optionally hide input objects
        if props.hide_inputs:
            props.target_a.hide_set(True)
            props.target_b.hide_set(True)

        return {'FINISHED'}

The scene functions handle mesh caching internally—you just pass Blender objects and get a Blender object back.

Property Updates

Properties can trigger preview updates when changed:

class MyAddonProperties(bpy.types.PropertyGroup):
    target_a: bpy.props.PointerProperty(
        name="Mesh A",
        type=bpy.types.Object,
        poll=lambda s, o: o.type == 'MESH',
        update=lambda s, c: _update_preview(c)  # Refresh on change
    )
    target_b: bpy.props.PointerProperty(
        name="Mesh B",
        type=bpy.types.Object,
        poll=lambda s, o: o.type == 'MESH',
        update=lambda s, c: _update_preview(c)
    )
    operation: bpy.props.EnumProperty(
        name="Operation",
        items=[
            ('DIFFERENCE', "Difference", ""),
            ('UNION', "Union", ""),
            ('INTERSECTION', "Intersection", "")
        ],
        update=lambda s, c: _update_preview(c)
    )
    interactive_preview: bpy.props.BoolProperty(
        name="Live Preview",
        default=True,
        update=_on_preview_toggle  # Toggle handler registration
    )

The update callbacks ensure the preview stays synchronized with the UI.

Performance Considerations

The scene module's caching makes live preview fast:

  1. First access: Mesh is converted and structures are built
  2. Subsequent access: Cached mesh is returned immediately
  3. On geometry change: Mesh is marked dirty (not rebuilt yet)
  4. Next access after change: Mesh is rebuilt lazily

This means dragging an object in the viewport only triggers a rebuild when scene.get() is called, not on every frame of the drag. Trueform is fast enough that no throttling is needed—the preview updates smoothly even during continuous interactions.

See Scene for API details and Convert for standalone script usage.