mcpField
F
freecad

freecad

github.com/jablonkai设计与 UI462 次安装今天更新
代码仓库

关于 freecad

通过 Python 脚本在运行中的 FreeCAD 会话内构建和检查模型的技能,支持迭代建模、测量、截图与导出,也说明了无头运行方式。

主要功能

  1. 01在实时 FreeCAD 会话中运行 Python 脚本
  2. 02构建参数化模型与实体模型
  3. 03检查几何并生成测量指标
  4. 04捕获视口图像
  5. 05导出 STEP、STL 和 FCStd 文件

适用场景

  1. 01通过 Python 构建脚本生成零件
  2. 02验证模型尺寸和实体有效性
  3. 03通过视口截图检查模型
  4. 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.md
scripts/
example-bracket.py
freecad-bridge.py
freecad-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 属性并不像你想的那样。

控制循环

  1. 确保桥接服务已启动: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 运行。
  2. 编写构建脚本到临时目录——使用 Python 调用 API(从 references/api-reference.md 和 scripts/example-bracket.py 开始)。
  3. 发送脚本: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"]。
  4. 检查,再测量:无法计算的特征不会引发异常——请参见下文的重新计算检查。然后写入 metrics.json(包含边界框、体积、isValid()、实体数量)并读取;如需图片,请在同一脚本中调用 saveImage(...),然后读取 PNG。将 STEP/STL/FCStd 导出到 $OUT 作为交付物。
  5. 迭代:检查输出/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。**没有远程关闭功能;进程结束时端口会释放。

评论

支持 Markdown