位置暴露給 Cursor:TaoToken 統(tǒng)一 Key 配置實(shí)戰(zhàn))
1. 為什么要把裝配體零件絕對(duì)位置喂給 Cursor做非標(biāo)設(shè)計(jì)的朋友大概率遇到過這個(gè)場(chǎng)景裝配體里幾十上百個(gè)零件你想讓 Cursor 或者某個(gè) agent 幫你做點(diǎn)自動(dòng)化的事比如批量導(dǎo)出、按位置分組、生成裝配說明結(jié)果發(fā)現(xiàn) agent 根本不知道每個(gè)零件在空間里的絕對(duì)坐標(biāo)。它只能看到文件名和 BOM 數(shù)量位置信息全靠你手動(dòng)截圖或者口述效率極低。SolidWorks 的裝配體本身是帶完整變換矩陣的每個(gè)零件相對(duì)于裝配體原點(diǎn)都有平移和旋轉(zhuǎn)。問題在于這些數(shù)據(jù)藏在 COM 接口里默認(rèn)不會(huì)以文本形式暴露出來。我們要做的就是寫一段 C# 代碼遍歷裝配體組件樹把每個(gè)零件的絕對(duì)位置X/Y/Z 平移 旋轉(zhuǎn)四元數(shù)或歐拉角提取出來序列化成 JSON讓 Cursor 這類工具能直接讀。這篇聚焦三件事一是可復(fù)制的 C# 提取腳本二是用 TaoToken 統(tǒng)一 Key 管理模型調(diào)用的 config.toml 骨架三是 Cursor 側(cè)的接入配置和驗(yàn)證步驟。適合有 SolidWorks 二次開發(fā)基礎(chǔ)、想讓 agent 讀懂裝配空間關(guān)系的工程師。整套流程我在實(shí)際項(xiàng)目里跑通過下面把踩過的坑和能直接用的代碼都攤開講。2. TaoToken 前置統(tǒng)一 Key 與 config.toml 骨架在寫提取腳本之前先把模型調(diào)用的通道理順。Cursor 里如果每個(gè)項(xiàng)目都單獨(dú)配 Key切換起來很煩而且 agent 調(diào)用模型時(shí)容易因?yàn)榄h(huán)境變量不一致報(bào) 401。TaoToken 的做法是給你一個(gè)統(tǒng)一 Key所有下游工具Cursor、腳本、CLI都指向同一個(gè)入口配置集中在一個(gè) config.toml 里。先到控制臺(tái)創(chuàng)建 Key地址是 https://taotoken.net/api-keys 登錄后新建一個(gè)復(fù)制出來。這個(gè) Key 后面會(huì)同時(shí)用在 Cursor 的模型配置和你的 C# 腳本里如果腳本需要調(diào)用模型做語義解析的話。config.toml 的骨架我建議這樣寫放在項(xiàng)目根目錄或者用戶目錄下都行# config.toml - TaoToken 統(tǒng)一配置骨架 [default] api_base https://taotoken.net/api api_key sk-你的統(tǒng)一Key timeout_seconds 60 [models] # 日常對(duì)話/輕量解析用這個(gè) chat claude-sonnet-4-20250514 # 復(fù)雜裝配邏輯推理用這個(gè) reasoning claude-opus-4-20250514 [cursor] # Cursor 讀取的模型別名映射 provider openai-compatible base_url https://taotoken.net/api model claude-sonnet-4-20250514 [solidworks] # 提取腳本輸出目錄agent 從這里讀位置數(shù)據(jù) export_dir E:\\code\\試驗(yàn)\\裝配體導(dǎo)出 position_file assembly_positions.json這里有個(gè)細(xì)節(jié)要注意api_base用https://taotoken.net/api不要在后面加多余的斜杠否則某些 OpenAI 兼容客戶端會(huì)拼出雙斜杠導(dǎo)致 404。Key 不要硬編碼進(jìn) git 倉庫實(shí)際項(xiàng)目里用環(huán)境變量TAOTOKEN_API_KEY覆蓋config.toml 里留占位符就行。如果你打算長(zhǎng)期用 Cursor 做編碼和 agent 任務(wù)可以看下 Coding Plan 的額度方案 https://taotoken.net/coding-plan 比按次調(diào)用劃算。模型對(duì)話的入口在 https://taotoken.net/chat 用來快速驗(yàn)證 Key 是否生效很方便。3. 可復(fù)制的 C# 提取腳本遍歷裝配體拿絕對(duì)位置核心思路是遞歸遍歷AssemblyDoc的組件對(duì)每個(gè)組件調(diào)用GetTotalTransform或者逐級(jí)累乘變換矩陣得到相對(duì)于裝配體原點(diǎn)的絕對(duì)變換。SolidWorks API 里Component2有個(gè)GetTotalTransform(bool)方法傳 true 會(huì)返回包含裝配體原點(diǎn)偏移的完整變換這正是我們要的。先定義數(shù)據(jù)結(jié)構(gòu)方便序列化using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using SolidWorks.Interop.sldworks; using SolidWorks.Interop.swconst; namespace SwPositionExporter { public sealed class PartPosition { public string PartName { get; set; } string.Empty; public string PartPath { get; set; } string.Empty; public double[] Translation { get; set; } new double[3]; // X, Y, Z 絕對(duì)位置 public double[] RotationMatrix { get; set; } new double[9]; // 3x3 旋轉(zhuǎn) public double[] EulerAngles { get; set; } new double[3]; // 便于人讀 public int Depth { get; set; } // 在裝配樹里的層級(jí) } }然后是遍歷邏輯。這里的關(guān)鍵是GetTotalTransform返回的是一個(gè) 16 元素的數(shù)組前 9 個(gè)是旋轉(zhuǎn)矩陣行優(yōu)先第 10 到 12 個(gè)是平移量最后 4 個(gè)是透視相關(guān)一般忽略。public static class AssemblyPositionService { public static bool TryExportPositions( SldWorks swApp, ModelDoc2 assemblyModel, string outputJsonPath, out ListPartPosition positions, out string error) { positions new ListPartPosition(); error string.Empty; if (swApp null) { error SolidWorks 未連接; return false; } if (assemblyModel null || assemblyModel.GetType() ! (int)swDocumentTypes_e.swDocASSEMBLY) { error 當(dāng)前文檔不是裝配體; return false; } try { var asmDoc (AssemblyDoc)assemblyModel; object[] components (object[])asmDoc.GetComponents(false); foreach (object obj in components) { var comp (Component2)obj; if (comp null || comp.IsSuppressed()) continue; // 關(guān)鍵true 表示包含裝配體原點(diǎn)偏移拿到絕對(duì)變換 double[] xform (double[])comp.GetTotalTransform(true); if (xform null || xform.Length 13) continue; var pos new PartPosition { PartName comp.Name2, PartPath comp.GetPathName(), Translation new[] { xform[9], xform[10], xform[11] }, RotationMatrix new[] { xform[0], xform[1], xform[2], xform[3], xform[4], xform[5], xform[6], xform[7], xform[8] }, Depth 0 }; pos.EulerAngles MatrixToEuler(pos.RotationMatrix); positions.Add(pos); } var options new JsonSerializerOptions { WriteIndented true }; File.WriteAllText(outputJsonPath, JsonSerializer.Serialize(positions, options)); return true; } catch (Exception ex) { error ex.Message; return false; } } // 旋轉(zhuǎn)矩陣轉(zhuǎn)歐拉角ZYX 順序方便 agent 理解朝向 private static double[] MatrixToEuler(double[] m) { double sy Math.Sqrt(m[0] * m[0] m[3] * m[3]); bool singular sy 1e-6; double x, y, z; if (!singular) { x Math.Atan2(m[7], m[8]); y Math.Atan2(-m[6], sy); z Math.Atan2(m[3], m[0]); } else { x Math.Atan2(-m[5], m[4]); y Math.Atan2(-m[6], sy); z 0; } return new[] { x * 180 / Math.PI, y * 180 / Math.PI, z * 180 / Math.PI }; } }調(diào)用的時(shí)候注意必須在 SolidWorks 主線程執(zhí)行COM 接口不是線程安全的。如果你在插件里跑直接調(diào)如果在獨(dú)立進(jìn)程里跑得先ConnectToSW拿到SldWorks實(shí)例。// 調(diào)用示例 var swApp (SldWorks)Activator.CreateInstance( Type.GetTypeFromProgID(SldWorks.Application)); var model swApp.ActiveDoc; if (AssemblyPositionService.TryExportPositions( swApp, model, E:\code\試驗(yàn)\裝配體導(dǎo)出\assembly_positions.json, out var positions, out var err)) { Console.WriteLine($導(dǎo)出 {positions.Count} 個(gè)零件位置); } else { Console.WriteLine($失敗: {err}); }輸出的 JSON 長(zhǎng)這樣agent 讀起來毫無壓力[ { PartName: L1015x95鏈條護(hù)板-14齒, PartPath: E:\\cqh-圖紙\\設(shè)計(jì)模型庫\\鏈條護(hù)板\\L1015x95鏈條護(hù)板-14齒.SLDPRT, Translation: [120.5, 0.0, 340.2], RotationMatrix: [1,0,0,0,1,0,0,0,1], EulerAngles: [0,0,0], Depth: 0 } ]4. Cursor 接入配置與運(yùn)行驗(yàn)證拿到 JSON 之后要讓 Cursor 的 agent 能讀到。有兩種方式一是把 JSON 放進(jìn)項(xiàng)目目錄agent 通過文件讀取工具直接讀二是通過一個(gè)輕量 HTTP 橋接agent 發(fā)請(qǐng)求拿數(shù)據(jù)。前者簡(jiǎn)單后者適合數(shù)據(jù)實(shí)時(shí)變化的場(chǎng)景。先配 Cursor 的模型通道。打開 Cursor 設(shè)置找到 Models 部分添加一個(gè) OpenAI 兼容的 provider{ models: [ { name: claude-sonnet-4-20250514, provider: openai, baseUrl: https://taotoken.net/api, apiKey: sk-你的統(tǒng)一Key } ] }如果你用的是 Cursor 的 config 文件方式直接寫進(jìn)~/.cursor/config.json。配好后在 Cursor 里新建一個(gè)對(duì)話問它「讀取 assembly_positions.json告訴我 X 坐標(biāo)最大的三個(gè)零件」如果它能正確解析并回答說明通道通了。驗(yàn)證提取腳本本身我建議分三步走。第一步先在只有一個(gè)零件的簡(jiǎn)單裝配體上跑確認(rèn)GetTotalTransform(true)返回的平移量和你手動(dòng)測(cè)量的位置一致。第二步加一個(gè)子裝配體檢查遞歸是否覆蓋到深層零件——注意GetComponents(false)只返回頂層要遞歸的話得對(duì)每個(gè)子裝配體再調(diào)一次。第三步對(duì)比 SolidWorks 界面里「評(píng)估 測(cè)量」的坐標(biāo)誤差應(yīng)該在 1e-6 以內(nèi)。遞歸版本的關(guān)鍵改動(dòng)private static void TraverseComponents( Component2 comp, int depth, ListPartPosition result) { if (comp null || comp.IsSuppressed()) return; double[] xform (double[])comp.GetTotalTransform(true); if (xform ! null xform.Length 13) { result.Add(new PartPosition { PartName comp.Name2, PartPath comp.GetPathName(), Translation new[] { xform[9], xform[10], xform[11] }, RotationMatrix new[] { xform[0],xform[1],xform[2], xform[3],xform[4],xform[5], xform[6],xform[7],xform[8] }, Depth depth }); } // 如果是子裝配體繼續(xù)往下鉆 var childAsm comp.GetModelDoc2() as AssemblyDoc; if (childAsm ! null) { object[] children (object[])childAsm.GetComponents(false); foreach (object c in children) TraverseComponents((Component2)c, depth 1, result); } }跑通之后你可以讓 Cursor 基于位置數(shù)據(jù)做更有意思的事比如「找出所有 Z 坐標(biāo)大于 300 的護(hù)板生成一份安裝順序建議」。這時(shí)候 agent 有了空間信息回答質(zhì)量完全不一樣。5. 本篇常見錯(cuò)排查報(bào)錯(cuò)一GetTotalTransform返回 null 或長(zhǎng)度不足 13。最常見原因是組件被壓縮suppressed或者輕化lightweight。輕化組件需要先Resolve再取變換。加一句comp.Resolve()或者遍歷前把裝配體設(shè)為完全還原。報(bào)錯(cuò)二位置全是 0。檢查你傳的參數(shù)是不是false。GetTotalTransform(false)返回的是相對(duì)于父級(jí)的變換頂層零件如果父級(jí)就是裝配體原點(diǎn)看起來就像 0。必須傳true才是絕對(duì)坐標(biāo)。報(bào)錯(cuò)三Cursor 報(bào) 401 Unauthorized。九成是 Key 沒對(duì)上。檢查 config.toml 里的api_key和 Cursor 設(shè)置里的是不是同一個(gè)以及有沒有多余空格。TaoToken 的 Key 以sk-開頭復(fù)制時(shí)別漏字符。接入文檔在 https://taotoken.net/doc 里面有各客戶端的配置示例。報(bào)錯(cuò)四JSON 中文亂碼。File.WriteAllText默認(rèn)用 UTF-8 無 BOM但如果你在中文 Windows 上用了Encoding.Default就會(huì)亂。顯式指定new UTF8Encoding(false)即可。報(bào)錯(cuò)五agent 讀不到文件。Cursor 的工作區(qū)根目錄和你 JSON 輸出目錄不一致。要么把 JSON 放到工作區(qū)內(nèi)要么在對(duì)話里給絕對(duì)路徑。我一般把export_dir設(shè)成項(xiàng)目子目錄省得來回切。報(bào)錯(cuò)六COM 調(diào)用拋InvalidCastException。多半是SldWorks實(shí)例沒拿到或者 SolidWorks 版本和 Interop 程序集版本不匹配。確認(rèn)引用的SolidWorks.Interop.sldworks.dll和你裝的 SolidWorks 主版本號(hào)一致。6. 把位置數(shù)據(jù)接進(jìn)你的 agent 工作流位置 JSON 導(dǎo)出只是第一步真正省時(shí)間的是讓 agent 基于這些數(shù)據(jù)做決策。我現(xiàn)在的做法是裝配體一改跑一次導(dǎo)出腳本JSON 覆蓋更新然后在 Cursor 里直接問「對(duì)比上一版位置哪些零件移動(dòng)超過 5mm」。agent 讀兩個(gè) JSON 做 diff比人一個(gè)個(gè)量快得多。如果你想讓 agent 直接調(diào)用模型做語義分析比如「根據(jù)零件位置生成裝配工藝卡」那就需要腳本里也帶上模型調(diào)用。這時(shí)候統(tǒng)一 Key 的價(jià)值就體現(xiàn)出來了——C# 腳本、Cursor、CLI 工具全用同一個(gè) Key不用到處配。模型對(duì)話入口 https://taotoken.net/chat 可以先手動(dòng)試幾個(gè) prompt確認(rèn)效果再寫進(jìn)自動(dòng)化流程。長(zhǎng)期跑編碼和 agent 任務(wù)的話Coding Plan https://taotoken.net/coding-plan 的額度更穩(wěn)不會(huì)因?yàn)閱未握{(diào)用超限中斷。API Key 管理在 https://taotoken.net/api-keys 接入細(xì)節(jié)看 https://taotoken.net/doc 。Claude Code 相關(guān)的配置參考 https://taotoken.net/claude-code 。最后留個(gè)實(shí)用技巧導(dǎo)出 JSON 的時(shí)候順手加一個(gè)exportedAt時(shí)間戳字段agent 判斷數(shù)據(jù)新舊會(huì)方便很多。還有裝配體零件名如果有中文JSON 序列化時(shí)確保JsonSerializerOptions沒開UnsafeRelaxedJsonEscaping之外的奇怪轉(zhuǎn)義否則 agent 解析中文名會(huì)出問題。