Generated banner for Freecad Scripts
Generated logo for Freecad Scripts

Freecad Scripts

GitHubOfficialFirst committed by jhaugaDistributed by GitHub

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Expert skill for writing FreeCAD Python scripts, macros, and automation. Use when asked to create FreeCAD models, parametric objects, Part/Mesh/Sketcher scripts, workbench tools, GUI dialogs with PySide, Coin3D scenegraph manipulation, or any FreeCAD Python API task. Covers FreeCAD scripting basics, geometry creation, FeaturePython objects, interface tools, and macro development.
— the description by jhauga

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides comprehensive support for the FreeCAD Python API, enabling users to automate 3D modeling, parametric design, and GUI development. It covers the entire FreeCAD ecosystem, including the Part, Mesh, and Sketcher workbenches, as well as advanced topics like FeaturePython objects, Coin3D scenegraph manipulation via Pivy, and custom PySide interface tools. It is designed to translate natural language requests into production-ready Python code for FreeCAD.

What it does

  • Generates Python scripts for automated 3D geometry creation and manipulation.
  • Assists in building parametric FeaturePython objects with custom properties.
  • Develops custom GUI dialogs and task panels using PySide and Qt.
  • Provides code for manipulating the Coin3D scenegraph via Pivy.
  • Automates repetitive CAD tasks through macro development.
  • Facilitates the creation of custom workbenches and GUI commands.

When to use it

  • When you need to automate repetitive 3D modeling operations in FreeCAD.
  • When creating custom parametric parts that require specific logic or properties.
  • When developing custom tools or workbenches to extend FreeCAD functionality.
  • When you need to perform complex geometric operations or conversions between mesh and solid formats.
  • When building interactive GUI interfaces for CAD workflows.

Advantages

  • Deep integration with FreeCAD's internal Python interpreter and API.
  • Supports both headless automation and interactive GUI-based tool development.
  • Provides standardized patterns for parametric object design and recomputation.
  • Includes best practices for macro structure and selection handling.
  • Simplifies complex OpenCASCADE-based BRep operations.

Usage examples

“Write a FreeCAD script to create a 10x10x10 box and fillet its edges by 1mm.”
The agent generates a Python script using Part.makeBox and the .makeFillet method, followed by a doc.recompute() call to update the document.
“Create a custom parametric object in FreeCAD that acts as a cylinder with a configurable radius.”
The agent provides a FeaturePython class implementation with an App::PropertyLength property for the radius and an execute method to regenerate the cylinder shape.
“How do I create a task panel in FreeCAD to input dimensions for a sketch?”
The agent provides a class structure inheriting from QWidget, including a layout with QDoubleSpinBox inputs and the necessary methods to integrate with FreeCADGui.Control.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/github/freecad-scripts/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip freecad-scripts.zip -d .agents/skills

Documentation by jhauga

FreeCAD Scripts

Expert skill for generating production-quality Python scripts for the FreeCAD CAD application. Interprets shorthand, quasi-code, and natural language descriptions of 3D modeling tasks and translates them into correct FreeCAD Python API calls.

When to Use This Skill

  • Writing Python scripts for FreeCAD's built-in console or macro system

  • Creating or manipulating 3D geometry (Part, Mesh, Sketcher, Path, FEM)

  • Building parametric FeaturePython objects with custom properties

  • Developing GUI tools using PySide/Qt within FreeCAD

  • Manipulating the Coin3D scenegraph via Pivy

  • Creating custom workbenches or Gui Commands

  • Automating repetitive CAD operations with macros

  • Converting between mesh and solid representations

  • Scripting FEM analyses, raytracing, or drawing exports

Prerequisites

  • FreeCAD installed (0.19+ recommended; 0.21+/1.0+ for latest API)

  • Python 3.x (bundled with FreeCAD)

  • For GUI work: PySide2 (bundled with FreeCAD)

  • For scenegraph: Pivy (bundled with FreeCAD)

FreeCAD Python Environment

FreeCAD embeds a Python interpreter. Scripts run in an environment where these key modules are available:

import FreeCAD          # Core module (also aliased as 'App')
import FreeCADGui       # GUI module (also aliased as 'Gui') — only in GUI mode
import Part             # Part workbench — BRep/OpenCASCADE shapes
import Mesh             # Mesh workbench — triangulated meshes
import Sketcher         # Sketcher workbench — 2D constrained sketches
import Draft            # Draft workbench — 2D drawing tools
import Arch             # Arch/BIM workbench
import Path             # Path/CAM workbench
import FEM              # FEM workbench
import TechDraw         # TechDraw workbench (replaces Drawing)
import BOPTools         # Boolean operations
import CompoundTools    # Compound shape utilities
The FreeCAD Document Model
# Create or access a document
doc = FreeCAD.newDocument("MyDoc")
doc = FreeCAD.ActiveDocument

# Add objects
box = doc.addObject("Part::Box", "MyBox")
box.Length = 10.0
box.Width = 10.0
box.Height = 10.0

# Recompute
doc.recompute()

# Access objects
obj = doc.getObject("MyBox")
obj = doc.MyBox  # Attribute access also works

# Remove objects
doc.removeObject("MyBox")

Core Concepts

Vectors and Placements
import FreeCAD

# Vectors
v1 = FreeCAD.Vector(1, 0, 0)
v2 = FreeCAD.Vector(0, 1, 0)
v3 = v1.cross(v2)          # Cross product
d = v1.dot(v2)              # Dot product
v4 = v1 + v2                # Addition
length = v1.Length           # Magnitude
v_norm = FreeCAD.Vector(v1)
v_norm.normalize()           # In-place normalize

# Rotations
rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 45)  # axis, angle(deg)
rot = FreeCAD.Rotation(0, 0, 45)                       # Euler angles (yaw, pitch, roll)

# Placements (position + orientation)
placement = FreeCAD.Placement(
    FreeCAD.Vector(10, 20, 0),    # translation
    FreeCAD.Rotation(0, 0, 45),   # rotation
    FreeCAD.Vector(0, 0, 0)       # center of rotation
)
obj.Placement = placement

# Matrix (4x4 transformation)
import math
mat = FreeCAD.Matrix()
mat.move(FreeCAD.Vector(10, 0, 0))
mat.rotateZ(math.radians(45))
Creating and Manipulating Geometry (Part Module)

The Part module wraps OpenCASCADE and provides BRep solid modeling:

import FreeCAD
import Part

# --- Primitive Shapes ---
box = Part.makeBox(10, 10, 10)               # length, width, height
cyl = Part.makeCylinder(5, 20)               # radius, height
sphere = Part.makeSphere(10)                  # radius
cone = Part.makeCone(5, 2, 10)               # r1, r2, height
torus = Part.makeTorus(10, 2)                 # major_r, minor_r

# --- Wires and Edges ---
edge1 = Part.makeLine((0, 0, 0), (10, 0, 0))
edge2 = Part.makeLine((10, 0, 0), (10, 10, 0))
edge3 = Part.makeLine((10, 10, 0), (0, 0, 0))
wire = Part.Wire([edge1, edge2, edge3])

# Circles and arcs
circle = Part.makeCircle(5)                   # radius
arc = Part.makeCircle(5, FreeCAD.Vector(0, 0, 0),
                       FreeCAD.Vector(0, 0, 1), 0, 180)  # start/end angle

# --- Faces ---
face = Part.Face(wire)                        # From a closed wire

# --- Solids from Faces/Wires ---
extrusion = face.extrude(FreeCAD.Vector(0, 0, 10))       # Extrude
revolved = face.revolve(FreeCAD.Vector(0, 0, 0),
                         FreeCAD.Vector(0, 0, 1), 360)    # Revolve

# --- Boolean Operations ---
fused = box.fuse(cyl)           # Union
cut = box.cut(cyl)              # Subtraction
common = box.common(cyl)        # Intersection
fused_clean = fused.removeSplitter()  # Clean up seams

# --- Fillets and Chamfers ---
filleted = box.makeFillet(1.0, box.Edges)          # radius, edges
chamfered = box.makeChamfer(1.0, box.Edges)        # dist, edges

# --- Loft and Sweep ---
loft = Part.makeLoft([wire1, wire2], True)          # wires, solid
swept = Part.Wire([path_edge]).makePipeShell([profile_wire],
                                              True, False)  # solid, frenet

# --- BSpline Curves ---
from FreeCAD import Vector
points = [Vector(0,0,0), Vector(1,2,0), Vector(3,1,0), Vector(4,3,0)]
bspline = Part.BSplineCurve()
bspline.interpolate(points)
edge = bspline.toShape()

# --- Show in document ---
Part.show(box, "MyBox")    # Quick display (adds to active doc)
# Or explicitly:
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument()
obj = doc.addObject("Part::Feature", "MyShape")
obj.Shape = box
doc.recompute()
Topological Exploration
shape = obj.Shape

# Access sub-elements
shape.Vertexes    # List of Vertex objects
shape.Edges       # List of Edge objects
shape.Wires       # List of Wire objects
shape.Faces       # List of Face objects
shape.Shells      # List of Shell objects
shape.Solids      # List of Solid objects

# Bounding box
bb = shape.BoundBox
print(bb.XMin, bb.XMax, bb.YMin, bb.YMax, bb.ZMin, bb.ZMax)
print(bb.Center)

# Properties
shape.Volume
shape.Area
shape.Length       # For edges/wires
face.Surface       # Underlying geometric surface
edge.Curve         # Underlying geometric curve

# Shape type
shape.ShapeType    # "Solid", "Shell", "Face", "Wire", "Edge", "Vertex", "Compound"
Mesh Module
import Mesh

# Create mesh from vertices and facets
mesh = Mesh.Mesh()
mesh.addFacet(
    0.0, 0.0, 0.0,   # vertex 1
    1.0, 0.0, 0.0,   # vertex 2
    0.0, 1.0, 0.0    # vertex 3
)

# Import/Export
mesh = Mesh.Mesh("/path/to/file.stl")
mesh.write("/path/to/output.stl")

# Convert Part shape to Mesh
import Part
import MeshPart
shape = Part.makeBox(1, 1, 1)
mesh = MeshPart.meshFromShape(Shape=shape, LinearDeflection=0.1,
                                AngularDeflection=0.5)

# Convert Mesh to Part shape
shape = Part.Shape()
shape.makeShapeFromMesh(mesh.Topology, 0.05)  # tolerance
solid = Part.makeSolid(shape)
Sketcher Module

Create a sketch on XY plane

sketch = doc.addObject("Sketcher::SketchObject", "MySketch") sketch.Placement = FreeCAD.Placement( FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 0, 0, 1) )

Add geometry (returns geometry index)

idx_line = sketch.addGeometry(Part.LineSegment( FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(10, 0, 0))) idx_circle = sketch.addGeometry(Part.Circle( FreeCAD.Vector(5, 5, 0), FreeCAD.Vector(0, 0, 1), 3))

Add constraints

sketch.addConstraint(Sketcher.Constraint("Coincident", 0, 2, 1, 1)) sketch.addConstraint(Sketcher.Constraint("Horizontal", 0)) sketch.addConstraint(Sketcher.Constraint("DistanceX", 0, 1, 0, 2, 10.0)) sketch.addConstraint(Sketcher.Constraint("Radius", 1, 3.0)) sketch.addConstraint(Sketcher.Constraint("Fixed", 0, 1))

Constraint types: Coincident, Horizontal, Vertical, Parallel, Perpendicular,

Tangent, Equal, Symmetric, Distance, DistanceX, DistanceY, Radius, Angle,

Fixed (Block), InternalAlignment

doc.recompute()


### Draft Module

```python
import Draft
import FreeCAD

# 2D shapes
line = Draft.makeLine(FreeCAD.Vector(0,0,0), FreeCAD.Vector(10,0,0))
circle = Draft.makeCircle(5)
rect = Draft.makeRectangle(10, 5)
poly = Draft.makePolygon(6, radius=5)   # hexagon

# Operations
moved = Draft.move(obj, FreeCAD.Vector(10, 0, 0), copy=True)
rotated = Draft.rotate(obj, 45, FreeCAD.Vector(0,0,0),
                        axis=FreeCAD.Vector(0,0,1), copy=True)
scaled = Draft.scale(obj, FreeCAD.Vector(2,2,2), center=FreeCAD.Vector(0,0,0),
                      copy=True)
offset = Draft.offset(obj, FreeCAD.Vector(1,0,0))
array = Draft.makeArray(obj, FreeCAD.Vector(15,0,0),
                         FreeCAD.Vector(0,15,0), 3, 3)

Creating Parametric Objects (FeaturePython)

FeaturePython objects are custom parametric objects with properties that trigger recomputation:

import FreeCAD
import Part

class MyBox:
    """A custom parametric box."""

    def __init__(self, obj):
        obj.Proxy = self
        obj.addProperty("App::PropertyLength", "Length", "Dimensions",
                         "Box length").Length = 10.0
        obj.addProperty("App::PropertyLength", "Width", "Dimensions",
                         "Box width").Width = 10.0
        obj.addProperty("App::PropertyLength", "Height", "Dimensions",
                         "Box height").Height = 10.0

    def execute(self, obj):
        """Called on document recompute."""
        obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)

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

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        return None


class ViewProviderMyBox:
    """View provider for custom icon and display settings."""

    def __init__(self, vobj):
        vobj.Proxy = self

    def getIcon(self):
        return ":/icons/Part_Box.svg"

    def attach(self, vobj):
        self.Object = vobj.Object

    def updateData(self, obj, prop):
        pass

    def onChanged(self, vobj, prop):
        pass

    def __getstate__(self):
        return None

    def __setstate__(self, state):
        return None


# --- Usage ---
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("Test")
obj = doc.addObject("Part::FeaturePython", "CustomBox")
MyBox(obj)
ViewProviderMyBox(obj.ViewObject)
doc.recompute()
Common Property Types
Property TypePython TypeDescription
App::PropertyBoolboolBoolean
App::PropertyIntegerintInteger
App::PropertyFloatfloatFloat
App::PropertyStringstrString
App::PropertyLengthfloat (units)Length with units
App::PropertyAnglefloat (deg)Angle in degrees
App::PropertyVectorFreeCAD.Vector3D vector
App::PropertyPlacementFreeCAD.PlacementPosition + rotation
App::PropertyLinkobject refLink to another object
App::PropertyLinkListlist of refsLinks to multiple objects
App::PropertyEnumerationlist/strDropdown selection
App::PropertyFilestrFile path
App::PropertyColortupleRGB color (0.0-1.0)
App::PropertyPythonObjectanySerializable Python object

Creating GUI Tools

Gui Commands
import FreeCAD
import FreeCADGui

class MyCommand:
    """A custom toolbar/menu command."""

    def GetResources(self):
        return {
            "Pixmap": ":/icons/Part_Box.svg",
            "MenuText": "My Custom Command",
            "ToolTip": "Creates a custom box",
            "Accel": "Ctrl+Shift+B"
        }

    def IsActive(self):
        return FreeCAD.ActiveDocument is not None

    def Activated(self):
        # Command logic here
        FreeCAD.Console.PrintMessage("Command activated\n")

FreeCADGui.addCommand("My_CustomCommand", MyCommand())
PySide Dialogs
from PySide2 import QtWidgets, QtCore, QtGui

class MyDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super().__init__(parent or FreeCADGui.getMainWindow())
        self.setWindowTitle("My Tool")
        self.setMinimumWidth(300)

        layout = QtWidgets.QVBoxLayout(self)

        # Input fields
        self.label = QtWidgets.QLabel("Length:")
        self.spinbox = QtWidgets.QDoubleSpinBox()
        self.spinbox.setRange(0.1, 1000.0)
        self.spinbox.setValue(10.0)
        self.spinbox.setSuffix(" mm")

        form = QtWidgets.QFormLayout()
        form.addRow(self.label, self.spinbox)
        layout.addLayout(form)

        # Buttons
        btn_layout = QtWidgets.QHBoxLayout()
        self.btn_ok = QtWidgets.QPushButton("OK")
        self.btn_cancel = QtWidgets.QPushButton("Cancel")
        btn_layout.addWidget(self.btn_ok)
        btn_layout.addWidget(self.btn_cancel)
        layout.addLayout(btn_layout)

        self.btn_ok.clicked.connect(self.accept)
        self.btn_cancel.clicked.connect(self.reject)

# Usage
dialog = MyDialog()
if dialog.exec_() == QtWidgets.QDialog.Accepted:
    length = dialog.spinbox.value()
    FreeCAD.Console.PrintMessage(f"Length: {length}\n")
class MyTaskPanel:
    """Task panel shown in the left sidebar."""

    def __init__(self):
        self.form = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout(self.form)
        self.spinbox = QtWidgets.QDoubleSpinBox()
        self.spinbox.setValue(10.0)
        layout.addWidget(QtWidgets.QLabel("Length:"))
        layout.addWidget(self.spinbox)

    def accept(self):
        # Called when user clicks OK
        length = self.spinbox.value()
        FreeCAD.Console.PrintMessage(f"Accepted: {length}\n")
        FreeCADGui.Control.closeDialog()
        return True

    def reject(self):
        FreeCADGui.Control.closeDialog()
        return True

    def getStandardButtons(self):
        return int(QtWidgets.QDialogButtonBox.Ok |
                   QtWidgets.QDialogButtonBox.Cancel)

# Show the panel
panel = MyTaskPanel()
FreeCADGui.Control.showDialog(panel)

Coin3D Scenegraph (Pivy)

from pivy import coin
import FreeCADGui

# Access the scenegraph root
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()

# Add a custom separator with a sphere
sep = coin.SoSeparator()
mat = coin.SoMaterial()
mat.diffuseColor.setValue(1.0, 0.0, 0.0)  # Red
trans = coin.SoTranslation()
trans.translation.setValue(10, 10, 10)
sphere = coin.SoSphere()
sphere.radius.setValue(2.0)
sep.addChild(mat)
sep.addChild(trans)
sep.addChild(sphere)
sg.addChild(sep)

# Remove later
sg.removeChild(sep)

Custom Workbench Creation

import FreeCADGui

class MyWorkbench(FreeCADGui.Workbench):
    MenuText = "My Workbench"
    ToolTip = "A custom workbench"
    Icon = ":/icons/freecad.svg"

    def Initialize(self):
        """Called at workbench activation."""
        import MyCommands  # Import your command module
        self.appendToolbar("My Tools", ["My_CustomCommand"])
        self.appendMenu("My Menu", ["My_CustomCommand"])

    def Activated(self):
        pass

    def Deactivated(self):
        pass

    def GetClassName(self):
        return "Gui::PythonWorkbench"

FreeCADGui.addWorkbench(MyWorkbench)

Macro Best Practices

# Standard macro header
# -*- coding: utf-8 -*-
# FreeCAD Macro: MyMacro
# Description: Brief description of what the macro does
# Author: YourName
# Version: 1.0
# Date: 2026-04-07

import FreeCAD
import Part
from FreeCAD import Base

# Guard for GUI availability
if FreeCAD.GuiUp:
    import FreeCADGui
    from PySide2 import QtWidgets, QtCore

def main():
    doc = FreeCAD.ActiveDocument
    if doc is None:
        FreeCAD.Console.PrintError("No active document\n")
        return

    if FreeCAD.GuiUp:
        sel = FreeCADGui.Selection.getSelection()
        if not sel:
            FreeCAD.Console.PrintWarning("No objects selected\n")

    # ... macro logic ...

    doc.recompute()
    FreeCAD.Console.PrintMessage("Macro completed\n")

if __name__ == "__main__":
    main()
Selection Handling
# Get selected objects
sel = FreeCADGui.Selection.getSelection()           # List of objects
sel_ex = FreeCADGui.Selection.getSelectionEx()       # Extended (sub-elements)

for selobj in sel_ex:
    obj = selobj.Object
    for sub in selobj.SubElementNames:
        print(f"{obj.Name}.{sub}")
        shape = obj.getSubObject(sub)  # Get sub-shape

# Select programmatically
FreeCADGui.Selection.addSelection(doc.MyBox)
FreeCADGui.Selection.addSelection(doc.MyBox, "Face1")
FreeCADGui.Selection.clearSelection()
Console Output
FreeCAD.Console.PrintMessage("Info message\n")
FreeCAD.Console.PrintWarning("Warning message\n")
FreeCAD.Console.PrintError("Error message\n")
FreeCAD.Console.PrintLog("Debug/log message\n")

Common Patterns

Parametric Pad from Sketch
doc = FreeCAD.ActiveDocument

# Create sketch
sketch = doc.addObject("Sketcher::SketchObject", "Sketch")
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(0,0,0), FreeCAD.Vector(10,0,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(10,0,0), FreeCAD.Vector(10,10,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(10,10,0), FreeCAD.Vector(0,10,0)))
sketch.addGeometry(Part.LineSegment(FreeCAD.Vector(0,10,0), FreeCAD.Vector(0,0,0)))
# Close with coincident constraints
for i in range(3):
    sketch.addConstraint(Sketcher.Constraint("Coincident", i, 2, i+1, 1))
sketch.addConstraint(Sketcher.Constraint("Coincident", 3, 2, 0, 1))

# Pad (PartDesign)
pad = doc.addObject("PartDesign::Pad", "Pad")
pad.Profile = sketch
pad.Length = 5.0
sketch.Visibility = False
doc.recompute()
Export Shapes
# STEP export
Part.export([doc.MyBox], "/path/to/output.step")

# STL export (mesh)
import Mesh
Mesh.export([doc.MyBox], "/path/to/output.stl")

# IGES export
Part.export([doc.MyBox], "/path/to/output.iges")

# Multiple formats via importlib
import importlib
importlib.import_module("importOBJ").export([doc.MyBox], "/path/to/output.obj")
Units and Quantities
# FreeCAD uses mm internally
q = FreeCAD.Units.Quantity("10 mm")
q_inch = FreeCAD.Units.Quantity("1 in")
print(q_inch.getValueAs("mm"))  # 25.4

# Parse user input with units
q = FreeCAD.Units.parseQuantity("2.5 in")
value_mm = float(q)  # Value in mm (internal unit)

Compensation Rules (Quasi-Coder Integration)

When interpreting shorthand or quasi-code for FreeCAD scripts:

  1. Terminology mapping: "box" → Part.makeBox(), "cylinder" → Part.makeCylinder(), "sphere" → Part.makeSphere(), "merge/combine/join" → .fuse(), "subtract/cut/remove" → .cut(), "intersect" → .common(), "round edges/fillet" → .makeFillet(), "bevel/chamfer" → .makeChamfer()

  2. Implicit document: If no document handling is mentioned, wrap in standard doc = FreeCAD.ActiveDocument or FreeCAD.newDocument()

  3. Units assumption: Default to millimeters unless stated otherwise

  4. Recompute: Always call doc.recompute() after modifications

  5. GUI guard: Wrap GUI-dependent code in if FreeCAD.GuiUp: when the script may run headless

  6. Part.show(): Use Part.show(shape, "Name") for quick display, or doc.addObject("Part::Feature", "Name") for named persistent objects

References

Bundled Reference Documents

See the references/ directory for topic-organized guides:

  1. scripting-fundamentals.md — Core scripting, document model, console

  2. geometry-and-shapes.md — Part, Mesh, Sketcher, topology

  3. parametric-objects.md — FeaturePython, properties, scripted objects

  4. gui-and-interface.md — PySide, dialogs, task panels, Coin3D

  5. workbenches-and-advanced.md — Workbenches, macros, FEM, Path, recipes

Files

references/parametric-objects.md

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
TypePythonNotes
App::PropertyIntegerintStandard integer
App::PropertyFloatfloatStandard float
App::PropertyLengthfloatLength with units (mm)
App::PropertyDistancefloatDistance (can be negative)
App::PropertyAnglefloatAngle in degrees
App::PropertyAreafloatArea with units
App::PropertyVolumefloatVolume with units
App::PropertySpeedfloatSpeed with units
App::PropertyAccelerationfloatAcceleration
App::PropertyForcefloatForce
App::PropertyPressurefloatPressure
App::PropertyPercentint0-100 integer
App::PropertyQuantityQuantityGeneric unit-aware value
App::PropertyIntegerConstraint(val,min,max,step)Bounded integer
App::PropertyFloatConstraint(val,min,max,step)Bounded float
String/Path Properties
TypePythonNotes
App::PropertyStringstrText string
App::PropertyFontstrFont name
App::PropertyFilestrFile path
App::PropertyFileIncludedstrEmbedded file
App::PropertyPathstrDirectory path
Boolean and Enumeration
TypePythonNotes
App::PropertyBoolboolTrue/False
App::PropertyEnumerationlist/strDropdown; 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
TypePythonNotes
App::PropertyVectorFreeCAD.Vector3D vector
App::PropertyVectorList[Vector,...]List of vectors
App::PropertyPlacementPlacementPosition + rotation
App::PropertyMatrixMatrix4x4 matrix
App::PropertyVectorDistanceVectorVector with units
App::PropertyPositionVectorPosition with units
App::PropertyDirectionVectorDirection vector
TypePythonNotes
App::PropertyLinkobj refLink 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::PropertyLinkChildobj refClaimed child link
App::PropertyLinkListChild[obj,...]Multiple claimed children
Shape and Material
TypePythonNotes
Part::PropertyPartShapePart.ShapeFull shape
App::PropertyColor(r,g,b)Color (0.0-1.0)
App::PropertyColorList[(r,g,b),...]Color per element
App::PropertyMaterialMaterialMaterial definition
Container Properties
TypePythonNotes
App::PropertyPythonObjectanySerializable 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)

Requirements and compatibility

Written for: GitHub Copilot

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

First committed by
GitHub avatar of jhaugajhauga
Publisher
GitHub (Official)
Distributed by
GitHub
Source
github/awesome-copilot · skills/freecad-scripts · commit 797337f
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Contributors
jhauga

AutoGineer is not affiliated with or endorsed by jhauga or GitHub.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download freecad-scripts.zip