关于 freecad
通过 Python 脚本在运行中的 FreeCAD 会话内构建和检查模型的技能,支持迭代建模、测量、截图与导出,也说明了无头运行方式。
主要功能
- 01在实时 FreeCAD 会话中运行 Python 脚本
- 02构建参数化模型与实体模型
- 03检查几何并生成测量指标
- 04捕获视口图像
- 05导出 STEP、STL 和 FCStd 文件
适用场景
- 01通过 Python 构建脚本生成零件
- 02验证模型尺寸和实体有效性
- 03通过视口截图检查模型
- 04导出模型以便编辑或制造
MCP 相关
本地 FreeCAD Bridge 接收配套 shell 脚本发送的 Python 脚本,并在实时 GUI 会话中执行。该代码执行服务绑定 localhost,且没有身份验证。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jablonkai/ skills --skill freecad安装后在 Claude Code 中输入/freecad确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/api-reference.mdscripts/example-bracket.pyfreecad-bridge.pyfreecad-send.sh详细介绍
FreeCAD 控制
FreeCAD(/Applications/FreeCAD.app)可通过 Python 脚本调用 FreeCAD /
FreeCADGui / Part / Sketcher / PartDesign / Draft / Mesh / TechDraw 模块。
通过 FreeCAD Bridge 驱动它——这是一个在 运行中的 FreeCAD GUI 内运行的小型脚本,可执行 POST 到 127.0.0.1:8735 的任意构建脚本。由于脚本在 GUI 线程中的实时会话内运行,构建时视口会随之更新,且 Gui.* / saveImage 可直接使用。以下内容均已在 FreeCAD 1.1.3 上验证。
- scripts/freecad-bridge.py — 在 FreeCAD 内运行的桥接脚本。
- scripts/freecad-send.sh — 将
.py文件(或-c 'inline code')发送到桥接脚本,并打印捕获的输出;--ping用于检查桥接脚本是否在运行。 - scripts/example-bracket.py — 可供参考的完整构建→检查→测量→导出→截图脚本。
- references/api-reference.md — 精简且经过版本验证的 Python API 参考(文档、Part、Sketcher 附着、PartDesign 特征、Draft、Mesh、TechDraw 工程图、导入/导出、GUI)。在编写超出下方速查表范围的内容前,请先阅读——若干 PartDesign/TechDraw 属性并不像你想的那样。
控制循环
- 确保桥接服务已启动:
bash scripts/freecad-send.sh --ping。正常运行的桥接服务会返回{"ok": true, "bridge": "freecad", "version": "1.1.3"}。如果没有返回,就必须启动桥接服务——它是本地代码执行服务(参见安全),因此请在启动前告知用户:- FreeCAD 未运行(
pgrep -xi freecad未找到任何进程):将桥接脚本作为启动脚本启动 FreeCAD——FreeCAD 会执行通过命令行传入的.py文件:open -a FreeCAD --args "$PWD/scripts/freecad-bridge.py"(绝对路径),然后在 GUI 启动期间最多轮询--ping约 60 秒。 - FreeCAD 已在运行:
open不会将参数传递给正在运行的应用,因此请用户将 scripts/freecad-bridge.py 粘贴到 Python 控制台(View ▸ Panels ▸ Python console),或将其复制到用户宏文件夹(该对话框中会显示)后,通过 Macro ▸ Macros… ▸ Execute 运行。
- FreeCAD 未运行(
- 编写构建脚本到临时目录——使用 Python 调用 API(从 references/api-reference.md 和 scripts/example-bracket.py 开始)。
- 发送脚本:
OUT=/path/to/outdir bash scripts/freecad-send.sh /path/build.py。桥接服务会在当前会话中运行脚本,并返回脚本的捕获的标准输出;如果发生 Python 异常,则会返回回溯信息,且发送器会以非零状态码退出。对于大型构建,可设置FREECAD_SEND_TIMEOUT=600(秒)。脚本的命名空间中已预先导入App/FreeCAD/Gui,并且会将OUT(来自$OUT)同时注入为全局变量和os.environ["OUT"]。 - 检查,再测量:无法计算的特征不会引发异常——请参见下文的重新计算检查。然后写入
metrics.json(包含边界框、体积、isValid()、实体数量)并读取;如需图片,请在同一脚本中调用saveImage(...),然后读取 PNG。将 STEP/STL/FCStd 导出到$OUT作为交付物。 - 迭代:检查输出/PNG,修正脚本,然后重新发送。脚本必须可重复运行:调用
App.newDocument(...)前,先关闭同名文档(FreeCAD 会静默地将重复名称改为thing1,而旧文档仍会留在会话中造成杂乱)。不要关闭非你创建的文档——该会话属于用户。
发送失败时,会打印桥接服务捕获的 Python 回溯信息。桥接服务会在多次发送之间保持运行;如果它变得无响应,请用户重新运行桥接脚本。
不需要 GUI? 对于纯粹的构建→测量→导出工作(不需要截图或 TechDraw PDF),/Applications/FreeCAD.app/Contents/Resources/bin/freecadcmd build.py 可以在无界面模式下运行同一脚本——适用于用户无法启动桥接服务的情况。此时 Gui 未定义(使用 globals().get("Gui") 进行保护),标准输出会包含 FreeCAD 自身的日志噪声,而且未捕获的异常仍会以 0 退出——请用 try/except 包装脚本,并调用 sys.exit(1),以便显示失败情况。
建模速查表(经过验证的模式)
单位为毫米和度。App = FreeCAD(两者均由桥接服务预先导入)。测量前务必调用 recompute()。
使用基本体和布尔运算创建实体(已验证:带钻孔的板)
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 measuring
检查、测量、截图(实时反馈)
doc.recompute() 永远不会引发异常:损坏的特征(错误的草图、未切到实体的凹槽、过大的圆角)会被标记为 Invalid,其 .Shape 也会过时或为空,因此测量结果可能会悄悄描述错误的零件。请先检查:
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")
将测量结果与用户的要求进行对比(例如手动计算的预期体积)——这能发现位置错误的孔或方向错误的拉伸,而这些问题不会仅凭一个有效的单实体形状体现出来。
导出(按格式选择模块——参见参考文档中的矩阵)
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-editable
参数化路径(Sketcher → PartDesign,已验证:矩形 → 拉伸)
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()
如果用户之后需要编辑尺寸,请使用尺寸约束(
DistanceX、DistanceY、Diameter,……)来控制尺寸,并确认 sk.FullyConstrained 为 True。要在其他平面或面上绘制草图,请设置 sk.AttachmentSupport + sk.MapMode = "FlatFace";
贯穿所有实体的凹槽设置为 pocket.Type = "ThroughAll";添加 Linear/PolarPattern 后,请自行设置 body.Tip = pattern。这些内容都在参考文档中,其中还包括旋转轴、圆角,以及如何根据几何特征选择边,而不是猜测 Edge1。
特征类型、约束种类、Draft/Mesh 调用、TechDraw 图纸和完整的导出矩阵,详见 references/api-reference.md。
验证
- 返回的 stdout 是即时信号——脚本中的
print(...)会通过发送端直接返回。 Invalid状态检查 可将静默的重新计算失败转换为回溯信息。- metrics.json(边界框 / 体积 /
isValid()/ 实体数量)是结构化检查——写入后再读取,即可确认几何形状,无需肉眼查看。 - 内嵌截图:由于桥接程序在实时 GUI 中运行,同一脚本中的
saveImage(...)会生成可供读取的视口 PNG——无需单独渲染。对于 TechDraw PDF,可使用qlmanage -t -s 1000 -o <dir> page.pdf渲染 PNG 缩略图以供读取。 - 交付:用户已有的任何切片软件/CAD 工具都能打开导出的
.stl/.step文件。
已知问题(在本机上摸索出的经验)
- 桥接在当前会话中运行,因此状态会在多次发送之间保留:打开的文档、 导入的资源和视图设置都会一直存在。请先关闭文档,再按名称创建自己的文档, 这样重新发送时才能从干净状态开始。
newDocument遇到重复名称时不会失败——它会静默创建name1,因此之后 调用App.getDocument("name")时返回的仍是旧版本。- 功能失败时不会抛出异常——
recompute()会正常返回,并将对象保留为Invalid。在信任任何测量结果之前,请检查o.State。 - 这里的
Gui可正常使用——saveImage、viewIsometric、fitAll都能运行。(在无头模式下的freecadcmd中,未定义该对象。) - 截图为空白或角度不对,是视图动画导致的:
viewIsometric()会启动相机 动画,而saveImage不会等待动画结束。请先调用view.setAnimationEnabled(False)。交付前务必 读取 PNG。 print()和捕获的标准输出会出现在响应中;App.Console.PrintMessage会输出到 FreeCAD 的报告视图,不会发给发送方——需要返回的内容请使用print()。- 每次测量或导出前都要调用
recompute()——参数化属性在此之前不会更新到.Shape中。 - TechDraw 视图在线程中计算——调用
recompute()后,视图一开始还没有边, 而引用Edge0的尺寸会测得0.0。添加尺寸前,请等待view.getVisibleEdges()(参考示例中有相应模式)。除非设置ScaleType = "Custom",否则视图的Scale会被忽略,返回的边长也会按比例缩放。 - TechDraw 导出会跳过图框,除非已在 GUI 中打开页面
(
page.ViewObject.doubleClicked());默认的 A4 模板是空白页,请使用 ISO 模板(见参考示例)。 - 阵列不会自动成为 Body 的 Tip——直到设置
body.Tip = pattern,body.Shape仍会显示未阵列的 实体。 - 猜测边或面的编号会出错——
Edge1通常是接缝;请按曲线类型、 半径或位置选择(参考示例中的代码片段)。 - PySide 绑定:FreeCAD 1.1 附带 PySide6(Qt6);桥接会在导入失败时回退到 PySide2。若直接编写 Qt 脚本,请用相同方式导入。
- 布尔运算会在树中保留输入对象——
Part::Cut/Fuse会自动隐藏Base/Tool,但Part::Fillet/Chamfer会让基体保持可见(因此会透过结果显示在截图中)。请自行隐藏输入对象;只导出结果特征。 - STL 分辨率:在 GUI 中,
Mesh.export会复用粗糙的显示网格。若用于打印,请使用MeshPart.meshFromShape(...)显式生成网格(见参考示例),并报告面片数量。 - 请在脚本中保存原生
.FCStd文件,这样参数化作品才能保留并可再次编辑。重新保存到已有文件时,旁边会留下带时间戳的*.FCBak——如果该文件夹中的内容要交付,请从$OUT中删除它。
安全
运行桥接意味着在用户的机器上运行代码执行服务。请在让用户启动之前说明这一点。
- 桥接绑定到
127.0.0.1:8735(FREECAD_BRIDGE_PORT),并会在当前会话中执行 POST 到/run的任何 Python 代码——使用用户的权限和用户已打开的文档。 请求没有身份验证:任何本地进程,以及共享机器上的其他用户,都可以通过它操控 FreeCAD。 - 网页无法访问。携带
Origin标头或跨站点Sec-Fetch-Site的请求会被拒绝并返回 403,因此用户浏览器中的网页无法连接到桥接。该检查是唯一的防护措施——没有令牌。 - **只有启动桥接后才会开始监听。**三种启动方式(将脚本作为参数启动、粘贴到 Python 控制台、Macro ▸ Execute)都仅对当前会话有效——复制到 Macro 文件夹只会保存粘贴操作,不会自动启动。将其改为自动加载宏会导致 FreeCAD 每次启动时都开始监听;除非说明这一点,否则不要建议这样做。
- **要停止桥接,请退出 FreeCAD。**没有远程关闭功能;进程结束时端口会释放。