freecad
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFreeCAD Control
FreeCAD Control
FreeCAD () is scriptable in Python against the /
/ / / / / / modules.
Drive it through the FreeCAD Bridge — a small script running inside a running FreeCAD
GUI that executes whatever build script is POSTed to . Because the script
runs in the live session on the GUI thread, the viewport updates as you build and
/ work directly. Everything below was proven on FreeCAD 1.1.3.
/Applications/FreeCAD.appFreeCADFreeCADGuiPartSketcherPartDesignDraftMeshTechDraw127.0.0.1:8735Gui.*saveImage- scripts/freecad-bridge.py — the bridge to run inside FreeCAD.
- scripts/freecad-send.sh — send a file (or
.py) to the bridge and print its captured output;-c 'inline code'checks it's up.--ping - scripts/example-bracket.py — a complete build→check→measure→ export→screenshot script to copy from.
- references/api-reference.md — condensed, version-checked Python API (document, Part, Sketcher attachment, PartDesign features, Draft, Mesh, TechDraw drawings, import/export, GUI). Read it before writing anything past the cheatsheet below — several PartDesign/TechDraw properties are not what you'd guess.
FreeCAD()可通过 Python 针对 / / / / / / / modules 编写脚本。
/Applications/FreeCAD.appFreeCADFreeCADGuiPartSketcherPartDesignDraftMeshTechDraw通过 FreeCAD Bridge 控制它——这是一个在正在运行的 FreeCAD GUI 内执行的小型脚本,会运行 POST 到 的构建脚本。由于脚本在 GUI 线程的实时会话中运行,viewport 会随着构建过程更新, / 也可以直接使用。以下所有内容均已在 FreeCAD 1.1.3 上验证。
127.0.0.1:8735Gui.*saveImage- scripts/freecad-bridge.py——需要在 FreeCAD 内运行的 bridge。
- scripts/freecad-send.sh——向 bridge 发送 文件(或使用
.py),并打印其捕获的输出;-c 'inline code'用于检查 bridge 是否在线。--ping - scripts/example-bracket.py——一个完整的 build→check→measure→export→screenshot 脚本,可直接复制使用。
- references/api-reference.md——经过版本校验的精简版 Python API 参考,涵盖 document、Part、Sketcher attachment、PartDesign features、Draft、Mesh、TechDraw drawings、import/export 和 GUI。在编写超出以下速查表范围的任何内容前,请先阅读它——若直接凭直觉猜测,若干 PartDesign/TechDraw 属性可能会出错。
The control loop
控制循环
- Make sure the bridge is up: . A live bridge answers
bash scripts/freecad-send.sh --ping. If it doesn't, the bridge has to be started — it is a local code-execution server (see Security), so say that before starting it:{"ok": true, "bridge": "freecad", "version": "1.1.3"}- FreeCAD not running (finds nothing): launch it with the bridge as a startup script — FreeCAD executes a
pgrep -xi freecadpassed on its command line:.py(absolute path), then pollopen -a FreeCAD --args "$PWD/scripts/freecad-bridge.py"for up to ~60 s while the GUI starts.--ping - FreeCAD already running: doesn't hand arguments to a running app, so ask the user to paste scripts/freecad-bridge.py into the Python console (View ▸ Panels ▸ Python console) or run it via Macro ▸ Macros… ▸ Execute after copying it into the user macro folder (shown in that dialog).
open
- FreeCAD not running (
- Write a build script to the scratchpad — Python against the API (start from references/api-reference.md and scripts/example-bracket.py).
- Send it: . The bridge runs it in the live session and returns the script's captured stdout; on a Python exception it returns the traceback and the sender exits non-zero.
OUT=/path/to/outdir bash scripts/freecad-send.sh /path/build.py(seconds) for heavy builds.FREECAD_SEND_TIMEOUT=600/App/FreeCADare pre-imported in the script's namespace, andGuiis injected (fromOUT) as both a global and$OUT.os.environ["OUT"] - Check, then measure: a feature that fails to compute does not raise — see the
recompute check below. Then write a
(bounding box, volume,
metrics.json, solid count) and Read it; for a picture,isValid()in the same script and Read the PNG. Export STEP/STL/FCStd intosaveImage(...)for the deliverable.$OUT - Iterate: inspect output/PNG, fix the script, re-send. Scripts must be re-runnable:
close any document of the same name before (FreeCAD silently renames a duplicate to
App.newDocument(...), and the old one keeps cluttering the session). Don't close documents you didn't create — the session is the user's.thing1
A failed send prints the Python traceback the bridge captured. The bridge stays up across
sends; if it becomes unresponsive, ask the user to re-run the bridge script.
No GUI needed? For pure build→measure→export work (no screenshot, no TechDraw PDF),
runs the same script
headless — useful when the user can't start the bridge. There is undefined (guard with
), stdout carries FreeCAD's own log noise, and an uncaught exception
still exits 0 — wrap the script in and so failures surface.
/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd build.pyGuiglobals().get("Gui")try/exceptsys.exit(1)- 确保 bridge 已启动:。在线的 bridge 会返回
bash scripts/freecad-send.sh --ping。如果没有响应,就必须启动 bridge——它是一个本地代码执行服务器(请参阅 Security),因此在启动前必须先向用户说明这一点:{"ok": true, "bridge": "freecad", "version": "1.1.3"}- FreeCAD 未运行(找不到任何进程):将 bridge 作为启动脚本启动 FreeCAD——FreeCAD 会执行命令行传入的
pgrep -xi freecad文件:.py(使用绝对路径),随后在 GUI 启动期间轮询open -a FreeCAD --args "$PWD/scripts/freecad-bridge.py",最长约 60 秒。--ping - FreeCAD 已在运行:无法向已运行的应用传递参数,因此请用户将 scripts/freecad-bridge.py 粘贴到 Python console(View ▸ Panels ▸ Python console),或者先将其复制到用户 macro 文件夹(该文件夹会显示在对话框中),再通过 Macro ▸ Macros… ▸ Execute 运行。
open
- FreeCAD 未运行(
- 编写构建脚本并放入 scratchpad——使用 Python 调用 API(从 references/api-reference.md 和 scripts/example-bracket.py 开始)。
- 发送脚本:。bridge 会在实时会话中运行脚本并返回脚本捕获的 stdout;如果发生 Python exception,则返回 traceback,发送器也会以非零状态退出。对于耗时较长的构建,可将
OUT=/path/to/outdir bash scripts/freecad-send.sh /path/build.py设置为FREECAD_SEND_TIMEOUT秒。600/App/FreeCAD已预先导入脚本 namespace,Gui则通过OUT注入,同时作为 global 和$OUT使用。os.environ["OUT"] - 检查,然后测量:计算失败的特征不会抛出异常——请参阅下方 recompute check。接着生成 (bounding box、volume、
metrics.json、solid count)并 Read;如需图片,则在同一脚本中调用isValid()并 Read PNG。将 STEP/STL/FCStd 导出到saveImage(...),作为最终交付物。$OUT - 迭代:检查输出和 PNG,修复脚本后重新发送。脚本必须能够重复运行:调用 前,先关闭同名 document(FreeCAD 会将重复文档静默重命名为
App.newDocument(...),旧文档会继续在会话中堆积)。不要关闭并非由你创建的 document——该会话属于用户。thing1
发送失败时,会打印 bridge 捕获的 Python traceback。bridge 会在多次发送之间持续运行;如果它停止响应,请用户重新运行 bridge 脚本。
不需要 GUI? 对于仅涉及 build→measure→export 的工作(不需要截图或 TechDraw PDF), 可以以 headless 模式运行同一脚本——当用户无法启动 bridge 时,这会非常有用。在该模式下 未定义(使用 进行保护),stdout 中会混入 FreeCAD 自身的日志,而且未捕获的 exception 仍会以 0 退出——应使用 和 包装脚本,让失败明确暴露出来。
/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd build.pyGuiglobals().get("Gui")try/exceptsys.exit(1)Modeling cheatsheet (proven patterns)
建模速查表(已验证模式)
Units are mm and degrees. = (both pre-imported by the bridge). Always
before measuring.
AppFreeCADrecompute()单位为 mm 和 degrees。 = (两者均已由 bridge 预先导入)。测量前始终要执行 。
AppFreeCADrecompute()Solid via primitives + boolean (verified: plate with a bored hole)
使用 primitives + Boolean 构建实体(已验证:带钻孔的板件)
python
import os, json, Part, Mesh
OUT = globals().get("OUT") or os.environ["OUT"]
if "bracket" in App.listDocuments(): # re-runnable: drop the previous build
App.closeDocument("bracket")
doc = App.newDocument("bracket")
box = doc.addObject("Part::Box", "Plate"); box.Length, box.Width, box.Height = 40, 20, 10
hole = doc.addObject("Part::Cylinder", "Hole"); hole.Radius, hole.Height = 5, 10
hole.Placement.Base = App.Vector(20, 10, 0) # centre of the plate
cut = doc.addObject("Part::Cut", "Bracket"); cut.Base, cut.Tool = box, hole
doc.recompute() # REQUIRED before measuringpython
import os, json, Part, Mesh
OUT = globals().get("OUT") or os.environ["OUT"]
if "bracket" in App.listDocuments(): # re-runnable: drop the previous build
doc = App.newDocument("bracket")
box = doc.addObject("Part::Box", "Plate"); box.Length, box.Width, box.Height = 40, 20, 10
hole = doc.addObject("Part::Cylinder", "Hole"); hole.Radius, hole.Height = 5, 10
hole.Placement.Base = App.Vector(20, 10, 0) # centre of the plate
cut = doc.addObject("Part::Cut", "Bracket"); cut.Base, cut.Tool = box, hole
doc.recompute() # REQUIRED before measuringCheck, measure, screenshot (the live feedback)
检查、测量、截图(实时反馈)
doc.recompute()Invalid.Shapepython
bad = [(o.Name, o.State) for o in doc.Objects if "Invalid" in o.State or "Error" in o.State]
if bad:
raise RuntimeError("recompute failed: %s" % bad) # comes back as the send's traceback
shp = cut.Shape; bb = shp.BoundBox
m = {"bbox": [round(bb.XLength,3), round(bb.YLength,3), round(bb.ZLength,3)],
"volume": round(shp.Volume,3), "valid": shp.isValid(), "solids": len(shp.Solids)}
json.dump(m, open(OUT+"/metrics.json","w"), indent=2)
print("METRICS", json.dumps(m)) # returned to the sender
view = Gui.getDocument(doc.Name).activeView() # live GUI — snapshot inline
view.setAnimationEnabled(False) # else saveImage catches the camera mid-turn
view.viewIsometric(); view.fitAll()
view.saveImage(OUT+"/check.png", 900, 675, "White")Compare the metrics against what the user asked for (e.g. expected volume computed by hand)
— that catches a hole in the wrong place or a pad in the wrong direction, which a valid,
single-solid shape would not.
doc.recompute()Invalid.Shapepython
bad = [(o.Name, o.State) for o in doc.Objects if "Invalid" in o.State or "Error" in o.State]
if bad:
raise RuntimeError("recompute failed: %s" % bad) # comes back as the send's traceback
shp = cut.Shape; bb = shp.BoundBox
m = {"bbox": [round(bb.XLength,3), round(bb.YLength,3), round(bb.ZLength,3)],
"volume": round(shp.Volume,3), "valid": shp.isValid(), "solids": len(shp.Solids)}
json.dump(m, open(OUT+"/metrics.json","w"), indent=2)
print("METRICS", json.dumps(m)) # returned to the sender
view = Gui.getDocument(doc.Name).activeView() # live GUI — snapshot inline
view.setAnimationEnabled(False) # else saveImage catches the camera mid-turn
view.viewIsometric(); view.fitAll()
view.saveImage(OUT+"/check.png", 900, 675, "White")将指标与用户要求的内容进行比较(例如,与手动计算的预期 volume 对比)——这样可以发现孔位错误或 pad 方向错误等问题,而仅检查有效且单一的 solid 无法发现这些问题。
Export (pick the module by format — see the reference's matrix)
导出(根据格式选择 module——请参阅参考文档中的矩阵)
python
Part.export([cut], OUT+"/bracket.step") # B-rep: .step/.stp .iges/.igs .brep
Mesh.export([cut], OUT+"/bracket.stl") # mesh: .stl .obj .ply .3mf .amf .off
doc.saveAs(OUT+"/bracket.FCStd") # native, re-editablepython
Part.export([cut], OUT+"/bracket.step") # B-rep: .step/.stp .iges/.igs .brep
Mesh.export([cut], OUT+"/bracket.stl") # mesh: .stl .obj .ply .3mf .amf .off
doc.saveAs(OUT+"/bracket.FCStd") # native, re-editableParametric route (Sketcher → PartDesign, verified: rectangle → pad)
参数化流程(Sketcher → PartDesign,已验证:矩形 → pad)
python
import Part, Sketcher
body = doc.addObject("PartDesign::Body", "Body")
sk = body.newObject("Sketcher::SketchObject", "Sketch") # unattached = lies on XY
V = App.Vector
for a, b in [((0,0),(30,0)), ((30,0),(30,20)), ((30,20),(0,20)), ((0,20),(0,0))]:
sk.addGeometry(Part.LineSegment(V(*a,0), V(*b,0)), False)
for i in range(4):
sk.addConstraint(Sketcher.Constraint("Coincident", i, 2, (i+1) % 4, 1))
doc.recompute()
pad = body.newObject("PartDesign::Pad", "Pad"); pad.Profile = sk; pad.Length = 10
doc.recompute()When the user wants to edit dimensions later, drive them with dimensional constraints
(, , , …) and confirm is . To
sketch on another plane or on a face, set + ;
through-all pockets are ; after adding a Linear/PolarPattern set
yourself. All are in the reference, with revolution axes, fillets, and how
to pick edges by geometry instead of guessing .
DistanceXDistanceYDiametersk.FullyConstrainedTruesk.AttachmentSupportsk.MapMode = "FlatFace"pocket.Type = "ThroughAll"body.Tip = patternEdge1Feature types, constraint kinds, Draft/Mesh calls, TechDraw drawings, and the full export
matrix are in references/api-reference.md.
python
import Part, Sketcher
body = doc.addObject("PartDesign::Body", "Body")
sk = body.newObject("Sketcher::SketchObject", "Sketch") # unattached = lies on XY
V = App.Vector
for a, b in [((0,0),(30,0)), ((30,0),(30,20)), ((30,20),(0,20)), ((0,20),(0,0))]:
sk.addGeometry(Part.LineSegment(V(*a,0), V(*b,0)), False)
for i in range(4):
sk.addConstraint(Sketcher.Constraint("Coincident", i, 2, (i+1) % 4, 1))
doc.recompute()
pad = body.newObject("PartDesign::Pad", "Pad"); pad.Profile = sk; pad.Length = 10
doc.recompute()当用户希望以后编辑尺寸时,使用 dimensional constraints(、、 等)驱动尺寸,并确认 为 。如果要在另一个平面或面上绘制 sketch,请设置 和 ;贯通 pocket 的设置为 ;添加 Linear/PolarPattern 后,需要自行设置 。这些内容以及 revolution axes、fillets 和按几何特征而非猜测 来选择边的方法,都记录在参考文档中。
DistanceXDistanceYDiametersk.FullyConstrainedTruesk.AttachmentSupportsk.MapMode = "FlatFace"pocket.Type = "ThroughAll"body.Tip = patternEdge1Feature types、constraint kinds、Draft/Mesh calls、TechDraw drawings 以及完整的 export matrix 参见 references/api-reference.md。
Verification
验证
- Returned stdout is the immediate signal — in the script comes straight back through the sender.
print(...) - The -state check is what turns a silent recompute failure into a traceback.
Invalid - metrics.json (bbox / volume / / solid count) is the structured check — write it and Read it to confirm geometry without eyeballing.
isValid() - Inline screenshot: because the bridge runs in the live GUI, in the same script produces a viewport PNG to Read — no separate render step. For a TechDraw PDF,
saveImage(...)renders a PNG thumbnail to Read.qlmanage -t -s 1000 -o <dir> page.pdf - Hand-off: an exported /
.stlopens in any slicer/CAD tool the user already has..step
- 返回的 stdout 是即时信号——脚本中的 会通过发送器直接返回。
print(...) - -state 检查可将静默的 recompute failure 转换为 traceback。
Invalid - (bbox / volume /
metrics.json/ solid count)是一种结构化检查——应将其写入文件并 Read,以确认几何结构,而不只是凭肉眼判断。isValid() - 内嵌截图:由于 bridge 在实时 GUI 中运行,同一脚本中的 会生成可供 Read 的 viewport PNG,无需单独的渲染步骤。对于 TechDraw PDF,
saveImage(...)会渲染可供 Read 的 PNG 缩略图。qlmanage -t -s 1000 -o <dir> page.pdf - 交付:导出的 /
.stl可在用户已有的任意 slicer/CAD tool 中打开。.step
Gotchas (hard-won on this machine)
常见问题(本机实践总结)
- The bridge runs in the live session, so state persists between sends: open documents, imported assets, and view settings all stick. Close-then-create your own document by name so a re-send starts clean.
- never fails on a duplicate name — it silently creates
newDocument, soname1later returns the old build.App.getDocument("name") - Failed features don't raise — returns normally and leaves the object
recompute(). CheckInvalidbefore trusting any measurement.o.State - is live here —
Gui,saveImage,viewIsometricall work. (Under headlessfitAllit is not defined.)freecadcmd - Blank or off-angle screenshots come from view animation: starts a camera animation that
viewIsometric()doesn't wait for.saveImagefirst. Always Read the PNG before handing it over.view.setAnimationEnabled(False) - and captured stdout come back in the response;
print()goes to FreeCAD's Report view, not to the sender — useApp.Console.PrintMessagefor anything you want returned.print() - before every measure or export — parametric attributes don't reach
recompute()until then..Shape - TechDraw views compute in a background thread — right after a view has no edges yet, and a dimension referencing
recompute()measuresEdge0. Wait for0.0before adding dimensions (pattern in the reference). A view'sview.getVisibleEdges()is ignored unlessScale, and edge lengths come back scaled.ScaleType = "Custom" - TechDraw exports skip the frame until the page has been opened in the GUI
(), and the default A4 template is an empty sheet — use the ISO templates (reference).
page.ViewObject.doubleClicked() - A pattern doesn't become the Body's Tip — keeps showing the unpatterned solid until you set
body.Shape.body.Tip = pattern - Guessed edge/face numbers break — is often a seam; select by curve type, radius or position (snippet in the reference).
Edge1 - PySide binding: FreeCAD 1.1 ships PySide6 (Qt6); the bridge imports it with a PySide2 fallback. If you script Qt directly, import the same way.
- Booleans keep their inputs in the tree — /
Part::CuthideFuse/Baseautomatically, butTool/Part::Filletleave their base visible (it shows through in screenshots). Hide inputs yourself; export only the result feature.Chamfer - STL resolution: in the GUI, reuses the coarse display mesh. For anything printed, mesh explicitly with
Mesh.export(reference) and report the facet count.MeshPart.meshFromShape(...) - Save the native from the script so parametric work survives and can be re-edited Re-saving over an existing file leaves a timestamped
.FCStdbeside it — delete it from*.FCBakif the folder is a deliverable.$OUT
- bridge 在实时会话中运行,因此状态会在多次发送之间持续保留:打开的 document、导入的资源以及 view settings 都会保留。按名称先关闭再创建自己的 document,使重新发送时从干净状态开始。
- 遇到重复名称时永远不会失败——它会静默创建
newDocument,因此后续调用name1会返回旧构建。App.getDocument("name") - 计算失败的特征不会抛出异常——会正常返回,并将对象保留为
recompute()。在信任测量结果前,请检查Invalid。o.State - 此处 是实时可用的——
Gui、saveImage、viewIsometric均可正常工作。(在 headless 的fitAll下,freecadcmd未定义。)Gui - 截图空白或角度异常通常由 view animation 导致:会启动 camera animation,而
viewIsometric()不会等待动画结束。请先执行saveImage。交付前始终要 Read PNG。view.setAnimationEnabled(False) - 和捕获的 stdout 会在响应中返回;
print()会输出到 FreeCAD 的 Report view,而不会发送给发送器——需要返回给发送器的内容请使用App.Console.PrintMessage。print() - 每次测量或导出前都要执行 ——否则 parametric attributes 不会传递到
recompute()。.Shape - TechDraw views 在后台线程中计算——刚执行 后,view 尚无 edges,引用
recompute()的 dimension 会测得Edge0。添加 dimensions 前请等待0.0。除非将view.getVisibleEdges()设置为ScaleType,否则 view 的"Custom"会被忽略,而且返回的 edge lengths 已应用缩放。Scale - TechDraw exports 会在页面通过 GUI 打开前跳过 frame(),并且默认 A4 模板是空白图纸——请使用 ISO templates(参考文档)。
page.ViewObject.doubleClicked() - Pattern 不会自动成为 Body 的 Tip——除非设置 ,否则
body.Tip = pattern仍会显示未应用 pattern 的 solid。body.Shape - 猜测 edge/face 编号会出错——经常是 seam;应根据 curve type、radius 或 position 进行选择(参考文档中有示例)。
Edge1 - PySide binding:FreeCAD 1.1 搭载 PySide6(Qt6);bridge 导入它时会回退到 PySide2。如果直接编写 Qt 脚本,也应采用相同方式导入。
- Boolean 会将输入对象保留在 tree 中——/
Part::Cut会自动隐藏Fuse/Base,但Tool/Part::Fillet会让 base 保持可见(因此会透过模型显示在截图中)。请自行隐藏输入对象,并且只导出结果 feature。Chamfer - STL resolution:在 GUI 中,会复用较粗的 display mesh。对于任何需要打印的模型,请使用
Mesh.export显式生成 mesh(参考文档),并报告 facet count。MeshPart.meshFromShape(...) - 从脚本保存原生 ,使 parametric work 能够保留并可再次编辑。覆盖现有文件重新保存时,会在旁边留下带时间戳的
.FCStd;如果该文件夹是交付目录,请将其从*.FCBak中删除。$OUT
Security
Security
Running the bridge means running a code-execution server on the user's machine. Say so
before asking them to start it.
- The bridge binds (
127.0.0.1:8735) and executes any Python POSTed toFREECAD_BRIDGE_PORTinside the live session — the user's privileges, the user's open documents. Requests carry no authentication: every local process, and every other user on a shared machine, can drive FreeCAD through it./run - Web pages cannot. Requests carrying an header or a cross-site
Originare rejected with 403, so a page in the user's browser can't reach the bridge. That check is the only gate — there is no token.Sec-Fetch-Site - Nothing listens until the bridge is started. All three routes (launch with the script as an argument, Python console paste, Macro ▸ Execute) are per-session — the Macro folder copy only saves the pasting, it does not autostart. Turning it into an autoload macro would make FreeCAD listen on every launch; don't suggest that without saying so.
- To stop the bridge, quit FreeCAD. There is no remote shutdown; the port is released with the process.
运行 bridge 意味着在用户计算机上运行一个代码执行服务器。在要求用户启动之前,必须先说明这一点。
- bridge 绑定 (
127.0.0.1:8735),并执行 POST 到FREECAD_BRIDGE_PORT的任意 Python 脚本,且这些脚本会在实时会话中运行——拥有用户权限,并能操作用户打开的 document。请求不携带身份验证:共享机器上的每个本地进程以及每位其他用户都可以控制 FreeCAD。/run - 网页无法访问。 携带 header 或跨站
Origin的请求会以 403 拒绝,因此用户浏览器中的页面无法访问 bridge。该检查是唯一的访问门槛——没有 token。Sec-Fetch-Site - 在 bridge 启动前,不会有任何服务监听。所有三种启动方式(作为参数启动、通过 Python console 粘贴、通过 Macro ▸ Execute 运行)均按 session 生效——Macro 文件夹中的副本只用于省去粘贴步骤,并不会自动启动。如果将其改成 autoload macro,FreeCAD 每次启动时都会开始监听;除非明确告知用户,否则不要建议这样做。
- 要停止 bridge,请退出 FreeCAD。 它没有远程关闭功能;该端口会随进程退出而释放。