戰(zhàn):從自然語言到三維 CAD 模型的 Python 實(shí)現(xiàn))
1. 從一句話到三維模型text-to-cad 到底在解決什么問題第一次聽到 text-to-cad 這個(gè)詞很多人腦子里冒出來的畫面是對(duì)著電腦敲一句“給我畫一個(gè)長寬高 100×60×40 的帶孔法蘭盤”然后屏幕上就自動(dòng)出現(xiàn)一個(gè)可以旋轉(zhuǎn)、可以導(dǎo)出、可以進(jìn)仿真軟件的三維模型。這個(gè)畫面放在幾年前還屬于科幻范疇但現(xiàn)在已經(jīng)有一批開源項(xiàng)目和工具鏈把它做成了能跑通的流程。我自己是從機(jī)械設(shè)計(jì)轉(zhuǎn)過來的早些年畫一個(gè)標(biāo)準(zhǔn)件要開 CAD 軟件、建草圖、拉伸、打孔、倒角一套下來十幾分鐘就沒了重復(fù)勞動(dòng)特別多。后來接觸到用代碼生成 CAD 的思路再往后看到 text-to-cad 這類方向第一反應(yīng)就是這東西要是穩(wěn)定了能省掉大量重復(fù)建模的時(shí)間。先把概念說清楚。text-to-cad 不是一個(gè)具體的軟件而是一類“用自然語言描述幾何體由程序自動(dòng)生成 CAD 模型文件”的技術(shù)路線。它的輸入是一段文本比如“一個(gè)外徑 80、內(nèi)徑 40、厚度 10 的圓環(huán)”輸出通常是 STEP、STL、URDF 這類標(biāo)準(zhǔn)格式文件。STEP 是通用三維交換格式幾乎所有主流 CAD 軟件都能打開URDF 是機(jī)器人領(lǐng)域描述連桿和關(guān)節(jié)的格式常用于仿真STL 則是 3D 打印最常用的網(wǎng)格格式。這幾個(gè)格式對(duì)應(yīng)了 text-to-cad 的三條主要落地路徑機(jī)械零件出圖、機(jī)器人模型搭建、3D 打印快速驗(yàn)證。為什么這件事值得單獨(dú)拿出來講因?yàn)閭鹘y(tǒng) CAD 建模是“手工活”而 text-to-cad 把它變成了“可編程、可批量、可版本管理”的工程流程。舉個(gè)最直接的例子你需要 20 個(gè)不同孔徑的法蘭盤傳統(tǒng)做法是一個(gè)個(gè)改參數(shù)重畫用代碼生成的話寫一個(gè)循環(huán)就出來了。再比如機(jī)器人仿真URDF 文件里全是數(shù)字手寫容易出錯(cuò)用文本描述生成再校驗(yàn)效率完全不一樣。適合看這篇內(nèi)容的人有三類一是做機(jī)械設(shè)計(jì)、想擺脫重復(fù)建模的工程師二是做機(jī)器人仿真、需要批量生成 URDF 的同學(xué)三是想用 Python 玩三維建模、但不想學(xué)復(fù)雜 CAD 軟件的開發(fā)者。我下面會(huì)按“整體設(shè)計(jì)思路—核心細(xì)節(jié)—實(shí)操流程—問題排查”這條線展開把 text-to-cad 從原理到能跑起來的代碼都講一遍。文中涉及的代碼都是 Python依賴主要是 CadQuery 和 build123d 這類基于 OpenCASCADE 的建模庫環(huán)境配置我會(huì)給到具體命令。你不需要有很深的 CAD 背景但最好有一點(diǎn) Python 基礎(chǔ)能看懂函數(shù)調(diào)用和列表循環(huán)就夠了。2. 整體設(shè)計(jì)思路為什么是“文本→代碼→幾何”這條鏈路2.1 三種技術(shù)路線的取舍邏輯text-to-cad 聽起來是一步到位實(shí)際上中間隔著好幾層。目前主流實(shí)現(xiàn)可以歸為三類理解這三類的區(qū)別你就知道該選哪條路。第一類是規(guī)則解析型。程序里預(yù)定義了一套語法模板比如“圓柱”“長方體”“圓環(huán)”“打孔”這些關(guān)鍵詞用戶輸入文本后用正則或簡單分詞提取參數(shù)再調(diào)用建模庫生成幾何。優(yōu)點(diǎn)是穩(wěn)定、可控、不依賴大模型缺點(diǎn)是只能處理預(yù)定義的句式稍微換個(gè)說法就識(shí)別不了。適合固定場景的批量出圖。第二類是大模型驅(qū)動(dòng)型。用語言模型把自然語言翻譯成建模代碼通常是 CadQuery 或 OpenSCAD 的腳本再執(zhí)行代碼生成模型。優(yōu)點(diǎn)是靈活能處理復(fù)雜描述缺點(diǎn)是模型可能“胡說”生成的代碼語法錯(cuò)誤或幾何不合理需要加校驗(yàn)和重試機(jī)制。第三類是混合型。先用規(guī)則處理高頻簡單指令復(fù)雜描述再交給大模型同時(shí)用幾何校驗(yàn)兜底。這是目前工程上最靠譜的做法也是我實(shí)際項(xiàng)目里采用的方案。選型的時(shí)候我踩過一個(gè)坑一開始想純靠大模型結(jié)果生成十個(gè)模型有三個(gè)打不開排查半天發(fā)現(xiàn)是布爾運(yùn)算的順序錯(cuò)了導(dǎo)致實(shí)體自相交。后來加了“生成后必須通過體積和包圍盒校驗(yàn)”這一步問題才壓下去。所以我的建議是不管走哪條路幾何校驗(yàn)環(huán)節(jié)不能省。2.2 為什么輸出格式首選 STEP很多人會(huì)問為什么不直接輸出 STL 就完事了這里要解釋一下。STL 是三角網(wǎng)格只記錄表面三角形沒有“圓柱面”“平面”這些幾何語義也沒有參數(shù)歷史。你拿到一個(gè) STL想改孔徑只能重新生成沒法在 CAD 里直接編輯特征。STEP 不一樣它保留了幾何實(shí)體的拓?fù)潢P(guān)系是 B-rep邊界表示格式能被 FreeCAD、SolidWorks、中望 CAD 這些軟件識(shí)別為可編輯實(shí)體。所以 text-to-cad 的輸出策略通常是主輸出 STEP按需轉(zhuǎn) STL 和 URDF。STEP 作為“源文件”STL 用于 3D 打印URDF 用于機(jī)器人仿真。轉(zhuǎn)換用 CadQuery 自帶的導(dǎo)出函數(shù)就能搞定不需要額外裝軟件。這個(gè)設(shè)計(jì)的好處是你的文本描述和生成的 STEP 之間是一一對(duì)應(yīng)的改文本重新生成版本管理用 Git 就能做比手動(dòng)改 CAD 文件靠譜得多。2.3 整體架構(gòu)拆解一個(gè)能用的 text-to-cad 流程我把它拆成四層輸入層接收自然語言做基礎(chǔ)清洗比如統(tǒng)一單位、去掉口語化詞匯。解析層把文本轉(zhuǎn)成結(jié)構(gòu)化的參數(shù)或者轉(zhuǎn)成建模代碼。建模層調(diào)用 CadQuery/build123d 執(zhí)行幾何構(gòu)建輸出實(shí)體對(duì)象。校驗(yàn)與導(dǎo)出層檢查實(shí)體是否有效體積大于零、無自相交然后導(dǎo)出 STEP/STL/URDF。這四層里解析層是最容易出問題的建模層是最需要經(jīng)驗(yàn)的。下面我會(huì)重點(diǎn)講這兩層。整個(gè)流程用 Python 串起來核心依賴就兩個(gè)cadquery和build123d前者成熟穩(wěn)定后者 API 更現(xiàn)代我兩個(gè)都用過后面會(huì)給對(duì)比。3. 核心細(xì)節(jié)解析文本怎么變成幾何參數(shù)3.1 自然語言到參數(shù)的映射規(guī)則文本轉(zhuǎn)幾何第一步是把“人話”翻譯成數(shù)字。這里面有幾個(gè)關(guān)鍵點(diǎn)。單位統(tǒng)一。用戶可能說“直徑 5 厘米”也可能說“半徑 25 毫米”程序必須統(tǒng)一到毫米。我的做法是在解析前先做單位歸一化識(shí)別“厘米”“米”“英寸”這些詞統(tǒng)一換算成毫米。英寸換算系數(shù)是 25.4這個(gè)別記錯(cuò)。尺寸指代消歧?!耙粋€(gè) 100 的方塊”這種描述100 到底是邊長還是對(duì)角線工程上默認(rèn)是邊長。但“一個(gè) 100 的圓”就默認(rèn)是直徑。這些約定要在解析規(guī)則里寫死否則大模型也會(huì)犯迷糊。特征順序。文本里說“帶兩個(gè)孔的板”孔的位置和孔徑如果沒給就得有默認(rèn)值。我的默認(rèn)策略是孔徑取板厚的 0.5 倍孔位對(duì)稱分布。這些默認(rèn)值不是拍腦袋是參考了常見機(jī)械件的比例。下面這張表是我整理的常見描述到參數(shù)的映射可以直接拿去用文本描述解析結(jié)果默認(rèn)值策略長方體/方塊length, width, height缺失項(xiàng)取已給項(xiàng)的 1 倍圓柱diameter/radius, height半徑缺失取高度 0.5 倍圓環(huán)outer_d, inner_d, thickness內(nèi)徑取外徑 0.5 倍打孔hole_d, count, position孔徑取板厚 0.5 倍對(duì)稱分布倒角radius取最小邊長的 0.05 倍3.2 建模庫選型CadQuery 還是 build123d這兩個(gè)庫都是基于 OpenCASCADE 的 Python 封裝但風(fēng)格差別很大。CadQuery 用的是鏈?zhǔn)秸{(diào)用寫起來像這樣import cadquery as cq result (cq.Workplane(XY) .box(100, 60, 40) .faces(Z) .workplane() .hole(10))build123d 用的是上下文管理器更接近傳統(tǒng) CAD 的操作邏輯from build123d import * with BuildPart() as p: Box(100, 60, 40) with Locations((0, 0, 20)): Hole(radius5)我的實(shí)際體驗(yàn)是CadQuery 生態(tài)更成熟文檔多遇到問題好搜build123d 代碼更直觀適合復(fù)雜裝配體。如果你是新手建議從 CadQuery 入手因?yàn)榫W(wǎng)上例子多。如果你要做機(jī)器人 URDFbuild123d 對(duì)裝配和坐標(biāo)系的支持更順手。注意兩個(gè)庫不能混用同一個(gè)進(jìn)程OpenCASCADE 的底層句柄會(huì)沖突。選一個(gè)就用到底。3.3 幾何有效性校驗(yàn)的三個(gè)指標(biāo)生成完實(shí)體別急著導(dǎo)出先校驗(yàn)。我用的三個(gè)指標(biāo)體積大于零。solid.Volume() 0如果等于零說明實(shí)體是空的或者退化了。包圍盒合理。solid.BoundingBox()的尺寸應(yīng)該和輸入?yún)?shù)吻合偏差超過 10% 就說明解析或建模有問題。實(shí)體數(shù)量正確。len(solid.Solids())應(yīng)該等于預(yù)期比如一個(gè)帶孔的板應(yīng)該還是一個(gè)實(shí)體如果變成兩個(gè)說明布爾運(yùn)算失敗了。這三個(gè)檢查加起來不到十行代碼但能攔下八成以上的錯(cuò)誤輸出。我見過太多人跳過校驗(yàn)結(jié)果導(dǎo)出的 STEP 在 CAD 里打開是空文件白白浪費(fèi)時(shí)間。4. 實(shí)操過程從零搭一個(gè)能跑的 text-to-cad 腳本4.1 環(huán)境準(zhǔn)備與依賴安裝先把環(huán)境弄干凈。我推薦用 conda 建虛擬環(huán)境避免和系統(tǒng) Python 沖突。conda create -n text2cad python3.10 conda activate text2cad pip install cadquery build123d這里有個(gè)坑CadQuery 在 Windows 上裝的時(shí)候如果之前裝過其他版本的 OpenCASCADE可能會(huì)報(bào) DLL 沖突。解決辦法是先卸載干凈再重裝。Linux 上一般沒這個(gè)問題但要注意libGL這類系統(tǒng)庫要裝全否則導(dǎo)入會(huì)報(bào)錯(cuò)。驗(yàn)證安裝import cadquery as cq print(cq.__version__)能打印出版本號(hào)就說明環(huán)境 OK。如果報(bào)ImportError八成是 OpenCASCADE 的動(dòng)態(tài)庫沒找到Windows 上檢查 PATHLinux 上用ldd看依賴。4.2 一個(gè)最小可用的文本解析器先寫一個(gè)規(guī)則版的解析器處理最常見的幾種形狀。這段代碼可以直接抄import re def parse_text(text): text text.lower().replace( , ) # 單位歸一化 if 厘米 in text or cm in text: factor 10 elif 米 in text and 毫米 not in text: factor 1000 else: factor 1 # 提取所有數(shù)字 nums [float(n) * factor for n in re.findall(r\d\.?\d*, text)] if 圓柱 in text or 圓 in text: if len(nums) 2: return {type: cylinder, d: nums[0], h: nums[1]} if 方塊 in text or 長方體 in text or 板 in text: if len(nums) 3: return {type: box, l: nums[0], w: nums[1], h: nums[2]} return None這個(gè)解析器很粗糙但能覆蓋“直徑 50 高 100 的圓柱”這類描述。實(shí)測下來對(duì)于格式規(guī)整的輸入識(shí)別率能到七成以上。剩下的復(fù)雜描述就得靠大模型或者更細(xì)的規(guī)則。4.3 建模函數(shù)與導(dǎo)出拿到參數(shù)字典后調(diào)用 CadQuery 建模import cadquery as cq def build_model(params): if params[type] cylinder: solid cq.Workplane(XY).circle(params[d]/2).extrude(params[h]) elif params[type] box: solid cq.Workplane(XY).box(params[l], params[w], params[h]) else: raise ValueError(unsupported type) # 校驗(yàn) shape solid.val() assert shape.Volume() 0, 體積為零 bb shape.BoundingBox() print(f包圍盒: {bb.xlen:.1f} x {bb.ylen:.1f} x {bb.zlen:.1f}) return solid def export(solid, name): cq.exporters.export(solid, f{name}.step) cq.exporters.export(solid, f{name}.stl)跑一遍params parse_text(直徑 50 高 100 的圓柱) solid build_model(params) export(solid, test_cylinder)如果一切正常當(dāng)前目錄下會(huì)出現(xiàn)test_cylinder.step和test_cylinder.stl。用 FreeCAD 打開 STEP應(yīng)該能看到一個(gè)圓柱體。這一步跑通整個(gè)鏈路就活了。4.4 批量生成與參數(shù)掃描text-to-cad 真正的價(jià)值在批量。比如你要生成一系列不同孔徑的法蘭盤for d in [20, 30, 40, 50]: params {type: cylinder, d: d, h: 10} solid build_model(params) export(solid, fflange_ddrlblxrh1)這段代碼幾秒鐘就能生成四個(gè)模型手動(dòng)畫的話至少半小時(shí)。參數(shù)掃描在優(yōu)化設(shè)計(jì)里特別有用比如你想看不同壁厚對(duì)重量的影響寫個(gè)循環(huán)把體積打出來就行。4.5 URDF 導(dǎo)出與機(jī)器人仿真對(duì)接如果你做機(jī)器人URDF 是繞不開的。URDF 本質(zhì)是 XML描述連桿的幾何、質(zhì)量、慣性矩陣和關(guān)節(jié)。text-to-cad 生成的 STEP 可以轉(zhuǎn)成 URDF 里的 mesh 引用也可以直接用基本幾何體box、cylinder描述。一個(gè)簡單的連桿 URDF 片段link namelink1 visual geometry cylinder radius0.05 length0.2/ /geometry /visual collision geometry cylinder radius0.05 length0.2/ /geometry /collision inertial mass value1.0/ inertia ixx0.001 ixy0 ixz0 iyy0.001 iyz0 izz0.0005/ /inertial /link用 Python 生成這段 XML 不難難的是慣性矩陣的計(jì)算。對(duì)于圓柱繞中心軸的轉(zhuǎn)動(dòng)慣量是0.5*m*r^2繞徑向是m*(3r^2h^2)/12。這些公式我建議直接寫成函數(shù)別每次手算。導(dǎo)入 CoppeliaSim 的時(shí)候注意 URDF 里的單位是米而 CAD 里通常是毫米導(dǎo)出前記得除以 1000否則模型會(huì)大一千倍在仿真里根本看不見。5. 常見問題與排查技巧實(shí)錄5.1 建模階段的典型報(bào)錯(cuò)問題一布爾運(yùn)算后實(shí)體消失。這通常是因?yàn)閮蓚€(gè)實(shí)體沒有實(shí)際相交或者相減時(shí)把整個(gè)實(shí)體減沒了。排查方法是先分別導(dǎo)出兩個(gè)實(shí)體確認(rèn)它們的位置關(guān)系。CadQuery 里可以用translate調(diào)整位置后再運(yùn)算。問題二導(dǎo)出的 STEP 打不開。先檢查文件大小如果是 0 字節(jié)說明導(dǎo)出失敗如果幾 KB 但打不開可能是幾何無效。用shape.isValid()檢查返回 False 就說明有自相交或退化面。修復(fù)辦法是加clean()或者調(diào)整建模順序。問題三圓角報(bào)錯(cuò)。倒圓角是最容易失敗的操