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