Fix Memory Leaks in FreeCAD Python Scripts

Long-running FreeCAD Python automation scripts often suffer from memory bloat due to the boundary between Python and FreeCAD's underlying C++ core (OpenCASCADE and Coin3D). This article covers how to identify, profile, and resolve memory leaks in FreeCAD Python scripts. You will learn how to monitor memory usage using Python’s built-in tools, eliminate hidden document history caches, manage object references correctly, and implement isolation strategies for large batch processes.

1. Disable the Undo Stack and Transaction History

FreeCAD documents record undo and redo actions by default. During long batch loops (such as generating hundreds of shapes), this history persists in memory even if objects are deleted from the tree.

Disable the undo mechanism at the start of your script or batch function:

import FreeCAD as App

doc = App.ActiveDocument
doc.disableUndo()

If you must keep the document open, periodically clear the undo buffer:

doc.clearUndos()

2. Properly Close and Clear Documents

Deleting an object via doc.removeObject("ObjectName") does not always immediately free the underlying C++ geometry from RAM. When processing multiple models sequentially, close each document completely rather than continually modifying a single active document.

Use this sequence to fully purge a document from memory:

import FreeCAD as App
import gc

doc_name = "BatchJob"
doc = App.newDocument(doc_name)

# ... run your processing operations here ...

# Properly close and release
App.closeDocument(doc_name)
del doc
gc.collect()

Calling gc.collect() forces Python's garbage collector to resolve circular references that often trap FreeCAD's C++ bindings.

3. Profile Memory with tracemalloc and objgraph

To pinpoint the exact line or object type causing memory growth, use Python's native profiling tools inside the FreeCAD environment.

Using tracemalloc

Place snapshots around your loop to detect memory growth:

import tracemalloc

tracemalloc.start()

snapshot1 = tracemalloc.take_snapshot()

# Run a single iteration of your processing loop
run_iteration()

snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno')

print("[ Top 5 Memory Consuming Lines ]")
for stat in top_stats[:5]:
    print(stat)

Using objgraph

If OpenCASCADE wrapper objects (such as Part.Shape or Part.TopoShape) are lingering, inspect the object counts:

import objgraph

# Inspect top object types currently living in memory
objgraph.show_most_common_types(limit=10)

If the count of Part.Shape or App.DocumentObject continues to rise after each loop, references are lingering in global variables, lists, or event callbacks.

4. Avoid Lingering ViewProvider and Coin3D References

Running scripts in GUI mode creates Coin3D scene graph nodes for every visual representation. These nodes often fail to release properly during high-throughput automation.

  • Run in CLI/Headless Mode: Always use freecadcmd instead of freecad for long-running batch jobs to prevent the GUI layer from allocating scene graphs.
  • Avoid GUI Calls in Scripts: If running inside the GUI is required, avoid using FreeCADGui.updateGui() inside tight loops.

5. Isolate Tasks via Subprocessing

The most robust architectural solution for massive batch jobs (e.g., thousands of STEP/IGES exports or mesh operations) is process isolation. Because C++ libraries like OpenCASCADE can fragment heap memory over time, Python's garbage collector cannot always return that allocated memory to the operating system.

Instead of running a single script that processes 10,000 files:

  1. Create a worker script that processes a small batch (e.g., 50 files) and exits.
  2. Use a master Python script using the subprocess module to call FreeCAD repeatedly:
import subprocess

files_to_process = ["part1.step", "part2.step", "part3.step"]

for target_file in files_to_process:
    subprocess.run(["freecadcmd", "worker_script.py", target_file], check=True)

When each sub-process terminates, the operating system unconditionally reclaims all memory allocated by both Python and OpenCASCADE.