build123d 0.11.1 建模模式精要:scientific-agent-skills 中 lab-hardware-cad 技能的几何编程实战指南
2026/9/10 0:37:01 网站建设 项目流程

build123d 0.11.1 建模模式精要:scientific-agent-skills 中 lab-hardware-cad 技能的几何编程实战指南

【免费下载链接】scientific-agent-skillsTurn any AI agent into an AI Scientist. The #1 Agent Skills library for science, used by 190,000+ scientists worldwide. 165 ready-to-use validated skills plus 100+ scientific databases covering biology, chemistry, medicine, and drug discovery. Compatible with Cursor, Claude Code, Codex, Pi, Antigravity, and the open Agent Skills standard.项目地址: https://gitcode.com/GitHub_Trending/cl/scientific-agent-skills

本指南是 lab-hardware-cad 技能配套的 build123d 0.11.1 API Cookbook 的系统化展开,面向"用参数化 Python 源码设计实验室硬件并导出可制造文件"这一核心工作流。文章以 build123d-patterns.md 为骨架,结合仓库内 gen.py、check.py、_common.py 等脚本源码与 standards.json 标准数据库,逐一拆解接口声明、几何量规、定位对齐、孔洞沉孔、选择器圆角、草图拉伸、多格式导出等模式,并总结 12 类真实踩坑点。读完你可以写出符合本技能契约、可被--param覆盖、可被机器校验的 build123d 模型,并理解每个模式背后的实现原理。

文中所有代码片段均针对 build123d 0.11.1 + Python 3.12 验证通过,与 SKILL.md 声明的一级依赖(build123d==0.11.1matplotlib>=3.8)保持一致。

模型文件契约:gen.py 眼中的一个"好模型"

在进入具体 API 之前,先明确本技能对模型文件的硬性契约(定义于 SKILL.md 的工作流第 4 步,并由 gen.py 强制):

  • 每个可被用户改动的尺寸都必须是模块级命名常量,且单位写进名字:bore_d_mmwall_t_mmpost_h_mm。除 0、1、2 外,函数体内不允许出现裸数字。
  • 暴露build() -> Partgen.py会调用它。
  • 参数分成INTERFACE块(由标准固定的尺寸,标注标准 ID)和DESIGN块(可自由选择的尺寸)。
  • 暴露interfaces()函数声明"本零件必须配合的标准尺寸"(第 4 步机器可校验的基础),以及checks()函数声明"从实体上实测的通过/不通过量规"(第 5 步构建门禁)。
  • 工艺(Process)、材料(Material)、每个接口的来源写在模块 docstring 里。

gen.py 的命令行形态决定了这些约束的由来:

python scripts/gen.py carrier_model.py --outdir out/ python scripts/gen.py carrier_model.py --outdir out/ --param wall_t_mm=4.0 python scripts/gen.py plate_model.py --outdir out/ --dxf # 2D 激光切割轮廓

其中--param支持重复传入KEY=VALUE,解析逻辑在 _common.py:值会被自动收窄为bool/int/float/str,随后在import_model()中通过setattr(module, key, value)直接改写模块属性(_common.py),并且在导入时即应用——这正是"派生尺寸必须放进函数"这一铁律的根源,下文会展开。

Builder 模式还是代数模式?

build123d 提供两套等价 API,本技能推荐混合使用,但同一个build()内绝不混用

# Builder 模式:上下文管理器收集操作,mode= 控制布尔运算 with BuildPart() as ex: Box(80.0, 60.0, 10.0) Cylinder(radius=11.0, height=10.0, mode=Mode.SUBTRACT) part = ex.part # 代数模式:普通对象与运算符 part = Box(80.0, 60.0, 10.0) - Cylinder(radius=11.0, height=10.0)

本技能中的零件一律使用 Builder 模式。理由有三,都与技能工作流直接相关:

  1. 选择器(ex.edges()ex.faces())从 builder 上下文读取十分自然,这是倒角(fillet)和在已找到的面上放置特征(如把沉孔放在顶面)的必要前提;
  2. gen.pycheck.pysnapshot.py都约定调用build()返回builder.part(见 _common.py 的run_model),而 Builder 对象本身不是 Part;
  3. 代数模式更适合短小、纯构造性的形状,作为理解布尔的速记即可。

声明接口:让内部特征可被机器校验

实验室硬件的多数接口是内部特征——沉孔(pocket)、孔(bore)、槽(slot),它们不会出现在零件外包围盒(bounding box)上。因此check.py fit无法通过测量 STEP 找到它们;而手工把数字再敲进--value,恰恰重新引入了本技能要消灭的转写错误。interfaces()声明就是为了闭合这个回路:gen.py 在每次生成时把声明写进 manifest,check.py 的interfaces子命令逐一校验。

契约模板与字段语义

def interfaces() -> list[dict]: return [ {"feature": "cage rod bore spacing", "standard": "cage-system-30mm", "dimension": "rod_spacing", "value": rod_spacing_mm, "intent": "match"}, {"feature": "cage rod bore diameter", "standard": "cage-system-30mm", "dimension": "rod_diameter", "value": rod_bore_d_mm, "intent": "envelope", "clearance": 0.4}, ]
含义
standard来自check.py standards --list的标准 ID
dimension该标准内部的一个尺寸名
value本模型算出的数字,单位 mm
feature校验输出中的人类可读标签(默认取尺寸名)
intentmatch:本零件自身必须符合标准;envelope:该特征必须能容纳任意合规零件(默认match
clearance期望的总间隙,单位 mm,两侧合计(默认 0)

_common.py 的normalise_interfaces()给出了校验细则:standarddimensionvalue三键必填且value必须可转浮点数;intent只接受matchenvelopeclearance默认 0.0。而 check.py 的_evaluate()揭示了两种 intent 的数学差异:

  • match:以名义值为中心的对称带,被 clearance 加宽(绝不平移)——low = nominal - tol_minus - offsethigh = nominal + tol_plus + offset
  • envelope单侧最小下限low = nominal + tol_plus + offset——即最大实体条件(MMC,名义值 + 正公差)加 clearance,actual >= low即通过。若按名义值设计容纳特征,只会适配合规零件中较小的那一半。

offset < 0会被直接拒绝(check.py):负 clearance 相当于声明自己移动验收带、认证一个不合格值,这是该技能明确禁止的编码行为。

用函数而不是静态列表

# 错误:--param plate_tol_mm=0 静默地让 pocket_l_mm 停留在旧值 pocket_l_mm = plate_l_mm + plate_tol_mm + 2 * pocket_clearance_mm # 正确:每次调用都重算,覆盖才能生效 def pocket_l_mm() -> float: return plate_l_mm + plate_tol_mm + 2 * pocket_clearance_mm

原因在 gen.py:模块级的INTERFACES = [...]列表在 import 时即求值——早于--param应用——任何由被覆盖参数派生的值都会被错误记录。gen.py一旦发现"静态 INTERFACES 列表 +--param"的组合就打印警告。同样的原则适用于几何本身:所有派生尺寸都在build()或辅助函数内部计算,绝不在模块级。

声明几何检查:对着实体实测的 go/no-go 量规

interfaces()只把声明数字与标准数据库比较,从不触碰实体checks()是它的实测对应物:一系列布尔量规,通过"与build()实际产出的零件做布尔交集"来求值。gen.py 每次生成都运行这些量规、逐条打印 PASS/FAIL、把结果写进 manifest,并在任一失败时以非零码退出——违反自身声明几何的零件永远不会静默变成交付物。check.py 的geometry子命令则对已导出的 STEP(权威工件)重跑同一组量规。

核心原则:请求中的每个几何要求都映射为一个条目——

  • 必须穿过的东西(螺丝、光束、探针)→clear区域;
  • 必须落入空腔的东西(放入 pocket 的板)→ 按配合零件最大实体条件尺寸的clearbox;
  • 必须保留的东西(脊、台阶、螺丝座)→material区域;
  • 用户声明的尺寸上限 →bbox_*边界。
def checks() -> list[dict]: top = plate_t_mm / 2 return [ # clear 区域:不得有材料侵入(螺丝轴,贯穿零件) {"feature": "M6 screws pass all four bores", "clear": {"cylinder": 6.0, "axis": "z", "at": bolt_xy()}}, # 带显式区间的净空(离台面 15 mm 高处的光束走廊) {"feature": "beam clear at 15 mm above the bench", "clear": {"cylinder": 5.0, "axis": "x", "at": [(0.0, 15.0)]}}, # 量规零件必须落入 pocket:MMC 下的配合件 {"feature": "SLAS plate at MMC drops into the pocket", "clear": {"box": (128.01, 85.73, pocket_depth_mm()), "at": [(0.0, 0.0, floor_t_mm + pocket_depth_mm() / 2)]}}, # 沉孔真的得是沉孔:凹槽顶部敞开、座面存在。 # 第二条才是抓住"凹槽贯穿到底"的关键。 {"feature": "counterbore recess open at the top", "clear": {"cylinder": cbore_d_mm - 0.2, "axis": "z", "at": bolt_xy(), "span": (top - cbore_depth_mm + 0.1, top + 0.1)}}, {"feature": "screw seat present below the recess", "material": {"cylinder": cbore_d_mm - 0.2, "axis": "z", "at": bolt_xy(), "span": (-top + 0.1, top - cbore_depth_mm - 0.1)}, "min_mm3": 50.0}, # 用户声明的硬性上限,从实体实测 {"feature": "clears the objective turret", "bbox_z": {"max": 15.0}}, ]

量规模式语义

含义
clear/material该区域必须不含材料 / 必须含材料
{"cylinder": DIA, "axis": "x"\|"y"\|"z", "at": [(a, b), ...], "span": (lo, hi)}at是垂直于轴平面上的 2D 坐标——轴 z:(x, y);轴 x:(y, z);轴 y:(x, z)。省略span则贯穿整个零件
{"box": (dx, dy, dz), "at": [(x, y, z), ...]}轴对齐盒形量规,以各位置为球心(中心点)
tol_mm3/min_mm3每个位置的通过阈值(clear 侵入体积上限 / material 材料体积下限,默认均 0.01)
bbox_xbbox_zbbox_min/mid/max实测包围盒的{"min": mm, "max": mm}边界

区域校验规则在 _common.py 的_normalise_region/normalise_checks中逐项强制:cylinder 直径必须 > 0、轴只能是 x/y/z、at坐标数量与轴向匹配、box 三边必须 > 0;每个条目必须恰好拥有clearmaterial或一个bbox_*测量之一,否则抛错。实测求值在evaluate_checks()(_common.py):对每个位置实体化量规 solid(圆柱沿轴、at平面定位;盒在 3D 中心),与零件做布尔交集并累计体积——clear要求每位置侵入体积 ≤tol_mm3material要求每位置材料体积 ≥min_mm3。注意intersection_volume()(_common.py)对内核返回None、空Compound或无.volumeShapeList一律按 0 处理,这正是"相切或分离实体的布尔结果是空的而非错误"这一坑的工程化应对。

量规尺寸从要求自身的数字来

只有当要求是关系型的(凹槽位于座面之上)才用与几何相同的命名常量去度量规;当要求是绝对型的——配合件的 MMC、用户的高度限制、光束位置——就用量规自身的要求数字,这样错误的参数无法把量规缩小到与错误几何"匹配"。

不改模型的一次性探针与钻孔普查

对"不改模型的一次性问题",check.py 的probe子命令直接从命令行运行单个量规:

python scripts/check.py probe out/part.step --cyl 6.6 --at 37.5,37.5 --at -37.5,37.5 python scripts/check.py probe out/part.step --box 40,40,5 --at 0,0,7 --expect material

bores子命令输出每个圆柱面的普查(直径、轴、位置、跨度、扫掠角),用于与模型意图对账(_common.py 的cylinder_census):约 360° 扫掠的圆柱面是孔/轴,约 90° 扫掠的是边倒角,沉孔则是沿轴堆叠的两个同心全扫掠面、半径不同。这正是 SKILL.md 第 6 步"渲染图分辨不出 0.3 mm 特征时"的仪器化替代。

定位:螺栓阵列与微孔板井格

Locations将其内部创建的对象整体放置,是螺栓阵列的主力:

with Locations((10.0, 0.0), (-10.0, 0.0)): # 当前平面上的两个位置 Hole(radius=3.3) with Locations((0.0, 0.0, floor_t_mm)): # z 方向偏移 Box(10.0, 10.0, 5.0, mode=Mode.SUBTRACT) with GridLocations(9.0, 9.0, 12, 8): # x 间距, y 间距, x 数量, y 数量 Hole(radius=1.5)

GridLocations把网格居中于原点;而微孔板井格是按板角定标的,所以要计算绝对坐标再交给Locations

a1_x_mm, a1_y_mm, pitch_mm = 14.38, 11.24, 9.0 # slas-well-positions-96 origin_x = -plate_l_mm / 2 origin_y = plate_w_mm / 2 wells = [ (origin_x + a1_x_mm + pitch_mm * col, origin_y - a1_y_mm - pitch_mm * row) for row in range(8) for col in range(12) ] with Locations(*wells): Hole(radius=well_clear_d_mm / 2)

这些数字的来源可在 standards.json 中核实:slas-well-positions-96定义的well_pitch名义值 9.0 mm、a1_offset_x14.38 mm(左外沿到第 1 列中心)、a1_offset_y11.24 mm(顶外沿到 A 行中心),且每个井心须落在名义位置的 0.70 mm 位置公差带内——本技能的命令行入口正是check.py standards --show slas-well-positions-96。微孔板的外轮廓则来自slas-microplate-footprintfootprint_length127.76 mm、footprint_width85.48 mm,±0.25 mm,角区 12.7 mm 内测量,侧边中点处放宽到 ±0.5 mm)。

对齐:经典"切穿地板" bug

默认情况下对象以原点为中心。align移动基准点,通常正是你想要的——让 pocket 从底面开始:

Box(x, y, z, align=(Align.CENTER, Align.CENTER, Align.MIN)) # 坐落在 z = 0 Box(x, y, z, align=(Align.MIN, Align.MIN, Align.MIN)) # 角点在原点

这个搞错就是经典的"pocket 切穿地板"故障——snapshot.py 生成的多视图 PNG 正是为捕捉这类问题而存在的(SKILL.md 第 6 步是强制步骤,不因确定性检查通过而豁免)。SKILL.md 的示例模型carrier_model.pyBox(..., align=(Align.CENTER, Align.CENTER, Align.MIN))配合Locations((0, 0, floor_t_mm))Mode.SUBTRACTbox,就是"对齐 + 抬升"的教科书组合:主体坐底、掏空从底板顶面开始。

孔:沉孔的工作平面陷阱

Hole贯穿整个零件;CounterBoreHoleCounterSinkHole增加头部凹槽。

CounterBoreHole从其所放置的工作平面向下切,凹槽就在该平面上。在居中的Box上,默认工作平面是零件的中间高度,所以 2 元组位置会把螺丝座埋进板内——或者在薄板上让凹槽吞掉整个顶部,留下一个螺丝头直接掉下去的直通孔。正确的做法是放在顶面上(或给位置一个显式的顶部 z):

with BuildPart() as plate: Box(60.0, 60.0, 10.0) # 覆盖 z = -5 .. +5 top = plate.faces().sort_by(Axis.Z)[-1] with Locations(top): with Locations((20.0, 20.0)): CounterBoreHole(radius=6.6 / 2, counter_bore_radius=11.0 / 2, counter_bore_depth=6.5)

counter_bore_depth要从螺丝头高度取,而不是凭习惯:M6 内六角圆柱头螺丝头高 6.0 mm,1/4-20 头高 6.35 mm(后者记录在 standards.json 的optical-breadboard-metricscrew_head_height中;counterbore_dia11.0 mm 对应 M6 沉头孔径)。4 mm 的沉孔会让两种头都高出 2 mm——别把这种叫齐平。生成后在快照(或剖面)里确认凹槽位于顶面且座面台阶存在:这两种失败模式都能让is_valid和包围盒原样通过。

另外牢记打印孔会偏小(参见 fabrication-limits.md:FDM 与 SLA 的 6.0 mm 建模孔通常实测不足 6.0 mm),功能孔要么放大要么计划铰孔。standards.json 中optical-breadboard-metricclearance_hole_normal(6.6 mm)正是"打印件优先"的数字。

选择器:安全地倒角与圆角

选择器用于找出要倒角、圆角或在其上构建的边与面。本技能需要的三个:

part.edges().filter_by(Axis.Z) # 保留平行于 Z 的边(竖直棱角) part.edges().group_by(Axis.Z)[-1] # 最高 Z 的那一组(顶部边) part.faces().sort_by(Axis.Z)[-1] # 单个最高面 part.edges().filter_by(GeomType.CIRCLE) # 只留圆形边

filter_by保留所有匹配项;group_by按键分区成列表,[-1]是最后一组、[0]是第一组;sort_by对单项排序。

with BuildPart() as ex: Box(80.0, 60.0, 10.0) chamfer(ex.edges().group_by(Axis.Z)[-1], length=4.0) # 顶面边倒角 fillet(ex.edges().filter_by(Axis.Z), radius=5.0) # 竖直棱角圆角

这些宽泛选择器只在零件仍是纯长方体时安全。一旦零件有了 pocket、孔、槽或微结构,filter_by(Axis.Z)group_by(Axis.Z)[-1]也会选中那些特征的边,圆角要么抛内核错误(Failed creating a filletBRep_API: command not done),要么——更糟——成功并悄悄吃掉一面墙或一条 0.3 mm 的脊。两种都在实践中发生过。因此:

  • 在添加内部特征之前对外部主体圆角/倒角,或用位置、长度、GeomType刻意过滤选择,只留下目标边;
  • 附近几何紧张时用part.max_fillet(edges)约束半径——它返回内核在该边集上实际能构建的最大半径;
  • 每个圆角/倒角半径都是命名参数,内核失败时回退数值而不是对抗选择器;
  • 然后检查快照:被吃掉的特性在图片里一目了然,在is_valid里隐形。

草图后拉伸:非图元轮廓与激光切割 DXF

轮廓不是图元时,画草图再拉伸:

with BuildPart() as bracket: with BuildSketch() as profile: Rectangle(40.0, 20.0) with Locations((15.0, 0.0)): Circle(radius=4.0, mode=Mode.SUBTRACT) extrude(amount=6.0)

这也是通向激光切割 DXF 的路径——草图就是切割轮廓。gen.py 的_export_dxf就是这条路径的成品实现:默认在零件中间高度切片(因为坐落在构建板上的零件在 z=0 处只有退化面),把剖面平移回 z=0(否则 DXF 写入器会对非平面形状告警),再把切割几何放到命名层CUT(层色ColorIndex.RED),因为激光加工商把功率/速度映射到层或颜色上。

导出:STEP 是唯一权威

gen.py负责这些,但作为参考:

export_step(part, "part.step", unit=Unit.MM) # 权威工件 export_stl(part, "part.stl", tolerance=1e-3, angular_tolerance=0.1) # 激光切割的 2D 剖面。section() 是模块级操作,不是形状的方法—— # part.section(...) 会抛 AttributeError。 from build123d.exporters import ColorIndex # 不在 `from build123d import *` 里 profile = section(part, Plane.XY.offset(z_mm), mode=Mode.PRIVATE) profile = profile.moved(Location((0, 0, -z_mm))) # 移回 z = 0,否则 DXF 写入器 # 会警告非平面形状 exporter = ExportDXF(unit=Unit.MM) exporter.add_layer("CUT", color=ColorIndex.RED) # 激光加工商把功率/速度映射到层 exporter.add_shape(profile, layer="CUT") exporter.write("part.dxf")

切面要穿过材料,而不是在 z=0:坐落在构建板上的零件在那里只有退化面。gen.py --dxf默认取零件中间高度,可用--dxf-z覆盖(gen.py 还会在只给了--dxf-z而没给--dxf时直接报错)。导出的其他参数化入口:--tolerance(STL 线性偏差,默认 0.001)、--angular-tolerance(角度偏差,默认 0.1)、--no-stl

STEP 保留精确的 BREP 几何;STL 是三角化近似。始终以 STEP 为真值源,网格从它重新生成,绝不反向。

在代码里测量

在模型内部断言接口时很有用:

bbox = part.bounding_box() print(bbox.size.X, bbox.size.Y, bbox.size.Z) print(part.volume, part.area) print(part.is_valid) # 0.11.1 里是属性,不是方法 print(part.center(CenterOf.MASS))

is_valid是属性而非方法,这是与旧版本及部分文档的真实差异,访问时不要带括号。check.py 的facts子命令正是这些测量的工程化集合(is_valid、包围盒、体积、面积、质心、实体数,且is_valid: false时以非零码退出);_common.py 的_is_valid甚至对"属性 vs 方法"两种形态做了兼容。

易踩坑清单:12 个真实教训

  1. is_valid是属性。part.is_valid()TypeError: 'bool' object is not callable
  2. section()是模块级操作,不是方法。part.section(Plane.XY)AttributeError。应调用section(part, plane, mode=Mode.PRIVATE)
  3. intersect()返回没有.volumeShapeList&运算符返回有.volumeSolid。check.py 的clearance子命令两者都处理(先试交集体积,再退化为最小距离)。
  4. 永远不要把脚本命名为inspect.py放在会进入sys.path的目录里。它会遮蔽标准库inspect,破坏typing_extensions,进而连 build123d 一起搞坏。这正是本技能内置脚本叫check.py的原因。
  5. Builder 对象不是 Part。返回builder.part,而不是 builder。
  6. Mode.SUBTRACT需要已存在的体。在空上下文里做减法会静默地什么都不发生。
  7. 扫掠或拉伸的轮廓默认以其路径/平面为中心,除非你对齐。沿曲面路径扫掠Rectangle(w, h)会让半个轮廓沉到曲面下方——一条"0.3 mm 的脊"其实是 0.15 mm 突出。传align=(并在轮廓平面上给显式x_dir),让轮廓落在你以为的位置,然后测量结果。
  8. Curve没有.length对边求和:sum(e.length for e in curve.edges())
  9. 相切或分离实体的布尔结果是空的,不是错误。取决于路径你会得到None、空Compound或无.volumeShapeList——在任何干涉检查里读取.volume之前先做守卫(_common.py 的处理即是范例)。
  10. ColorIndexLineType住在build123d.exporters,不在顶层命名空间;from build123d import *带不进它们,add_layer(color=1)会失败。
  11. OpenCascade 内核会抛多种异常类型。在布尔运算周围宽泛捕获并报告失败,而不是让 traceback 逃逸(gen.py 与 _common.py 均如此处理)。
  12. 孔洞建模后的沉孔/倒角陷阱:见上文"选择器"与"孔"两节——特征选择错误与工作平面错误都既过得了is_valid、也过得了包围盒检查,只能靠快照与check.py bores普查来发现。

配套材料与下一步

  • SKILL.md:完整工作流(8 步:路由到器件族 → 建立接口尺寸 → 先选工艺 → 参数化建模 → 生成与检查 → 快照目检 → 源码修复 → 制造前报告),以及全部命令的速查表。
  • fabrication-limits.md:工艺公差与最小特征、配合间隙表(自由滑动 FDM 0.40 / SLA 0.20 / CNC 0.10 mm 每侧)、螺纹与热熔嵌件、材料热/化学/生物相容性。
  • standards.json:11 个内置标准(slas-microplate-footprintslas-microplate-heightslas-microplate-flangeslas-well-positions-96/384/1536cuvette-standard-10mmoptical-breadboard-metric/imperialcage-system-30mmsm1-lens-tube-thread),全部可由check.py standards --list / --show浏览。
  • gen.py / check.py / _common.py / snapshot.py:本文引用的所有契约、校验与渲染逻辑的源码所在。
  • 器件族参考:microfluidics.md、optomechanics.md、labware-adapters.md、behavior-rigs.md,以及制造前清单 validation.md。

编写模型时请记住技能的底线:模型文件是被执行的而非被解析的——只运行本会话编写或用户可信来源提供的模型;界面尺寸永远查标准文件或问用户,绝不凭记忆写;STEP 是权威工件,绝不手改导出文件,也绝不从网格逆向重建。

【免费下载链接】scientific-agent-skillsTurn any AI agent into an AI Scientist. The #1 Agent Skills library for science, used by 190,000+ scientists worldwide. 165 ready-to-use validated skills plus 100+ scientific databases covering biology, chemistry, medicine, and drug discovery. Compatible with Cursor, Claude Code, Codex, Pi, Antigravity, and the open Agent Skills standard.项目地址: https://gitcode.com/GitHub_Trending/cl/scientific-agent-skills

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询