Create Custom Parametric Features in FreeCAD Python

This guide provides a practical walkthrough for building custom parametric features in FreeCAD using its Python Feature framework (FeaturePython). You will learn how to create a custom document object, attach a Python proxy class to define dynamic properties, implement recompute algorithms using the Part module, and bind view providers to handle 3D visualization inside FreeCAD.


Understanding the FeaturePython Architecture

FreeCAD separates an object's functional data from its graphical representation. A custom parametric feature relies on two core components:

  1. Document Object (Part::FeaturePython): The C++ object that lives inside the FreeCAD document tree, holds data properties, and serializes to the file.
  2. Proxy Class: A standard Python class attached to the document object's Proxy attribute. It handles property definitions, state changes, and geometry calculation during document recomputations.

To support the graphical interface, a corresponding ViewProvider Proxy attaches to the object's graphical node (ViewObject) to define icons, display modes, and color properties.


Step 1: Define the Feature Proxy Class

The proxy class requires an initialization method to define user-editable properties and an execute method that FreeCAD runs whenever a property changes.

import FreeCAD as App
import Part

class ParametricBox:
    def __init__(self, obj):
        """Initialize the custom object and attach properties."""
        obj.Proxy = self
        
        # Add custom parametric properties: (Type, Name, Group, Tooltip)
        obj.addProperty("App::PropertyLength", "Length", "Dimensions", "Length of the box").Length = 20.0
        obj.addProperty("App::PropertyLength", "Width", "Dimensions", "Width of the box").Width = 15.0
        obj.addProperty("App::PropertyLength", "Height", "Dimensions", "Height of the box").Height = 10.0

    def execute(self, obj):
        """Called by FreeCAD whenever the object or its dependencies recompute."""
        # Validate values to prevent invalid geometry
        if obj.Length <= 0 or obj.Width <= 0 or obj.Height <= 0:
            return

        # Generate geometry using the Part kernel
        obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)

Step 2: Define the ViewProvider Proxy

To integrate with the 3D scene and the tree view, define a view provider class:

class ViewProviderParametricBox:
    def __init__(self, vobj):
        """Initialize the view provider."""
        vobj.Proxy = self

    def getIcon(self):
        """Return the XPM or path to an SVG icon (optional)."""
        return """
            /* XPM */
            static const char *box_icon[] = {
            "16 16 2 1",
            "  c None",
            ". c #0000FF",
            "   ..........   ",
            "  .        ..   ",
            " .        . .   ",
            "..........  .   ",
            ".        .  .   ",
            ".        .  .   ",
            ".        . ..   ",
            ".        .. .   ",
            "..........  .   ",
            " .        . .   ",
            "  .        ..   ",
            "   ..........   ",
            "                ",
            "                ",
            "                ",
            "                "
            };
        """

    def attach(self, vobj):
        """Setup default display modes."""
        self.ViewObject = vobj
        return

    def updateData(self, fp, prop):
        """Called when a property of the data object changes."""
        return

    def getDisplayModes(self, vobj):
        """Return available display modes."""
        return ["Shaded", "Wireframe", "Points"]

    def getDefaultDisplayMode(self):
        return "Shaded"

    def setDisplayMode(self, mode):
        return mode

Step 3: Instantiate and Register the Feature

Combine the data object and view provider inside an execution script or workbench tool:

def create_parametric_box(doc=None):
    if doc is None:
        doc = App.ActiveDocument or App.newDocument()

    # Create the core geometry object
    obj = doc.addObject("Part::FeaturePython", "ParametricBox")
    ParametricBox(obj)

    # Attach the GUI view provider if running with graphical interface
    if App.GuiUp:
        import FreeCADGui as Gui
        ViewProviderParametricBox(obj.ViewObject)

    doc.recompute()
    return obj

# Run the creation
if __name__ == "__main__":
    create_parametric_box()

Key Properties Reference

FreeCAD provides standard property types that determine how values appear in the Data tab:

  • App::PropertyLength: Floating-point value rendered with unit suffixes (e.g., mm, in).
  • App::PropertyAngle: Value representing an angle, automatically converted to degrees/radians.
  • App::PropertyInteger: Standard integer property.
  • App::PropertyBool: Boolean checkbox.
  • App::PropertyString: Text input field.
  • App::PropertyLink: Allows the feature to reference another document object as an input.
  • App::PropertyEnumeration: Dropdown list of string choices.

Serialization and Persistence

When saving and reloading documents, Python objects must handle serialization:

  1. Ensure the Python module containing your class is in FreeCAD's Python path (e.g., standard macros folder or an active Workbench directory).
  2. Avoid storing non-serializable objects (such as open file handles or complex transient states) directly in self. Always read from and write to the obj properties, as FreeCAD automatically serializes obj.PropertiesList.