freecad-scripts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

FreeCAD Scripts

FreeCAD 脚本

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.
用于为FreeCAD CAD应用生成生产级Python脚本的专业技能。可解析3D建模任务的简写、伪代码和自然语言描述,并将其转换为正确的FreeCAD Python API调用。

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
  • 为FreeCAD内置控制台或宏系统编写Python脚本
  • 创建或操作3D几何图形(Part、Mesh、Sketcher、Path、FEM)
  • 构建带有自定义属性的参数化FeaturePython对象
  • 在FreeCAD中使用PySide/Qt开发GUI工具
  • 通过Pivy操作Coin3D场景图
  • 创建自定义工作台或Gui命令
  • 使用宏自动化重复的CAD操作
  • 在网格和实体表示形式之间转换
  • 编写FEM分析、光线追踪或绘图导出的脚本

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(推荐0.19+版本;最新API需0.21+/1.0+版本)
  • Python 3.x(FreeCAD已捆绑)
  • 用于GUI开发:PySide2(FreeCAD已捆绑)
  • 用于场景图开发:Pivy(FreeCAD已捆绑)

FreeCAD Python Environment

FreeCAD Python环境

FreeCAD embeds a Python interpreter. Scripts run in an environment where these key modules are available:
python
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
FreeCAD嵌入了Python解释器。脚本运行环境中可使用以下核心模块:
python
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

FreeCAD文档模型

python
undefined
python
undefined

Create or access a document

Create or access a document

doc = FreeCAD.newDocument("MyDoc") doc = FreeCAD.ActiveDocument
doc = FreeCAD.newDocument("MyDoc") doc = FreeCAD.ActiveDocument

Add objects

Add objects

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

Recompute

Recompute

doc.recompute()
doc.recompute()

Access objects

Access objects

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

Remove objects

Remove objects

doc.removeObject("MyBox")
undefined
doc.removeObject("MyBox")
undefined

Core Concepts

核心概念

Vectors and Placements

向量与位置

python
import FreeCAD
python
import FreeCAD

Vectors

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
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

Rotations

rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 45) # axis, angle(deg) rot = FreeCAD.Rotation(0, 0, 45) # Euler angles (yaw, pitch, roll)
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)

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
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)

Matrix (4x4 transformation)

import math mat = FreeCAD.Matrix() mat.move(FreeCAD.Vector(10, 0, 0)) mat.rotateZ(math.radians(45))
undefined
import math mat = FreeCAD.Matrix() mat.move(FreeCAD.Vector(10, 0, 0)) mat.rotateZ(math.radians(45))
undefined

Creating and Manipulating Geometry (Part Module)

几何创建与操作(Part模块)

The Part module wraps OpenCASCADE and provides BRep solid modeling:
python
import FreeCAD
import Part
Part模块封装了OpenCASCADE,提供BRep实体建模能力:
python
import FreeCAD
import Part

--- Primitive Shapes ---

--- 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
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 ---

--- 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])
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

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
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 ---

--- Faces ---

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

--- Solids from Faces/Wires ---

--- 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
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 ---

--- Boolean Operations ---

fused = box.fuse(cyl) # Union cut = box.cut(cyl) # Subtraction common = box.common(cyl) # Intersection fused_clean = fused.removeSplitter() # Clean up seams
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 ---

--- Fillets and Chamfers ---

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

--- Loft and Sweep ---

--- Loft and Sweep ---

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

--- BSpline Curves ---

--- 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()
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 ---

--- Show in document ---

Part.show(box, "MyBox") # Quick display (adds to active doc)
Part.show(box, "MyBox") # Quick display (adds to active doc)

Or explicitly:

Or explicitly:

doc = FreeCAD.ActiveDocument or FreeCAD.newDocument() obj = doc.addObject("Part::Feature", "MyShape") obj.Shape = box doc.recompute()
undefined
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument() obj = doc.addObject("Part::Feature", "MyShape") obj.Shape = box doc.recompute()
undefined

Topological Exploration

拓扑结构探索

python
shape = obj.Shape
python
shape = obj.Shape

Access sub-elements

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
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

Bounding box

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

Properties

Properties

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

Shape type

Shape type

shape.ShapeType # "Solid", "Shell", "Face", "Wire", "Edge", "Vertex", "Compound"
undefined
shape.ShapeType # "Solid", "Shell", "Face", "Wire", "Edge", "Vertex", "Compound"
undefined

Mesh Module

Mesh模块

python
import Mesh
python
import Mesh

Create mesh from vertices and facets

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 )
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

Import/Export

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

Convert Part shape to Mesh

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)
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

Convert Mesh to Part shape

shape = Part.Shape() shape.makeShapeFromMesh(mesh.Topology, 0.05) # tolerance solid = Part.makeSolid(shape)
undefined
shape = Part.Shape() shape.makeShapeFromMesh(mesh.Topology, 0.05) # tolerance solid = Part.makeSolid(shape)
undefined

Sketcher Module

Sketcher模块

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) )
python
undefined

Add geometry (returns geometry index)

Create a sketch on XY plane

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))
sketch = doc.addObject("Sketcher::SketchObject", "MySketch") sketch.Placement = FreeCAD.Placement( FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 0, 0, 1) )

Add constraints

Add geometry (returns geometry index)

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))
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))

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

Add constraints

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

Fixed (Block), InternalAlignment

doc.recompute()
undefined
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))

Draft Module

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

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

Fixed (Block), InternalAlignment

python
import Draft
import FreeCAD
doc.recompute()
undefined

2D shapes

Draft模块

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
python
import Draft
import FreeCAD

Operations

2D shapes

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)
undefined
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

Creating Parametric Objects (FeaturePython)

Operations

FeaturePython objects are custom parametric objects with properties that trigger recomputation:
python
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
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)
undefined

--- Usage ---

创建参数化对象(FeaturePython)

doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("Test") obj = doc.addObject("Part::FeaturePython", "CustomBox") MyBox(obj) ViewProviderMyBox(obj.ViewObject) doc.recompute()
undefined
FeaturePython对象是带有自定义属性的自定义参数化对象,属性变更会触发重新计算:
python
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

Common Property Types

--- Usage ---

Property TypePython TypeDescription
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 refLink to another object
App::PropertyLinkList
list of refsLinks 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
anySerializable Python object
doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("Test") obj = doc.addObject("Part::FeaturePython", "CustomBox") MyBox(obj) ViewProviderMyBox(obj.ViewObject) doc.recompute()
undefined

Creating GUI Tools

常用属性类型

Gui Commands

python
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())
属性类型Python类型描述
App::PropertyBool
bool
布尔值
App::PropertyInteger
int
整数
App::PropertyFloat
float
浮点数
App::PropertyString
str
字符串
App::PropertyLength
float
(units)
带单位的长度
App::PropertyAngle
float
(deg)
角度(单位为度)
App::PropertyVector
FreeCAD.Vector
3D向量
App::PropertyPlacement
FreeCAD.Placement
位置+旋转
App::PropertyLink
object ref指向其他对象的链接
App::PropertyLinkList
list of refs指向多个对象的链接
App::PropertyEnumeration
list
/
str
下拉选择项
App::PropertyFile
str
文件路径
App::PropertyColor
tuple
RGB颜色(取值范围0.0-1.0)
App::PropertyPythonObject
any可序列化的Python对象

PySide Dialogs

创建GUI工具

Gui命令

python
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)
python
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())

Usage

PySide对话框

dialog = MyDialog() if dialog.exec_() == QtWidgets.QDialog.Accepted: length = dialog.spinbox.value() FreeCAD.Console.PrintMessage(f"Length: {length}\n")
undefined
python
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)

Task Panel (Recommended for FreeCAD integration)

Usage

python
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)
dialog = MyDialog() if dialog.exec_() == QtWidgets.QDialog.Accepted: length = dialog.spinbox.value() FreeCAD.Console.PrintMessage(f"Length: {length}\n")
undefined

Show the panel

任务面板(推荐用于FreeCAD集成)

panel = MyTaskPanel() FreeCADGui.Control.showDialog(panel)
undefined
python
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)

Coin3D Scenegraph (Pivy)

Show the panel

python
from pivy import coin
import FreeCADGui
panel = MyTaskPanel() FreeCADGui.Control.showDialog(panel)
undefined

Access the scenegraph root

Coin3D场景图(Pivy)

sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()
python
from pivy import coin
import FreeCADGui

Add a custom separator with a sphere

Access the scenegraph root

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)
sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph()

Remove later

Add a custom separator with a sphere

sg.removeChild(sep)
undefined
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)

Custom Workbench Creation

Remove later

python
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)
sg.removeChild(sep)
undefined

Macro Best Practices

自定义工作台创建

python
undefined
python
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)

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
python
undefined

Guard for GUI availability

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

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()
undefined
import FreeCAD import Part from FreeCAD import Base

Selection Handling

Guard for GUI availability

python
undefined
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()
undefined

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
python
undefined

Select programmatically

Get selected objects

FreeCADGui.Selection.addSelection(doc.MyBox) FreeCADGui.Selection.addSelection(doc.MyBox, "Face1") FreeCADGui.Selection.clearSelection()
undefined
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

Console Output

Select programmatically

python
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")
FreeCADGui.Selection.addSelection(doc.MyBox) FreeCADGui.Selection.addSelection(doc.MyBox, "Face1") FreeCADGui.Selection.clearSelection()
undefined

Common Patterns

控制台输出

Parametric Pad from Sketch

python
doc = FreeCAD.ActiveDocument
python
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")

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)))
python
doc = FreeCAD.ActiveDocument

Close with coincident constraints

Create sketch

for i in range(3): sketch.addConstraint(Sketcher.Constraint("Coincident", i, 2, i+1, 1)) sketch.addConstraint(Sketcher.Constraint("Coincident", 3, 2, 0, 1))
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)))

Pad (PartDesign)

Close with coincident constraints

pad = doc.addObject("PartDesign::Pad", "Pad") pad.Profile = sketch pad.Length = 5.0 sketch.Visibility = False doc.recompute()
undefined
for i in range(3): sketch.addConstraint(Sketcher.Constraint("Coincident", i, 2, i+1, 1)) sketch.addConstraint(Sketcher.Constraint("Coincident", 3, 2, 0, 1))

Export Shapes

Pad (PartDesign)

python
undefined
pad = doc.addObject("PartDesign::Pad", "Pad") pad.Profile = sketch pad.Length = 5.0 sketch.Visibility = False doc.recompute()
undefined

STEP export

导出形状

Part.export([doc.MyBox], "/path/to/output.step")
python
undefined

STL export (mesh)

STEP export

import Mesh Mesh.export([doc.MyBox], "/path/to/output.stl")
Part.export([doc.MyBox], "/path/to/output.step")

IGES export

STL export (mesh)

Part.export([doc.MyBox], "/path/to/output.iges")
import Mesh Mesh.export([doc.MyBox], "/path/to/output.stl")

Multiple formats via importlib

IGES export

import importlib importlib.import_module("importOBJ").export([doc.MyBox], "/path/to/output.obj")
undefined
Part.export([doc.MyBox], "/path/to/output.iges")

Units and Quantities

Multiple formats via importlib

python
undefined
import importlib importlib.import_module("importOBJ").export([doc.MyBox], "/path/to/output.obj")
undefined

FreeCAD uses mm internally

单位与数量

q = FreeCAD.Units.Quantity("10 mm") q_inch = FreeCAD.Units.Quantity("1 in") print(q_inch.getValueAs("mm")) # 25.4
python
undefined

Parse user input with units

FreeCAD uses mm internally

q = FreeCAD.Units.parseQuantity("2.5 in") value_mm = float(q) # Value in mm (internal unit)
undefined
q = FreeCAD.Units.Quantity("10 mm") q_inch = FreeCAD.Units.Quantity("1 in") print(q_inch.getValueAs("mm")) # 25.4

Compensation Rules (Quasi-Coder Integration)

Parse user input with units

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
q = FreeCAD.Units.parseQuantity("2.5 in") value_mm = float(q) # Value in mm (internal unit)
undefined

References

适配规则(伪代码集成)

Primary Links

当解析FreeCAD脚本的简写或伪代码时:
  1. 术语映射:"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. 隐式文档:如果未提及文档处理,使用标准封装
    doc = FreeCAD.ActiveDocument or FreeCAD.newDocument()
  3. 单位假设:除非另有说明,默认使用毫米
  4. 重新计算:修改完成后始终调用
    doc.recompute()
  5. GUI防护:当脚本可能在无GUI环境运行时,将依赖GUI的代码封装在
    if FreeCAD.GuiUp:
  6. Part.show():快速显示使用
    Part.show(shape, "Name")
    ,需要命名持久化对象时使用
    doc.addObject("Part::Feature", "Name")

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

捆绑参考文档

查看 references/ 目录获取按主题整理的指南:
  1. scripting-fundamentals.md — 核心脚本、文档模型、控制台
  2. geometry-and-shapes.md — Part、Mesh、Sketcher、拓扑结构
  3. parametric-objects.md — FeaturePython、属性、脚本化对象
  4. gui-and-interface.md — PySide、对话框、任务面板、Coin3D
  5. workbenches-and-advanced.md — 工作台、宏、FEM、Path、实用方案