☰
text-to-cad实战:从自然语言到STEP/URDF的几何生成与Agent搭建
2026/10/8 5:39:53 网站建设 项目流程

1. 从一句话到三维实体:text-to-cad 到底在解决什么问题

第一次听到 "text-to-cad" 这个词,很多人脑子里浮现的画面大概是:对着电脑说一句"给我画个法兰盘",屏幕上就自动长出一个带螺栓孔的三维模型。这个想象不算离谱,但真正落地到工程实践里,它要解决的问题比"语音画图"要具体得多,也有意思得多。

text-to-cad 的核心命题,是把自然语言描述转换成结构化的 CAD 几何数据。这里的"CAD 几何数据"不是一张截图,也不是一段渲染视频,而是能被下游工具真正读取、编辑、装配、仿真的东西——比如 STEP 文件、URDF 描述、参数化脚本。换句话说,它要产出的是"可计算的几何",而不是"看起来像几何的图片"。

这件事为什么值得做?因为传统 CAD 工作流里,从"想法"到"模型"之间横着一道很高的门槛。你得会软件操作、懂建模逻辑、熟悉约束关系,一个简单的支架可能就要画半小时。而在很多场景下——比如机器人仿真、批量零件生成、教学演示、快速原型验证——人们需要的往往不是精雕细琢的工业级模型,而是一个结构正确、尺寸合理、能直接导入下游工具的基础几何体。text-to-cad 瞄准的正是这个空档。

从热搜词也能看出这个方向的真实需求分布:一边是CAD、STEP、URDF、agent这些偏技术侧的关键词,另一边是cad制图初学入门、cad安装教程、cad转pdf这类偏使用侧的长尾词。这说明关注 text-to-cad 的人群其实分两类:一类是想用 AI 自动生成模型的开发者,另一类是被 CAD 操作门槛卡住的普通用户。这两类人的诉求不一样,但都指向同一个痛点——建模这件事,能不能更省事一点。

这篇文章会围绕 text-to-cad 的完整链路展开:它背后的技术构成是什么、为什么输出格式要选 STEP 和 URDF、一个可用的 agent 该怎么搭、实际跑起来会遇到哪些坑、以及怎么把它接到机器人仿真这类真实场景里。内容会尽量往实操靠,能抄的配置和代码我都会给出来,同时把"为什么这么做"讲清楚,避免只给步骤不给理由。

提示:text-to-cad 目前还不是"一句话生成工业级零件"的成熟方案,它更擅长生成结构清晰、参数明确的基础几何体。把它当成"建模加速器"而不是"建模替代品",预期会更合理。

2. 拆解 text-to-cad 的技术链路:从文本到几何要过几道关

2.1 文本理解层:把口语描述翻译成结构化参数

用户输入的自然语言往往是模糊的。比如"做一个 100 毫米长、带四个孔的板子",这句话里隐含了大量没说的信息:板子多宽?多厚?孔多大?孔在什么位置?孔是通孔还是沉头孔?

所以 text-to-cad 的第一步不是画图,而是参数抽取与补全。常见做法是让大语言模型把自然语言解析成一个结构化的 JSON 或字典,字段包括几何类型、尺寸参数、特征列表。举个实际会用的中间表示:

{ "shape": "plate", "length": 100, "width": 60, "thickness": 5, "features": [ {"type": "hole", "diameter": 6, "count": 4, "pattern": "corner", "offset": 10} ], "unit": "mm" }

这个中间层非常关键。它把"人话"和"几何内核"解耦了——语言模型只负责理解意图,几何生成只负责按参数建模。这样做的好处是:当用户说"孔再大一点"时,你只需要改diameter字段,而不用重新训练整个模型。

这里有个经验:不要让语言模型直接输出 CAD 脚本代码。早期我试过让模型直接生成 CadQuery 或 OpenSCAD 代码,结果经常出现语法错误、变量未定义、单位混乱的问题,调试成本极高。改成"先出 JSON,再由确定性代码生成几何"之后,稳定性提升非常明显。语言模型擅长的是理解意图,不是写严谨的几何代码,把这两件事分开,各司其职。

2.2 几何生成层:参数化建模内核的选择

拿到结构化参数后,就需要一个几何内核把它变成真正的三维实体。目前主流的几条路线:

方案特点适合场景
CadQueryPython 原生,基于 OCCT,代码即模型参数化零件、批量生成
OpenSCAD脚本化,语法简单,社区大教学、简单几何、快速验证
build123dCadQuery 的继任者,API 更现代新项目、复杂特征
FreeCAD 脚本功能全,可调用完整工作台需要工程图、装配的场景

我个人的选择是CadQuery 或 build123d,原因是它们底层都跑在 OpenCASCADE(OCCT)上,而 OCCT 是工业界公认的几何内核,导出的 STEP 文件兼容性最好。OpenSCAD 虽然上手快,但它本质上是网格建模(CSG),导出的模型在需要精确 B-rep 的场合会吃亏。

用 CadQuery 生成上面那块带孔板子,代码大概是这样:

import cadquery as cq result = ( cq.Workplane("XY") .box(100, 60, 5) .faces(">Z") .workplane() .rect(80, 40, forConstruction=True) .vertices() .hole(6) ) cq.exporters.export(result, "plate.step")

这段代码的逻辑很直白:先建一个 100×60×5 的长方体,选中顶面,在顶面上画一个 80×40 的构造矩形,取它的四个顶点打孔。整个过程是确定性的,只要参数对,结果就一定对。

2.3 格式输出层:为什么 STEP 和 URDF 是绕不开的两个格式

生成几何只是中间产物,真正交付给下游的格式才是决定这套流程能不能用起来的关键。热搜词里STEP和URDF同时出现,其实点出了两个完全不同的下游场景。

STEP是 CAD 领域的通用交换格式,几乎所有的机械设计软件都能读。它的优势是保留精确的 B-rep 几何信息,尺寸、圆角、孔位都不会丢。如果你生成的模型要拿去加工、出图、做装配,STEP 是首选。

URDF则是机器人领域的描述格式,它描述的不是"一个零件长什么样",而是"一个机器人由哪些连杆和关节组成、它们怎么连接、各自的惯性参数是多少"。URDF 里引用的几何体通常是 STL 或 DAE 网格,而不是 STEP。

这就带来一个实际问题:text-to-cad 生成的模型,怎么变成 URDF 能用的东西?答案是中间要做一次格式转换。STEP 转 STL 可以用 CadQuery 或 FreeCAD 的命令行工具完成:

# STEP 转 STL import cadquery as cq shape = cq.importers.importStep("plate.step") cq.exporters.export(shape, "plate.stl", tolerance=0.01)

tolerance参数控制网格精度,值越小网格越细、文件越大。做仿真时一般取 0.01 到 0.001 之间,太粗会导致碰撞检测不准,太细会让仿真变慢。

2.4 Agent 编排层:把上面三层串成一个能对话的系统

单次生成不难,难的是让它变成一个能连续对话、能改参数、能记住上下文的 agent。热搜词里agent、agent架构、agent记忆、agent tool反复出现,说明大家真正关心的是怎么把这套流程工程化。

一个最小可用的 text-to-cad agent 通常包含这几个部分:

  • 意图解析工具:调用语言模型,把用户输入转成结构化参数
  • 几何生成工具:接收参数,调用 CadQuery 生成模型
  • 格式转换工具:按需导出 STEP / STL / URDF
  • 记忆模块:保存当前模型的参数状态,支持"把长度改成 120"这类增量修改
  • 校验工具:检查生成结果是否合法(比如孔是否超出板面)

记忆模块是很多人会忽略的一环。没有它,用户每次说"改一下"都得重新描述整个模型,体验很差。实现上不需要多复杂,一个字典存当前参数就够了,关键是每次修改后要更新这个字典。

3. 手把手搭一个能跑通的 text-to-cad 最小系统

3.1 环境准备:依赖装不对,后面全是坑

先把环境搭起来。我推荐用 Python 3.10 或 3.11,太新的版本有时候 OCCT 的 wheel 还没跟上。

python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install cadquery pip install openai # 或你用的其他模型 SDK

CadQuery 的安装是第一个坎。它依赖 OCCT,在部分系统上需要预编译的二进制包。如果pip install cadquery报错,优先检查是不是 Python 版本太新或太旧。实测 3.10 和 3.11 最稳。

注意:不要试图从源码编译 OCCT,除非你有充足的时间和编译经验。直接用官方 wheel 是最省事的路。

3.2 意图解析:给语言模型一个明确的输出契约

这一步的目标是让模型稳定输出 JSON。关键在于 prompt 里要把 schema 写死,并给出示例。我常用的模板大致是这样:

SYSTEM_PROMPT = """你是一个 CAD 参数解析器。 用户会用自然语言描述一个零件,你需要输出 JSON。 JSON 必须包含以下字段: - shape: 几何类型,可选 plate / cylinder / box / bracket - dimensions: 尺寸字典,单位统一为 mm - features: 特征列表,每个特征包含 type 和参数 只输出 JSON,不要输出任何解释。如果用户没提到某个尺寸,用合理默认值补全。 """

这里有个细节:默认值的选择会直接影响可用性。比如用户说"做个板子"没说厚度,你默认 5mm 还是 10mm?我一般按常见工程习惯给:板厚 5mm、孔径 6mm、边距 10mm。这些值不一定对,但至少能生成一个"看起来合理"的模型,用户可以在此基础上改。

解析完拿到 JSON 后,一定要做一次字段校验。语言模型偶尔会漏字段或给错类型,比如把count写成字符串"4"。加一层校验能避免后面几何生成时崩溃:

def validate_params(params): required = ["shape", "dimensions"] for key in required: if key not in params: raise ValueError(f"缺少必要字段: {key}") # 数值字段强制转 float for k, v in params["dimensions"].items(): params["dimensions"][k] = float(v) return params

3.3 几何生成:把参数映射到 CadQuery 调用

有了干净参数,生成几何就是纯工程活了。核心思路是写一个分发函数,根据shape字段调用不同的建模逻辑:

def build_model(params): shape = params["shape"] dims = params["dimensions"] if shape == "plate": model = cq.Workplane("XY").box( dims["length"], dims["width"], dims["thickness"] ) # 处理孔特征 for feat in params.get("features", []): if feat["type"] == "hole": model = model.faces(">Z").workplane().rect( dims["length"] - 2 * feat["offset"], dims["width"] - 2 * feat["offset"], forConstruction=True ).vertices().hole(feat["diameter"]) return model elif shape == "cylinder": return cq.Workplane("XY").circle(dims["radius"]).extrude(dims["height"]) else: raise ValueError(f"不支持的形状: {shape}")

这段代码看起来简单,但里面藏着几个容易踩的坑。第一,faces(">Z")选的是 Z 方向最高的面,如果模型方向不对,选面会失败。第二,打孔时构造矩形的尺寸要减去边距,否则孔会跑到板子外面。第三,多个特征叠加时顺序很重要,先打孔再倒角,和先倒角再打孔,结果可能不一样。

3.4 导出与验证:生成完不算完,得确认它真的能用

模型生成后,导出只是第一步,验证才是保证质量的关键。我一般会做三层检查:

  1. 几何合法性:用 CadQuery 的isValid()检查实体是否有效
  2. 尺寸合理性:检查包围盒尺寸是否和输入参数一致
  3. 下游兼容性:把导出的 STEP 重新导入一次,确认没有丢失特征
def export_and_verify(model, path): if not model.val().isValid(): raise ValueError("生成的几何体无效") bb = model.val().BoundingBox() print(f"包围盒: {bb.xlen:.2f} x {bb.ylen:.2f} x {bb.zlen:.2f}") cq.exporters.export(model, path) # 回读验证 reimported = cq.importers.importStep(path) print("回读成功,实体数量:", len(reimported.val().Solids()))

回读这一步很多人会跳过,但它能抓到不少问题。比如某些复杂特征在导出时可能被简化,回读后实体数量对不上,就说明有问题。

4. 把生成的模型接进机器人仿真:URDF 转换的实操细节

4.1 URDF 到底需要什么:不只是几何

很多人以为 URDF 就是"把模型文件塞进去",其实远不止。URDF 描述的是一个运动学树,它需要:

  • link(连杆):每个刚体,包含视觉几何、碰撞几何、惯性参数
  • joint(关节):连接两个 link,定义运动类型(旋转/平移)、轴向、限位
  • 坐标系关系:每个 link 的原点在哪,joint 的轴朝哪

text-to-cad 生成的通常只是单个零件的几何,要变成 URDF,你得手动或自动补上关节和惯性信息。惯性参数尤其容易被忽略,但它在动力学仿真里至关重要。一个简单的长方体,惯性张量可以按公式算:

def box_inertia(mass, x, y, z): # 长方体绕质心的惯性张量对角项 Ixx = mass * (y**2 + z**2) / 12 Iyy = mass * (x**2 + z**2) / 12 Izz = mass * (x**2 + y**2) / 12 return Ixx, Iyy, Izz

如果惯性参数随便填,仿真里机器人可能会抖得厉害,或者关节力矩异常。这是我在做仿真时踩过的真实坑——模型看着没问题,一跑动力学就发散,最后发现是惯性张量填错了。

4.2 STEP 转 STL 再进 URDF 的完整流程

URDF 里引用的几何一般是 STL 或 DAE。完整流程是:

# 1. 生成 STEP model = build_model(params) cq.exporters.export(model, "part.step") # 2. 转 STL,注意精度 shape = cq.importers.importStep("part.step") cq.exporters.export(shape, "part.stl", tolerance=0.001, angularTolerance=0.1) # 3. 写 URDF urdf_template = """<?xml version="1.0"?> <robot name="generated"> <link name="base_link"> <visual> <geometry> <mesh filename="part.stl"/> </geometry> </visual> <collision> <geometry> <mesh filename="part.stl"/> </geometry> </collision> <inertial> <mass value="{mass}"/> <inertia ixx="{ixx}" ixy="0" ixz="0" iyy="{iyy}" iyz="0" izz="{izz}"/> </inertial> </link> </robot> """

tolerance和angularTolerance这两个参数要一起调。只调tolerance的话,曲面上的三角形可能还是很大。实测tolerance=0.001、angularTolerance=0.1是个不错的平衡点,既能保证曲面光滑,文件又不会太大。

4.3 导入仿真环境时的常见问题

模型进仿真环境后,最常见的问题有三个:

第一个是单位问题。CAD 里默认是毫米,但很多仿真环境默认用米。一个 100mm 的板子直接导进去会变成 100 米,机器人瞬间变成巨型建筑。解决办法是在导出 STL 时统一缩放,或者在 URDF 里加scale属性:

<mesh filename="part.stl" scale="0.001 0.001 0.001"/>

第二个是坐标系原点问题。CadQuery 生成的模型原点通常在几何中心或某个角点,但 URDF 里 link 的原点决定了旋转中心。如果原点不对,关节转起来会绕着奇怪的位置转。建议在建模时就明确原点位置,或者在 URDF 里用<origin>标签调整。

第三个是碰撞几何太复杂。直接用视觉网格做碰撞检测,计算量会很大。实践中通常用简化几何(比如包围盒、圆柱)做碰撞体,视觉用精细网格。这个在 URDF 里就是<collision>和<visual>分开写。

5. 实测中那些文档不会告诉你的坑

5.1 语言模型的"想当然":参数幻觉怎么防

语言模型有个毛病:你问它要参数,它会给,但给的不一定对。比如你说"做个能装下手机的盒子",它可能给你一个 150×80×10 的板子——尺寸看着合理,但完全不是盒子。这种"参数幻觉"在 text-to-cad 里特别常见。

我的应对办法是加一层语义校验。生成参数后,用规则检查关键约束。比如盒子必须有六个面或者至少是个封闭体,板子的厚度不能大于长度。这些规则不复杂,但能挡掉大部分离谱结果:

def semantic_check(params): dims = params["dimensions"] if params["shape"] == "plate": if dims["thickness"] > dims["length"] / 2: raise ValueError("板厚不合理,可能参数解析错误") if params["shape"] == "box": if "height" not in dims: raise ValueError("盒子缺少高度参数")

另一个办法是在 prompt 里明确要求模型"如果不确定,输出 null 而不是猜"。这样至少能知道哪些参数是模型编的,可以追问用户。

5.2 几何内核的边界情况:什么时候会失败

CadQuery 虽然强大,但有些操作会失败。我遇到过的典型情况:

  • 孔打在了边缘上:如果孔的位置加上半径超过了板面边界,hole()会报错或生成无效几何
  • 倒角半径过大:倒角半径超过相邻边长度的一半,操作会失败
  • 布尔运算的共面问题:两个面完全重合时做布尔运算,结果可能不稳定

这些问题的共同点是:参数在数学上合法,但在几何上不可行。解决办法是在生成前做几何可行性检查,比如打孔前先算一下孔边缘到板边的距离:

def check_hole_fit(plate_len, plate_wid, offset, hole_dia): margin = offset - hole_dia / 2 if margin < 1: # 至少留 1mm 边距 raise ValueError(f"孔边距过小: {margin:.2f}mm")

5.3 批量生成时的性能问题

如果你要批量生成几十上百个模型,会发现 CadQuery 每次调用都要初始化几何内核,速度很慢。优化思路有两个:

一是复用 Workplane 对象,避免重复初始化。二是并行化,用多进程跑生成任务:

from multiprocessing import Pool def generate_one(params): model = build_model(params) path = f"output/{params['id']}.step" cq.exporters.export(model, path) return path with Pool(4) as p: results = p.map(generate_one, param_list)

实测 4 进程能把批量生成速度提升 3 倍左右。注意不要开太多进程,OCCT 本身会吃内存,进程太多反而会拖慢。

5.4 版本兼容性:STEP 不是万能交换格式

虽然 STEP 兼容性很好,但不同软件对 STEP 的支持程度不一样。我遇到过 CadQuery 导出的 STEP 在某些软件里打开后圆角丢失、孔位偏移的情况。这通常是因为 STEP 有不同的协议版本(AP203、AP214、AP242),不同软件默认读的版本不同。

CadQuery 默认导出的是 AP214,兼容性较好。如果下游软件读不了,可以试试显式指定:

cq.exporters.export(model, "part.step", opt={"write_pcurves": False})

write_pcurves关掉后,文件会更"干净",兼容性通常更好,但会丢失一些参数化曲线信息。这个取舍要看下游用途。

6. 这套方案还能往哪些方向延伸

text-to-cad 目前能做到的是"结构清晰的基础几何体生成",但它的延伸空间很大。

一个方向是参数化模板库。与其让模型每次从零生成,不如预置一批常用零件模板(法兰、支架、齿轮坯),模型只负责填参数。这样稳定性和速度都会好很多。热搜词里盘扣cad插件、cad插件这类词,其实反映的就是用户对"现成模板"的需求。

另一个方向是多轮对话式修改。现在的实现大多是一次性生成,但真实使用中用户会不断调整。"把孔改成 8 个""厚度加到 10mm""整体放大 1.5 倍"——这些增量修改需要 agent 有状态记忆和参数追踪能力。实现上不难,关键是设计好参数版本管理。

还有一个方向是和仿真流程深度集成。生成模型只是第一步,后面还有装配、运动学验证、动力学仿真。如果 text-to-cad 能直接输出带关节和惯性参数的完整 URDF,甚至能自动生成仿真场景,那价值会大很多。这也是URDF、agent这些词频繁出现在热搜里的原因——大家要的不是孤立的模型,而是能直接跑起来的完整方案。

我自己在实际项目里的体会是:text-to-cad 的价值不在于"替代建模",而在于"消除重复劳动"。那些结构固定、只是尺寸不同的零件,用这套流程生成比手动画快得多,而且不会出错。但真正需要创意和复杂曲面的设计,还是得靠人。把 AI 放在它擅长的位置,比指望它什么都能干要靠谱得多。

最后分享一个小技巧:如果你要长期用这套流程,建议把每次生成的参数和结果都存下来,形成一个"参数-模型"数据集。积累多了之后,你会发现很多需求其实是重复的,直接查历史记录比重新生成快得多。这个习惯我坚持了半年,现在常用零件的生成基本是秒级响应。

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

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

立即咨询