
我做設(shè)計(jì)這行快十年了每天跟CAD打交道的時(shí)間比跟家人說話都多。這兩年AI工具一堆接一堆冒出來但老實(shí)說真正能落進(jìn)日常工作流、不扯淡的少。前段時(shí)間我一直在琢磨一個(gè)事既然現(xiàn)在大語言模型能聽懂人話了那能不能讓CAD也聽懂人話比如我敲一句在這張圖里畫一條從原點(diǎn)出發(fā)、角度30度、長(zhǎng)度100的線圖紙上就真的出現(xiàn)這條線。這個(gè)想法聽起來像是科幻片但實(shí)際上做個(gè)能跑的text-to-cad工具真沒有想象中那么玄乎。這篇文章就是把我從零搭起這個(gè)系統(tǒng)的完整過程、踩過的坑、還有最終跑通的效果都記錄下來給想在這條路上試試水的朋友一個(gè)參考。1. text-to-cad到底在解決什么問題先說個(gè)最直接的場(chǎng)景。我見過太多設(shè)計(jì)師和工程師每天有一大堆時(shí)間花在重復(fù)性的繪圖操作上畫矩形、拉標(biāo)注、批量改圖層、把散落的圖紙拼到一起。這些操作本身不難但架不住量大、繁瑣、容易出錯(cuò)。你要是讓一個(gè)剛?cè)胄械闹砣ジ?00張圖的標(biāo)注樣式他改到第30張的時(shí)候眼睛已經(jīng)花了改到第70張的時(shí)候基本是在憑肌肉記憶點(diǎn)鼠標(biāo)。text-to-cad這個(gè)方向本質(zhì)上就是想把這些一句話能說清楚的操作變成一句話就能執(zhí)行的命令。它的核心價(jià)值不是替代設(shè)計(jì)師思考而是把那些不需要?jiǎng)幽X子的重復(fù)勞動(dòng)自動(dòng)化掉。用我自己的話總結(jié)這個(gè)技術(shù)方向主要解決三類問題批量操作的效率問題。比如熱詞里提到的python批量對(duì)cad修改100張圖紙統(tǒng)一改圖層、改字體、改標(biāo)注樣式用傳統(tǒng)方式逐張打開、修改、保存一個(gè)下午就沒了。但如果能通過文字指令驅(qū)動(dòng)腳本批量跑幾分鐘搞定。降低CAD的使用門檻。很多人不是不會(huì)畫圖而是記不住那些命令。什么BL在CASS里對(duì)應(yīng)哪個(gè)功能、F命令用不了是因?yàn)槭裁催@些細(xì)碎問題對(duì)老手不值一提但對(duì)新手就是勸退級(jí)別的障礙。自然語言指令能把這個(gè)門檻削掉一大截??畿浖f(xié)同的接口問題。CAD不是孤立存在的它要和CASS、Revit、SketchUp甚至Excel打交道。text-to-cad可以把這些跨軟件操作統(tǒng)一成一個(gè)指令入口不用再記每個(gè)軟件里不同的操作方式。為什么是現(xiàn)在這個(gè)時(shí)間點(diǎn)因?yàn)镻ython生態(tài)已經(jīng)足夠成熟了。不管你是直接用pyautocad這種庫(kù)去驅(qū)動(dòng)AutoCAD的COM接口還是用ezdxf這種庫(kù)直接解析和生成DXF文件都有現(xiàn)成的工具和大量的案例可以參考。加上現(xiàn)在大語言模型對(duì)自然語言的理解能力越來越強(qiáng)把人話翻譯成參數(shù)這一步也比以前靠譜得多。2. 第一步把“畫一條直線”翻譯成CAD能聽懂的參數(shù)任何一個(gè)text-to-cad系統(tǒng)最核心的環(huán)節(jié)不是怎么操作CAD——那個(gè)反而是最不難的——而是怎么把亂七八糟的自然語言指令變成CAD能執(zhí)行的精確參數(shù)。人說話是模糊的。我說在圖的右下角畫個(gè)螺絲孔的符號(hào)這句話人類能理解但CAD不行。CAD需要知道螺絲孔符號(hào)畫在哪里具體坐標(biāo)、多大直徑和比例、放什么圖層我習(xí)慣把符號(hào)放符號(hào)層、用什么顏色。如果你不把這些定死程序根本沒法干活。我一開始的思路是純規(guī)則解析也就是寫一堆正則表達(dá)式和關(guān)鍵詞匹配規(guī)則把句子里的信息摳出來。比如匹配到從x...到y(tǒng)...這種結(jié)構(gòu)就識(shí)別為畫線指令匹配到長(zhǎng)方形或矩形就識(shí)別為矩形指令再把坐標(biāo)和尺寸參數(shù)提出來。規(guī)則解析的問題很明顯它只能處理你已經(jīng)預(yù)先定義好的句式。用戶說從0,0到100,50畫條線它能聽懂但用戶如果說給我來一條從起點(diǎn)到右上角的直線它就傻眼了。起點(diǎn)和右上角這種相對(duì)概念需要額外去算。所以我在規(guī)則解析之上又加了一層簡(jiǎn)單的語義歸一化。做法是維護(hù)一個(gè)同義詞表把常見的口語表達(dá)映射到標(biāo)準(zhǔn)參數(shù)上。比如用戶說程序理解右上角/右上方視口內(nèi)最大X、最大Y偏移點(diǎn)從原點(diǎn)/從零點(diǎn)(0,0)畫條直線/連線/拉一條線LINE命令跟這條線一樣/按這個(gè)來復(fù)制上一條指令的參數(shù)這一層做好之后系統(tǒng)的魯棒性會(huì)明顯提升至少常見句式都能覆蓋到。但說實(shí)話靠規(guī)則解析做到80%的覆蓋率以后再往上堆的性價(jià)比就很低了。這時(shí)候就得考慮上大語言模型做翻譯這個(gè)我在后面單獨(dú)展開聊。2.1 指令解析的最小實(shí)現(xiàn)我先寫了一個(gè)非常輕量的解析器核心就干兩件事識(shí)別意圖畫線、畫圓、加標(biāo)注、改圖層抽取參數(shù)坐標(biāo)、長(zhǎng)度、角度、半徑。核心代碼大概長(zhǎng)這樣import re class CADCommandParser: def __init__(self): self.intent_patterns { line: r(畫|繪制|拉|連).{0,4}(線|直線)|line, circle: r(畫|繪制).{0,4}(圓|圓圈)|circle, rect: r(畫|繪制).{0,4}(矩形|長(zhǎng)方形|方框)|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: 無法識(shí)別意圖} 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這個(gè)解析器很簡(jiǎn)單但已經(jīng)能處理從(0,0)到(100,50)畫一條直線、畫圓這類基礎(chǔ)指令。實(shí)際用下來覆蓋率大概在40%左右。要把覆蓋率提到90%以上還得做同義詞表、上下文推斷、歷史參數(shù)引用這些增強(qiáng)。但這個(gè)雛形作為驗(yàn)證概念完全夠用。3. 讓Python真正驅(qū)動(dòng)CAD三條路線我全試過了解析出指令參數(shù)只是第一步接下來要讓CAD真正執(zhí)行操作。這一步我前后試了三種方案各有各的適用場(chǎng)景。3.1 路線一pyautocad——最輕量也最脆弱pyautocad是對(duì)AutoCAD ActiveX接口的Python封裝安裝以后調(diào)用非常簡(jiǎn)單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的優(yōu)點(diǎn)是門檻極低三分鐘上手。缺點(diǎn)是它依賴AutoCAD已經(jīng)打開或者能自動(dòng)啟動(dòng)而且它是通過COM接口跟AutoCAD通訊的中間任何一個(gè)環(huán)節(jié)出問題——AutoCAD卡死、彈窗攔截、權(quán)限問題——腳本就直接掛了。我拿它做原型驗(yàn)證的時(shí)候還好但一旦開始跑批量的真實(shí)繪圖任務(wù)穩(wěn)定性就成了大問題。AutoCAD本來就時(shí)不時(shí)彈個(gè)對(duì)話框COM調(diào)用被彈窗擋一下整個(gè)腳本就卡在那里不動(dòng)了。3.2 路線二pywin32直接調(diào)COM——穩(wěn)定但要寫更多代碼pyautocad底層就是用pywin32調(diào)COM但封裝層掩蓋了很多細(xì)節(jié)。直接調(diào)COM的配置空間更大錯(cuò)誤處理也更精細(xì)。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調(diào)用失敗: {e}) finally: pythoncom.CoUninitialize()注意這里坐標(biāo)要用VARIANT數(shù)組傳進(jìn)去這是COM接口的硬性要求。很多人第一次寫這個(gè)會(huì)直接在AddLine里傳Python列表然后被報(bào)錯(cuò)搞得一頭霧水。我的建議是直接封裝一個(gè)輔助函數(shù)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這條路線的穩(wěn)定性和可控性明顯比pyautocad好。尤其當(dāng)你的操作開始涉及圖層管理、圖塊插入、標(biāo)注樣式設(shè)置這些更復(fù)雜的功能時(shí)COM接口最直接。3.3 路線三ezdxf——不進(jìn)AutoCAD也能改圖紙如果你不想依賴AutoCAD本體比如你只是要批量處理DXF文件或者你手上根本沒裝AutoCAD那ezdxf就是最優(yōu)解。它直接讀寫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處理批量修改特別順手尤其是改圖層、改字體、合并圖紙這類不需要重新計(jì)算幾何關(guān)系的操作。它的性能比COM方案好很多因?yàn)閴焊筒挥脝?dòng)AutoCAD純文件級(jí)操作幾十張圖唰唰就處理完了。三條路線怎么選我現(xiàn)在的建議是使用場(chǎng)景推薦方案原型驗(yàn)證、單次小批量操作pyautocad完整工作流、依賴現(xiàn)有圖紙環(huán)境pywin32 COM批量處理、無頭環(huán)境、自動(dòng)化流水線ezdxf實(shí)際項(xiàng)目里我是COM和ezdxf混合用的。批量流程用ezdxf需要實(shí)時(shí)操作當(dāng)前打開的圖紙、或者要調(diào)用AutoCAD特有的功能時(shí)就切回COM。4. text-to-cad最小系統(tǒng)實(shí)戰(zhàn)把指令變成真正的圖紙光有解析器和CAD驅(qū)動(dòng)還不夠得把它們串成一個(gè)完整可用的系統(tǒng)。我做了一個(gè)非常精簡(jiǎn)但能跑通全流程的版本整個(gè)過程分為四層。第一層是輸入層接收用戶的自然語言指令。第二層是解析層把自然語言轉(zhuǎn)成結(jié)構(gòu)化命令。第三層是執(zhí)行層根據(jù)結(jié)構(gòu)化命令調(diào)用對(duì)應(yīng)的CAD驅(qū)動(dòng)程序。第四層是反饋層把執(zhí)行結(jié)果成功、失敗、參數(shù)補(bǔ)充提醒翻譯回人能看懂的描述。模塊之間用JSON傳遞數(shù)據(jù)結(jié)構(gòu)很簡(jiǎn)單{ intent: add_line, params: { start: [0, 0], end: [100, 50], layer: 默認(rèn), color: 1 } }這樣一個(gè)全鏈路的text-to-cad最小實(shí)現(xiàn)就成型了。我拿真實(shí)場(chǎng)景測(cè)了一輪效果還過得去。比如輸入把圖里所有圖層為臨時(shí)的實(shí)體移到已完成圖層程序就能通過ezdxf遍歷所有實(shí)體過濾出圖層為臨時(shí)的實(shí)體改到已完成圖層然后保存。再比如熱詞里提到的cad圖紙合并。多張圖紙要合并成一張我做了個(gè)指令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個(gè)單位 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)思路是把每張圖的實(shí)體復(fù)制到一個(gè)新文檔里然后通過平移避免圖紙重疊。這個(gè)實(shí)現(xiàn)比較粗糙真實(shí)合并圖紙還要考慮圖框、比例、標(biāo)注互相干擾的問題但作為text-to-cad的一個(gè)具體場(chǎng)景驗(yàn)證了思路是通的。5. 從能跑到能用我在實(shí)操中踩過的坑與對(duì)策做原型容易做到能放心交給同事用是另一回事。我在這個(gè)過程中踩了不少坑每一個(gè)都是文檔里不會(huì)寫、只有真正跑起來才會(huì)遇到的那種。我把最關(guān)鍵的幾個(gè)列出來給后來人省點(diǎn)時(shí)間。5.1 坐標(biāo)系與單位你以為的1就是1嗎最典型的問題是單位。CAD圖紙里1個(gè)單位到底是1毫米還是1英寸完全取決于圖紙?jiān)O(shè)置。如果用戶的自然語言指令里說畫一個(gè)100x50的矩形但沒說明單位程序就只能用圖紙當(dāng)前的單位體系去解釋。我的解決方案是在系統(tǒng)里增加一個(gè)單位聲明環(huán)節(jié)強(qiáng)制用戶明確單位。如果用戶沒說就默認(rèn)使用圖紙模板的單位設(shè)置并在反饋里提醒默認(rèn)使用毫米單位。5.2 圖層和線型的命名規(guī)范比你想的重要?jiǎng)傞_始我在測(cè)試時(shí)圖層全用代碼里寫死的名字比如Layer1Layer2。結(jié)果一接入真實(shí)項(xiàng)目圖紙就出問題了——人家的圖層叫框架梁-配筋機(jī)電-消防-噴淋。沒有任何規(guī)則能猜到這些圖層的命名邏輯只靠程序硬猜必然翻車。所以我現(xiàn)在在系統(tǒng)里內(nèi)置了一層圖層映射配置每個(gè)用戶或每個(gè)項(xiàng)目都有獨(dú)立的圖層字典文件就是個(gè)JSON把自然語言里的結(jié)構(gòu)層機(jī)電層裝飾層映射到具體的CAD圖層名。這個(gè)配置看起來笨但實(shí)際非常有效。5.3 異常恢復(fù)腳本跑一半CAD崩了怎么辦批量處理時(shí)最怕的情況是前面20張圖都處理成功了第21張圖屬性有問題CAD直接崩潰然后后面80張全部沒處理。以前做普通腳本時(shí)對(duì)這種問題睜一只眼閉一只眼但text-to-cad系統(tǒng)是給人用的工具用戶不可能接受這種半途而廢。我的做法是增加一個(gè)檢查點(diǎn)機(jī)制每處理完一個(gè)文件就寫一條處理記錄到日志。下次運(yùn)行時(shí)先比對(duì)日志已經(jīng)處理過的自動(dòng)跳過只處理未完成的。另外每個(gè)文件處理前先備份原文件處理失敗就一鍵回滾。5.4 輸入的自然語言總是出乎意料最后必須承認(rèn)純規(guī)則解析的魯棒性是有限的用戶的實(shí)際表達(dá)總是超出你的預(yù)期。比如把那根線稍微延長(zhǎng)一點(diǎn)規(guī)則解析完全處理不了稍微、一點(diǎn)這種模糊量詞。對(duì)這些問題我開始引入大語言模型作為解析層的補(bǔ)充先用規(guī)則解析快速處理90%的標(biāo)準(zhǔn)指令剩下的模糊指令扔給LLM讓LLM結(jié)合上下文輸出結(jié)構(gòu)化JSON。這是目前text-to-cad從玩具走向生產(chǎn)力工具的最關(guān)鍵一步。我給LLM寫的系統(tǒng)提示詞大致是你是CAD指令解析器。把用戶的自然語言轉(zhuǎn)換為JSON指令。 指令格式 {intent: add_line|add_circle|change_layer|..., params: {...}} 如果用戶指令中的參數(shù)不完整在params中用null標(biāo)記缺失項(xiàng)。實(shí)測(cè)下來LLM對(duì)模糊表達(dá)的處理能力遠(yuǎn)超規(guī)則解析。比如把最后畫的線變成紅色LLM能根據(jù)上下文推斷出最后畫的線指的是上一條指令創(chuàng)建的實(shí)體并輸出帶實(shí)體ID的修改指令。這種上下文推斷能力是傳統(tǒng)解析器完全做不到的。6. 下一步text-to-cad能做到什么程度這個(gè)系統(tǒng)目前在我的工作流里已經(jīng)能承擔(dān)相當(dāng)一部分重復(fù)性勞動(dòng)了。但我心里很清楚距離真正用自然語言自由操控CAD還有很長(zhǎng)一段路。眼下最現(xiàn)實(shí)的擴(kuò)展方向有三個(gè)。方向一是語義理解向設(shè)計(jì)意圖延伸?,F(xiàn)在做的是理解指令下一步可以嘗試?yán)斫庖鈭D。比如幫我排一下這面墻的插座系統(tǒng)需要的不僅是簡(jiǎn)單的指令翻譯還需要理解插座布置的基本邏輯間距、高度、避開柱位)。這已經(jīng)接近AI輔助設(shè)計(jì)了雖然不容易但值得探索。方向二是和設(shè)計(jì)規(guī)范結(jié)合做自動(dòng)檢查。既然text-to-cad能解析并操作圖紙實(shí)體那反過來它也能檢查圖紙里有沒有違反規(guī)范的地方比如標(biāo)注有沒有重疊、圖層有沒有用錯(cuò)、線型是不是統(tǒng)一。我計(jì)劃下一步在這個(gè)方向上加點(diǎn)東西把檢查邏輯也做成自然語言驅(qū)動(dòng)檢查一下這張圖里所有字體是不是都是HZ這種指令。方向三是把語音輸入加進(jìn)來。CAD操作本身有兩個(gè)痛點(diǎn)一是雙手離不開鍵盤鼠標(biāo)二是眼睛離不開屏幕。如果text-to-cad的入口能接上語音識(shí)別設(shè)計(jì)師就能一邊看圖一邊說把這個(gè)改成虛線——這對(duì)現(xiàn)場(chǎng)修改圖紙的場(chǎng)景會(huì)非常友好。我做這個(gè)項(xiàng)目最大的體會(huì)是text-to-cad不是要取代設(shè)計(jì)師而是要把設(shè)計(jì)師從低效的重復(fù)操作里解放出來。它的核心價(jià)值在于把想法和操作之間的距離壓縮到最短——在CAD里想是一句話的事做卻要幾十次點(diǎn)擊。text-to-cad就是把這幾十次點(diǎn)擊壓縮回一句話。