FreeCAD Backward Compatibility for Custom Scripts
Maintaining custom Python scripts across major FreeCAD releases requires handling underlying architectural changes, such as shifts in the core C++ bindings, GUI toolkit migrations, and Python API refactoring. This guide outlines practical strategies to keep your FreeCAD scripts backward compatible across major updates, focusing on dynamic version checking, API abstraction, Qt framework handling, and robust testing techniques.
Detect and Branch by FreeCAD Version
FreeCAD provides runtime version metadata through the base application object. Use this data to execute version-specific branches when an API changes significantly.
The FreeCAD.Version() function returns a list containing
the major, minor, and build versions as strings:
import FreeCAD
major = int(FreeCAD.Version()[0])
minor = int(FreeCAD.Version()[1])
if (major, minor) < (0, 21):
# Legacy API implementation
pass
else:
# Modern FreeCAD 0.21+ / 1.0+ implementation
passAvoid hardcoding strict equality checks (minor == 20).
Always use comparison tuples ((major, minor) >= (0, 21))
so your fallback logic functions properly on future point releases.
Handle the PySide2 to PySide6 Transition
One of the largest breaking changes between FreeCAD versions (notably moving toward FreeCAD 1.0) is the migration from Qt5 (PySide2) to Qt6 (PySide6).
To ensure your custom GUI dialogs and panels work across these environments, write agnostic Qt import wrappers using exception handling:
try:
from PySide import QtGui, QtCore, QtWidgets
except ImportError:
try:
from PySide6 import QtGui, QtCore, QtWidgets
except ImportError:
from PySide2 import QtGui, QtCore, QtWidgetsKeep in mind that certain Qt modules and enum locations shifted in Qt6. For example, flags often require fully qualified enum namespaces in PySide6:
# Instead of relying on direct namespace access:
# alignment = QtCore.Qt.AlignLeft
# Use a safe helper or conditional assignment:
if hasattr(QtCore.Qt.AlignmentFlag, "AlignLeft"):
ALIGN_LEFT = QtCore.Qt.AlignmentFlag.AlignLeft
else:
ALIGN_LEFT = QtCore.Qt.AlignLeftBuild an API Abstraction Layer
When methods in core modules like Part,
Draft, or Sketcher are renamed or their
signatures change, avoid scattering if/else checks
throughout your script. Instead, isolate these differences inside a
dedicated compatibility module or wrapper class.
# compat.py
import FreeCAD
def create_circle(radius):
"""Abstracts differences in circle creation across workbench versions."""
import Part
if hasattr(Part, "makeCircle"):
return Part.makeCircle(radius)
else:
# Fallback for alternative or legacy implementations
import Draft
return Draft.makeCircle(radius)By centralizing version quirks in one file, maintaining and updating your codebase for future releases only requires updating the adapter layer.
Stick to the Documented Public API
Breaking changes often affect undocumented internals and private attributes. Minimize compatibility breakage by adhering to best practices:
- Use standard property types: Define custom
properties using canonical identifiers like
App::PropertyLength,App::PropertyAngle, orApp::PropertyFloat. - Rely on documented proxy methods: Implement
standard feature methods (
execute,onChanged,mustExecute) inside your custom Document Object proxies rather than hooking into transient C++ events. - Avoid monkey patching: Do not override core FreeCAD methods or inject unmanaged attributes directly into built-in objects.
Handle Headless Environments Defensively
Scripts that run in both the graphical interface and the background (such as automated export or rendering scripts) must gracefully handle the absence of the GUI:
import FreeCAD
# FreeCADGui may not be present in command-line mode
try:
import FreeCADGui
GUI_AVAILABLE = True
except ImportError:
GUI_AVAILABLE = False
def update_view():
if GUI_AVAILABLE:
FreeCADGui.updateGui()This prevents unexpected crashes when users switch execution modes between major versions.
Automate Multi-Version Testing
Code-level compatibility checks are only as reliable as your testing suite. Implement continuous integration that tests your script against multiple FreeCAD release environments using containerized or headless FreeCAD binaries.
Run test scripts via the command line:
freecadcmd -c "import test_suite; test_suite.run_all()"Running tests across Docker images targeting different target releases (such as stable 0.20, 0.21, and the latest 1.0 release candidates) guarantees that deprecated calls are surfaced before breaking end-user workflows.