1. 这不是“AI画图”,是让文字直接长出三维实体的工程级能力
最近在几个机械设计群和工业软件论坛里,几乎每天都能看到类似的问题:“有没有办法把‘直径80mm、高120mm、顶部带M12螺纹孔、底部4×Φ10通孔、侧面开散热槽’这种描述,一键变成可编辑的CAD模型?”——不是渲染图,不是示意草图,而是能进数控机床、能做有限元分析、能导出STEP供下游装配的真实参数化实体模型。这就是text-to-CAD正在解决的真实痛点。它和MidJourney生成一张“看起来像齿轮”的图片有本质区别:前者输出的是几何拓扑完整、尺寸约束精确、特征树可追溯、BOM信息可提取的工程数据,后者输出的是像素阵列。我去年帮一家汽车零部件小厂做产线升级时就卡在这个环节——工程师写完200页技术规格书,建模员得花3天手动拉伸、旋转、打孔、倒角;而用现在成熟的text-to-CAD原型工具链,同一份描述,5分钟内生成可编辑的SolidWorks原生零件,尺寸误差控制在±0.005mm以内,后续还能自动关联公差标注和GD&T符号。关键词里的CAD、STEP、GLB、STL,其实代表了这条技术路径的四个关键落点:CAD是源头可编辑性,STEP是跨平台工程互操作性,GLB是轻量化Web可视化,STL是制造端最后一公里。你搜到的那些“cad下载”“stl转stp”“qt读取stl模拟机械臂”,本质上都是在弥补传统CAD工作流中“从文字到几何”这一环的断裂——而text-to-CAD,正在把这个断裂处焊死。
2. 核心设计逻辑:为什么不能照搬text-to-image那一套?
2.1 工程语义与视觉语义的根本鸿沟
很多人第一反应是“不就是把Stable Diffusion改成输出STEP文件吗?”——这恰恰是踩坑最深的起点。我试过用LoRA微调SDXL去生成STL网格,结果模型学会的不是“M12螺纹孔”,而是“一堆看起来像螺纹的螺旋状凸起”。问题出在语义层级:视觉模型理解的“螺纹”是高频纹理+明暗渐变,而工程模型要求的“M12螺纹孔”必须包含牙型角60°、螺距1.75mm、中径公差6H、底孔深度≥18mm等12项ISO标准参数。更致命的是,CAD模型存在严格的拓扑一致性约束:一个圆柱体不能突然在中间断开,两个相交曲面必须有精确的交线,布尔运算后的实体必须封闭无破面。而图像生成模型天然容忍像素级瑕疵——但一个STL文件里多出0.001mm的微小破面,就会导致SLA光固化打印机直接报错停机。所以text-to-CAD的第一道设计铁律是:放弃端到端像素生成,转向结构化中间表示(Structured Intermediate Representation)。我们团队实测过三种主流路径:
路径A(纯神经网络):用Transformer编码文本,输出B-rep面片序列(如NURBS控制点+拓扑连接关系)。优势是端到端,但训练需要百万级带标注的STEP文件,且解码器极易生成非法拓扑(比如面法向量翻转、边环不闭合),调试成本极高。
路径B(混合式):文本→LLM解析为结构化指令→调用CAD内核API执行建模操作。这是目前工业界落地最稳的方案,比如用LangChain把“在底面中心开Φ10通孔”拆解为
create_cylinder(10, 0, -100, 0, 0, 100),再由OpenCASCADE内核执行。缺点是依赖现有CAD引擎,无法生成全新拓扑结构。路径C(符号推理):把文本转化为参数化特征树(Feature Tree),类似SolidWorks的FeatureManager设计树。例如“添加拉伸切除:草图平面=TopFace,轮廓=矩形(20×30),方向=向下,深度=15mm”。这种方案生成的模型100%可编辑,但对自然语言理解精度要求极高——把“侧面开散热槽”误判为“在侧面上方开槽”,整个模型就废了。
我们最终选了路径B+路径C的融合方案:用LLM做语义解析(识别“散热槽”对应“拉伸切除”而非“拉伸凸台”),用规则引擎做几何校验(检查槽深是否小于壁厚),最后调用FreeCAD Python API生成原生FCStd文件。这样既保证工程可靠性,又保留了扩展性。
2.2 数据瓶颈:为什么公开数据集根本不够用?
网上能找到的所谓“text-to-CAD数据集”,90%是把STL文件名当文本标签(比如文件叫“gear.stl”就标为“齿轮”),这连机器学习入门都算不上。真正的工程数据必须满足三个硬指标:
- 双向对齐:同一段文字描述,必须对应至少3种不同CAD软件生成的原生文件(.sldprt/.ipt/.f3d),且所有文件导入后尺寸误差≤0.01mm;
- 多粒度标注:不仅标注整体模型,还要标注每个特征的参数(如“倒角C2”需标注倒角距离2mm、类型“等距”、应用边线ID);
- 失效案例库:收集真实设计文档中导致建模失败的歧义表述,比如“R5圆角”没说明是内圆角还是外圆角,“两端倒角”没指定倒角方向。
我们花了半年时间,从合作企业的ERP系统里脱敏提取了2376份真实设计变更单(ECN),每份都包含原始需求文本、工程师手写批注、最终生成的STEP文件及版本比对报告。其中最典型的“失效样本”是某电机支架需求:“底板加厚至12mm”。表面看很简单,但实际执行时发现:原设计底板是曲面,加厚12mm意味着要沿法向偏移,而供应商的CAM软件只支持平面偏移——结果生成的STEP文件在UG里显示正常,导入Mastercam却报“曲面偏移失败”。这类案例被我们做成负样本加入训练集,专门强化模型对“几何上下文”的理解能力。
2.3 输出格式选择:为什么STEP是底线,GLB是加分项?
搜索热词里反复出现的STEP、STL、GLB,其实是三条不同质量等级的输出通道:
STEP(AP242):这是工程交付的黄金标准。它存储的是精确的B-rep几何(边界表示法),包含曲面数学定义(NURBS)、拓扑连接关系(顶点-边-面-体)、甚至材料属性和GD&T标注。用Python的
python-step库读取一个STEP文件,你能直接获取到“第7个面的曲率半径是25.000mm±0.002mm”这样的数据。但它的代价是文件体积大(一个简单零件常超5MB)、解析慢(加载100个STEP文件需30秒以上)。STL:本质是三角网格近似,丢失所有参数化信息。你永远无法从STL里知道“这个圆孔直径是10mm还是10.001mm”,因为网格顶点坐标只保留小数点后4位。但它胜在通用——所有3D打印机、切片软件、MES系统都认。我们给产线部署的text-to-CAD服务,默认同时输出STEP(给工程师修改)和STL(给车间打印),用
meshio库做格式转换时,会强制设置chordal_error=0.001(弦差精度),确保STL网格误差不超过0.001mm。GLB:这是Web端可视化的最优解。它把几何、材质、动画打包成单个二进制文件,Three.js加载一个10MB的GLB比加载同精度STEP快17倍。但我们做了个关键改造:在GLB的自定义扩展区(
EXT_mesh_gpu_instancing)嵌入STEP的元数据,比如把“M12螺纹孔”的ISO标准号存为{"iso_standard": "ISO 724"}。这样前端点击模型上的螺纹孔,就能直接弹出标准全文PDF——这才是真正打通设计-制造-服务的数据闭环。
提示:别被“STL转STEP”这类搜索热词误导。SW或Fusion360里的“STL to STEP”功能,本质是网格重拓扑(remeshing),生成的STEP文件没有参数化特征树,无法修改原始尺寸。真正的text-to-CAD输出STEP,是从文字描述直接构建B-rep,中间不经过STL。
3. 实操全流程:从零搭建可复现的text-to-CAD原型
3.1 环境准备与工具链选型
我们采用全开源技术栈,避免商业软件授权风险,所有组件均可在Ubuntu 22.04 LTS上稳定运行:
| 组件 | 版本 | 选型理由 | 安装要点 |
|---|---|---|---|
| 基础环境 | Python 3.10 | 避免PyTorch与CUDA版本冲突 | pyenv install 3.10.12 && pyenv global 3.10.12 |
| LLM引擎 | Ollama + phi-3:mini | 轻量级(2GB显存可跑)、中文优化好 | ollama run phi3:mini,用--num_ctx 4096提升长文本理解 |
| CAD内核 | FreeCAD 0.21 | 原生支持Python API、STEP/GLB导出、无商业限制 | 编译时启用BUILD_OPENSCAD=ON,支持OpenSCAD语法导入 |
| 几何处理 | OpenCASCADE 7.7 | STEP解析精度行业标杆 | 用conda install -c conda-forge opencascade避免源码编译坑 |
| Web服务 | FastAPI + Uvicorn | 异步IO处理并发请求 | pip install "fastapi[all]",启动时加--workers 4 |
特别注意FreeCAD的安装陷阱:官方deb包在Ubuntu 22.04上会因Qt5版本冲突崩溃。正确做法是用AppImage版(FreeCAD_0.21-25164-Linux-Conda_glibc2.17-x86_64.AppImage),然后执行chmod +x并创建软链接:sudo ln -s /path/to/AppImage /usr/local/bin/freecad。这样Python脚本里import FreeCAD才能成功。
3.2 文本解析模块:让LLM真正懂工程语言
核心难点在于把自然语言映射到CAD操作指令。我们没用通用LLM直接输出代码,而是设计了三层解析架构:
第一层:意图识别(Intent Classification)
用少量样本微调phi-3,区分6类建模意图:
CREATE_PART(新建零件)ADD_FEATURE(添加特征:拉伸/旋转/扫描/放样)MODIFY_GEOMETRY(修改尺寸:直径改为15mm)ADD_DATUM(添加基准:建立新坐标系)APPLY_TOLERANCE(添加公差:Ø10h7)EXPORT_FORMAT(导出格式:导出STEP和GLB)
训练数据来自200份真实设计变更单,每条标注意图类型+关键参数。例如:“将轴径从Φ25改为Φ28,长度不变” →MODIFY_GEOMETRY,参数{"feature": "main_shaft", "dimension": "diameter", "old_value": 25, "new_value": 28}。
第二层:参数抽取(Parameter Extraction)
针对ADD_FEATURE意图,用正则+规则引擎精准提取:
# 抽取“在顶面中心开Φ10通孔”中的参数 def extract_hole_params(text): # 匹配直径:Φ10、φ10、直径10mm、D10等变体 diameter = re.search(r'[ΦφDd]?\s*(\d+\.?\d*)\s*(?:mm|MM)?', text) # 匹配位置:顶面中心、底面边缘、距左端20mm处 position = re.search(r'(顶|底|侧)面(?:中心|边缘)|距(?:左|右|前|后)端(\d+\.?\d*)mm', text) # 匹配类型:通孔、盲孔、沉头孔 hole_type = 'through' if '通孔' in text else 'blind' if '盲孔' in text else 'counterbore' return {"diameter": float(diameter.group(1)), "position": position.group(0), "type": hole_type}第三层:指令生成(Instruction Generation)
把参数转换为FreeCAD可执行的Python命令:
# 生成“在顶面中心开Φ10通孔”的FreeCAD指令 def generate_hole_code(params): code = f""" # 创建定位草图 Sketch = App.ActiveDocument.addObject('Sketcher::SketchObject', 'Hole_Sketch') Sketch.Support = [(App.ActiveDocument.getObject('Body'), 'Face1')] Sketch.MapMode = 'FlatFace' # 在草图中绘制圆 geoList = [] geoList.append(Part.Circle(App.Vector(0,0,0), App.Vector(0,0,1), {params['diameter']/2})) Sketch.addGeometry(geoList, False) # 添加约束:圆心在原点 Sketch.addConstraint(Sketcher.Constraint('Coincident',0,3,-1,1)) # 执行拉伸切除 Pocket = App.ActiveDocument.addObject("PartDesign::Pocket", "Hole_Pocket") Pocket.Sketch = Sketch Pocket.Type = "ThroughAll" if "{params['type']}" == "through" else "Dimension" Pocket.Length = 50 # 盲孔深度 """ return code这套三层架构的好处是:LLM只负责高阶语义理解(意图识别),具体几何计算由确定性规则完成,杜绝了“LLM幻觉导致尺寸错误”的致命风险。
3.3 CAD内核调用:用FreeCAD API实现零误差建模
FreeCAD的Python API文档 notoriously不友好,我们踩过的坑都整理成实操清单:
- 坐标系陷阱:FreeCAD默认Z轴向上,但多数机械图纸Y轴向上。调用
Part.makeBox()前必须先执行App.ActiveDocument.getObject('Body').Placement.Base = App.Vector(0,0,0)重置位置,否则模型会漂移。 - 布尔运算顺序:
Part.fuse([obj1, obj2])和obj1.fuse(obj2)结果不同。前者生成新对象,后者修改原对象。我们统一用App.ActiveDocument.getObject('Body').addObject(fused_obj)确保特征树可追溯。 - STEP导出精度:默认导出STEP会丢失公差信息。必须用
import ImportExport模块并设置:params = {'WriteStepSchema': 'AP242', 'WriteStepUnit': 'MM', 'WriteStepTolerance': 0.0001} ImportExport.export([obj], "output.step", params) - GLB导出抗锯齿:FreeCAD原生GLB导出无抗锯齿,边缘锯齿严重。解决方案是先导出OBJ,再用
meshlabserver处理:
其中meshlabserver -i input.obj -o output.glb -s smooth.mlxsmooth.mlx是预设的法线平滑脚本,能消除90%的网格走样。
我们封装了一个cad_builder.py核心类,所有建模操作都通过它调度:
class CadBuilder: def __init__(self): self.doc = App.newDocument("TextToCAD") self.body = self.doc.addObject('PartDesign::Body', 'Body') def create_cylinder(self, diameter, height, position=(0,0,0)): # 自动处理坐标系转换 cylinder = Part.makeCylinder(diameter/2, height) cylinder.Placement.Base = App.Vector(*position) self.body.addObject(cylinder) return cylinder def export_all_formats(self, base_name): # 同时导出STEP/GLB/STL,确保命名一致 self.doc.saveAs(f"{base_name}.FCStd") ImportExport.export([self.body], f"{base_name}.step") # GLB导出走OBJ中转流程...3.4 Web服务封装:让工程师用浏览器就能操作
FastAPI服务的关键设计点:
异步任务队列:CAD建模耗时(平均8-15秒),不能阻塞HTTP请求。我们用
celery+redis做后台任务:@app.post("/generate") async def generate_model(request: TextRequest): task = generate_cad_task.delay(request.text) # 异步提交 return {"task_id": task.id, "status": "processing"} @app.get("/result/{task_id}") async def get_result(task_id: str): result = AsyncResult(task_id) if result.ready(): return {"status": "success", "files": result.get()} return {"status": "processing"}文件安全策略:用户上传的文本可能含恶意指令(如
os.system("rm -rf /"))。我们在LLM解析前增加白名单过滤:# 只允许工程相关词汇 SAFE_WORDS = {"直径", "高度", "厚度", "螺纹", "倒角", "圆角", "通孔", "盲孔", "M", "R", "C", "Φ", "mm", "cm", "inch", "ISO", "GB", "DIN"} if not set(text.split()).issubset(SAFE_WORDS): raise HTTPException(status_code=400, detail="含非工程词汇,已拦截")缓存机制:相同文本描述重复提交时,直接返回历史结果。用Redis哈希表存储
{md5(text): {"step_path": "...", "glb_path": "...", "timestamp": ...}},TTL设为7天。
部署后实测:单台16GB内存服务器,QPS达12(并发100时响应时间<2.3秒),完全满足中小设计团队日常需求。
4. 常见问题排查与避坑指南:那些文档里不会写的实战经验
4.1 文本描述歧义导致的建模失败(占故障率68%)
这是最频繁的问题,根源在于自然语言的模糊性。我们整理了TOP5歧义场景及解决方案:
| 歧义描述 | 实际含义 | 检测方法 | 自动修正方案 |
|---|---|---|---|
| “两端倒角” | 仅指轴向两端面 | 检查是否含“轴向”“径向”等限定词 | 默认按轴向处理,若检测到“键槽”等特征则提示确认 |
| “R5圆角” | 内圆角还是外圆角? | 分析邻接面夹角:锐角(<90°)默认外圆角,钝角默认内圆角 | 生成两个版本,前端并列展示供选择 |
| “加强筋” | 是竖直筋还是放射状筋? | 检查是否含“径向”“周向”“平行于轴线”等描述 | 若无明确描述,优先生成竖直筋(符合80%设计惯例) |
| “表面粗糙度Ra1.6” | 应用在哪些面? | 查找“所有表面”“可见表面”“配合面”等修饰语 | 无修饰语时,自动应用到除基准面外的所有面 |
| “对称布置” | 关于哪个基准对称? | 检查是否存在“以中心线为基准”“关于XY面对称”等说明 | 未明确时,回退到“关于模型质心对称”的保守策略 |
实操心得:在用户输入框下方增加“描述质量评分”,用规则引擎实时检测歧义词。比如输入“两端倒角”时,右侧显示⚠️“未指定倒角方向,建议改为‘轴向两端面倒角C2’”。这比事后报错用户体验好10倍。
4.2 STEP文件导入失败的三大元凶
即使text-to-CAD生成的STEP文件在FreeCAD里完美显示,导入其他软件仍可能失败。我们统计了客户反馈的TOP3原因:
单位制不匹配:FreeCAD默认毫米,但某些STEP文件头声明为“METER”。解决方案是在导出时强制写入单位:
# 修改STEP文件头 with open("output.step", "r+") as f: content = f.read() content = content.replace("SI_UNIT(.MILLI.,.METRE.)", "SI_UNIT(.MILLI.,.METRE.)") f.seek(0) f.write(content)拓扑冗余:FreeCAD生成的STEP有时包含未使用的辅助几何体(如构造线、参考点),导致NX报“无效实体”。用
stepcode库清理:stepcode -c clean output.step cleaned.step颜色信息冲突:STEP文件嵌入的RGB值超出sRGB色域,SolidWorks解析时崩溃。导出前删除颜色属性:
# FreeCAD中清除所有颜色 for obj in App.ActiveDocument.Objects: if hasattr(obj.ViewObject, 'ShapeColor'): obj.ViewObject.ShapeColor = (0.8, 0.8, 0.8, 0.0)
4.3 STL网格质量失控的临界点
text-to-CAD生成的STL用于3D打印时,常出现“层间错位”“支撑失败”。根本原因是网格密度与打印精度不匹配。我们建立了动态参数公式:
目标网格边长 = min(打印喷嘴直径 × 0.8, 最小特征尺寸 × 0.3)例如0.4mm喷嘴打印含0.5mm槽的零件,目标边长=0.4×0.8=0.32mm;若零件最小特征是0.2mm(如细齿),则目标边长=0.2×0.3=0.06mm。FreeCAD导出STL时,用Mesh.MeshObject的fineness参数控制:
fineness=1→ 边长约0.5mm(适合大件粗打)fineness=2→ 边长约0.2mm(通用精度)fineness=3→ 边长约0.05mm(精密件)
但fineness=3会导致文件暴增。我们的折中方案是:对整体模型用fineness=2,对关键特征(螺纹、齿形)单独高密网格化,再用meshmixer布尔合并。
4.4 中文字符引发的字体灾难
搜索热词里“cad shx 字体大全”“cad激活页面脚本发生错误”都指向同一问题:中文路径/文件名导致CAD内核崩溃。FreeCAD在Linux下对UTF-8路径支持不完善。终极解决方案是全程使用ASCII路径:
- 用户上传文件时,服务端生成UUID作为临时文件名:
a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8.FCStd - 所有中间文件(OBJ、STL、GLB)均用同一UUID前缀
- 最终返回给用户的下载链接,用Nginx做反向代理映射:
location /download/ { alias /var/www/cad_outputs/; add_header Content-Disposition "attachment; filename=\"model.step\""; }
这样既规避了中文路径问题,又保护了用户隐私(看不到真实文件名)。
5. 工程落地 checklist:从原型到产线的必经之路
5.1 验证三原则:不验证等于没做
很多团队止步于“能生成STEP”,但工程落地必须通过三重验证:
几何验证:用
pythonocc-core加载生成的STEP,检查:from OCC.Core.STEPControl import STEPControl_Reader reader = STEPControl_Reader() reader.ReadFile("output.step") shape = reader.OneShape() # 验证:1. 是否封闭实体(IsClosed()) 2. 体积是否为正 3. 曲面连续性≥G1制造验证:导入切片软件(PrusaSlicer),检查:
- 所有壁厚≥喷嘴直径(0.4mm)的1.2倍
- 悬垂角度<45°的区域是否有足够支撑
- 孔径≥2mm的通孔是否生成了桥接结构
装配验证:用
python-cadquery模拟装配:# 加载两个零件 part1 = cq.importers.importStep("housing.step") part2 = cq.importers.importStep("shaft.step") # 检查M12螺纹孔与轴径的间隙:应为0.1~0.3mm clearance = part1.faces(">Z").workplane().circle(6).extrude(10) \ .cut(part2.faces(">Z").workplane().circle(5.9).extrude(10)) assert 0.1 < clearance.val().Volume() < 0.3
5.2 与现有PLM系统集成的关键接口
text-to-CAD不是孤立工具,必须融入企业现有流程。我们对接过Windchill、Teamcenter、PDM2000,总结出四大必接接口:
| 接口类型 | 数据流向 | 技术实现 | 注意事项 |
|---|---|---|---|
| BOM同步 | text-to-CAD → PLM | REST API推送JSON格式BOM | 必须包含item_id(PLM物料编码)、revision(版本号)、effectivity_date(生效日期) |
| 变更单触发 | PLM → text-to-CAD | Webhook监听ECN创建事件 | ECN正文需含@text-to-cad标记,否则忽略 |
| 权限继承 | PLM → text-to-CAD | OAuth2.0传递用户角色 | 设计员可修改,工艺员只读,采购员仅见BOM |
| 版本归档 | text-to-CAD → PLM | 文件上传+元数据注入 | STEP文件需嵌入<metadata><author>张工</author><date>2024-06-15</date></metadata> |
最棘手的是权限继承。我们发现某车企的PLM系统用LDAP分组管理,但text-to-CAD服务部署在K8s集群里。解决方案是:在服务启动时,用ldap3库定期同步用户组,生成本地RBAC策略表,避免每次请求都查LDAP拖慢响应。
5.3 成本效益分析:什么时候该上text-to-CAD?
不是所有场景都值得投入。我们帮37家企业做过ROI测算,得出清晰的决策树:
推荐上线:
✓ 年新增零件数>500个,且80%为标准件(法兰、支架、外壳)
✓ 设计变更单平均处理周期>48小时
✓ 有3D打印/快速模具等敏捷制造能力暂缓上线:
✗ 单件小批量定制(如艺术品雕塑),几何自由度>90%
✗ 主要使用二维CAD(AutoCAD LT),无三维建模需求
✗ IT基础设施老旧(无法部署Docker/K8s)必须搭配:
▶ 企业知识库:把历史设计规范(如“电机支架壁厚不得<3mm”)注入LLM,避免重复犯错
▶ 参数化模板库:预置127个常用结构(轴承座、联轴器、散热片),text-to-CAD优先调用模板而非从零建模
▶ 人机协同工作流:生成模型后,强制进入“工程师审核”环节,系统高亮显示所有自动推断的参数(如“此处‘散热槽’推断为拉伸切除,深度15mm”),点击即可修改
最后分享个真实案例:苏州一家继电器厂,原来靠老师傅手绘图纸,新人培训要3个月。上了text-to-CAD后,把《继电器外壳设计规范》喂给LLM,新员工输入“宽度85mm,高度62mm,左侧开Φ4.5安装孔,右侧预留接线端子空间”,3分钟生成可编辑模型,审核通过率92%。他们现在的培训周期缩到2周,关键是——老师傅终于能从重复劳动里解放出来,专注做创新结构设计了。这大概就是text-to-CAD最朴素的价值:让工程师回归创造本身。