Files
awesome-copilot/skills/freecad-scripts/references/parametric-objects.md
John Haugabook c037695901 new skill freecad-scripts (#1328)
* new skill freecad-scripts

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestions from code review

* resolve: codepsellrc, readme

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* add suggestions from review

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-10 11:02:57 +10:00

11 KiB

FreeCAD Parametric Objects

Reference guide for creating FeaturePython objects, scripted objects, properties, view providers, and serialization.

Official Wiki References

FeaturePython Object — Complete Template

import FreeCAD
import Part

class MyParametricObject:
    """Proxy class for a custom parametric object."""

    def __init__(self, obj):
        """Initialize and add properties."""
        obj.Proxy = self
        self.Type = "MyParametricObject"

        # Add custom properties
        obj.addProperty("App::PropertyLength", "Length", "Dimensions",
                         "The length of the object").Length = 10.0
        obj.addProperty("App::PropertyLength", "Width", "Dimensions",
                         "The width of the object").Width = 10.0
        obj.addProperty("App::PropertyLength", "Height", "Dimensions",
                         "The height of the object").Height = 5.0
        obj.addProperty("App::PropertyBool", "Chamfered", "Options",
                         "Apply chamfer to edges").Chamfered = False
        obj.addProperty("App::PropertyLength", "ChamferSize", "Options",
                         "Size of chamfer").ChamferSize = 1.0

    def execute(self, obj):
        """Called when the document is recomputed. Build the shape here."""
        shape = Part.makeBox(obj.Length, obj.Width, obj.Height)
        if obj.Chamfered and obj.ChamferSize > 0:
            shape = shape.makeChamfer(obj.ChamferSize, shape.Edges)
        obj.Shape = shape

    def onChanged(self, obj, prop):
        """Called when any property changes."""
        if prop == "Chamfered":
            # Show/hide ChamferSize based on Chamfered toggle
            if obj.Chamfered:
                obj.setPropertyStatus("ChamferSize", "-Hidden")
            else:
                obj.setPropertyStatus("ChamferSize", "Hidden")

    def onDocumentRestored(self, obj):
        """Called when the document is loaded. Re-initialize if needed."""
        self.Type = "MyParametricObject"

    def __getstate__(self):
        """Serialize the proxy (for saving .FCStd)."""
        return {"Type": self.Type}

    def __setstate__(self, state):
        """Deserialize the proxy (for loading .FCStd)."""
        if state:
            self.Type = state.get("Type", "MyParametricObject")

ViewProvider — Complete Template

import FreeCADGui
from pivy import coin

class ViewProviderMyObject:
    """Controls how the object appears in the 3D view and tree."""

    def __init__(self, vobj):
        vobj.Proxy = self
        # Add view properties if needed
        # vobj.addProperty("App::PropertyColor", "Color", "Display", "Object color")

    def attach(self, vobj):
        """Called when the view provider is attached to the view object."""
        self.Object = vobj.Object
        self.standard = coin.SoGroup()
        vobj.addDisplayMode(self.standard, "Standard")

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

    def getDefaultDisplayMode(self):
        """Return the default display mode."""
        return "Standard"

    def setDisplayMode(self, mode):
        return mode

    def getIcon(self):
        """Return the icon path for the tree view."""
        return ":/icons/Part_Box.svg"
        # Or return an XPM string, or path to a .svg/.png file

    def updateData(self, obj, prop):
        """Called when the model object's data changes."""
        pass

    def onChanged(self, vobj, prop):
        """Called when a view property changes."""
        pass

    def doubleClicked(self, vobj):
        """Called on double-click in the tree."""
        # Open a task panel, for example
        return True

    def setupContextMenu(self, vobj, menu):
        """Add items to the right-click context menu."""
        action = menu.addAction("My Action")
        action.triggered.connect(lambda: self._myAction(vobj))

    def _myAction(self, vobj):
        FreeCAD.Console.PrintMessage("Context menu action triggered\n")

    def claimChildren(self):
        """Return list of child objects to show in tree hierarchy."""
        # return [self.Object.BaseFeature] if hasattr(self.Object, "BaseFeature") else []
        return []

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        return None

Creating the Object

def makeMyObject(name="MyObject"):
    """Factory function to create the parametric object."""
    doc = FreeCAD.ActiveDocument
    if doc is None:
        doc = FreeCAD.newDocument()

    obj = doc.addObject("Part::FeaturePython", name)
    MyParametricObject(obj)

    if FreeCAD.GuiUp:
        ViewProviderMyObject(obj.ViewObject)

    doc.recompute()
    return obj

# Usage
obj = makeMyObject("ChamferedBlock")
obj.Length = 20.0
obj.Chamfered = True
FreeCAD.ActiveDocument.recompute()

Complete Property Type Reference

Numeric Properties

Type Python Notes
App::PropertyInteger int Standard integer
App::PropertyFloat float Standard float
App::PropertyLength float Length with units (mm)
App::PropertyDistance float Distance (can be negative)
App::PropertyAngle float Angle in degrees
App::PropertyArea float Area with units
App::PropertyVolume float Volume with units
App::PropertySpeed float Speed with units
App::PropertyAcceleration float Acceleration
App::PropertyForce float Force
App::PropertyPressure float Pressure
App::PropertyPercent int 0-100 integer
App::PropertyQuantity Quantity Generic unit-aware value
App::PropertyIntegerConstraint (val,min,max,step) Bounded integer
App::PropertyFloatConstraint (val,min,max,step) Bounded float

String/Path Properties

Type Python Notes
App::PropertyString str Text string
App::PropertyFont str Font name
App::PropertyFile str File path
App::PropertyFileIncluded str Embedded file
App::PropertyPath str Directory path

Boolean and Enumeration

Type Python Notes
App::PropertyBool bool True/False
App::PropertyEnumeration list/str Dropdown; set list then value
# Enumeration usage
obj.addProperty("App::PropertyEnumeration", "Style", "Options", "Style choice")
obj.Style = ["Solid", "Wireframe", "Points"]  # set choices FIRST
obj.Style = "Solid"                              # then set value

Geometric Properties

Type Python Notes
App::PropertyVector FreeCAD.Vector 3D vector
App::PropertyVectorList [Vector,...] List of vectors
App::PropertyPlacement Placement Position + rotation
App::PropertyMatrix Matrix 4x4 matrix
App::PropertyVectorDistance Vector Vector with units
App::PropertyPosition Vector Position with units
App::PropertyDirection Vector Direction vector
Type Python Notes
App::PropertyLink obj ref Link to one object
App::PropertyLinkList [obj,...] Link to multiple objects
App::PropertyLinkSub (obj, [subs]) Link with sub-elements
App::PropertyLinkSubList [(obj,[subs]),...] Multiple link+subs
App::PropertyLinkChild obj ref Claimed child link
App::PropertyLinkListChild [obj,...] Multiple claimed children

Shape and Material

Type Python Notes
Part::PropertyPartShape Part.Shape Full shape
App::PropertyColor (r,g,b) Color (0.0-1.0)
App::PropertyColorList [(r,g,b),...] Color per element
App::PropertyMaterial Material Material definition

Container Properties

Type Python Notes
App::PropertyPythonObject any Serializable Python object
App::PropertyIntegerList [int,...] List of integers
App::PropertyFloatList [float,...] List of floats
App::PropertyStringList [str,...] List of strings
App::PropertyBoolList [bool,...] List of booleans
App::PropertyMap {str:str} String dictionary

Object Dependency Tracking

# InList: objects that reference this object
obj.InList          # [objects referencing obj]
obj.InListRecursive # all ancestors

# OutList: objects this object references
obj.OutList         # [objects obj references]
obj.OutListRecursive # all descendants

Migration Between Versions

class MyParametricObject:
    # ... existing code ...

    def onDocumentRestored(self, obj):
        """Handle version migration when document loads."""
        # Add properties that didn't exist in older versions
        if not hasattr(obj, "NewProp"):
            obj.addProperty("App::PropertyFloat", "NewProp", "Group", "Tip")
            obj.NewProp = default_value

        # Rename properties (copy value, remove old)
        if hasattr(obj, "OldPropName"):
            if not hasattr(obj, "NewPropName"):
                obj.addProperty("App::PropertyFloat", "NewPropName", "Group", "Tip")
                obj.NewPropName = obj.OldPropName
            obj.removeProperty("OldPropName")

Attachment Support

import Part

class MyAttachableObject:
    def __init__(self, obj):
        obj.Proxy = self
        obj.addExtension("Part::AttachExtensionPython")

    def execute(self, obj):
        # The attachment sets the Placement automatically
        if not obj.MapPathParameter:
            obj.positionBySupport()
        # Build your shape at the origin; Placement handles positioning
        obj.Shape = Part.makeBox(10, 10, 10)