基准对比:CadQuery与OpenSCAD在代理CAD工作中的表现
摘要
一项控制基准测试对比了CadQuery和OpenSCAD在AI智能体生成功能性3D打印部件时的表现,揭示了两者能力相似,但在故障模式和工具特定优势方面存在差异。
暂无内容
查看缓存全文
缓存时间: 2026/09/12 20:32
# CadQuery 与 OpenSCAD:AI 生成功能性零件的对比 | ModelRift 博客
来源:https://modelrift.com/blog/cadquery-vs-openscad
ModelRift 平台上的每个模型都使用 OpenSCAD 生成。这一选择值得定期重新评估,因此我们针对代码优先 CAD 最可靠的替代方案进行了受控对比测试:**CadQuery** (https://cadquery.readthedocs.io/),这是一个基于 OpenCascade 边界表示内核的 Python 库。
我们的测试问题很明确:哪个工具能在无人监督的情况下,由 AI 智能体驱动生成正确、可打印、功能正常的零件?至于哪个工具更便于手工编写,不在本次考量范围内。
**六个智能体,三项任务,两种工具。** 每个生成的 STL 文件都由一个不信任任何工具的解析器进行检查。
所有六个零件均可打印。能力层面的结果是乏味的。两者的差异体现在**失败方式**的不同上。
**两个螺纹软管接头并排放置,一个用 OpenSCAD 构建,另一个用 CadQuery 构建,两者几乎相同。**
*本组任务中最难的一项,由两种工具共同解决。左侧为 OpenSCAD,右侧为 CadQuery。*
## 测试设置
所有六次运行均由 Claude Opus 5(100 万上下文)通过 Claude Code 驱动。每个单元格作为独立的通用子智能体运行,继承相同的模型,彼此之间无交叉通信。每个单元格一个智能体,因此下文中的部分差异源于智能体的随机性而非工具本身。工具版本为 CadQuery 2.8.0(运行于 Python 3.14)和 OpenSCAD 2026.06.12,均运行在 M 系列 Mac 上。
OpenSCAD 端运行的是我们自有的 openscad-skill (https://github.com/ModelRift/openscad-skill),这是我们发布并内部使用的智能体技能。它涵盖了文件与版本命名、渲染-检查-修复的 QA 循环、CLI 预览的摄像机预设、截面调试以及 Customizer 语法。
对于 CadQuery,我们逐操作移植了该技能,保持相同结构,仅替换了 OpenSCAD 没有对应功能的部分。CadQuery 没有 CLI 渲染器,因此移植工作需要额外编写一个小型离屏渲染器,并用边界表示有效性指标替代 CSG 状态行。两个文件随后都加载了相同的 3D 打印设计规则、壁厚、间隙和悬垂角度,因此没有哪一方获得了对方所缺乏的建议。最终文件大小为:OpenSCAD 12.4 KB,CadQuery 13.1 KB。
一个不对称点被保留了下来:CadQuery 文件包含一份 API 速查表,而 OpenSCAD 文件不需要,因为模型本身已经很熟悉 OpenSCAD 语法。这有助于 CadQuery 处理语法问题,但对几何建模没有帮助。
每个智能体被限制在 12 个版本迭代内,被要求不得伪造成功,并必须报告每一次失败及其原始错误信息。它们在无人值守下运行。
我们不盲信智能体的报告。每个最终 STL 都经过一个解析器直接读取文件,并报告三角形数量、包围盒、体积、水密性、非流形边、边界边、翻转面和连通组件。这比我们预想的更为重要,原因将在下文说明。
## 三项任务
处理同一任务的两个智能体收到相同的文本描述,没有针对特定工具的提示。
**简单任务 T1** 是一个壁挂式 L 形支架:两块板呈 90 度角,厚度 4 毫米,两个三角形加强筋,两个用于沉头螺丝的沉孔(90 度,头部直径 9 毫米),两个普通孔,外垂直角半径 R3,内角圆角 R4,无需支撑即可打印。
**任务 T2** 升级为一个用于容纳 50 x 26 毫米 PCB 的两件式卡扣外壳。壁厚 2 毫米,腔体每侧间隙 0.4 毫米,四个 M2 螺柱,一个 9.5 x 3.5 毫米的 USB-C 开口,一个带周边唇边(间隙 0.2 毫米)且具有可用卡扣的独立盖子,五个通风槽。两个部分必须能够实际装配。
**任务 T3** 是硬核任务:一个 M24x2 螺纹软管宝塔接头,六角法兰对边宽度 30 毫米,真正的螺旋螺纹长度 12 毫米,长度为 25 毫米的宝塔头,带三个用于内径 12 毫米软管的倒刺,以及一个直径 8 毫米的直通孔道。规格明确禁止使用堆叠环结构,螺纹必须是真正的螺旋几何形状。
## 结果
| 项目 | T1 OpenSCAD | T1 CadQuery | T2 OpenSCAD | T2 CadQuery | T3 OpenSCAD | T3 CadQuery |
| ------------------ | ----------- | ----------- | ----------- | ----------- | ----------- | ----------- |
| 迭代版本数 | 2 | 3 | 8 | 5 | 1 | 3 |
| 代码行数 | 103 | 135 | 220 | 266 | 150 | 172 |
| 工具报告的错误数 | 1 | 4 | 0 | 0 | 1 | 1 |
| 无声的几何错误 | 0 | 0 | 5 | 3 | 0 | 1 |
| 智能体实际运行时间 | 461 秒 | 562 秒 | 1058 秒 | 909 秒 | 542 秒 | 935 秒 |
| 智能体消耗的 Token | 77 k | 89 k | 138 k | 139 k | 82 k | 119 k |
| 几何重算时间 | 12 毫秒 | 1.56 秒 | 16 毫秒 | 1.93 秒 | 43 毫秒 | 1.95 秒 |
| 最终 STL 判定结果 | 完好 | 完好 | 完好 | 完好 | 完好 | 完好 |
**三项任务汇总:**
| 项目 | OpenSCAD | CadQuery |
| ------------------ | -------- | -------- |
| 迭代版本数 | 11 | 11 |
| 代码行数 | 473 | 573 |
| 工具报告的错误数 | 2 | 5 |
| 无声的几何错误 | 5 | 4 |
| 智能体实际运行时间 | 2061 秒 | 2406 秒 |
| 智能体消耗的 Token | 297 k | 347 k |
“完好”意味着水密、单一连通组件、零非流形边和边界边、位于 z = 0 平面上,其测量依据是文件本身,而非生成工具的自我报告。
迭代次数完全相同,均为 11 个版本。整体工作量形态也相似。差异在于两者遇到的**问题性质**不同。
## T1:简单的支架
*两者均正确,从内角拍摄以显示加强筋。可见差异在于诠释:规格对加强筋的尺寸和位置留有余地。*
对四个外角进行圆角处理清晰地展现了两个工具的世界模型差异。OpenSCAD 先对 2D 轮廓进行圆角再挤出,此操作不关心实体是如何组装的:
``
module corner_mask() {
translate([0, 0, -eps]) linear_extrude(vert_h + 2*eps)
offset(r = corner_r) offset(delta = -corner_r) square([plate_w, horiz_d]);
}
``
CadQuery 则必须先命名四条边,而命名是难点:
``
result = result.edges("|Z").edges(
BoxSelector((-WIDTH, -EPS, -EPS), (WIDTH, EPS, V_HEIGHT + EPS))
+ BoxSelector((-WIDTH, H_DEPTH - EPS, -EPS), (WIDTH, H_DEPTH + EPS, V_HEIGHT + EPS))
).fillet(R_OUTER)
``
OpenSCAD 首次尝试即编译出正确的几何,两个版本即完成。CadQuery 大约三分之一的运行时间耗费在单个错误上:`.fillet(3.0)` 失败并报错 `BRep_API: command not done`,这条消息既未指明是哪条边也未说明半径。智能体必须手动二分查找来发现问题根源,最终发现是算术问题:两个 R3 的圆角无法容纳在 4 毫米的壁厚中。
## T2:必须匹配的两件式外壳
*两者都从间隙堆叠中推导出相同的 54.8 x 30.8 x 25.0 毫米盒体尺寸。*
这里 CadQuery 的参数链发挥了作用。改变唇边深度会联动地移动法兰边、底板、槽、凹槽和倒刺,因为每个尺寸都是推导出来的,而不是重复输入两次。这种尺寸链与优秀的参数化 UI 向用户暴露的方式相同,我们曾在此处讨论过:[构建更好的 OpenSCAD 自定义器](https://modelrift.com/blog/openscad-customizer-history)。CadQuery 没有 Customizer 对应物,因此常量块就是整个参数接口。
更有趣的是双方如何检查配合度。OpenSCAD 打印数字供人阅读:
``
echo(str("cavity LxW = ", cav_l, " x ", cav_w, " (clearance/side ", pcb_clr, ")"));
``
CadQuery 使用断言,当断言为假时构建停止:
``
assert BOX.val().intersect(LID.val()).Volume() < 1e-6, "box and lid interfere"
``
`echo` 仅在有人阅读时才有用。`assert` 则会自行导致构建失败。对于无人值守的生成,这一差距是关键。
OpenSCAD 需要八个版本,而 CadQuery 只需五个。OpenSCAD 的八个版本中有三个是用于布尔运算的清理,而非设计:清理相切和重合面产生的薄片。
## T3:真正的螺纹
这个困难任务带来了最大的意外。OpenSCAD 一个版本即完成,首次编译即正确,耗时 43 毫秒,无需外部库。
OpenSCAD 没有扫掠操作,因此智能体通过原始的顶点和面算术编写了螺旋线:一个四点 ISO 轮廓,每圈 96 个截面,作为单个 `polyhedron` 输出。这段代码没有任何保护措施。绕序错误时,工具会保持沉默。
``
module helical_thread(turns, z0) {
prof = [ [r_in, -flank_hz], [r_maj, -crest_hz],
[r_maj, crest_hz], [r_in, flank_hz] ];
n = round(turns * STEPS);
pts = [ for (i = [0:n]) let(a = i * 360 / STEPS,
zo = z0 + i * thread_pitch / STEPS)
for (j = [0:3])
[ prof[j][0] * cos(a), prof[j][0] * sin(a), prof[j][1] + zo ] ];
fcs = concat(
[ [3, 2, 1, 0] ],
[ for (i = [0:n-1]) for (j = [0:3]) let(k = (j + 1) % 4)
[ 4*i + j, 4*i + k, 4*(i+1) + k, 4*(i+1) + j ] ],
[ [4*n + 0, 4*n + 1, 4*n + 2, 4*n + 3] ]
);
polyhedron(points = pts, faces = fcs, convexity = 8);
}
``
该智能体在编写第一行代码之前就预防了经典的螺纹陷阱:ISO 齿形恰好跨越一个螺距,因此相邻的根部平面会共面,导致联合运算出错。它将扫掠轮廓沉到小径以下,使螺旋线穿过芯轴而非仅仅接触它。这就是为什么布尔运算是小菜一碟。
CadQuery 用六行可读的代码表达了相同的几何,且该部分首次尝试即成功:
``
helix = cq.Wire.makeHelix(pitch=THREAD_P, height=h, radius=R_ROOT, center=(0, 0, z0))
prof = (cq.Workplane("XZ", origin=(0, 0, z0)).center(R_ROOT, 0)
.polyline(thread_profile_points()).close())
ridge = prof.sweep(path, isFrenet=True)
core = cq.Workplane("XY", origin=(0, 0, z0)).circle(R_ROOT).extrude(h)
rod = core.union(ridge)
``
失败发生在最后一行,这也是唯一一个改变了我们对 QA 循环看法的结果。
*CadQuery T3 版本 1 在左侧。螺纹部分只是悬浮的螺旋线,没有芯轴圆柱。*
`union()` 静默地丢弃了芯轴圆柱,因为螺纹根部恰好位于芯轴半径上。沿着螺旋曲线的精确相切,导致 OCCT 默默丢弃了一个实体。从外部看,这个零件堪称完美。智能体看了四次渲染都没发现问题。最终是一个体积测量值抓住了它:7065 立方毫米,而预期值是 10323。
更糟的是,这个损坏的零件报告 `valid=True` 和 `solids=1`。将布尔运算公差提高到 `1e-3` 产生了一个负体积的实体,它同样报告 `valid=True`。
OpenSCAD 在此也并非无辜。在 T2 中,其 Manifold 后端为包含 4 条非流形边和 60 个零面积三角形的 STL 文件认证了 `Status: NoError`。两个工具的自我报告都不是最终结论,这就是我们自行解析每个网格的原因。
*两个成品接头的轴向剖面图,显示螺纹齿顶相互错开半个螺距。*
*两个成品的螺纹。左侧齿顶与右侧齿顶错开半个螺距,这是真正单头螺旋的标志,也是区分真实螺纹与堆叠环的检验标准。*
## 下载零件
以下是八个最终网格文件,完全由智能体导出,未经我们清理或修复。二进制 STL 格式,单位毫米,零件置于 z = 0 平面以适配打印。
| 零件 | OpenSCAD | CadQuery |
| ------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| T1 支架 | [STL, 2660 三角面](https://modelrift.com/blog/cadquery-vs-openscad/models/t1-bracket-openscad.stl) | [STL, 4232 三角面](https://modelrift.com/blog/cadquery-vs-openscad/models/t1-bracket-cadquery.stl) |
| T2 外壳盒体 | [STL, 2944 三角面](https://modelrift.com/blog/cadquery-vs-openscad/models/t2-enclosure-box-openscad.stl) | [STL, 14136 三角面](https://modelrift.com/blog/cadquery-vs-openscad/models/t2-enclosure-box-cadquery.stl) |
| T2 外壳盖子 | [STL, 2960 三角面](https://modelrift.com/blog/cadquery-vs-openscad/models/t2-enclosure-lid-openscad.stl) | [STL, 1872 三角面](https://modelrift.com/blog/cadquery-vs-openscad/models/t2-enclosure-lid-cadquery.stl) |
| T3 螺纹接头 | [STL, 10754 三角面](https://modelrift.com/blog/cadquery-vs-openscad/models/t3-adapter-openscad.stl) | [STL, 13748 三角面](https://modelrift.com/blog/cadquery-vs-openscad/models/t3-adapter-cadquery.stl) |
所有八个文件都是水密的、单一连通组件、零非流形边、零边界边和零翻转面。间隙假设为 FDM 工艺,喷嘴直径 0.4 毫米,因此每对 T2 零件打印后应该能卡扣装配。
三角面数量值得关注,它们并不一致偏向某个工具。CadQuery 的盒体三角面数几乎是 OpenSCAD 的五倍,而其盖子则更少。网格密度在这里反映的是每个智能体选择添加的圆角细节量,而非内核差异。
## 我们的发现
### 渲染未能捕捉到任何重要问题
这是我们未曾预料的结果。在六次运行中,图像只能捕捉到粗大的错误,比如被腔体减法删除的四个安装柱,却无法发现细微问题。每一个可能毁掉打印的缺陷,都是通过数值发现的:体积、角度、干涉测试、应变计算。在 T3 中,OpenSCAD 智能体遇到了相反的问题,差点拒绝了正确的几何,因为螺纹特写渲染出来的样子看起来不对。它自己的笔记是,渲染结果无法作为任一方面的充分证据。
### 失败模式是镜像对称的
CadQuery 失败得响亮而早期。它的消息虽然不好,但异常会停止运行,而停止的模型无法意外交付。OpenSCAD 失败得安静而晚期:在 T2 中,它在约 45 次调用中未报告任何错误或警告,却生成了缺失的安装柱、错位的槽以及一个它刚刚认证为“完好”的损坏导出文件。对于无人值守的生成,静默的成功是代价更高的失败。
### CadQuery 可以被询问,OpenSCAD 不行
CadQuery 能回答关于其自身几何的问题。T1 智能体通过读取 B-rep 上的锥体半角,证明了其沉孔是 90 度且直径为 9 毫米,然后在每次构建时运行断言来重新检查规格。OpenSCAD 无法查询几何,因此两个 OpenSCAD 智能体都独立编写了二进制 STL 解析器(代码量几乎与模型本身相当)来测量它们构建的内容。这可行,但它只在导出后才能看到网格,无法感知设计本身。
### 速度有利于 OpenSCAD,但这几乎不重要
几何重算速度快 30 到 100 倍,16 毫秒对 1.9 秒。在模型推理主导的智能体循环内,这是噪声。对于实时自定义器或大型参数扫描,这会很重要。
### OpenSCAD 的渲染没有零件边缘的概念
CSG 之后只有三角面片汤,因此 `--view=edges` 绘制的是三角剖分线而非轮廓线。CadQuery 的 B-rep 知道两个面实际相交的位置。由于图像是智能体的反馈通道,这在我们的设置中造成了真正的不对称,因此我们通过渲染导出的 STL 并根据相邻面之间的夹角重建轮廓线来解决这个问题。
*同一个零件,三种渲染器。左图:CLI 默认渲染,无轮廓线。中图:`--view=edges`,绘制了每个三角面边。右图:基于 STL 的特征边重建,还会用红色标注网格缺陷。所有六次运行均在此次修复之前完成。*
## 结论
没有哪个工具在输出上获胜。六个干净的可打印零件,每种工具各三个。
决定因素是**可验证性**而非表达能力,CadQuery 在此方面的优势远比语法差异所显示的更大。能够向内核询问你刚刚构建了什么,并对答案进行断言,在智能体循环中的价值远超任何便捷的语法。
任务形态比工具选择更重要。简单的棱柱状工作交给了 OpenSCAD。必须配合的两件式零件交给了 CadQuery。而螺旋螺纹——这个看似为边界表示内核量身定做的任务——则完全被 OpenSCAD 搞定了。
如果生成流程依赖截图,它依赖的就是那个在这里一无所获的渠道。需要强制执行的是数值断言:壁厚、间隙、干涉体积、悬垂角度。
必须独立验证网格,因为两个工具链都曾为存在缺陷的几何进行过认证。
相似文章
CADWorld:长期计算机辅助设计的计算机使用基准
CADWorld是一个用于评估在FreeCAD中进行长期机械CAD工作流中计算机使用代理的基准,揭示了当前AI性能与专家水平之间的显著差距。
它是否具备足够的代理能力?使用你自己的工具对开放模型进行基准测试
这篇博客文章介绍了一种基准测试方法,用于评估开放模型在代理编程任务上的表现,不仅关注准确性,还关注代理过程的效率。它提供了一个使用 pi coding agent 的可定制工具框架,并在不同模型和库版本上进行测试。
六次无人值守的CAD运行:渲染仅捕获粗略错误,所有破坏打印的缺陷均由测量数值捕获
一项使用CadQuery和OpenSCAD的编码代理实验表明,可视化渲染和工具状态报告遗漏了关键的CAD缺陷,而这些缺陷仅通过数值检查如体积测量才被捕获。
ArtisanCAD: 一种基于专家知识蒸馏的工业级CAD智能体
ArtisanCAD是一种技能引导的工业级CAD智能体,利用专家驱动的知识蒸馏将模糊提示转化为可执行的CAD程序,在Text2CAD基准上提升了性能,并能生成复杂汽车部件的可编辑B-Rep模型。
CADFather:通过协同工具调用实现自主CAD重建
CADFather 是一个自主智能体系统,通过协调视觉-语言助手与互补的学习型、算法型和优化工具,从 3D 网格重建可编辑的参数化 CAD 程序,并在 DeepCAD、Fusion360 和 CADENA-Bench 等基准上进行了评估,无需额外训练。