尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

text-to-cad实战:LLM+参数化建模生成STEP与URDF

text-to-cad实战:LLM+参数化建模生成STEP与URDF 1. 从一句话到三维模型text-to-cad 到底在解决什么问题第一次听到 text-to-cad 这个词很多人会下意识觉得它离自己很远像是实验室里的概念演示。但如果你真正在机械设计、机器人仿真或者工业建模这条线上待过就会明白它戳中的是一个极其现实的痛点从自然语言描述到可用的 CAD 几何文件中间那道手工建模的鸿沟太深了。传统流程是什么样的你脑子里有一个零件或者一个机械臂的结构先要在脑子里把它拆成基本体然后打开 CAD 软件一个草图一个草图地画一个拉伸一个拉伸地做最后导出 STEP 或者 STL。一个中等复杂度的零件熟练工程师也要花上几十分钟到几个小时。而 text-to-cad 想做的事情就是让你用一段文字描述直接生成对应的三维几何模型输出成 STEP、URDF 这类下游能直接消费的格式。这个方向的核心价值不在于炫技而在于把重复性的、结构化的建模工作自动化。比如你要批量生成一批标准件、要快速迭代一个机器人连杆的尺寸、要在仿真环境里快速搭一个场景原型手工建模的效率瓶颈就非常明显。text-to-cad 把大语言模型的语义理解能力和参数化建模能力结合起来让说人话就能出模型成为可能。这篇文章适合几类人看一是做机器人仿真、需要频繁生成 URDF 模型的工程师二是做工业设计、想了解 AI 辅助建模边界的产品人员三是有 Python 基础、想自己动手搭一套 text-to-cad 流水线的开发者。我会从整体设计思路讲起把核心细节、实操步骤、踩坑经验全部摊开尽量让你看完就能自己跑起来。2. 整体设计思路为什么是LLM 参数化建模这条路线2.1 三条技术路线的取舍text-to-cad 这个方向目前主流有三条实现路径各有各的适用场景我先把它们摆出来对比一下你就明白为什么我最终选了现在这套方案。路线核心思路优势劣势适用场景直接生成网格用扩散模型或自回归模型直接输出三角网格形状自由度高能生成有机曲面不可参数化编辑精度差难导出 STEP概念展示、艺术造型代码生成LLM 生成 CadQuery/OpenSCAD 代码再执行可参数化、可复现、精度高依赖 LLM 代码能力复杂形状易出错工程零件、标准件参数填充预定义模板LLM 只填参数稳定、可控、几乎不出错灵活性差只能做模板内的形状批量标准件、系列化产品我最终选的是代码生成路线为主、参数填充为辅的混合方案。原因很直接工程场景要的是精度和可编辑性网格模型看着好看但没法用。而纯参数填充又太死板稍微超出模板就歇菜。代码生成路线用 CadQuery 作为后端LLM 负责把自然语言翻译成 CadQuery 的 Python 代码执行后直接导出 STEP这条链路既灵活又能保证几何精度。2.2 为什么后端选 CadQuery 而不是 OpenSCAD这里要展开说一下选型逻辑因为这是整个项目的地基。OpenSCAD 是很多人的第一选择语法简单社区大。但它有个致命问题它本质上是一个网格建模工具导出的 STL 是三角面片没有 B-rep边界表示信息。你要导 STEP 文件OpenSCAD 基本无能为力得绕一大圈。而 STEP 是工业界通用的交换格式CAD 软件、仿真工具、PLM 系统都认它。CadQuery 就不一样了它底层是 OCCTOpen CASCADE Technology这是工业级的几何内核天生支持 B-rep导出 STEP 是一行代码的事。而且 CadQuery 的 API 是链式的写起来很接近人的建模思维LLM 生成起来也相对容易。比如画一个带孔的板子import cadquery as cq result ( cq.Workplane(XY) .box(60, 40, 5) .faces(Z) .workplane() .hole(8) .edges(|Z) .fillet(2) ) cq.exporters.export(result, plate.step)这段代码的可读性很强LLM 只要理解了一块 60x40x5 的板子中心一个直径 8 的孔四个角倒圆角 2mm基本能一次生成正确。这就是我选 CadQuery 的核心原因几何内核工业级 API 对 LLM 友好。2.3 整体架构拆解整套流水线我拆成了四个模块每个模块职责单一方便单独调试和替换。第一个模块是语义解析层。用户输入一段自然语言比如一个长 100mm、宽 50mm、高 20mm 的铝合金底座四角各有一个 M6 的安装孔孔中心距边缘 10mm。这一层负责把这段话拆成结构化的参数尺寸、材料、特征、约束。我用的是 LLM 的 function calling 能力让它输出一个 JSON而不是直接生成代码。这一步很关键因为结构化参数可以被校验、被修正、被复用。第二个模块是代码生成层。拿到结构化参数后再让 LLM 生成 CadQuery 代码。这里我做了个设计先生成参数化的模板代码再把参数注入。而不是让 LLM 从零写代码。这样做的好处是稳定性大幅提升LLM 只需要做它擅长的填空和微调而不是从零构造复杂几何逻辑。第三个模块是执行与校验层。代码生成后不能直接信得在沙箱里执行捕获异常还要做几何校验——比如体积是否为正、是否有自相交、导出文件是否有效。校验不通过就带着错误信息回传给 LLM 让它修正形成一个自修复循环。第四个模块是导出与适配层。根据目标格式导出 STEP、STL 或者 URDF。URDF 这块要特别处理因为 URDF 描述的是带关节的机器人模型需要额外的运动学信息不能只靠几何。3. 核心细节解析从自然语言到几何的每一步3.1 语义解析怎么让 LLM 稳定输出结构化参数这一步是整个流水线的入口也是最容易出问题的地方。我踩过的坑是直接让 LLM 输出 JSON它经常自由发挥字段名忽大忽小单位忽 mm 忽 cm甚至把直径和半径搞混。解决办法是用 JSON Schema 强约束输出格式。我定义了一套固定的 schema包含 dimensions、features、material、units 这几个顶层字段每个字段下面再细分。LLM 通过 function calling 调用时必须严格按 schema 来否则调用失败。这样虽然牺牲了一点灵活性但换来了极高的稳定性。单位问题我单独做了处理。schema 里强制要求所有长度单位统一为毫米LLM 在解析时如果遇到5cm必须转换成 50。这个转换看起来简单但实测下来 LLM 经常偷懒直接填 5所以我在 prompt 里反复强调并且在代码层做了一次二次校验——如果某个尺寸数值小于 1 且原文里出现了 cm 字样就自动乘以 10 并打日志。还有一个细节是特征顺序。同样一个零件先打孔再倒角和先倒角再打孔结果可能完全不同。我在 schema 里加了一个 features 数组要求 LLM 按建模顺序排列特征并且每个特征标注类型hole、fillet、chamfer、pocket 等和依赖关系。这样代码生成层就能按正确顺序组装。3.2 代码生成模板 参数注入的混合策略纯让 LLM 写 CadQuery 代码简单形状没问题稍微复杂一点就开始胡编 API。我试过让 GPT-4 直接生成一个带阵列孔的支架代码十次里有三次会用到不存在的 API 或者参数顺序搞错。所以我改成了模板库 参数注入的方案。我预先写好了几十个常用特征的代码模板比如基础体模板box、cylinder、sphere孔特征模板单孔、阵列孔、沉头孔倒角圆角模板抽壳模板布尔运算模板LLM 的任务变成了根据结构化参数选择合适的模板填入参数并决定模板的组装顺序。这本质上是一个代码组装任务比代码创作简单得多稳定性自然高。举个实际例子。用户说一个 80x60x10 的板四个角各有一个直径 6 的通孔孔中心距边 8mm。结构化参数出来后代码生成层会这样组装import cadquery as cq # 基础体 result cq.Workplane(XY).box(80, 60, 10) # 计算孔位 x_offset 80/2 - 8 y_offset 60/2 - 8 hole_positions [ (x_offset, y_offset), (-x_offset, y_offset), (-x_offset, -y_offset), (x_offset, -y_offset) ] # 打孔 result ( result.faces(Z).workplane() .pushPoints(hole_positions) .hole(6) ) cq.exporters.export(result, bracket.step)注意孔位计算这段我是用代码算的不是让 LLM 算的。LLM 只需要告诉我孔中心距边 8mm具体坐标由确定性代码计算。这个分工很重要LLM 负责语义代码负责算术。让 LLM 做算术是灾难它会在 80/2-8 这种地方翻车。3.3 执行校验沙箱、超时与几何自检生成的代码绝对不能直接在主进程里 exec这是安全底线。我用的是子进程 资源限制的方案具体做法是import subprocess import tempfile import os def execute_cadquery(code_str, timeout30): with tempfile.NamedTemporaryFile( modew, suffix.py, deleteFalse ) as f: f.write(code_str) script_path f.name try: result subprocess.run( [python, script_path], capture_outputTrue, textTrue, timeouttimeout, cwdtempfile.gettempdir() ) return { success: result.returncode 0, stdout: result.stdout, stderr: result.stderr } except subprocess.TimeoutExpired: return {success: False, stderr: Execution timeout} finally: os.unlink(script_path)超时设 30 秒是有讲究的。CadQuery 做复杂布尔运算时确实会慢但超过 30 秒基本说明代码有问题要么是死循环要么是几何运算爆炸。宁可让它失败重试也不要卡死整个流水线。几何校验这块我做了三层检查。第一层是文件存在性检查导出的 STEP 文件是否存在且大小大于 0。第二层是几何有效性检查用 CadQuery 重新读入 STEP检查 solid 是否 valid、体积是否为正。第三层是特征符合性检查比如用户要求四个孔我就数一下模型里的圆柱面数量是否匹配。这三层下来基本能拦住 95% 以上的错误输出。3.4 URDF 导出的特殊处理URDF 和普通 CAD 模型最大的区别是URDF 描述的是带关节的运动学树不是单一几何体。所以 text-to-cad 生成 URDF 时不能只生成一个零件而要生成多个 link 和它们之间的 joint。我的处理方式是在语义解析阶段就识别出这是一个多体系统。比如用户说一个两连杆机械臂第一段长 200mm第二段长 150mm中间一个旋转关节解析层会输出一个包含两个 link 和一个 revolute joint 的结构。然后代码生成层分别生成每个 link 的几何再组装成 URDF。URDF 的 link 几何我推荐用 STL 而不是 STEP因为主流的仿真工具对 STL 支持更好加载也更快。导出时要注意坐标系对齐每个 link 的几何原点要和 joint 的旋转轴对齐否则仿真时会出现莫名其妙的偏移。这个坑我踩过调了半天才发现是 link 原点没对齐。4. 实操过程从零搭一套可用的 text-to-cad 流水线4.1 环境准备与依赖安装先把环境搭起来。我用的 Python 3.10太新的版本有些几何库还没跟上太老的版本 CadQuery 装不上。依赖清单如下pip install cadquery2.4.0 pip install openai pip install jsonschema pip install trimesh pip install numpyCadQuery 的安装是重点它在不同平台上的依赖不太一样。Linux 上一般直接 pip 就能装Windows 上如果报 OCCT 相关的错建议用 conda 装conda install -c conda-forge cadquery装完之后验证一下import cadquery as cq box cq.Workplane(XY).box(10, 10, 10) cq.exporters.export(box, test.step) print(CadQuery OK)能导出 test.step 就说明环境没问题。这一步看着简单但我见过太多人卡在 CadQuery 安装上所以别跳过验证。4.2 语义解析模块的实现先定义 JSON Schema这是整个解析层的契约PARAM_SCHEMA { type: object, properties: { shape_type: { type: string, enum: [single_part, assembly] }, dimensions: { type: object, properties: { length: {type: number}, width: {type: number}, height: {type: number} } }, features: { type: array, items: { type: object, properties: { type: {type: string}, params: {type: object}, order: {type: integer} }, required: [type, params, order] } }, units: {type: string, enum: [mm]} }, required: [shape_type, dimensions, features, units] }然后调用 LLM 做解析。这里我用的是 function calling把 schema 作为参数约束传进去import json from openai import OpenAI client OpenAI() def parse_description(text): response client.chat.completions.create( modelgpt-4, messages[ { role: system, content: ( 你是一个 CAD 参数解析器。 把用户的自然语言描述转换成结构化参数。 所有长度单位统一为毫米遇到 cm 要乘以 10。 特征要按建模顺序排列。 ) }, {role: user, content: text} ], tools[{ type: function, function: { name: extract_cad_params, parameters: PARAM_SCHEMA } }], tool_choice{type: function, function: {name: extract_cad_params}} ) tool_call response.choices[0].message.tool_calls[0] return json.loads(tool_call.function.arguments)实测下来加了 schema 约束之后解析成功率从大概 70% 提升到了 95% 以上。剩下 5% 主要是描述本身太模糊比如做个差不多大的盒子这种神仙也救不了只能让用户补充信息。4.3 代码生成与模板库设计模板库我按特征类型组织每个模板是一个函数接收参数字典返回 CadQuery 代码字符串。这样设计的好处是模板可以独立测试也可以被 LLM 通过名称引用。TEMPLATES { base_box: result cq.Workplane(XY).box({length}, {width}, {height}) , through_hole: result ( result.faces({face}).workplane() .pushPoints({positions}) .hole({diameter}) ) , fillet_edges: result result.edges({selector}).fillet({radius}) , shell: result result.faces({face}).shell(-{thickness}) }代码生成层的工作流是遍历结构化参数里的 features 数组按 order 排序依次查找对应模板填入参数拼接成完整代码。基础体模板总是第一个执行后续特征依次叠加。这里有个细节要注意特征之间的坐标系传递。比如先打孔再抽壳抽壳的开口面选择器要基于打孔后的模型来写。我的做法是在模板里统一用 faces(Z) 这种相对选择器而不是绝对坐标这样即使前面的操作改变了模型选择器依然有效。4.4 完整流水线的串联把四个模块串起来主流程大概长这样def text_to_cad(description, output_formatstep): # 1. 语义解析 params parse_description(description) # 2. 代码生成 code generate_code(params) # 3. 执行校验 result execute_cadquery(code) if not result[success]: # 自修复带着错误信息重试 code generate_code(params, error_feedbackresult[stderr]) result execute_cadquery(code) if not result[success]: raise RuntimeError(f生成失败: {result[stderr]}) # 4. 几何校验 if not validate_geometry(result[output_path]): raise RuntimeError(几何校验未通过) # 5. 格式转换 if output_format urdf: return convert_to_urdf(result[output_path], params) return result[output_path]自修复循环我限制最多重试两次。实测下来第一次失败后带着错误信息重试成功率能到 80% 左右两次还不行基本就是描述本身有问题继续重试也是浪费 token。4.5 参数计算的实际案例拿一个真实案例走一遍。用户输入一个 L 型支架底板 100x60x8立板高 80、厚 8立板上有两个直径 10 的孔孔中心距立板顶部 20mm水平间距 40mm。解析层输出的结构化参数{ shape_type: single_part, dimensions: {length: 100, width: 60, height: 88}, features: [ {type: base_box, params: {length: 100, width: 60, height: 8}, order: 1}, {type: vertical_plate, params: {height: 80, thickness: 8}, order: 2}, {type: through_hole, params: {diameter: 10, count: 2, spacing: 40, from_top: 20}, order: 3} ], units: mm }代码生成层会先做 L 型的基础体再打孔。孔位计算是这样的立板顶部在 Z88孔中心距顶部 20所以孔中心 Z68。水平间距 40对称分布所以 X 坐标是 ±20。这些计算全部由确定性代码完成LLM 只负责提供距顶部 20、间距 40这两个语义参数。5. 常见问题与排查技巧实录5.1 生成失败类问题速查问题现象可能原因排查方法解决方案LLM 输出 JSON 解析失败schema 约束没生效检查 tool_choice 参数强制 function callingCadQuery 报 API 不存在LLM 幻觉了 API看 stderr 里的函数名用模板库替代自由生成执行超时几何运算爆炸看是否有多层布尔嵌套简化特征顺序拆分操作导出 STEP 为空模型是空 solid检查体积是否为正加几何有效性校验孔位偏移坐标系没对齐打印孔中心坐标统一用相对选择器5.2 几个我踩过的深坑第一个坑是单位混乱。早期版本我没强制单位统一结果 LLM 有时候把5cm解析成 5有时候解析成 50同一个描述两次生成结果差十倍。后来我在 schema 里把 units 字段锁死为 mm并且在 prompt 里明确要求转换才彻底解决。第二个坑是特征顺序。有一次生成一个带倒角的零件LLM 把倒角排在了打孔前面结果倒角把孔边缘也倒了出来的模型完全不对。后来我在 schema 里强制要求 features 带 order 字段并且规定基础体永远是 order 1倒角圆角类特征必须排在最后。第三个坑是 URDF 的坐标系。第一次生成 URDF 导入仿真环境模型位置全乱。查了半天发现是 link 的几何原点没有和 joint 轴对齐。URDF 里每个 link 的视觉几何是相对于 link 坐标系定义的如果几何原点不在关节轴上旋转时就会偏心。解决办法是在生成几何时就把原点移到关节轴位置或者在 URDF 里加 origin 偏移。第四个坑是 STEP 文件的版本兼容。CadQuery 默认导出的 STEP 是 AP214有些老版本 CAD 软件读不了。如果下游要用老软件得指定导出 AP203cq.exporters.export(result, part.step, exportTypeSTEP, opt{write_pcurves: False})5.3 提升生成成功率的几个实操技巧第一个技巧是在 prompt 里给例子。我在 system prompt 里放了三个完整的输入输出示例覆盖单零件、多特征、装配体三种情况。加了例子之后解析准确率明显提升。LLM 这东西你给它看几个样例比写一堆规则管用。第二个技巧是把复杂描述拆成多轮。如果用户描述特别复杂比如一个十几个特征的零件一次性生成容易出错。我会先让 LLM 把描述拆成几个子任务每个子任务单独生成代码最后合并。虽然多花点 token但成功率提升明显。第三个技巧是缓存常用模板的生成结果。像标准法兰、常见支架这类高频形状第一次生成成功后把代码缓存起来下次遇到相似描述直接复用既快又稳。第四个技巧是给几何校验加容差。浮点数比较不能用等号体积、尺寸这些都要设容差。我一般用相对容差 1e-3绝对容差 1e-6。这个细节不注意会出现明明模型对的但校验不通过的情况。6. 工具选型与扩展方向的一些个人看法6.1 LLM 选型的实际体验我试过好几个模型来做这个任务。GPT-4 在代码生成上确实稳但成本高开源模型里CodeLlama 和 DeepSeek Coder 在 CadQuery 这种相对小众的 API 上表现一般经常编造不存在的函数。我的建议是语义解析可以用便宜模型代码生成用强模型。因为解析任务相对简单而代码生成对准确性要求高这里省不得。如果预算有限还有个折中方案用强模型生成一批高质量的描述-代码对微调一个开源模型专门做 CadQuery 代码生成。我试过用 500 条数据微调效果能接近 GPT-4 的 80%成本却低很多。这个方向适合有长期需求的团队。6.2 从 STEP 到下游的衔接生成 STEP 只是第一步真正用起来还要考虑下游。如果是做仿真STEP 要转成 STL 或者直接导入仿真软件如果是做 3D 打印要检查壁厚和悬垂如果是做装配还要考虑公差和配合。我一般会在导出后加一个后处理步骤根据目标用途做针对性检查。比如 3D 打印就检查最小壁厚是否大于 1mm装配就检查配合面间隙是否在合理范围。这些检查用 trimesh 或者 CadQuery 都能做加进去之后整个流水线的可用性会高很多。6.3 这个方向后续可以怎么扩展第一个扩展方向是多模态输入。现在只能文字输入但实际工作中很多需求是照着这张草图做一个如果能支持图片输入实用性会大幅提升。技术上就是把图片编码后和文字一起喂给多模态模型让它输出结构化参数。第二个扩展方向是参数化模板库的积累。随着使用会遇到越来越多重复的形状模式把这些沉淀成模板LLM 的工作就越来越轻整个系统也越来越稳。这其实是一个正向循环用得越多模板越丰富生成越准。第三个扩展方向是和 PLM/PDM 系统集成。生成的模型如果能自动带上物料编码、版本号、材料信息直接入库那对工程团队的价值就完全不一样了。这块需要对接企业现有系统属于工程化落地阶段的事。我在实际使用中最大的体会是text-to-cad 不是一个替代工程师的工具而是一个放大工程师效率的工具。它擅长的是把结构化的、重复性的建模工作自动化让人把精力放在真正需要创造力和判断力的地方。把它当成一个高级的建模助手而不是一个全自动的黑盒心态就对了。
返回列表