戰(zhàn):從自然語言到STEP/URDF的完整鏈路)
1. 從一句話到三維模型text-to-cad 到底在解決什么問題第一次聽到 “text-to-cad” 這個(gè)詞很多人腦子里浮現(xiàn)的畫面大概是對(duì)著電腦說一句“給我畫個(gè)齒輪”屏幕上就自動(dòng)蹦出一個(gè)帶參數(shù)的三維模型。這個(gè)想象不算離譜但也不完全準(zhǔn)確。text-to-cad 本質(zhì)上是一套把自然語言描述轉(zhuǎn)換成結(jié)構(gòu)化 CAD 數(shù)據(jù)的技術(shù)鏈路它的輸出通常不是某個(gè)私有格式的圖紙而是像 STEP、URDF 這類通用、可被下游工具繼續(xù)消費(fèi)的中間格式。我在實(shí)際項(xiàng)目里接觸這個(gè)方向最初是因?yàn)橐粋€(gè)很具體的痛點(diǎn)團(tuán)隊(duì)里做機(jī)械設(shè)計(jì)的同事和做仿真、做機(jī)器人算法的同事中間隔著一道“格式墻”。設(shè)計(jì)端給過來的是 STEP仿真端要的是 URDF中間還得有人手動(dòng)重建一遍關(guān)節(jié)、坐標(biāo)系、連桿關(guān)系。這個(gè)過程又慢又容易出錯(cuò)一個(gè)尺寸標(biāo)錯(cuò)后面整條鏈路都得返工。text-to-cad 想干的事情就是讓“描述”直接變成“可用的模型數(shù)據(jù)”把中間那段重復(fù)勞動(dòng)壓縮掉。它適合誰來參考如果你是從業(yè)者比如做機(jī)器人仿真、做參數(shù)化設(shè)計(jì)、做自動(dòng)化建模工具鏈那這套思路能幫你省掉大量手工建模時(shí)間。如果你是剛?cè)腴T Python、對(duì) CAD 感興趣的新手那它也是一個(gè)非常好的練手項(xiàng)目——因?yàn)樗炎匀徽Z言處理、幾何建模、文件格式轉(zhuǎn)換這幾個(gè)知識(shí)點(diǎn)串成了一條完整的線做完一遍你對(duì)“數(shù)據(jù)怎么在工具之間流動(dòng)”會(huì)有完全不一樣的理解。需要先說明一點(diǎn)text-to-cad 不是一個(gè)開箱即用的成品軟件它更像一個(gè)技術(shù)方向或者項(xiàng)目骨架。市面上有一些商業(yè)工具在做類似的事但真正落到自己的業(yè)務(wù)場(chǎng)景里往往需要自己搭一套流程。下面我就按我實(shí)際踩過的路把這條鏈路拆開講清楚。2. 整體架構(gòu)設(shè)計(jì)為什么是“文本 → 中間表示 → CAD 文件”這條鏈路2.1 核心思路不要讓大模型直接吐 STEP很多人第一反應(yīng)是既然有大語言模型那直接讓它輸出 STEP 文件內(nèi)容不就行了我試過結(jié)論是——不靠譜。STEP 是一種基于 ISO 10303 標(biāo)準(zhǔn)的文本格式里面有大量的實(shí)體定義、坐標(biāo)系變換、拓?fù)潢P(guān)系語法極其嚴(yán)格。讓模型直接生成幾百行 STEP稍微一個(gè)括號(hào)或者參數(shù)錯(cuò)位整個(gè)文件就打不開而且排查起來非常痛苦因?yàn)?STEP 的報(bào)錯(cuò)信息通常只告訴你“第幾行解析失敗”不會(huì)告訴你幾何哪里錯(cuò)了。所以更穩(wěn)的做法是引入一個(gè)中間表示層。我的方案是自然語言 → 結(jié)構(gòu)化參數(shù)JSON→ 用代碼生成幾何 → 導(dǎo)出 STEP/URDF。這個(gè)中間層的好處是每一段都可以單獨(dú)驗(yàn)證。文本解析錯(cuò)了看 JSON 就知道JSON 對(duì)了但模型不對(duì)那就是幾何生成代碼的問題。責(zé)任邊界清晰調(diào)試成本大幅下降。這個(gè)思路其實(shí)和編譯器很像源代碼不會(huì)直接變成機(jī)器碼中間要經(jīng)過詞法分析、語法分析、中間代碼生成。text-to-cad 的“中間代碼”就是那份結(jié)構(gòu)化的參數(shù)描述。2.2 技術(shù)選型Python 生態(tài)里的幾個(gè)關(guān)鍵角色選 Python 幾乎是必然的。原因很簡(jiǎn)單CAD 相關(guān)的開源庫(kù)、自然語言處理的庫(kù)、文件格式轉(zhuǎn)換的庫(kù)Python 生態(tài)最全。具體來說我用的組合是這樣的自然語言解析用大語言模型的 API 做意圖識(shí)別和參數(shù)抽取輸出 JSON。這一步也可以用本地的規(guī)則引擎做但泛化能力差很多。幾何建模CadQuery或者build123d。這兩個(gè)庫(kù)都是基于 OpenCASCADE 的能用代碼描述幾何體而且原生支持導(dǎo)出 STEP。機(jī)器人描述如果要生成 URDF用urdfpy或者直接手寫 XML 模板。URDF 本質(zhì)上是 XML結(jié)構(gòu)比 STEP 簡(jiǎn)單得多手寫模板反而更可控。輔助計(jì)算numpy做矩陣運(yùn)算trimesh做網(wǎng)格處理cv2偶爾用來處理圖紙截圖如果輸入是圖片而不是純文本。這里要特別說一下 CadQuery 和 build123d 的選擇。CadQuery 更成熟文檔多社區(qū)大build123d 是后來者API 設(shè)計(jì)更現(xiàn)代更接近“用代碼畫圖”的直覺。如果你是新手我建議從 CadQuery 入手因?yàn)橛龅絾栴}更容易搜到答案。如果你已經(jīng)熟悉了參數(shù)化建模的思路build123d 寫起來會(huì)更順手。2.3 為什么輸出格式選 STEP 和 URDFSTEP 是 CAD 領(lǐng)域的“通用語”。幾乎所有的機(jī)械設(shè)計(jì)軟件都能打開 STEP它記錄的是精確的邊界表示B-Rep不是網(wǎng)格所以放大不會(huì)失真。如果你要把模型交給別人繼續(xù)做設(shè)計(jì)STEP 是首選。URDF 則是機(jī)器人領(lǐng)域的“通用語”。它描述的是連桿、關(guān)節(jié)、坐標(biāo)系之間的關(guān)系本質(zhì)上是運(yùn)動(dòng)學(xué)模型不是幾何模型。URDF 里可以引用 STL 或 DAE 作為視覺網(wǎng)格但它本身不包含精確幾何。所以這兩個(gè)格式面向的是不同的下游需求STEP 給設(shè)計(jì)端URDF 給仿真端。text-to-cad 如果能把這兩個(gè)都生成出來那它就能同時(shí)打通兩條鏈路。這也是我在項(xiàng)目里堅(jiān)持要支持雙格式輸出的原因。3. 核心細(xì)節(jié)拆解從文本到參數(shù)的每一步3.1 文本解析怎么讓模型穩(wěn)定輸出 JSON這一步是整個(gè)鏈路里最“玄學(xué)”的部分因?yàn)榇笳Z言模型的輸出有隨機(jī)性。我的做法是用函數(shù)調(diào)用function calling或者結(jié)構(gòu)化輸出模式強(qiáng)制模型按照預(yù)定義的 schema 返回 JSON。如果用的模型不支持結(jié)構(gòu)化輸出那就退而求其次在 prompt 里給出嚴(yán)格的 JSON 示例并且在解析的時(shí)候做容錯(cuò)。一個(gè)典型的參數(shù) schema 長(zhǎng)這樣{ object_type: gear, parameters: { module: 2.0, teeth: 20, thickness: 10.0, bore_diameter: 8.0 }, units: mm }這里有幾個(gè)經(jīng)驗(yàn)點(diǎn)。第一單位必須顯式聲明。我踩過一次坑模型默認(rèn)用了英寸我以為是毫米結(jié)果生成的模型大了 25.4 倍。第二參數(shù)名要標(biāo)準(zhǔn)化。不要用“齒數(shù)”“齒的數(shù)量”這種自然語言統(tǒng)一成teeth。第三給默認(rèn)值。如果用戶沒說厚度schema 里要有默認(rèn)值否則模型可能返回 null后面代碼就崩了。提示在 prompt 里明確告訴模型“如果某個(gè)參數(shù)沒有提到使用默認(rèn)值并在輸出中標(biāo)記 is_default: true”這樣后續(xù)可以提示用戶確認(rèn)。3.2 幾何生成用代碼描述形狀的邏輯拿到 JSON 之后下一步是把它變成幾何體。以齒輪為例用 CadQuery 寫大概是這樣的import cadquery as cq import math def make_gear(module, teeth, thickness, bore_diameter): pitch_radius module * teeth / 2.0 outer_radius pitch_radius module root_radius pitch_radius - 1.25 * module # 簡(jiǎn)化版齒形實(shí)際項(xiàng)目需要用漸開線 result ( cq.Workplane(XY) .circle(outer_radius) .extrude(thickness) .faces(Z) .workplane() .hole(bore_diameter) ) return result這段代碼是簡(jiǎn)化版真實(shí)的漸開線齒形要復(fù)雜得多需要用到齒廓方程。但這里想說明的是幾何生成代碼是可測(cè)試的。你可以寫單元測(cè)試給定一組參數(shù)檢查生成的體積、包圍盒尺寸是否符合預(yù)期。這比直接檢查 STEP 文件內(nèi)容要可靠得多。對(duì)于 URDF邏輯不太一樣。URDF 描述的是連桿和關(guān)節(jié)所以文本解析出來的應(yīng)該是“有幾個(gè)連桿”“關(guān)節(jié)類型是什么”“關(guān)節(jié)軸朝向哪里”。生成的時(shí)候用模板填充urdf_template ?xml version1.0? robot name{name} link namebase_link visual geometry box size{length} {width} {height}/ /geometry /visual /link /robot URDF 的坑在于坐標(biāo)系。每個(gè) link 都有自己的坐標(biāo)系joint 的 origin 是相對(duì)于 parent link 的。如果 origin 寫錯(cuò)了模型在仿真里會(huì)飛到奇怪的位置。我的經(jīng)驗(yàn)是先在紙上畫清楚坐標(biāo)系樹再寫代碼。不要一邊想一邊寫那樣很容易亂。3.3 文件導(dǎo)出STEP 和 URDF 的注意事項(xiàng)導(dǎo)出 STEP 用 CadQuery 的exportStep就行但要注意版本。不同版本的 OpenCASCADE 導(dǎo)出的 STEP 在兼容性上略有差異。如果下游用的是比較老的 CAD 軟件建議導(dǎo)出 AP214 而不是 AP242。URDF 導(dǎo)出更簡(jiǎn)單就是寫 XML 文件。但有一個(gè)容易忽略的點(diǎn)mesh 文件的路徑。URDF 里引用的 STL 或 DAE 文件路徑可以是相對(duì)路徑也可以是絕對(duì)路徑。如果是要分發(fā)給別人一定要用相對(duì)路徑并且把 mesh 文件和 URDF 放在同一個(gè)包目錄下。注意URDF 本身不包含幾何只包含運(yùn)動(dòng)學(xué)。如果你只導(dǎo)出 URDF 而不導(dǎo)出 mesh在仿真里看到的就是一堆線框。所以完整的輸出應(yīng)該是 URDF STL/DAE 網(wǎng)格文件。4. 實(shí)操過程搭一套能跑通的 text-to-cad 流水線4.1 環(huán)境準(zhǔn)備與依賴安裝先把環(huán)境搭起來。我習(xí)慣用虛擬環(huán)境避免污染系統(tǒng) Pythonpython -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows然后安裝核心依賴pip install cadquery build123d numpy trimesh urdfpy如果你要用大語言模型做文本解析還需要安裝對(duì)應(yīng)的 SDK。這里不指定具體廠商因?yàn)楦骷?API 差異較大按官方文檔來就行。安裝 CadQuery 的時(shí)候可能會(huì)遇到編譯問題因?yàn)樗蕾?OpenCASCADE。在 Linux 上通常沒問題在 Windows 上建議直接用 conda 安裝conda install -c conda-forge cadquery4.2 完整流程代碼框架下面是一個(gè)最小可運(yùn)行的框架把整條鏈路串起來import json import cadquery as cq def parse_text_to_params(text): # 這里調(diào)用大語言模型返回結(jié)構(gòu)化參數(shù) # 實(shí)際項(xiàng)目中替換成真實(shí)的 API 調(diào)用 params { object_type: box, parameters: { length: 50.0, width: 30.0, height: 20.0 }, units: mm } return params def generate_geometry(params): obj_type params[object_type] p params[parameters] if obj_type box: result ( cq.Workplane(XY) .box(p[length], p[width], p[height]) ) elif obj_type cylinder: result ( cq.Workplane(XY) .circle(p[radius]) .extrude(p[height]) ) else: raise ValueError(fUnsupported object type: {obj_type}) return result def export_step(model, filepath): cq.exporters.export(model, filepath) print(fExported STEP to {filepath}) if __name__ __main__: text 生成一個(gè)長(zhǎng)50毫米、寬30毫米、高20毫米的盒子 params parse_text_to_params(text) print(Parsed params:, json.dumps(params, indent2)) model generate_geometry(params) export_step(model, output.step)這個(gè)框架跑通之后你就可以逐步替換里面的模塊。比如把parse_text_to_params換成真實(shí)的大模型調(diào)用把generate_geometry擴(kuò)展成支持更多形狀。4.3 參數(shù)計(jì)算以齒輪為例的完整推導(dǎo)齒輪是一個(gè)很好的例子因?yàn)樗婕岸鄠€(gè)參數(shù)之間的數(shù)學(xué)關(guān)系。假設(shè)用戶說“生成一個(gè)模數(shù)2、20個(gè)齒、厚度10毫米的齒輪”我們需要計(jì)算分度圓直徑( d m \times z 2 \times 20 40 ) mm齒頂圓直徑( d_a d 2m 40 4 44 ) mm齒根圓直徑( d_f d - 2.5m 40 - 5 35 ) mm這些計(jì)算必須在代碼里完成不能依賴模型去算。模型可能會(huì)算錯(cuò)但代碼不會(huì)。所以我的原則是模型只負(fù)責(zé)抽取參數(shù)所有計(jì)算交給代碼。提示如果用戶給的參數(shù)不足以確定形狀比如只說了“一個(gè)齒輪”沒說齒數(shù)那就要在解析階段返回一個(gè)“參數(shù)不完整”的狀態(tài)讓用戶補(bǔ)充。不要自己瞎猜。5. 常見問題與排查技巧實(shí)錄5.1 模型輸出格式不對(duì)怎么辦這是最常見的問題。模型可能返回 Markdown 代碼塊包裹的 JSON也可能在 JSON 前后加一堆解釋文字。解決辦法有兩個(gè)一是用結(jié)構(gòu)化輸出模式從源頭約束二是在解析前做清洗用正則把 JSON 部分提取出來。import re import json def extract_json(text): # 嘗試直接解析 try: return json.loads(text) except json.JSONDecodeError: pass # 嘗試提取代碼塊中的 JSON match re.search(r(?:json)?\s*(\{.*?\})\s*, text, re.DOTALL) if match: return json.loads(match.group(1)) # 嘗試提取第一個(gè)完整的 JSON 對(duì)象 match re.search(r\{.*\}, text, re.DOTALL) if match: return json.loads(match.group(0)) raise ValueError(No valid JSON found in model output)5.2 STEP 文件打不開的排查思路如果導(dǎo)出的 STEP 在 CAD 軟件里打不開按這個(gè)順序排查排查項(xiàng)可能原因解決方法文件大小0 字節(jié)或異常小檢查幾何生成是否報(bào)錯(cuò)版本兼容AP242 太新改用 AP214 導(dǎo)出幾何有效性自相交、零厚度用model.val().isValid()檢查單位問題尺寸異常大或小確認(rèn)導(dǎo)出時(shí)的單位設(shè)置編碼問題中文路徑改用英文路徑測(cè)試我遇到最多的是幾何有效性問題。CadQuery 生成的模型偶爾會(huì)有自相交的面尤其是在做布爾運(yùn)算之后。解決辦法是在導(dǎo)出前做一次clean()model model.clean()5.3 URDF 在仿真里表現(xiàn)異常的排查URDF 的問題通常出在坐標(biāo)系和慣性參數(shù)上。如果模型在仿真里抖動(dòng)、飛走、或者穿模先檢查這幾個(gè)地方j(luò)oint origin是不是相對(duì)于 parent link 的很多人誤以為是全局坐標(biāo)。joint axis旋轉(zhuǎn)關(guān)節(jié)的軸向量是不是單位向量inertia慣性矩陣是不是正定的如果隨便填的物理引擎會(huì)不穩(wěn)定。mesh scaleSTL 的單位是米還是毫米URDF 默認(rèn)單位是米如果 STL 是毫米要加 scale 參數(shù)。注意URDF 里的慣性參數(shù)如果不知道怎么算可以用trimesh根據(jù)網(wǎng)格體積和假設(shè)密度估算一個(gè)近似值。不要填零矩陣那會(huì)導(dǎo)致物理引擎報(bào)錯(cuò)。5.4 文本解析的邊界情況處理用戶輸入千奇百怪我整理了幾種典型情況和處理方式模糊描述“一個(gè)大一點(diǎn)的盒子”——沒有具體尺寸。處理方式是返回默認(rèn)尺寸并提示用戶確認(rèn)。矛盾描述“直徑10毫米、半徑20毫米的圓”——參數(shù)沖突。處理方式是標(biāo)記沖突讓用戶選擇。超出范圍“齒數(shù)0.5個(gè)齒輪”——非法參數(shù)。處理方式是返回錯(cuò)誤說明參數(shù)范圍。多對(duì)象“一個(gè)盒子和一個(gè)圓柱”——需要返回?cái)?shù)組而不是單個(gè)對(duì)象。這些邊界情況在 demo 里可能遇不到但一旦上線就會(huì)冒出來。建議在解析層就做好校驗(yàn)不要等到幾何生成階段才報(bào)錯(cuò)。6. 工具鏈擴(kuò)展還能往哪些方向走6.1 從文本到圖紙二維輸出的可能性text-to-cad 不一定只輸出三維模型。有時(shí)候用戶需要的是一張二維工程圖。CadQuery 支持生成二維投影可以導(dǎo)出 DXF 或 SVG。這條路我試過可行但細(xì)節(jié)很多比如視圖方向、標(biāo)注、線型。如果只是做概念驗(yàn)證導(dǎo)出 SVG 預(yù)覽就夠了。6.2 批量處理用 Python 批量修改 CAD熱詞里有個(gè)“python批量對(duì)cad修改”這其實(shí)是 text-to-cad 的一個(gè)自然延伸。如果你已經(jīng)能把文本變成參數(shù)那批量修改就變成了“批量替換參數(shù) 重新生成”。我做過一個(gè)腳本讀取 Excel 里的參數(shù)表每一行生成一個(gè) STEP 文件。核心代碼就是一個(gè)循環(huán)import pandas as pd df pd.read_excel(params.xlsx) for index, row in df.iterrows(): params { object_type: box, parameters: { length: row[length], width: row[width], height: row[height] }, units: mm } model generate_geometry(params) export_step(model, foutput_{index}.step)這個(gè)思路可以用在任何需要“參數(shù)化批量出圖”的場(chǎng)景比如盤扣腳手架、標(biāo)準(zhǔn)件庫(kù)、家具定制。6.3 與仿真工具對(duì)接URDF 導(dǎo)入的注意事項(xiàng)URDF 生成之后通常要導(dǎo)入到仿真環(huán)境里。不同仿真工具對(duì) URDF 的支持程度不一樣。有的工具對(duì) mesh 路徑很敏感有的對(duì) inertia 的格式有要求。我的經(jīng)驗(yàn)是先在 RViz 或者類似的輕量可視化工具里驗(yàn)證 URDF 能不能正常顯示再去接復(fù)雜的物理仿真。這樣能把“模型描述問題”和“物理引擎問題”分開排查。如果 URDF 導(dǎo)入后模型位置不對(duì)先檢查base_link的坐標(biāo)系。很多工具默認(rèn)base_link在地面如果你的模型原點(diǎn)在幾何中心就會(huì)看起來“陷進(jìn)地里”。7. 我踩過的坑和總結(jié)的經(jīng)驗(yàn)做這個(gè)方向一年多最大的體會(huì)是text-to-cad 的難點(diǎn)不在“text”也不在“cad”而在中間的“to”。文本解析和幾何生成都有成熟的工具但把兩者穩(wěn)定地串起來需要大量的工程細(xì)節(jié)。模型輸出的隨機(jī)性、幾何庫(kù)的版本差異、文件格式的兼容性每一個(gè)都可能讓你卡半天。第二個(gè)體會(huì)是不要追求一步到位。一開始不要想著支持所有形狀、所有格式。先把“盒子”這一種形狀跑通從文本到 STEP 完整走一遍。跑通之后再加圓柱、再加齒輪。每加一種形狀就加一組測(cè)試用例。這樣出了問題你知道是新加的代碼的問題而不是整個(gè)鏈路的問題。第三個(gè)體會(huì)是單位、坐標(biāo)系、路徑這三樣?xùn)|西要反復(fù)檢查。我遇到的 bug 里至少一半和這三個(gè)有關(guān)。單位錯(cuò)了模型尺寸不對(duì)坐標(biāo)系錯(cuò)了模型位置不對(duì)路徑錯(cuò)了文件找不到。每次調(diào)試先查這三樣能省很多時(shí)間。最后分享一個(gè)小技巧如果你用大語言模型做解析在 prompt 里加一句“如果用戶描述不完整請(qǐng)列出缺失的參數(shù)并詢問”這樣模型會(huì)主動(dòng)暴露信息缺口而不是自己編一個(gè)值。這個(gè)改動(dòng)很小但能顯著提升解析的可靠性。