
我做设计这行快十年了每天跟CAD打交道的时间比跟家人说话都多。这两年AI工具一堆接一堆冒出来但老实说真正能落进日常工作流、不扯淡的少。前段时间我一直在琢磨一个事既然现在大语言模型能听懂人话了那能不能让CAD也听懂人话比如我敲一句在这张图里画一条从原点出发、角度30度、长度100的线图纸上就真的出现这条线。这个想法听起来像是科幻片但实际上做个能跑的text-to-cad工具真没有想象中那么玄乎。这篇文章就是把我从零搭起这个系统的完整过程、踩过的坑、还有最终跑通的效果都记录下来给想在这条路上试试水的朋友一个参考。1. text-to-cad到底在解决什么问题先说个最直接的场景。我见过太多设计师和工程师每天有一大堆时间花在重复性的绘图操作上画矩形、拉标注、批量改图层、把散落的图纸拼到一起。这些操作本身不难但架不住量大、繁琐、容易出错。你要是让一个刚入行的助理去改100张图的标注样式他改到第30张的时候眼睛已经花了改到第70张的时候基本是在凭肌肉记忆点鼠标。text-to-cad这个方向本质上就是想把这些一句话能说清楚的操作变成一句话就能执行的命令。它的核心价值不是替代设计师思考而是把那些不需要动脑子的重复劳动自动化掉。用我自己的话总结这个技术方向主要解决三类问题批量操作的效率问题。比如热词里提到的python批量对cad修改100张图纸统一改图层、改字体、改标注样式用传统方式逐张打开、修改、保存一个下午就没了。但如果能通过文字指令驱动脚本批量跑几分钟搞定。降低CAD的使用门槛。很多人不是不会画图而是记不住那些命令。什么BL在CASS里对应哪个功能、F命令用不了是因为什么这些细碎问题对老手不值一提但对新手就是劝退级别的障碍。自然语言指令能把这个门槛削掉一大截。跨软件协同的接口问题。CAD不是孤立存在的它要和CASS、Revit、SketchUp甚至Excel打交道。text-to-cad可以把这些跨软件操作统一成一个指令入口不用再记每个软件里不同的操作方式。为什么是现在这个时间点因为Python生态已经足够成熟了。不管你是直接用pyautocad这种库去驱动AutoCAD的COM接口还是用ezdxf这种库直接解析和生成DXF文件都有现成的工具和大量的案例可以参考。加上现在大语言模型对自然语言的理解能力越来越强把人话翻译成参数这一步也比以前靠谱得多。2. 第一步把“画一条直线”翻译成CAD能听懂的参数任何一个text-to-cad系统最核心的环节不是怎么操作CAD——那个反而是最不难的——而是怎么把乱七八糟的自然语言指令变成CAD能执行的精确参数。人说话是模糊的。我说在图的右下角画个螺丝孔的符号这句话人类能理解但CAD不行。CAD需要知道螺丝孔符号画在哪里具体坐标、多大直径和比例、放什么图层我习惯把符号放符号层、用什么颜色。如果你不把这些定死程序根本没法干活。我一开始的思路是纯规则解析也就是写一堆正则表达式和关键词匹配规则把句子里的信息抠出来。比如匹配到从x...到y...这种结构就识别为画线指令匹配到长方形或矩形就识别为矩形指令再把坐标和尺寸参数提出来。规则解析的问题很明显它只能处理你已经预先定义好的句式。用户说从0,0到100,50画条线它能听懂但用户如果说给我来一条从起点到右上角的直线它就傻眼了。起点和右上角这种相对概念需要额外去算。所以我在规则解析之上又加了一层简单的语义归一化。做法是维护一个同义词表把常见的口语表达映射到标准参数上。比如用户说程序理解右上角/右上方视口内最大X、最大Y偏移点从原点/从零点(0,0)画条直线/连线/拉一条线LINE命令跟这条线一样/按这个来复制上一条指令的参数这一层做好之后系统的鲁棒性会明显提升至少常见句式都能覆盖到。但说实话靠规则解析做到80%的覆盖率以后再往上堆的性价比就很低了。这时候就得考虑上大语言模型做翻译这个我在后面单独展开聊。2.1 指令解析的最小实现我先写了一个非常轻量的解析器核心就干两件事识别意图画线、画圆、加标注、改图层抽取参数坐标、长度、角度、半径。核心代码大概长这样import re class CADCommandParser: def __init__(self): self.intent_patterns { line: r(画|绘制|拉|连).{0,4}(线|直线)|line, circle: r(画|绘制).{0,4}(圆|圆圈)|circle, rect: r(画|绘制).{0,4}(矩形|长方形|方框)|rect, text: r(写|加|插入).{0,4}(文字|文本|字)|text, } self.coord_pattern re.compile( r(?:from|从)?\s*[\(]?\s* r(?Px1[-]?\d(?:\.\d)?)\s*[,]\s* r(?Py1[-]?\d(?:\.\d)?)\s*[\)]? r\s*(?:to|到|至|和|,|)?\s* r[\(]?\s*(?Px2[-]?\d(?:\.\d)?)\s*[,]\s* r(?Py2[-]?\d(?:\.\d)?)\s*[\)]? ) def parse(self, command_text: str) - dict: intent None for key, pattern in self.intent_patterns.items(): if re.search(pattern, command_text.lower()): intent key break if intent is None: return {success: False, reason: 无法识别意图} coords self.extract_coords(command_text) result { success: True, intent: intent, params: {} } if coords: result[params][start] (coords[0], coords[1]) result[params][end] (coords[2], coords[3]) return result def extract_coords(self, text: str): match self.coord_pattern.search(text) if match: return ( float(match.group(x1)), float(match.group(y1)), float(match.group(x2)), float(match.group(y2)) ) return None这个解析器很简单但已经能处理从(0,0)到(100,50)画一条直线、画圆这类基础指令。实际用下来覆盖率大概在40%左右。要把覆盖率提到90%以上还得做同义词表、上下文推断、历史参数引用这些增强。但这个雏形作为验证概念完全够用。3. 让Python真正驱动CAD三条路线我全试过了解析出指令参数只是第一步接下来要让CAD真正执行操作。这一步我前后试了三种方案各有各的适用场景。3.1 路线一pyautocad——最轻量也最脆弱pyautocad是对AutoCAD ActiveX接口的Python封装安装以后调用非常简单from pyautocad import Autocad, APoint acad Autocad(create_if_not_existsTrue) point APoint(0, 0) acad.model.AddLine(point, APoint(100, 50))就这几行就能在AutoCAD里画一条从(0,0)到(100,50)的直线。pyautocad的优点是门槛极低三分钟上手。缺点是它依赖AutoCAD已经打开或者能自动启动而且它是通过COM接口跟AutoCAD通讯的中间任何一个环节出问题——AutoCAD卡死、弹窗拦截、权限问题——脚本就直接挂了。我拿它做原型验证的时候还好但一旦开始跑批量的真实绘图任务稳定性就成了大问题。AutoCAD本来就时不时弹个对话框COM调用被弹窗挡一下整个脚本就卡在那里不动了。3.2 路线二pywin32直接调COM——稳定但要写更多代码pyautocad底层就是用pywin32调COM但封装层掩盖了很多细节。直接调COM的配置空间更大错误处理也更精细。import win32com.client import pythoncom pythoncom.CoInitialize() try: acad win32com.client.Dispatch(AutoCAD.Application) doc acad.ActiveDocument model doc.ModelSpace line model.AddLine( win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [0.0, 0.0, 0.0]), win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [100.0, 50.0, 0.0]) ) except Exception as e: print(fCAD调用失败: {e}) finally: pythoncom.CoUninitialize()注意这里坐标要用VARIANT数组传进去这是COM接口的硬性要求。很多人第一次写这个会直接在AddLine里传Python列表然后被报错搞得一头雾水。我的建议是直接封装一个辅助函数def make_point(x, y, z0): return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [float(x), float(y), float(z)]) def add_line(p1, p2, layerNone): line model.AddLine(make_point(*p1), make_point(*p2)) if layer: line.Layer layer return line这条路线的稳定性和可控性明显比pyautocad好。尤其当你的操作开始涉及图层管理、图块插入、标注样式设置这些更复杂的功能时COM接口最直接。3.3 路线三ezdxf——不进AutoCAD也能改图纸如果你不想依赖AutoCAD本体比如你只是要批量处理DXF文件或者你手上根本没装AutoCAD那ezdxf就是最优解。它直接读写DXF文件完全不碰CAD程序。import ezdxf doc ezdxf.readfile(input.dxf) msp doc.modelspace() msp.add_line((0, 0), (100, 50), dxfattribs{layer: 线条层}) doc.saveas(output.dxf)ezdxf处理批量修改特别顺手尤其是改图层、改字体、合并图纸这类不需要重新计算几何关系的操作。它的性能比COM方案好很多因为压根就不用启动AutoCAD纯文件级操作几十张图唰唰就处理完了。三条路线怎么选我现在的建议是使用场景推荐方案原型验证、单次小批量操作pyautocad完整工作流、依赖现有图纸环境pywin32 COM批量处理、无头环境、自动化流水线ezdxf实际项目里我是COM和ezdxf混合用的。批量流程用ezdxf需要实时操作当前打开的图纸、或者要调用AutoCAD特有的功能时就切回COM。4. text-to-cad最小系统实战把指令变成真正的图纸光有解析器和CAD驱动还不够得把它们串成一个完整可用的系统。我做了一个非常精简但能跑通全流程的版本整个过程分为四层。第一层是输入层接收用户的自然语言指令。第二层是解析层把自然语言转成结构化命令。第三层是执行层根据结构化命令调用对应的CAD驱动程序。第四层是反馈层把执行结果成功、失败、参数补充提醒翻译回人能看懂的描述。模块之间用JSON传递数据结构很简单{ intent: add_line, params: { start: [0, 0], end: [100, 50], layer: 默认, color: 1 } }这样一个全链路的text-to-cad最小实现就成型了。我拿真实场景测了一轮效果还过得去。比如输入把图里所有图层为临时的实体移到已完成图层程序就能通过ezdxf遍历所有实体过滤出图层为临时的实体改到已完成图层然后保存。再比如热词里提到的cad图纸合并。多张图纸要合并成一张我做了个指令def merge_drawings(input_files, output_file): merged ezdxf.new() for idx, f in enumerate(input_files): doc ezdxf.readfile(f) msp doc.modelspace() offset_x idx * 200 # 每张图在新图中横向偏移200个单位 for entity in msp: new_entity entity.copy() new_entity.translate(offset_x, 0, 0) merged.modelspace().add_entity(new_entity) merged.saveas(output_file)思路是把每张图的实体复制到一个新文档里然后通过平移避免图纸重叠。这个实现比较粗糙真实合并图纸还要考虑图框、比例、标注互相干扰的问题但作为text-to-cad的一个具体场景验证了思路是通的。5. 从能跑到能用我在实操中踩过的坑与对策做原型容易做到能放心交给同事用是另一回事。我在这个过程中踩了不少坑每一个都是文档里不会写、只有真正跑起来才会遇到的那种。我把最关键的几个列出来给后来人省点时间。5.1 坐标系与单位你以为的1就是1吗最典型的问题是单位。CAD图纸里1个单位到底是1毫米还是1英寸完全取决于图纸设置。如果用户的自然语言指令里说画一个100x50的矩形但没说明单位程序就只能用图纸当前的单位体系去解释。我的解决方案是在系统里增加一个单位声明环节强制用户明确单位。如果用户没说就默认使用图纸模板的单位设置并在反馈里提醒默认使用毫米单位。5.2 图层和线型的命名规范比你想的重要刚开始我在测试时图层全用代码里写死的名字比如Layer1Layer2。结果一接入真实项目图纸就出问题了——人家的图层叫框架梁-配筋机电-消防-喷淋。没有任何规则能猜到这些图层的命名逻辑只靠程序硬猜必然翻车。所以我现在在系统里内置了一层图层映射配置每个用户或每个项目都有独立的图层字典文件就是个JSON把自然语言里的结构层机电层装饰层映射到具体的CAD图层名。这个配置看起来笨但实际非常有效。5.3 异常恢复脚本跑一半CAD崩了怎么办批量处理时最怕的情况是前面20张图都处理成功了第21张图属性有问题CAD直接崩溃然后后面80张全部没处理。以前做普通脚本时对这种问题睁一只眼闭一只眼但text-to-cad系统是给人用的工具用户不可能接受这种半途而废。我的做法是增加一个检查点机制每处理完一个文件就写一条处理记录到日志。下次运行时先比对日志已经处理过的自动跳过只处理未完成的。另外每个文件处理前先备份原文件处理失败就一键回滚。5.4 输入的自然语言总是出乎意料最后必须承认纯规则解析的鲁棒性是有限的用户的实际表达总是超出你的预期。比如把那根线稍微延长一点规则解析完全处理不了稍微、一点这种模糊量词。对这些问题我开始引入大语言模型作为解析层的补充先用规则解析快速处理90%的标准指令剩下的模糊指令扔给LLM让LLM结合上下文输出结构化JSON。这是目前text-to-cad从玩具走向生产力工具的最关键一步。我给LLM写的系统提示词大致是你是CAD指令解析器。把用户的自然语言转换为JSON指令。 指令格式 {intent: add_line|add_circle|change_layer|..., params: {...}} 如果用户指令中的参数不完整在params中用null标记缺失项。实测下来LLM对模糊表达的处理能力远超规则解析。比如把最后画的线变成红色LLM能根据上下文推断出最后画的线指的是上一条指令创建的实体并输出带实体ID的修改指令。这种上下文推断能力是传统解析器完全做不到的。6. 下一步text-to-cad能做到什么程度这个系统目前在我的工作流里已经能承担相当一部分重复性劳动了。但我心里很清楚距离真正用自然语言自由操控CAD还有很长一段路。眼下最现实的扩展方向有三个。方向一是语义理解向设计意图延伸。现在做的是理解指令下一步可以尝试理解意图。比如帮我排一下这面墙的插座系统需要的不仅是简单的指令翻译还需要理解插座布置的基本逻辑间距、高度、避开柱位)。这已经接近AI辅助设计了虽然不容易但值得探索。方向二是和设计规范结合做自动检查。既然text-to-cad能解析并操作图纸实体那反过来它也能检查图纸里有没有违反规范的地方比如标注有没有重叠、图层有没有用错、线型是不是统一。我计划下一步在这个方向上加点东西把检查逻辑也做成自然语言驱动检查一下这张图里所有字体是不是都是HZ这种指令。方向三是把语音输入加进来。CAD操作本身有两个痛点一是双手离不开键盘鼠标二是眼睛离不开屏幕。如果text-to-cad的入口能接上语音识别设计师就能一边看图一边说把这个改成虚线——这对现场修改图纸的场景会非常友好。我做这个项目最大的体会是text-to-cad不是要取代设计师而是要把设计师从低效的重复操作里解放出来。它的核心价值在于把想法和操作之间的距离压缩到最短——在CAD里想是一句话的事做却要几十次点击。text-to-cad就是把这几十次点击压缩回一句话。