freecad-scripts
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.
- License 许可
- MIT
- 最近更新
- 2026-11-14
- Stars
- ★ 4.1w
- Forks
- 5.4k
01 前言 Front Matter
- name
- freecad-scripts
- description
- 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.
02 资源文件 6
- 参考文档agents/openai.yaml156 B
- 参考文档references/geometry-and-shapes.md8.6 KB
- 参考文档references/gui-and-interface.md11.0 KB
- 参考文档references/parametric-objects.md10.6 KB
- 参考文档references/scripting-fundamentals.md6.0 KB
- 参考文档references/workbenches-and-advanced.md9.8 KB
03 SKILL.md 正文
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 Type | Python Type | Description |
|---|---|---|
App::PropertyBool |
bool |
Boolean |
App::PropertyInteger |
int |
Integer |
App::PropertyFloat |
float |
Float |
App::PropertyString |
str |
String |
App::PropertyLength |
float (units) |
Length with units |
App::PropertyAngle |
float (deg) |
Angle in degrees |
App::PropertyVector |
FreeCAD.Vector |
3D vector |
App::PropertyPlacement |
FreeCAD.Placement |
Position + rotation |
App::PropertyLink |
object ref | Link to another object |
App::PropertyLinkList |
list of refs | Links to multiple objects |
App::PropertyEnumeration |
list/str |
Dropdown selection |
App::PropertyFile |
str |
File path |
App::PropertyColor |
tuple |
RGB color (0.0-1.0) |
App::PropertyPythonObject |
any | Serializable 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")
Task Panel (Recommended for FreeCAD integration)
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:
- 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() - Implicit document: If no document handling is mentioned, wrap in standard
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument() - Units assumption: Default to millimeters unless stated otherwise
- Recompute: Always call
doc.recompute()after modifications - GUI guard: Wrap GUI-dependent code in
if FreeCAD.GuiUp:when the script may run headless - Part.show(): Use
Part.show(shape, "Name")for quick display, ordoc.addObject("Part::Feature", "Name")for named persistent objects
References
Primary Links
- Writing Python code
- Manipulating FreeCAD objects
- Vectors and Placements
- Creating and manipulating geometry
- Creating parametric objects
- Creating interface tools
- Python
- Introduction to Python
- Python scripting tutorial
- FreeCAD scripting basics
- Gui Command
Bundled Reference Documents
See the references/ directory for topic-organized guides:
- scripting-fundamentals.md — Core scripting, document model, console
- geometry-and-shapes.md — Part, Mesh, Sketcher, topology
- parametric-objects.md — FeaturePython, properties, scripted objects
- gui-and-interface.md — PySide, dialogs, task panels, Coin3D
- workbenches-and-advanced.md — Workbenches, macros, FEM, Path, recipes
04 同仓库更多 Skills
supabase-rls-policies
Write, review, and test Supabase Row Level Security (RLS) policies so that one user cannot read or change another user's rows. Use when creating or changing tables, policies, views, or security definer functions in a Supabase project, when reviewing a migration that touches RLS, or when asked to prove user isolation with pgTAP tests that sign in as two users.
skill-distribution-audit
Audit a skill collection before release or investigate installer omissions, archived/template leakage, duplicate identities, and manifest drift. Reconcile the intended public source set, distribution manifest, and actual discovery output for a specified consumer version. Includes a deterministic set-comparison helper.
logicapps-custom-code-net10-migration
Migrate Azure Logic Apps Standard local-function custom-code projects from .NET 8 to .NET 10. Use for legacy Logic Apps custom-code workspace upgrades, project/package configuration, custom-worker settings, publication, and integrated VS Code debugging. Not for ordinary .NET applications, standalone Azure Functions, Consumption workflows, or cloud deployment.
winmd-api-search
Find and explore Windows desktop APIs. Use when building features that need platform capabilities — camera, file access, notifications, UI controls, AI/ML, sensors, networking, etc. Discovers the right API for a task and retrieves full type details (methods, properties, events, enumeration values).
resemble-detect
Deepfake detection and media safety — detect AI-generated audio, images, video, and text, trace synthesis sources, apply watermarks, verify speaker identity, and analyze media intelligence using Resemble AI
quality-playbook
Run a complete quality engineering audit on any codebase. Derives behavioral requirements from the code, generates spec-traced functional tests, runs a three-pass code review with regression tests, executes a multi-model spec audit (Council of Three), and produces a consolidated bug report with TDD-verified patches. Finds the 35% of real defects that structural code review alone cannot catch. Works with any language. Trigger on 'quality playbook', 'spec audit', 'Council of Three', 'fitness-to-purpose', or 'coverage theater'.