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
freecadcmdinstead offreecadfor 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:
- Create a worker script that processes a small batch (e.g., 50 files) and exits.
- Use a master Python script using the
subprocessmodule 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.