text2cad-mcp
v0.16.4
Published
Natural-language driven CAD MCP server (2D/3D): generate part/assembly drawings and bridge to AutoCAD & CATIA. Auto-installs its Python backend into a managed virtualenv.
Maintainers
Readme
Text2CAD-MCP
契约驱动的通用 CAD 生成 MCP 服务。LLM 把设计结论写成一份 DesignSpec 契约(零件构成 / 装配约束 / 运动原理)
→ 输出 2D 工程图 + 3D 模型 + 可交互 Web 查看器,并桥接到 AutoCAD(DXF/DWG) 与 CATIA(CATPart/CATProduct)。
内核零品类硬编码。 加一个全新产品的唯一动作是再写一份 JSON 契约,
src/一行不改。 判别标准:*换一个完全不同的产品,这段代码要不要改?*要改,就是把设计能力放错地方了。
职责边界:LLM 管设计,MCP 管制造 ⭐
LLM(设计方) MCP(制造方)
零件分解、装配关系、运动原理 ──▶ 造型 → 装配求解 → 2D 出图 → 3D 建模 → Web 交互
写成一个 DesignSpec JSON 契约 (产品差异 100% 来自契约内容)
▲ │
└────── diagnostics 诊断回灌 ◀────────┘
(八闸给出的可操作修改建议)只靠两个工具就能走通全流程:
| 工具 | 作用 |
|---|---|
| design_spec_schema() | 契约自描述:词表(图元/剖面/算子/组合子/约束/关节/出图)+ 规则 + 八闸说明 + 当前环境的 capability_gaps(哪些算子会被跳过/不可用/只能网格近似)+ 可运行示例 |
| build_from_spec(spec_json) | 唯一通用生成入口:契约 → DXF/SVG(2D)+ STEP/STL(3D)+ 可交互 Web 查看器 + BOM/质量/干涉报告 + diagnostics |
契约规格见 docs/DESIGN_SPEC.md。
四份互不相关的契约(自行车 / 6 轴机器人臂 / 步枪机构 / 桌面台钳,共 45 个零件)由同一份内核、零 per-product 分支产出,
作为回归夹具放在 tests/fixtures/:
| 契约 | 覆盖的难点 |
|---|---|
| spec_bicycle.json | 极坐标阵列(辐条/齿)、sweep 管件、mirror 后三角、source 传动链、插接配合、多刚体(车架组 + 前叉组各自锚定) |
| spec_robot_arm.json | point_to_point 相对约束链(除底座外零手算坐标)、parent 串链、prismatic 相位反向、cut 减重孔 |
| spec_ak47.json | 18 零件完整总装、sweep 弧形弹匣、translate+rotate 组合搭枪托/握把、parent + mode="oscillate" 自动机循环 |
| spec_bench_vise.json | 螺旋传动(丝杠 revolute + 活动钳口 prismatic)、linear 阵列导杆、丝杠与手柄同轴同步回转 |
能力边界(两层)
1. 契约通道(⭐ 任意产品,主推) —— LLM 写 DesignSpec JSON → build_from_spec:
- 零件几何是声明式 op 树(
{"op": …}递归求值):8 种图元 + 7 种剖面算子(extrude 含 taper/twist、revolve、sweep、loft、shell、fillet、chamfer)+ 7 种组合子(union/cut/intersect/ translate/rotate/mirror/pattern)+ 通用修饰键(at/rot/mirror/pattern)。 - 装配用相对约束表达:
fixed / coaxial / coincident / distance / angle / point_to_point / pattern, 配合零件自声明 anchors,可以一个绝对坐标都不写。 - 运动用关节表达:
revolute(含往复摆动mode="oscillate")/prismatic/orbit/slider_crank/screw。source表达传动比链,parent表达结构串链(下游自动跟随上游)。 - 出图:
drawing.views(front/back/top/bottom/left/right 或自定义 eye/right)+ 尺寸 + 标题栏。
2. 构件级通用层 —— 只需单个零件、不想写整份契约时更直接:
generate_part——sketch基元(rect / circle / line / polyline / arc)任意组合 → DXF。generate_3d_part——extrude(含孔)/revolve/tube(两点管段)→ STEP/STL。validate/solve_assembly_layout/kb_search/list_standard_parts—— 老PartSpec/AssemblySpec链路的校验与布局工具,仍保留可用。
几何后端(可插拔 + 能力协商)
同一份契约可以跑在不同几何内核上;换后端不换结果,只换速度与保真度。
| 后端 | 依赖 | 能力 | 定位 |
|---|---|---|---|
| manifold | manifold3d(默认核心依赖,原生约 2 MB,只依赖 numpy) | 精确布尔、拉伸/旋转/扫掠/螺旋、输出保证流形、流形/亏格自检 | 全新安装即可 3D 建模,速度比 cadquery 快 7~25 倍 |
| cadquery | cadquery(可选,重) | B-rep 实体、STEP/IGES、圆角/倒角/抽壳/放样 | 需要可编辑实体或精确特征时安装 |
调度方式:TEXT2CAD_BACKEND=manifold|cadquery 显式指定;不设则按能力权重自动挑最强的。
能力协商:后端缺某个算子时降级而不失败 —— 软算子(fillet/chamfer/shell)静默跳过并在
result.degraded 记账;硬算子(loft)明确报错。design_spec_schema() 的 capability_gaps
会把当前环境的缺口提前告诉 LLM。
pip install manifold3d # 默认内核(已随包安装,通常无需手动执行)
pip install "text2cad-mcp[three-d]" # 或 pip install cadquery —— 解锁 STEP / 圆角 / 抽壳元素选择:select 谓词 + anchors 特征拾取(P3)
不用写死坐标,让 MCP 从真实几何里算出要的那一组元素:
// ① 倒角/抽壳要"哪几条边/哪几个面" → 几何谓词(cadquery 精确;网格后端跳过并记账)
{"op":"chamfer","of":{…},"distance":1,
"select":{"kind":"edges","type":"circle","r_near":8}} // 只倒 Φ8 孔的口
// ② 装配锚点不想算坐标 → 特征拾取(planar_face / hole_axis / cylinder_axis)
{"anchors":{"seat":{"pick":"hole_axis","dir":"z","r":14}}}- 谓词支持
kind / type / parallel / normal / at_max / at_min / x_ge … / within_box / r_near, 多键之间是与的关系。 - 特征拾取 B-rep 后端为精确解(实测 0 误差),网格后端是网格近似(返回值带
approx=true)。 - 诚实性纪律:信息不足(不给
r而存在多根轴)或压根没有该特征时返回 None, 绝不编造一个"看起来合理"的点 —— 静默取错点比报错危险得多。
为什么不用"面的稳定编号":那会引入参数化 CAD 著名的拓扑命名问题(TNP)。 本仓库的 op 树每次全量重算,本来就没有 TNP,所以借的是"按几何性质选元素"这个 表达力,而不是 TNP 的缓解机制。
确定性八闸(L0 → L3,外加闸 7 公差、闸 8 可制造性)+ 诊断回灌
每次生成都会过八道闸,结论在返回值的 gates 里逐条列出:
| 闸 | 层级 | 检查内容 |
|---|---|---|
| 1_schema | — | 契约词表 / 引用 / 锚点 / id 合法性。有 error 则不生成任何几何。 |
| 2_kinematics | — | 约束是否全部解出;每个子装配必须有 fixed 锚定件(否则绝对位置无定义);未参与约束而停在原点的零件。并入约束图分析:约束回代残差(过约束矛盾)、环秩(冗余约束)、无基准子装配。 |
| 3_geometry | — | 逐零件几何自检(空实体 / 非流形 / 无效体)+ 装配干涉(包围盒预筛 + 精确求交体积)。 |
| 4_design_calc | L1 选型 | 契约参数 vs KB 经验公式(轴径 / 扭矩 / 传动比自洽)。需求(L0 requirements)缺失时静默跳过,不编造。并入计算深度扩展:契约显式声明的 calculations 由 KB 公式库(kb/calc/*.yaml)求值——轴承寿命 / 螺栓预紧 / 弹簧 / 带传动 / 焊缝 / 动平衡 / 电机损耗 / 过盈传扭,加一种计算只加 YAML;缺输入记 missing_input 并点名缺哪个符号。 |
| 5_motion_sweep | L3 全行程扫掠 | 运动链按驱动器相位采样(默认 24 点、覆盖含传动比展开的完整周期),逐相位复算世界位姿后对候选对求交——静态检查照不到的"运动中咬死"(平衡重扫过活塞裙、连杆摆到极限撞缸体……)。 |
| 6_part_standards | L1b 零件标准 | 按 part.kind 从 KB part_standards/ 拉适用设计标准(强度 / 公差 / 重量 / 工艺 / 成本五维),逐项把契约值 / 几何反推值与公式要求比对。error 折入 ok(强度类),warning 不折(优化建议)。 |
| 7_tolerance | L1c 公差配合 | 孔轴配合(GB/T 1800.1 / ISO 286)极限间隙与过盈、过盈接触压力(厚壁筒 Lamé)、尺寸链闭环(极值法 / RSS)、GD&T 基准完整性、IT 精度工艺可达性。KB 未收录的代号/尺寸段记 skipped 不插值。 |
| 8_dfm | L1d 可制造性 | 特征规则(最小孔径 / 深径比 / 壁厚 / 孔壁 / 圆角 / 薄壁长厚比,门槛按工艺族取)+ 射线量测(真实几何量壁厚、孔壁,超预算如实跳过)+ 装配可达性(沿装入方向扫掠;声明 dfm.assembly_direction 才判错)。外购件不套自制工艺门槛。 |
ok=True 要求八闸全绿 —— 避免"看起来成功其实装错了"。
闸门覆盖率:ok=True 不等于"检过了"
闸 4/6/7/8 依赖契约显式声明(requirements / calculations / tolerances /
dfm.process),契约没写就会大面积跳过。诚实性要求"没输入就不编造结论",
但只做到一半 —— 还得告诉调用方我什么都没查,否则 LLM 拿到全绿会以为设计被全面检视过。
返回值因此带 gate_coverage,逐闸给出:
"gate_coverage": {
"gates": {
"8_dfm": {"checked": 42, "skipped": 8, "rate": 0.84,
"fixable_skipped": 4, // 补契约/补 KB 就能激活
"structural_skipped": 6, // 外购件,本就不该套自制门槛
"reference_skipped": 0, // KB-only 设计参考(不计入分母)
"status": "active", "top_reasons": ["三角面…超过预算…(1 条)"]}
},
"active_gates": ["6_part_standards", "7_tolerance", "8_dfm"],
"idle_gates": ["4_design_calc"]
}- 闸 1-3 是基础闸(有没有零件都跑,不会空转),这里只统计 4/6/7/8。
- 空转的闸会在
diagnostics里补一条 info 级提示(不折ok,输入缺失不是设计缺陷), 点名该补哪段契约。 - KB-only 参考条目不计入分母 —— 否则往 KB 里多写参考标准会让覆盖率不升反降,指标就没意义了。
- 只看
checks会低估 DFM:它只在不合格时写 checks,通过的留痕在evaluated。
实测(BLDC64 14 件 / 陀飞轮 20 件):闸 7 公差 100% / 81%,闸 8 DFM 84% / 71%, 闸 6 零件标准 4% / 14%(受零件类别覆盖度限制,见下)。
缓存诚实性:产物快照的指纹覆盖导出器 + 各闸分析模块 + 编排器自身源码 + 整个 KB 的内容哈希。 改一行 YAML 门槛、改一版判定逻辑,旧快照立刻失效 —— 否则会出现"升级后还在返回上一版体检报告"。
扫掠闸的两条诚实性约定:
- 包围盒预筛 + 预算上限(默认 20000 次精确求交);超预算如实报告
complete=false, 不假装查全。V6 发动机(31 件 / 17 运动件)全量实测约 1.2 万对,可完整查全。 - 采样用的关节变换与 Web 查看器逐分支一致(闭式解同源);判定用交集体积 (重叠 > 0.5 mm³ 记命中),共面 / 数值噪声不误报。
失败时返回 diagnostics:[{gate, level, where, what, hint}, …],每条 error 都带可操作的修改建议,
外加一句 next_step。LLM 直接照着改契约重跑即可闭环,不需要猜。
零件设计标准库(kb/part_standards/)
零件级检视(闸 6)的标准是数据不是代码:kb/part_standards/<类别>.yaml 按零件类别组织,
每条标准登记 dimension(strength / tolerance / weight / process / cost)、check(求值器注册名)、
inputs(数据来源映射)、params(KB 系数)、formula、source、hint。新增零件类别 = 加一个 YAML。
kinds 支持多值(一份标准覆盖语义相近的多个类别,如 panel 同时管 plate/cover/frame/bracket,
bolt 同时管 bolt/screw),不必为每个类别复制一份文件。
当前已登记 7 类 22 条(shaft / gear / bolt / bearing / housing / panel / …),材料强度表在 kb/materials.yaml。
设计期 LLM 可用 kb_search 按需检索(返回整条标准,含公式与来源),例如
kb_search("轴 扭转 强度")、kb_search("轴承 配合 公差");KB-only 标准(契约无对应可反推输入,
如公差配合推荐)在闸 6 里如实记 skipped 并引导检索——没有输入就不假装检视。
诊断 → 优化:不合格时给反解建议
光说"不合格"还不够 —— 设计者要的是"改成多少"。求值器在判定时会对同一个公式做代数反解 (不另立经验系数),把建议值一并写进结论:
{"check": "shaft.torsion_strength", "level": "error",
"msg": "lead_screw 的 shaft.torsion_strength:契约值 195.3MPa,公式值 1856MPa(低于 -89.5%)",
"fix": {"param": "diameter_mm", "value": 59.31, "current": 28.0, "note": "轴径"},
"hint": "… 按同一公式反解:轴径 建议取 59.31 mm(当前 28)"}τ = 16T/(πd³) ≤ [τ] 反解出 d ≥ (16T/(π[τ]))^(1/3) —— 已随轴扭转强度与板件跨厚比落地,
其余标准按同一模式补 fix 即可(是求值器内部的代数变形,不是新增一套数)。
一键安装(npm,推荐 ✅)
发布为 npm 包 text2cad-mcp,会自动检测 Python ≥ 3.10、在 ~/.text2cad/venv 建虚拟环境、
pip install 后端依赖(含默认几何内核 manifold3d),然后以 stdio 启动 MCP 服务。
客户端 Agent 无需手动配置任何 Python 环境。
{
"mcpServers": {
"text2cad": {
"command": "npx",
"args": ["-y", "text2cad-mcp"]
}
}
}也可全局安装后用命令名直接启动:
npm install -g text2cad-mcp,配置里写"command": "text2cad-mcp"。
可选环境变量(一般无需改):
| 变量 | 作用 | 默认 |
|---|---|---|
| TEXT2CAD_HOME | venv 缓存根(不影响产物位置) | ~/.text2cad |
| TEXT2CAD_OUTPUT_DIR | 产物输出目录 | 桌面/outputs/ |
| TEXT2CAD_KB_DIR | 领域知识库目录(默认用包内自带;指向外部目录可追加自有知识) | 包内 src/text2cad/kb |
| TEXT2CAD_TEMPLATE_DIR | 国标标注模板目录 | 包内 src/text2cad/templates/gb |
| TEXT2CAD_DXF_VERSION | 默认 DXF 版本 | R2007 |
| TEXT2CAD_BACKEND | 强制几何后端(manifold / cadquery) | 自动挑最强 |
输出组织(同名产品固定一个文件夹)
每次生成都会在输出根目录下确定一个 产品文件夹,该产品的全部产出都归入其中:
桌面/outputs/
├─ 6轴串联工业机器人手臂_20261011_211500/
│ ├─ <产品名>.dxf # 2D 多视图工程图(AutoCAD 2007+ 可开)
│ ├─ <产品名>_views.svg # 2D 预览
│ ├─ <产品名>.stl # 3D 网格
│ ├─ <产品名>_3d.html # 可交互 Web 查看器(含按运动原理联动)
│ ├─ <产品名>.step # 3D B-rep 实体(需 cadquery)
│ ├─ <产品名>_spec.json # 回写的契约(可追溯本次生成的确切输入)
│ ├─ parts/ # 逐零件 STEP(每件旁带 .fp 几何指纹)
│ └─ bom.json # 物料清单(含质量估算)
└─ …目录命名有两种模式,由 TEXT2CAD_DIR_MODE 控制:
| 模式 | 行为 | 适用 |
|---|---|---|
| stable(默认) | 同名产品始终复用同一目录并覆盖写 | 反复调参迭代——桌面上只有一份产物 |
| timestamp | 每次新建 名称_时间戳 目录 | 需要并排保留多个版本做对比 |
stable 模式下,上一轮有、这一轮没有的零件文件会被挪进 parts/.stale/(只挪不删),
不会被误当成本轮产物。
早期默认是
timestamp,导致一次调参就在桌面堆出七八个几乎一样的文件夹。 配合下节的零件级缓存,stable才让"改一处、只重算一处"真正成立。
桌面路径会被自动探测(含 OneDrive 重定向与中文「桌面」),探测不到时回退到用户主目录。
增量生成:零件级几何缓存
契约是纯函数 DesignSpec → 几何,没有持久化模型状态。这是刻意的取舍——单份 JSON
就是完整真相(可 diff / 可回滚 / 可并行 / 免疫拓扑命名漂移),代价是每次都要全量重算。
缓存补的正是这一刀:契约仍然无状态,但几何多了一层中间结果缓存。
三道闸门,输入越像上次就越省:
| 闸门 | 判据 | 命中时省掉 |
|---|---|---|
| 整单复用 | 契约逐字符相同(fast_key) | 一切,直接返回上次结果(毫秒级) |
| 产物复用 | 零件几何 + 位姿 + 图纸配置相同(art_key) | 全部建模 / 投影 / 导出 |
| 零件复用 | 单个零件的 op 树 + 后端相同(几何指纹) | 该零件的求值、三角化、STEP 导出 |
指纹是内容寻址的:SHA256(规范化 op 树 + 后端名),改参数指纹就变,旧条目自动失效。
三角化结果额外按网格容差建键;干涉结论按"零件指纹 + 世界位姿 + 允许接触集"建键,
且只缓存查全了的结论(提前早停的不缓存,免得把"没查完"固化成"没问题")。
整单/产物两级指纹还额外掺入一份导出器代码指纹(_exporter_fingerprint():把
assembly_viewer / project2d / glb_writer / shading / appearance 等产物写出模块的
源码内容哈希进去)。原因很实际:渲染管线一升级,同一份契约仍会命中旧快照、把上一版外观
原样返回——命中率好看,交出去的却是旧样子。这类变化不体现在零件几何里,只能靠代码指纹兜住。
存储分两级:进程内存(MCP 常驻,同进程重复请求零成本)+ 磁盘
(%LOCALAPPDATA%/text2cad/cache,跨进程有效,LRU 上限 TEXT2CAD_CACHE_MAX_MB,默认 4 GB)。
序列化由各后端自己负责——B-rep 走 OCCT 原生 BREP(实测往返体积位级一致),
网格后端走 npz 顶点/索引。读回后一律比对体积,往返不等价就当作没命中重新求值。
任何缓存异常都静默降级为未命中,绝不让缓存把生成搞失败。排查时可整体关闭:
TEXT2CAD_CACHE=0 # 关闭缓存
TEXT2CAD_DIR_MODE=timestamp # 回到每次新建目录
TEXT2CAD_ICE_MAX_PAIRS=800 # 放宽干涉精确求交的预算实测(2.0T V6 / 31 零件 / 201k 三角面 / cadquery 后端):
| 场景 | 改造前 | 改造后 | |---|---|---| | 契约没变,再跑一次(同进程) | ~66 s | 0.02 s | | 只改一个零件(皮带轮轮毂) | ~66 s | 35–38 s(30/31 件命中,STEP 全部沿用) | | 冷启动(空缓存) | ~66 s | 58–66 s(多出的部分是写缓存) | | 桌面 V6 文件夹 | 7 个 / 177 MB | 1 个(覆盖写) |
改一件仍要 35 秒,是因为总装图纸必须重投影:2D 消隐要拿全部 201k 三角面重新算 遮挡关系,这一步与"改了哪一件"无关。已把 5 个视图各自的顶点去重合并成一次 (24.0 s → 17.8 s,逐段位级一致);再往下就要动消隐算法本身了。
产物级增量:连图纸都不重画
几何缓存只省掉"建模",出图仍然是逐零件重算的——而一张零件图只依赖它自己的网格、 元数据、公差/表面要求与图框。所以这一层缓存的是文件字节,键是这些输入的内容指纹:
零件图(DXF/SVG):mesh 指纹 + 材料/图号/数量/工艺/注释 + 公差 + 表面 + 形位公差
+ 图框 + 质量档 + 绘图代码指纹
装配视图:视图配置 + 各零件 mesh 指纹 + 质量档 + 剖切 + 绘图代码指纹命中就直接写回上次的字节,跳过投影与 ezdxf 序列化;还原时逐文件校验 sha256, 校验不过即当作未命中(绝不用可疑字节冒充结果)。任何异常都静默降级为"没命中"。
实测(SS250 / 34 件 / manifold 后端,改一个曲柄臂半径):
| 场景 | 关掉增量复用 | 开启后 | |---|---|---| | 改一个零件参数 | 38.3 s | 22.2 s(1.73×) | | 只改公差表数值 | 13.1 s | 13.2 s,其中 2D 投影 5.4 s → 0.04 s | | 冷启动(空缓存) | — | 39.3 s |
关掉这一层做 A/B 或排查"是缓存还是算错了":
TEXT2CAD_INCREMENTAL=0 # 只关产物/视图缓存,几何缓存仍然生效为什么不做"按零件增量投影总装图":消隐是全局的——某条边看不看得见取决于 前面所有零件,拆不开。能增量的是单零件图纸(已经做了),不是总装投影。 总装视图只能靠"输入完全一致"整段复用,以及后续对消隐本身做向量化。
外部路径边界(沙箱)
render_preview / export_dwg / export_catpart / build_catproduct /
export_freecad_step / cae_export / plm_export / open_design_studio 都吃调用方
给的路径。单机自用无妨,一旦作为服务挂给别人用,等于把整台机器的读写权限交出去。
TEXT2CAD_ROOT="/data/cad;/mnt/share" # 多个根用系统路径分隔符- 未设置时完全不限制(保持原有行为,不给出图添堵);
- 设置后只允许访问这些根目录,
..穿越一并拦住,越界返回明确的诊断而不是裸报错; - 自己生成的产物目录(
<桌面>/outputs或TEXT2CAD_OUTPUT_DIR)始终允许读回。
依赖可复现(lock)
requirements.txt 里的 >= 只是下限,上游一次小版本升级就可能换掉行为。
requirements.lock 钉住实机验证过的完整依赖闭包(含传递依赖、带平台标记):
python scripts/gen_lock.py # 重新生成
python scripts/gen_lock.py --check # 校验 lock 与当前环境一致(CI 用)启动器首次安装时先按 lock 装依赖,再 pip install --no-deps 装本包——
避免每次安装都重新解析一遍 >=。lock 里只锁版本不锁 hash:跨平台 wheel 哈希不同,
一份带 --hash 的 lock 在别的平台会直接装不上(需要强校验请按平台分别生成)。
代码保护:哪些公开、哪些编译
发布包会把 src/ 原样带出去。python scripts/protect.py report 给出暴露面账本:
合计 67 个 .py,28129 行
其中受保护 26 个 / 13833 行 —— 编译后应从包里消失
明文保留 10 个 / 4528 行 —— 接口层
其余 31 个 / 9768 行 —— 导出桥/后端适配/studio 等分层原则是契约是接口、实现才是秘密:工具签名与 DesignSpec 保持明文(生态入口),
八闸规则、消隐算法、公差推理、质量特性积分等编译成 .pyd/.so。
pip install cython # 构建机需要 C 编译器
python scripts/protect.py build --marker "acme-2026-11"
npm pack build/protect # 发布受保护产物完整策略(含许可证选择、KB 数据处理、薄启动器方案、水印溯源)见 PROTECTION.md(仓库根目录,不随 npm 包发布)。
从 0.16.0 起本项目的许可证已由 MIT 改为专有(All Rights Reserved), 详见 LICENSE。0.16.0 之前的版本曾以 MIT 发布,其已分发副本仍受 MIT 约束——这一点无法追溯撤销,只能靠撤回旧版本 + 新版本专有化来止血。
长任务与可观测性
长任务:为什么不能同步等
build_from_spec 是同步工具。整机量级实测十几分钟,MCP 的请求-响应模型撑不住
——客户端会超时、会话会卡死。所以另有一条不阻塞的通道:
| 工具 | 作用 |
|---|---|
| submit_build(spec_json, ...) | 提交生成,立即返回 job_id |
| job_status(job_id) | 查 state / progress(0~1) / stage / elapsed_s;留空则列最近任务 |
| job_result(job_id, wait_seconds=0) | 取完整结果(与 build_from_spec 同构);wait_seconds>0 可阻塞等一会 |
| job_cancel(job_id) | 请求取消 |
submit_build(spec) → {"job_id": "a1b2c3d4", "state": "queued"}
job_status("a1b2c3d4") → {"state": "running", "progress": 0.52, "stage": "3d"}
job_result("a1b2c3d4") → 完整结果(ok / dxf_path / step_path / gates / diagnostics …)取消是协作式的,这一点必须说清楚:Python 无法从外部强杀线程,job_cancel 只置
标志,任务在下一个阶段边界(contract → geometry → mates → mesh → 3d → 2d → web
→ checks)才停。所以返回 cancelling 只代表"受理了",要再看 job_status 确认变成
cancelled。已在跑的单个阶段不会被打断。
build_from_spec 本身也升级了:它会按阶段发 notifications/progress(客户端需带
progressToken)。不支持进度通知的客户端,就用上面的轮询通道。
日志:默认安静,需要时看得见时间线
全部走 stderr(stdout 是 MCP 协议通道,写一个字节就破帧)。每次工具调用一个
run_id,结束时落一条汇总,含各阶段耗时、缓存命中、降级原因:
INFO [443f62c0] done tool=build_from_spec ok=True elapsed_ms=31690.7
steps: contract=1.5ms geometry=321.6ms mates=23.8ms mesh=1896.1ms 3d=2609.8ms 2d=6822.7ms web=2611.4ms checks=6901.1ms
counters: parts=18 instances=18 parts_built=0 parts_cached=18
degraded: fillet(后端不支持,已跳过)| 环境变量 | 默认 | 说明 |
|---|---|---|
| TEXT2CAD_LOG_LEVEL | INFO | DEBUG / INFO / WARNING / ERROR / OFF;DEBUG 会逐阶段打 |
| TEXT2CAD_LOG_FORMAT | text | json 输出一行一条,便于 grep / jq / 采集 |
| TEXT2CAD_LOG_FILE | 空 | 额外落盘路径 |
| TEXT2CAD_JOB_WORKERS | 1 | 并发执行的后台任务数(几何受 GIL 限制,默认 1 更可预测) |
| TEXT2CAD_JOB_KEEP | 50 | 保留的已结束任务数(内存上限) |
上面那组 steps 就是排查"到底慢在哪"的入口。实测热缓存下几何只要 0.3 秒,
真正的大头是 2D 投影和八闸校验(各占三成左右),这与"零件太多"无关。
快速开始(从源码开发)
以下仅面向本机开发/调试。终端用户请用上面的 npm 一键安装方式,无需接触源码。
python -m venv .venv && .venv\Scripts\activate # Windows
pip install -e . # 核心:mcp + ezdxf + pyyaml + manifold3d
python -m text2cad.server # 启动 MCP 服务
python tests/run_all.py # 零依赖跑全套测试(equiv/gates/e2e/smoke)
python -m pytest tests -q # 或走 pytesttests/test_mcp_smoke.py 用运行它的解释器启动 server,也可用环境变量 TEXT2CAD_PYTHON 指定。
MCP 客户端配置见 config.example.json(源码 / Python 方式)或 config.example.npm.json(npm 一键方式,推荐)。
Studio 可视化编辑页
对已有设计目录(含 params.json 与参数化生成器)起一个本地 Web 编辑页:
python run_studio.py designs/engine250 # 默认 8765 端口并自动开浏览器
python run_studio.py designs/ss250 --port 8766 --quality high- 参数手柄:拖动滑块 / 改数值 → 三视图与 3D 实时重算(内存试算,不写盘)
- 试算 / 保存分离:改动先落
dirty标记,点「保存到设计文件」才写回params.json;「确认生成」提交前会隐式保存,保证产物与参数一致 - 八闸复检:每次生成走完整八闸流水线,绿灯即交付
- 细节见
docs/STUDIO.md(含/api/patch、/api/save、/api/commit接口语义)
任意契约直接可编辑(propose_spec → open_spec_studio)
上面的编辑页要求先有 params.json 和手写生成器。这对成熟设计是对的——生成器里才装着
"缸径 → 活塞直径"这类派生关系;但对"刚出契约、想先让人确认参数"太重:还没确认就要求先写
一个生成器,等于把确认推到了成本之后。
所以推荐的工作流是先体检、再开页:
# 第 1 步:体检——不跑几何、不启服务,返回可确认性盘点 + 顺手备好编辑目录
propose_spec(spec_file="outputs/xxx/spec_xxx.json")
# → readiness(几何/运动/演示层/交互层/参数/公差/表面各块做没做)
# → ask(哪些是推断出来的、哪些没做需要人拍板)
# → deliverables(可选产物清单)
# 第 2 步:确认参数——把可调页面推到用户面前
open_spec_studio(spec_file="outputs/xxx/spec_xxx.json")
# 或本地:python run_studio.py outputs/xxx/spec_xxx.json参数表按契约的 parameters[].set(JSON 指针)自动生成;契约没写 parameters 时,
引擎会挖一层"共享尺寸"候选:同一个字段同值出现在 ≥2 个零件里(如六扇门共有的 h=2100),
多半就是一个可调尺寸。候选进 自动推断(共享尺寸) 分组,与设计方声明的参数分得开,
且只保证数值一致、不保证设计协调——门洞变宽时门扇不会自动跟着变。
三条硬边界:
- 只挖尺寸类字段(w/d/h/r/厚/径),不碰
translate.at坐标——改坐标能让零件错位 - 一次出现不算证据:只在单个零件出现的尺寸不会被挖
- 名字诚实:挖不出语义就写"共享高度 2100(6 处)",不编造"门扇高"
编辑页本身(_edit_<契约名>/,只读底稿 + params.json + 生成物,只听 127.0.0.1):
- 改一个尺寸秒级看到 2D + 3D:只重算受影响的零件(实测:电梯改"轿门高度"0.6 s 重建 11 件; 无刷电机改机壳外径 3.1 s)
affects自动派生:从指针路径直接说出"改这个会重建哪几件",不用手写影响范围- 回写是纯函数:改的是契约副本,底稿
spec_src.json永远不动,"重置"回得去 - 只改已经存在的数:指针取不到值就记 issue 跳过,不新建层级
- 产物勾选:确认生成时只出勾选的那几种(总装图/零件图/STEP/GLB/网页/BOM/CAE/PLM)
没写 parameters 且没有可推断的共享尺寸时,只剩运动量(关节行程 + 驱动转速)可调。
"能打开编辑页"不等于"有东西可调"——要让用户确认尺寸,就在契约里写 parameters,
写法见 docs/DESIGN_SPEC.md 第 18 节。
目录
仅供开发参考。终端用户通过 npm 安装后无需关心内部结构。
text2cad_mcp/
├─ bin/text2cad-mcp.js # npm 启动器(建 venv、装依赖、起服务)
├─ src/text2cad/
│ ├─ spec.py # DesignSpec 契约层(词汇表唯一真源 + 校验 + 自描述)
│ ├─ studio/ # 可视化编辑页(参数手柄 → 试算/保存分离 → 确认生成)
│ ├─ spec_pipeline.py # build_from_spec:契约 → 零件 → 装配 → 2D/3D/Web + 八闸 + 诊断
│ ├─ design_calc.py # 闸4 L1 选型计算(KB 经验公式对照,宁缺勿编)
│ ├─ calc_engine.py # 闸4 计算深度:KB 公式库求值器(安全 AST,零 eval)+ 单位换算
│ ├─ family.py # 参数化族:{{占位}} 展开 + 变型批量生成 + 系列对比表
│ ├─ plm_export.py # PLM/ERP:物料主数据 + 多级 BOM(项号/版本)+ ECN
│ ├─ cae.py # 质量特性(散度定理积分)+ Abaqus/Nastran 输入卡导出
│ ├─ part_review.py # 闸6 L1b 零件级设计标准检视(按 kind 分派 KB 标准)
│ ├─ tolerance.py # 闸7 公差与配合:孔轴配合/过盈接触压力/尺寸链闭环/形位基准
│ ├─ dfm.py # 闸8 可制造性:特征规则 + 射线量测壁厚/孔壁 + 装配可达性扫掠
│ ├─ constraint_graph.py # 闸2 补充:约束回代残差/环秩/自由度掩码/无基准子装配
│ ├─ sourcing.py # 可采购 BOM:物料编码 + 工艺路线 + 成本估算 + 装配树展开
│ ├─ agent_loop.py # design() 唯一入口(只编排,零品类知识)
│ ├─ geometry/
│ │ ├─ backend/ # 可插拔后端:base(协议+注册表)/ selector / common / manifold / cadquery
│ │ ├─ opgraph.py # 纯调度器:契约 op 树 → 后端实体,零内核细节
│ │ ├─ mates.py # 装配约束求解(相对位姿传播)
│ │ ├─ motion.py # 运动编译(source 传动链 / parent 串链)
│ │ ├─ sweep.py # 闸5 L3 全行程扫掠干涉
│ │ ├─ standard_parts.py # L2 标准件几何算子
│ │ ├─ cache.py # 内容寻址几何缓存(三级指纹 + 磁盘 LRU)
│ │ ├─ glb_writer.py # 零依赖 glTF 2.0 写出器(GLB)
│ │ ├─ shading.py # 折边角顶点劈分(硬边看起来是硬的)
│ │ └─ appearance.py # 按 part.kind 的 PBR 材质分档(GLB 与查看器同一份)
│ ├─ drawing/ # project2d(投影+消隐)/ assembly_viewer(Web 查看器)/ freecad_export(FCStd+TechDraw)/ autocad_export + catia_export(COM 桥接)
│ ├─ knowledge.py # 领域知识库(目录自动装载 + 通用树检索)
│ ├─ kb/ # 知识库 YAML(标准件 + 经验公式 + part_standards/ + tolerance/ 公差表 + dfm/ 工艺门槛 + calc/ 计算公式库 + manufacturing/ 单价费率 + plm/ 编码规则 + constraint/ + materials.yaml)——打包进 wheel
│ ├─ templates/gb/ # 国标标注模板(GB/T 131/1182/324)——打包进 wheel
│ ├─ models.py # 构件级模型(PartSpec / AssemblySpec)——只服务单零件场景
│ ├─ deps.py # 依赖自检 + 就绪等级
│ └─ server.py # MCP 工具入口(27 个工具)
├─ tests/ # 回归测试(fixtures/ 契约 + 24 个套件 + run_all.py)
├─ scripts/ # 开发辅助:gen_v6_engine.py(V6 参数化契约)/ run_v6_check.py(八闸复跑)
├─ run_studio.py # Studio 编辑页启动器(python run_studio.py <设计目录>)
└─ docs/DESIGN_SPEC.md # 契约规格(LLM 写契约的权威参考);docs/STUDIO.md 为编辑页说明依赖说明与自动自检
原则:有的直接用,缺的才提示一次。
| 依赖 | 用途 | 必需 |
|---|---|---|
| mcp / fastmcp | MCP 服务(stdio) | 核心 |
| ezdxf | 完整工程图 DXF(多视图/尺寸标注/中心线/标题栏) | 核心 |
| pyyaml | 知识库 YAML 读取 | 核心 |
| manifold3d | 默认几何内核(布尔/拉伸/旋转/扫掠/螺旋 + 流形自检) | 核心 |
| cadquery | 可选 B-rep 内核:STEP/IGES、圆角/倒角/抽壳/放样 | 可选 |
| pywin32 | COM 自动化:AutoCAD 桥接(DWG)+ CATIA 桥接(CATPart/CATProduct) | 可选 |
| pycatia | CATIA 高层 Python 封装(有 pywin32 就非必须) | 可选 |
| matplotlib | DXF → SVG/PNG 预览渲染 | 可选 |
| FreeCAD 1.0+(可执行文件,非 pip) | .FCStd(可继续编辑)+ TechDraw 工程图 | 可选 |
FreeCAD 不是 Python 包,pip install 装不到 —— 它是带自己 Qt/OCCT 的独立程序,只能当子进程调用。
所以它不会出现在 pip 安装命令里,缺失时给出的是一条手动安装说明 + TEXT2CAD_FREECAD_CMD 环境变量指引。
没装的影响见下面「FreeCAD 未安装时缺什么」。
调用 check_environment() 即可拿到各能力可用性、一次性安装命令与就绪等级:
FULL(B-rep + CATIA / AutoCAD 原生)/ B_REP / MESH_3D / 2D_ONLY / MINIMAL。
FULL 也会如实标注 FreeCAD 是否在位——等级高不等于每一条链路都在。
FreeCAD 未安装时缺什么
生成主流程完全不受影响。 这条链路在 deps.py 里是 core: False,不参与八闸,
spec_pipeline 一次都不 import 它。缺了就只有两样东西拿不到:
| 拿不到 | 会拿到什么替代 |
|---|---|
| .FCStd —— 能在 FreeCAD 里继续改特征、接着建模的活文档 | .STEP(B-rep 实体,可再导入任何 CAD,但没有特征历史) |
| TechDraw 工程图(fmt=dxf/svg) | 自带 2D 出图链路(ezdxf 多视图 DXF + SVG 预览),零件图不缺 |
调用 export_freecad_step() 会返回 {"ok": false, "note": <安装指引>},
不会静默产出一个空文件或假成功;check_freecad() 可提前问,避免生成完才发现。
3D 产出优先级
CATProduct(CATIA 装配,可编辑·最终目标)
↑ 自动由 CATPart 组装
CATPart(CATIA 零件,可编辑原生格式)
↑ 自动由 STEP 转换(CATIA COM)
STEP(B-rep 实体,CATIA/AutoCAD 通用首选交换格式)
↑ 由 cadquery 生成(pip install cadquery)
FCStd(FreeCAD 原生文档,**可继续编辑**,跨平台·免授权)
↑ 由 FreeCADCmd 无头子进程转换(装 FreeCAD 后可用)
GLB(glTF 2.0 二进制,**生态通用**:Blender/three.js/Windows 3D 查看器/Unity/网页)
↑ 零依赖自研导出器(默认可用)
STL(三角网格,网页预览 / MeshLab / 3D 打印)
↑ 由 manifold3d 生成(默认可用)依赖可用就一路产出到 CATProduct;缺依赖则结果里一次性给出 pip 命令,不阻塞已有产出。
GLB:对外的主交付格式(P1)
build_from_spec 每次都会在 _3d.html 旁再写一份 .glb(result.glb_path):
- 零依赖自研 glTF 2.0 写出器(
geometry/glb_writer.py),不装任何东西就有; - 同一份装配:GLB 约 1.9 MB vs STL 约 3.9 MB(顶点焊接 + uint32 索引 + 单 BIN 块), 且保留零件分组、名称、颜色与透明度 —— STL 一个颜色都没有;
- 根节点带 Z-up → Y-up 四元数,写进 Blender / three.js / Windows 3D 查看器不会躺倒;
- 自研紧凑格式仍保留在
_3d.html内部(那是给自家查看器用的,不是给外部生态的)。
三维页面:从"能转"到"能讲"(演示层 / 交互层)
_3d.html 不只是个能转的模型查看器。同样的外壳对任何产品都成立——它不认识"电梯",
只认识契约里的两段可选数据:
| 段 | 管什么 | 不写会怎样 |
|---|---|---|
| presentation | 标题/主题/机位/布景/引线标注/信息卡/读数/控件 | 按包络自动出四向机位、材料信息卡、通用开关——缺省即合格 |
| interaction | 状态量、动作、时序、关节绑定、点击行为 | 退回一条正弦驱动(老契约行为不变) |
三件事值得单独说:
- 机位用
fit而不是坐标。{"fit":["motor","sheave"]}表示"装下这几个零件", 距离由包围盒算,换产品不用改数字;pad调松紧、fov调透视强度。 要"站在轿厢里往外看"这种第一人称机位,用anchor:{part, pos:[0..1]}在零件盒里按比例取点。 手写pos/target只在fit确实表达不了时才用。 - 关节绑到状态量,不是绑到全局相位。
vars/actions/sequences/bindings才表达得了"先关门 → 走车 → 到站开门",以及"只有当前层的厅门开"(when条件)。move走梯形速度曲线,加减速与实时速度都能直接读数。 - 页面是单文件离线的。 three.js 随包内联成
data:模块,_3d.html双击即可打开, 不联网、不依赖同目录任何文件;断网环境下 Playwright 实测 ~2.6s 就绪。 开发时可设TEXT2CAD_THREE=cdn切回 CDN 省体积(此时不再离线)。
调页面不必反复重编译:页面上任何状态都能从控制台驱动——
__t2c.vars、__t2c.setView(id)、__t2c.fire(action)、__t2c.setToggle('ghost', true)。
URL 深链同样支持:?view=shaft&ghost=1¬es=1&go=to2。
观感:为什么棱是硬的(折边角劈分)
早期版本把"焊接顶点 + 邻接面法向求和"当成优化,结果机加工件的 90° 棱被抹成柔和渐变带, 零件看着像注塑外壳。V6 实测:气缸体 5265 条共享边里有 33.6% 是大于 30° 的真硬边, 61.7% 的顶点跨在 90° 棱上——全部被平均成了 45° 折中法向。
现在统一走 geometry/shading.py 的 split_by_crease():先焊接,再按折边角把顶点裂开。
| | 圆柱面内部(64 段,相邻 5.6°) | 90° 棱 | |---|---|---| | 处理 | 同一簇 → 保持光滑 | 裂成两个顶点,各带各的法向 | | 材质 | 法向连续过渡 | 高光断开,出现明确的明暗分界 |
代价:V6 全装配 202k 三角面,顶点数约 ×2.0,GLB 4.6 → 5.9 MB,HTML 2.3 → 2.8 MB。
crease_deg 在 15°~45° 区间结果几乎不变(需要劈的就是那批 90° 棱),不需要按模型调参。
同一套劈分同时用于 .glb 与 _3d.html:GLB 直接写劈分后的法向;HTML 把裂开的顶点
喂给 three.js,computeVertexNormals() 按拓扑平均后自然还原硬边——前端无需任何配合,
两处观感也不会不一致。
材质:按零件类型分档,而不是全局一套
全装配统一 metalness 0.55 / roughness 0.38 是"抛光件"的参数,套在铸件上就把砂型表面
的粗糙感抹平了。现在 assembly_viewer.KIND_MATERIAL 按 part.kind 给参数(单一真相在
Python 侧,算好后随 payload 传给前端):
| 分档 | 典型零件 | metalness / roughness | |---|---|---| | 铸件 | 缸体、缸盖、油底壳、进气/排气歧管 | 0.20–0.32 / 0.70–0.82(漫反射为主) | | 磨光钢 | 主轴颈、曲柄销、凸轮轴、气门 | 0.86–0.88 / 0.18–0.20(高光锐利) | | 铝加工件 | 活塞、带轮、法兰 | 0.66–0.76 / 0.32–0.44 | | 铜合金 | 轴瓦、十字滑环 | 0.70–0.80 / 0.36–0.44 | | 塑料罩盖 | 气门室罩、正时罩 | 0.06 / 0.82(几乎全漫反射) | | 未登记 | — | 0.40 / 0.50 |
kind 未标注时落 default,行为可预期。光照也相应调整:环境贴图强度压到 0.32(均匀
环境光会把每个面照得一样亮、显得"平"),主方向光提到 1.55 且刻意偏 +X 侧(跟相机同轴
打光会被模型自己挡住,阴影全看不见),启用 ACES 胶片曲线让高光滚降不过曝,并打开自阴影
——零件之间与凹槽内部的暗部是"有体积感"的关键。
这张表放在 geometry/appearance.py,GLB 写出口用的是同一份(写进
pbrMetallicRoughness.metallicFactor / roughnessFactor)。否则同一台装配在 _3d.html
里是铸件质感、在 Blender 里是抛光塑料,等于两份产物。
FreeCAD 桥接(P4):把"终态文件"变成"可续的起点"
STEP 是交换格式,.FCStd 才是能被人接着改的活文档。装了 FreeCAD 后:
export_freecad_step(step_path="out/bench_vise.step", fmt="all")
# → out/bench_vise.FCStd 用 FreeCAD 打开可继续加特征
# → out/bench_vise_techdraw.dxf 整页工程图(带图框 + 多视图排布)
# → out/bench_vise_techdraw_{front,top,right,iso}.svg 逐视图矢量图
check_freecad() # 先看本机有没有 FreeCADCmd(含版本、路径与格式能力)实现要点:走 FreeCADCmd 无头子进程(FreeCAD 自带内嵌 Python 与 Qt/OCCT,
在 MCP 进程里 import FreeCAD 几乎必炸);没装 FreeCAD 时只抛
FreeCADNotAvailable + 一次性安装说明,不影响 STEP/STL/GLB/DXF/HTML 任何链路。
headless 的真实能力边界(FreeCAD 1.1.4 实测,不是推测)
| 格式 | headless 可用 | 通道 |
|---|---|---|
| DXF | ✅ 最完整 | TechDraw.writeDXFPage(page, path) —— 整页、带图框、多视图排布 |
| SVG | ✅ 逐视图 | TechDraw.viewPartAsSvg(view) —— 返回字符串(不写文件),按视图落盘 |
| PDF | ❌ 不支持 | 只有 GUI 会话的 TechDrawGui.exportPageAsPdf;控制台会报 Cannot load Gui module |
要整页带图框的产出,用 DXF;PDF 需要桌面会话里跑 FreeCAD GUI。工具不会把不可用的格式伪装成成功,
每格式的失败明细都在返回值的 drawing_errors 里。
安装
Windows 下本模块会自动扫描 C:\Program Files\FreeCAD*\bin\FreeCADCmd.exe(按版本倒序),
# Windows:官方安装包落到 C:\Program Files\FreeCAD\
# 本模块会自动扫描 C:\Program Files\FreeCAD*\bin\FreeCADCmd.exe(版本倒序)
# 装在别处 / 便携版:设环境变量
set TEXT2CAD_FREECAD_CMD=C:\path\to\FreeCAD\bin\FreeCADCmd.exe三个只有真机才暴露、桩测试永远发现不了的问题(已在代码里修掉并留了注释):
- 脚本路径含中文 → FreeCADCmd 直接崩。中文用户名的机器上 Temp 天然是
C:\Users\<中文>\AppData\Local\Temp,脚本落在那儿会以Application unexpectedly terminated(exit=1、零输出)退出。 实测 cwd 含中文是安全的,只有 argv 里的脚本路径不行;本机 NTFS 也关了 8.3 短名(GetShortPathNameW原样返回)绕不过去 —— 所以脚本改落到C:\Users\Public\AppData\Local\Temp\text2cad。 - 子进程 stdout 是 GBK。FreeCAD 内嵌 Python 的输出跟着 Windows locale 走,
父进程按 UTF-8 解码会把返回的路径读成乱码,于是"图纸报错没生成"——可文件明明在磁盘上。
对策:
json.dumps(ensure_ascii=True),让这条回传对编码彻底免疫。 - 模板文件名全猜错。TechDraw 页面不挂模板会报
Template not set for Page; 而 1.1.4 实际给的是Default_Template_A4_Landscape.svg+ ISO/、ASME/ 两个子目录共 126 个模板,写死候选名单一个都命中不了 —— 改成递归扫描 + 偏好打分(A4 > A3 > A2、横向优先)。
耗时预期:小件约 4 秒;真实台钳(8 MB STEP、十余个零件)走完全套实测 264 秒,
所以默认超时设的是 300 秒,复杂装配请用 timeout= 继续放大。
⚠️ CATIA 打开 3D 的正确方式(重要)
STL 是网格(mesh),CATIA 不能用"文件 → 打开"直接吃它 —— 直接双击/打开会"毫无反应"或只进树不显示。
- 首选:用 STEP。
pip install cadquery后重跑,得到.step,在 CATIA 里文件 → 打开, 会作为实体零件直接进入"零件设计",可编辑、可出工程图。之后可用export_catpart(step_path)(需 pywin32 + 已装 CATIA,pycatia 可选)另存为.CATPart,或用build_catproduct(...)组装成.CATProduct。 - 用 STL(网格):在 CATIA 中
文件 → 新建 → 零件,进入 数字化外形编辑器(DSE) 或 快速曲面重建 工作台,再插入 → 导入 → STL,把网格导入为 Mesh 特征。
一句话:给 CATIA 用 STEP,不要直接"打开"STL。STL 更适合 FreeCAD / MeshLab / 3D 打印 / 网页预览。
DXF 版本(AutoCAD 兼容性)
默认导出 R2007(AC1021),可被 AutoCAD 2007 及以后打开(避免旧版 CAD 报
"图形版本中遇到未知值 AC1024")。
- 更老的 CAD / 通用查看器:
R2000(AC1015)或R12(AC1009,兼容性最强)。 - 更新特性:
R2010(AC1024,需 AutoCAD 2010+)。
设置方式:环境变量 TEXT2CAD_DXF_VERSION=R2007,或工具参数 dxf_version="R2007"。
零依赖降级后端(工程图层面)
若未安装 ezdxf,服务会自动使用零依赖后端产出可打开的简化 DXF(圆/矩形/线/文字 + 标题栏 + BOM);
装上 ezdxf 后同一链路自动切换为完整工程图(多视图 + 尺寸标注 + 中心线)。
构件级通用层用法
任意产品(多零件 + 装配 + 运动)请走契约通道;这层适合只需输出单个零件轮廓/实体的场合。
1) 2D:generate_part(sketch / plate / tube)
from text2cad.server import generate_part
from text2cad.models import PartSpec
import json
plate = PartSpec(name="带孔板", part_type="sketch", params={
"profile": {
"outer": {"shape": "rect", "w": 120, "h": 80},
"holes": [{"cx": 30, "cy": 40, "r": 10}, {"cx": 90, "cy": 40, "r": 10}],
}})
print(generate_part(json.dumps(plate.to_dict()))) # → {"ok": True, "dxf_path": "…", …}part_type="sketch" 的 profile 由以下基元任意拼成:
| 基元 | 字段 | 说明 |
|---|---|---|
| rect | w,h,x,y | 矩形(x,y 为左下角,默认 0,0) |
| circle | cx,cy,r | 圆 |
| line | points:[[x,y],[x,y]…] | 直线段(侧视图骨架、车架管等) |
| polyline | points:[[x,y]…] | 闭合/开放折线 |
| arc | cx,cy,r,a0,a1 | 圆弧 |
plate(带孔板)与tube(圆环)是 sketch 之上的便捷封装,内部同样走 sketch 基元。
2) 3D:generate_3d_part(extrude / revolve / tube → STEP/STL)
未装 cadquery 时走网格后端出 STL;装了则同时给出 STEP。
from text2cad.server import generate_3d_part
# 带中心孔的法兰
print(generate_3d_part(json.dumps({
"name": "法兰", "part_type": "sketch",
"params": {"operation": "extrude",
"profile": {"outer": {"shape": "circle", "r": 80},
"holes": [{"cx": 0, "cy": 0, "r": 20}]},
"depth": 15}}))) # → {"ok": True, "path": "法兰.step", …}
# 旋转成体(外径 40、内径 20、厚 10 的环)
print(generate_3d_part(json.dumps({
"name": "环", "part_type": "sketch",
"params": {"operation": "revolve",
"section": {"shape": "rect", "w": 10, "h": 10, "x": 25, "y": 0},
"angle": 360}})))车架/管线用 operation:"tube":{"a":[x,y,z],"b":[x,y,z],"r":半径}。
测试
tests/
├─ fixtures/ # 4 份 DesignSpec 契约(回归基线:台钳 / 自行车 / AK-47 / 机械臂)
├─ test_backend_equiv.py # 15 个几何不变量 × 每个后端 + 双后端逐点等价
├─ test_gates.py # 八闸反向用例:坏契约必须被拦下且诊断带 hint
├─ test_e2e_specs.py # 4 份契约跑 build_from_spec(断言 ok + 八闸 + 产物落盘)
├─ test_mcp_smoke.py # 真实 stdio 握手 + tools/list + 断言品类工具不在注册表
├─ test_glb_writer.py # P1:GLB 容器自洽 / Z-up 不被偷转 / 空件处理
├─ test_pick_feature.py # P3:特征拾取精度 + 诚实性,谓词按半径区分孔
├─ test_freecad_bridge.py # P4:桩跑通协议 + **装了 FreeCAD 就自动跑真机**(FCStd/DXF/SVG 校验)
├─ test_catia_bridge.py # P5:CATIA 桥双轨——桩守协议 + 装了 CATIA 就跑真机(CATPart/CATProduct)
├─ test_autocad_bridge.py # P5:AutoCAD 桥双轨——桩守协议 + 装了 AutoCAD 就跑真机(DXF→DWG)
├─ test_cache.py # 增量生成:三级指纹 / 磁盘缓存跨进程 / "契约没变"秒回
├─ test_deps_env.py # 环境自描述与降级记账
├─ test_design_calc.py # L1 选型计算:KB 公式对照 + 宁缺勿编(缺需求即跳过)
├─ test_part_review.py # L1b 零件标准检视:kind 分派 + KB-only 记 skipped
├─ test_standard_part_op.py # L2 标准件算子(轴承/螺栓/卡簧…)
├─ test_shading.py # 材质分档与棱边观感
├─ test_sweep.py # L3 扫掠:缓存失效 / 相位语义 / 让位集合 / 进程相关几何
├─ test_tolerance.py # 闸7 公差:数值表自洽(带宽=IT)/ ISO 286 手册对照 / 未收录记 skipped
├─ test_sourcing.py # 可采购 BOM:编码规则 / 自制外购判定 / 成本汇总 / 装配树
├─ test_dfm.py # 闸8 可制造性:工艺族门槛 / 射线量测 / 可达性(BLDC 三条真实问题)
├─ test_cae.py # 质量特性解析解 + Abaqus/Nastran 卡结构
├─ test_calc.py # KB 计算引擎:整库自洽扫描 / 求值沙箱 / 8 族数值 / 诚实边界
├─ test_family.py # 参数化族:占位替换 / 变型隔离 / 对比表不美化
├─ test_plm.py # PLM 导出:一物一码 / 多级 BOM 项号 / ECN 差异
├─ test_constraint.py # 约束图:过约束矛盾(回代残差)/ 环秩 / 无基准子装配 / 自由度
└─ run_all.py # 零依赖总入口(20 套件)python tests/run_all.py # 全部
python tests/run_all.py gates # 只跑八闸反向用例
python tests/run_all.py pick # 只跑特征拾取(含"宁缺勿编"反向用例)
python tests/run_all.py freecad # P4 桥接(没装 FreeCAD 时真机那条自动跳过)
python tests/run_all.py catia # P5 CATIA 桥(没装 CATIA 时真机那条自动跳过)
python tests/run_all.py autocad # P5 AutoCAD 桥(没装 AutoCAD 时真机那条自动跳过)
python tests/run_all.py tolerance # 只跑闸7 公差配合
python tests/run_all.py dfm # 只跑闸8 可制造性
python tests/run_all.py sourcing # 只跑可采购 BOM
python tests/run_all.py constraint # 只跑约束图分析freecad / catia / autocad 三个桥接套件的策略都是双轨:桩用例守协议、
本机装了对应 CAD 就自动追加真机用例(真实 STEP → FCStd + DXF/SVG 校验、
STEP → CATPart/CATProduct、DXF → DWG 重开验证)。
这样既不会因为"环境没装"而漏掉回归,也不会因为"只有桩"而对真机行为产生虚假信心。
测试可零依赖运行(不要求 pytest);装了 pytest 也可 python -m pytest tests -q。
双后端逐点等价只在同一解释器同时装了
manifold3d与cadquery时才会执行, 否则该节自动跳过(套件会打印"只装了一个后端")。要复现完整对照:pip install manifold3d cadquery后重跑python tests/run_all.py。
状态
- 契约通道 ✅ 契约词表 + 环境自描述 →
build_from_spec产出 2D/3D/Web + BOM/质量/干涉报告,内核零 per-product 分支。 - 可插拔几何后端 ✅ manifold(默认)/ cadquery,双后端逐点等价有测试守住。
- 能力协商 ✅ 软算子降级并记账、硬算子明确报错,
capability_gaps随环境自描述。 - 确定性八闸 + 诊断回灌 ✅
gates逐条结论 +diagnostics[{gate,level,where,what,hint}]。 - 闸 7 公差与配合 ✅ 契约新增
tolerances段(fits孔轴配合 /chains尺寸链 /gdnt形位公差),把原本只能写进note文本的 H7/m6、气隙 ±0.05、同轴度 Ø0.03 变成可计算字段:极限间隙过盈、过盈接触压力(厚壁筒解)、尺寸链极值法/RSS 闭环、 基准完整性、工艺经济精度可达性。数值取自kb/tolerance/limits.yaml(GB/T 1800.1 节选),未收录的公差带/尺寸段记 skipped,不插值不外推; 全表有"带宽 == IT 等级标准公差"的自洽性测试(164 个带)防抄错。 - L0 → L3 工程深度 ✅ L1 选型计算(KB 经验公式对照,缺需求静默跳过)、
L1b 零件级设计标准检视(5 类 16 条,KB-only 标准记 skipped 并引导
kb_search)、 L1c 公差配合(见上)、L1d 可制造性(闸 8)、L2 标准件算子、 L3 全行程扫掠干涉(与查看器同源的闭式解,超预算如实complete=false)。 V6 发动机(31 件 / 17 运动件,scripts/gen_v6_engine.py参数化生成)八闸全绿: 扫掠 24 相位 × 11832 次精确求交 0 命中、完整查全。 - 闸 8 可制造性(DFM) ✅ 规则是数据(
kb/dfm/rules.yaml,加规则/改门槛只改 YAML): 最小孔径、深径比、壁厚、孔壁、圆角、薄壁长厚比,门槛按工艺族(machining / casting / injection / sheet / spring)取,未登记族记 skipped 不套别的族;射线采样从真实几何 量最小壁厚与孔壁(不是读契约声明),超三角面/射线预算如实跳过并写明哪些件没量; 装配可达性沿装入方向扫掠(默认方向仅供参考,声明dfm.assembly_direction才判错), 并单列名义几何干涉(过盈配合请走tolerances.fits)。外购件不套自制工艺门槛。 - 约束图分析(闸 2 补充) ✅ mate 图连通分量 / 环秩 / 自由度掩码并集 / 逐条约束回代残差
—— 抓"位姿解得出来但图纸自相矛盾"(后写的约束把先写的顶掉);
angle类需初始姿态, 如实记 skipped;无基准子装配单独报出(否则整个子装配可整体漂移)。 - 计算深度扩展(闸 4) ✅ 工程公式是数据不是代码(
kb/calc/*.yaml):轴承寿命 L10/L10h、 螺栓预紧与拧紧等效应力、弹簧刚度与 Wahl 修正切应力、带传动几何、角焊缝、动平衡许用 不平衡量、电机电流/损耗自洽/温升、过盈接触压力与可传扭矩。求值器不用 eval(AST 白名单: 属性/下标/lambda/导入/超长全拒),单位按 KB 别名换算(kN/GPa/r·min⁻¹/g·mm…,未登记不猜); 缺必需输入 →missing_input点名符号,取 KB 默认值 →skipped留痕,依赖可选参数的判据 → 单独记 skipped 不装作查过。整库有自洽性扫描(符号齐备 / 表达式可解析 / 消息占位可 格式化 / id 唯一)与跨模块一致性断言(过盈压力必须等于闸 7 的 Lamé 解)。 - PLM / ERP 导出 ✅
plm_export出导入格式而不是报告:物料主数据(一物一码, 装配体虚拟件也有码,MAKE/BUY/ASSEMBLY + 类别码 + 单位 + 图号 + 工艺路线 + 单重 + 版本)、多级 BOM(父项/子项/数量/层级/项号 1·2.1/生效版本,根节点是产品)、 CSV(UTF-8-BOM,Excel/ERP 直接吃)+ JSON 双份。plm_ecn对两版契约出工程变更单 (新增/删除/改材料/改图号/改数量/换父项 MOVE,比对键是物料号不是零件 id)。 诚实边界:自制件缺图号留空并告警(图号是图档系统的键,编了会打架;外购标准件靠 型号识别,没有图号是常态、不告警)、缺料缺价留空不编、 物料号撞车直接 ok=false(同码不同物进系统会把两件合成一条库存);不做 XLSX (不为导出引入 openpyxl,CSV 另存即可)。契约bom段声明的数量优先于"一零件一实例" 默认值(14 片磁钢不会在 ERP 里变成 1 片)。 - 零件类别(
kind)语义与编码归一 ✅kind是零件身份而不是材质标签:一句话决定 物料号类别码、序号分组、第 6 闸标准检索、配色与 PBR。kb/plm/coding.yaml的kind_aliases归一复数 / 连字符 / 大小写 / 行业同义(magnets→magnet、stator-core→stator、endcap→cover、hex_bolt→bolt),不做语义外推(metal、part这类含糊写法照样落99,逼着人补契约);序号按归一后的 kind 分组,否则endcap与cover各排各的序号会撞出同一个物料号。未登记 kind 在契约校验(warning, 点名后果)与第 6 闸(skipped说明"该类别无标准可拉")两处都喊出来 —— 静默跳过会 让整台机器"看起来全过闸",实际一条标准都没检视(BLDC 真实踩过:14 件 kind 全写metal,物料号全落99且第 6 闸零覆盖)。 - 参数化族 / 系列化变型 ✅ 契约里写
"{{表达式}}"占位(与计算引擎同一安全求值器, 可写{{bore/2}}/{{max(bore,40)}}),family_build按参数矩阵展开成一组变型, 逐变型真的跑一遍流水线(闸门 / 质量 / 成本)并给出对比表与参数变化区间; 变型产物落在产品名-<变型id>目录互不覆盖。占位没解析出来 → 该变型失败并写明缺哪个 符号(绝不把"{{bore}}"当字符串交给几何层);跑不通的变型照样占一行,表不美化。 - 可采购 BOM ✅ 物料编码规则引擎(
kb/plm/coding.yaml)、自制/外购判定(显式 > 标准号 > kind)、 工艺路线与费率(kb/manufacturing/prices.yaml)、装配树展开、成本汇总; 未收录材料/工艺记 warning 而非编价。 - 运动系统 ✅
revolute(含oscillate)/prismatic/orbit/slider_crank/screw;source传动比链、parent结构串链。 - 构件级通用层 ✅
generate_part/generate_3d_part/validate/solve_assembly_layout。 - CATIA 原生桥接 ✅ 真机验证(CATIA V5R21):单实体 STEP →
.CATPart; 装配 STEP 直达.CATProduct(各组件位姿随 STEP 保留,positioning="step"); 多零件路径逐件 CATPart + 恒等位姿(如实标positioning="identity")。 关键实证已固化进代码注释:必须纯动态 Dispatch、DisplayFileAlerts=False、 先关同名文档、V5R21 无AddExistingComponent、冷启动 30 s 不能 gate 探测。 - AutoCAD 原生桥接 ✅ 真机验证(AutoCAD 2007):
export_dwg把 DXF 另存为 原生 DWG(R2007,magicAC1021,落盘后校验,中文路径正常)。关键实证: 必须用带版本号 ProgID(AutoCAD.Application.17,无后缀的报"无效的类字符串");SaveAs(路径)不能传格式参数——旧桩的SaveAs(path, 1)会把 DXF 文本 写进 .dwg(1 = acR13_dxf);目标 DWG 被已打开文档占用时 SaveAs 会被拒(点名占用者)。 能力边界诚实说明:2007 的 IMPORT 不支持 STEP(2013+ 才有), 3D 实体请走 CATIA / FreeCAD 桥,本桥只做 2D DXF→DWG。 - FreeCAD 桥接 ✅ 真机验证(FreeCAD 1.1.4):STEP →
.FCStd(可继续编辑)+ TechDraw 工程图; headless 下 DXF 是最完整的整页通道、SVG 按视图导出、PDF 无通道(如实记账,不伪装成功)。 - 增量生成 ✅ 三道指纹闸门(整单 / 产物 / 零件)+ 内容寻址几何缓存 +
stable固定目录; V6 实测"契约没变再跑一次"从 66 s 降到 0.02 s,"只改一件"降到 35 s,桌面文件夹从 7 个收敛到 1 个。 - 干涉检查完整性 ✅ 修复早停
break只跳内层的缺陷;现在会报告candidate_pairs/checked_pairs/complete,早停时明确写出还剩多少对没判定。
许可
专有软件,保留所有权利(Proprietary — All Rights Reserved)。
全文见 LICENSE。要点:
| 允许 | 禁止 | |---|---| | 在你自己的主体内部安装并运行(含商业/生产用途) | 复制、再分发、再许可、出售、出租 | | 用它的输出(图纸/模型/BOM/报告)做任何合法用途,输出归你 | 修改、改编、创作衍生作品 | | 为备份而保留副本 | 逆向工程、反编译、反汇编 | | — | 以 SaaS / API / 内嵌组件形式提供给第三方 | | — | 删除或篡改版权声明与水印 | | — | 用它开发、训练或改进竞品 |
需要再分发、OEM、SaaS 或源码访问,请单独洽谈商业授权(eiry16 @ npm / GitHub)。
关于历史版本:0.15.0 及更早版本曾以 MIT 发布。MIT 的授权一旦给出, 对已经拿到副本的人不可撤销——撤回 registry 上的版本只能阻止新的获取, 不能追回已经下载的副本。这是许可证本身的语义,不是执行疏漏。
工程输出在投入制造前必须由具备资质的工程师复核;本软件不附带任何担保, 详见 LICENSE 第 6、7 条。
