注:環(huán)境搭建到批量自動化實戰(zhàn))
1. 為什么你會需要C#來操控圖紙標(biāo)注先從一個場景說起。我接過一個挺典型的活客戶那邊有幾十張SolidWorks工程圖需要把所有帶有特定前綴的尺寸標(biāo)注全部替換成新前綴同時把每張圖紙的標(biāo)注信息尺寸類型、名義值、公差、關(guān)聯(lián)特征匯總成一個Excel清單。圖紙數(shù)量一大手工改不僅容易漏而且時間上根本不允許。手動操作一張復(fù)雜圖紙的標(biāo)注檢查加修改快則十分鐘慢則半小時幾十張圖下來一整天就沒了關(guān)鍵還容易出錯。這時候就需要用程序來干了。用C#寫SolidWorks標(biāo)注處理程序本質(zhì)上是通過SolidWorks的API接口和模型打交道。SolidWorks提供了一整套COM接口C#可以通過Interop程序集訪問這些接口實現(xiàn)在外部程序中創(chuàng)建、讀取、修改模型及工程圖里的標(biāo)注對象。和SolidWorks自帶的宏VBA相比C#的優(yōu)勢在于可以方便地寫復(fù)雜的業(yè)務(wù)邏輯比如對接數(shù)據(jù)庫、做Excel導(dǎo)出、寫正則匹配替換規(guī)則可以打包成獨立工具分發(fā)部署到?jīng)]有VBA開發(fā)環(huán)境的機器上也能跑而且調(diào)試體驗比在宏編輯器里寫VBA舒服很多。這里要先破除一個迷思很多人覺得“標(biāo)注”就是圖紙上那些數(shù)字和符號不值得用程序處理。但實際在制造企業(yè)里標(biāo)注是設(shè)計信息的下游載體圖號變更、標(biāo)準(zhǔn)件代號調(diào)整、公差體系切換都會牽動大量標(biāo)注修改。標(biāo)注信息的準(zhǔn)確性和一致性直接影響下游工藝、加工和質(zhì)檢。所以圍繞標(biāo)注的批量化操作、信息提取、模板化生成其實是很有需求的一塊自動化場景。這篇文章我會從環(huán)境準(zhǔn)備、API對象模型、讀取標(biāo)注、批量修改標(biāo)注、常見問題排查幾個環(huán)節(jié)把我實際做過的C#加SolidWorks標(biāo)注處理項目的思路和踩過的坑完整寫出來。適合有C#基礎(chǔ)、需要做SolidWorks二次開發(fā)的工程師或者被重復(fù)性標(biāo)注工作折磨得想“偷懶”的結(jié)構(gòu)設(shè)計師。2. 開發(fā)環(huán)境搭建與首次連接2.1 環(huán)境清單不止是裝了SolidWorks就行要做C#二次開發(fā)本機環(huán)境有幾個硬性要求裝有SolidWorks最好是我這次用的2018 SP5及以上版本版本太老API對象模型差異會比較明顯。Visual Studio 2019或2022社區(qū)版就行。.NET Framework 4.7.2或.NET 6/8看你的目標(biāo)運行環(huán)境如果只是本機工具建議直接用.NET Framework 4.7.2省心。在項目中引用兩個Interop程序集SolidWorks.Interop.sldworks.dll 和 SolidWorks.Interop.swconst.dll。這兩個DLL一般位于SolidWorks安裝目錄下比如C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\api\redist。添加引用時直接瀏覽到這個路徑選DLL即可。版本選擇上有個實在的建議如果你們的SolidWorks是2020或2021就下載對應(yīng)版本的Interop程序集。雖然高版本API基本向下兼容但低版本引用在高版本上會更穩(wěn)妥反過來就偶爾會出現(xiàn)類型轉(zhuǎn)換問題。SolidWorks安裝目錄里自帶的DLL就是和本機版本最匹配的。2.2 用Interop還是用COM動態(tài)調(diào)用實際開發(fā)中有兩種路線強類型Interop引用具體版本的Interop DLL代碼里可以直接使用SldWorks、ModelDoc2等類型編譯期就有智能提示代碼可讀性好。這是我推薦的方式。動態(tài)綁定通過ProgID創(chuàng)建對象不引用Interop DLL運行時再解析。這種方式部署時不用管版本但寫代碼時沒有智能提示報錯也晚非常適合“跑通驗證”的場景不適合正式項目。我這里全程用Interop強類型方式。Interop還有一個好處Visual Studio對這類COM程序集支持得比較好會生成對應(yīng)的互操作程序集遇到方法簽名不明確時可以直接右鍵“轉(zhuǎn)到定義”查看真實的參數(shù)約束。2.3 連接SolidWorks的代碼骨架using SolidWorks.Interop.sldworks; using System; using System.Runtime.InteropServices; public class SwConnection : IDisposable { private SldWorks _swApp; public bool Connect(string progId SldWorks.Application) { // 先嘗試獲取已運行的實例 try { _swApp Marshal.GetActiveObject(progId) as SldWorks; } catch { // 沒有運行則創(chuàng)建新實例 Type swType Type.GetTypeFromProgID(progId); _swApp Activator.CreateInstance(swType) as SldWorks; } if (_swApp null) return false; // 設(shè)為可見 _swApp.Visible true; return true; } public void Dispose() { if (_swApp ! null) { Marshal.FinalReleaseComObject(_swApp); _swApp null; } GC.Collect(); GC.WaitForPendingFinalizers(); } }這段代碼有一個細節(jié)要提醒Marshal.GetActiveObject如果當(dāng)前沒啟動SolidWorks會拋異常所以一定要用try-catch包住。Activator.CreateInstance創(chuàng)建實例時SolidWorks會起一個完整進程打開速度取決于機器性能建議開發(fā)時先把SolidWorks開著再跑程序。連接后別忘了主動釋放COM對象。COM引用計數(shù)問題在C#里非常隱蔽你以為對象可以被GC回收了其實SolidWorks進程還占著大量內(nèi)存。不釋放干凈的典型表現(xiàn)是程序跑幾輪后SolidWorks越來越卡最后蹦出內(nèi)存不足的報錯。3. 標(biāo)注對象模型搞懂層級才好下手3.1 從零件到工程圖的完整鏈路SolidWorks中標(biāo)注主要集中在工程圖環(huán)境但模型的尺寸草圖尺寸、特征尺寸也可以被程序訪問。對象層級大致是SldWorks.Application → ModelDoc2 → Sheet → View → Dimension / Annotation這里有兩個容易混淆的概念要理清Dimension尺寸標(biāo)注比如直徑標(biāo)注、線性標(biāo)注、角度標(biāo)注在API里對應(yīng)Dimension對象。Annotation注釋對象包括文字注釋Note、表面粗糙度符號、形位公差符號等。注釋是一個更大的概念尺寸標(biāo)注也屬于注釋的范疇。API里它們的關(guān)聯(lián)關(guān)系是Dimension對象繼承自IAnnotation也就是說一個尺寸本身也是一種注釋。所以遍歷時通過Annotation可以拿到所有標(biāo)注類對象再判斷其類型可以區(qū)分是尺寸、注釋還是其他符號。先把這個關(guān)系弄清楚再寫遍歷代碼否則容易陷入“為什么我拿到了關(guān)聯(lián)尺寸卻拿不到文字”的困惑。3.2 尺寸標(biāo)注的命名規(guī)則SolidWorks里的每個尺寸都有一個唯一名稱最常見的格式是D1草圖1 D1凸臺-拉伸1 D1工程圖視圖1前綴D就是尺寸的標(biāo)識后面的符號后跟所屬特征或視圖名稱。這個命名規(guī)則對程序特別重要因為很多API方法比如Parameter、SelectByID2都要靠名稱字符串去定位對象。實際項目里我踩過一個坑尺寸名稱中會有變體名稱的情況。SolidWorks允許同一個特征里存在同名的草圖這時尺寸名稱會展示為類似D1草圖1特征1的格式。遍歷時如果直接用名字匹配容易漏掉這類“重名”尺寸。所以建議優(yōu)先采用遍歷集合的方式而不是用名稱字符串去精確匹配。3.3 三種常用的獲取標(biāo)注方式方式一按名稱獲取尺寸ModelDoc2 swDoc _swApp.ActiveDoc; Dimension swDim swDoc.Parameter(D1草圖1) as Dimension;方式二通過選擇管理器獲取選中對象ModelDoc2 swDoc _swApp.ActiveDoc; bool sel swDoc.Extension.SelectByID2( D1工程圖視圖1, DIMENSION, 0, 0, 0, false, 0, null, 0); if (sel) { SelectionMgr selMgr swDoc.SelectionManager; Dimension swDim selMgr.GetSelectedObject6(1) as Dimension; }方式三遍歷視圖內(nèi)所有注解ModelDoc2 swDoc _swApp.ActiveDoc; View swView swDoc.GetFirstView(); while (swView ! null) { Annotation ann swView.GetFirstAnnotation(); swView swView.GetNextView(); }這三種方式各有適用場景按名稱最直接但依賴命名唯一性按選擇管理適合交互式工具遍歷集合適合批量處理。下面實戰(zhàn)部分主要用遍歷方式最穩(wěn)。4. 實戰(zhàn)一遍歷工程圖并導(dǎo)出標(biāo)注到Excel4.1 需求定義與方案設(shè)計這類需求很常見設(shè)計部門每季度要出全量圖紙標(biāo)注臺賬供工藝和采購核對。手工做法是打開一張圖逐個查看標(biāo)注復(fù)制信息到Excel百張圖紙工作量巨大。我的實現(xiàn)思路是打開SolidWorks工程圖不顯示界面后臺運行。遍歷所有圖紙Sheet。遍歷每張圖紙的所有視圖View。遍歷視圖內(nèi)的所有注解Annotation。按類型篩選出尺寸標(biāo)注讀取名稱、值、公差、顯示的文字等信息。寫入Excel文件我用開源的NPOI避免裝Office。4.2 核心代碼遍歷與讀取public ListDimInfo CollectDimensions(IModelDoc2 doc) { var result new ListDimInfo(); int sheetCount doc.SheetCount; for (int i 1; i sheetCount; i) { Sheet sheet doc.GetSheet(i); doc.ActivateSheet(sheet.GetName()); View curView sheet.GetFirstView(); while (curView ! null) { Annotation ann curView.GetFirstAnnotation(); while (ann ! null) { int annType ann.GetType2(); if (annType (int)swAnnotationType_e.swAnnotationDimension) { Dimension dim ann as Dimension; if (dim ! null) { DimInfo info new DimInfo { SheetName sheet.GetName(), ViewName curView.GetName(), DimName dim.GetName(), DimText dim.GetText() }; result.Add(info); } } ann curView.GetNextAnnotation(ann); } curView curView.GetNextView(); } } return result; }這里有一個關(guān)鍵細節(jié)GetNextAnnotation的參數(shù)是當(dāng)前Annotation對象。剛寫這個遍歷時我犯了個錯誤——只調(diào)GetFirstAnnotation忘記傳參調(diào)用GetNextAnnotation(ann)結(jié)果程序死循環(huán)或者只能拿到第一個標(biāo)注。視圖遍歷也一樣GetNextView()必須有當(dāng)前視圖作為上下文。SolidWorks API這些遍歷方法基本都是“先把當(dāng)前對象傳進去返回下一個”的模式和.NET集合的迭代器風(fēng)格完全不同寫的時候很容易轉(zhuǎn)不過彎來。4.3 屬性讀取不只是文字尺寸標(biāo)注值得讀取的屬性遠不止顯示文字。我在項目里主要讀這幾項屬性API方法說明名稱dim.GetName()如 D1草圖1顯示文字dim.GetText()實際顯示的字符串含前綴后綴名義值dim.GetValue()尺寸數(shù)值double公差上下偏差dim.GetToleranceValues()返回上下偏差數(shù)組顯示精度dim.GetDimensionText()相關(guān)小數(shù)位數(shù)信息關(guān)聯(lián)特征dim.GetFeature()返回關(guān)聯(lián)的特征對象有個坑要提醒GetText()返回的是“最終顯示文本”SolidWorks在顯示時會根據(jù)尺寸值和當(dāng)前單位設(shè)置把數(shù)值格式化。如果你要拿原始表達式比如形如“D1草圖1 0.5”的驅(qū)動表達式需要調(diào)用更底層的GetDimensionText()之類的方法。我實際遇到過一個場景程序要把所有標(biāo)注的“顯示文本”和“驅(qū)動表達式”分別導(dǎo)出結(jié)果發(fā)現(xiàn)GetText()返回“15”而驅(qū)動表達式其實是“D1草圖3 0.2”查API文檔查了一會兒才定位到正確方法。5. 實戰(zhàn)二批量修改標(biāo)注與自動添加注釋5.1 批量修改標(biāo)注前綴的完整案例我在開頭提到的那個任務(wù)——把一批工程圖中所有直徑標(biāo)注的前綴從舊代號改成新代號。實現(xiàn)邏輯其實不復(fù)雜public int ReplaceDimPrefix(IModelDoc2 doc, string oldPrefix, string newPrefix) { int count 0; foreach (var dim in GetDimensions(doc)) { string text dim.GetText(); if (text.Contains(oldPrefix)) { string newText text.Replace(oldPrefix, newPrefix); bool ok dim.SetText(newText); if (ok) count; } } return count; }SetText方法返回布爾值。這里要特別注意不是每次SetText都會成功。如果傳入的字符串不符合當(dāng)前系統(tǒng)的標(biāo)注格式比如在直徑標(biāo)注里傳了個沒有前綴的數(shù)字SolidWorks可能會拒絕修改。我實測遇到的情況是SetText返回false但沒有任何異常拋出來。所以一定要檢查返回值否則你以為改成功了實際圖紙紋絲不動。尺寸文字修改還有一個陷阱當(dāng)你調(diào)用SetText后尺寸關(guān)聯(lián)的3D模型可能也會跟著變因為尺寸文字本身可能承載了驅(qū)動語義尤其是引用方程式或全局變量的尺寸。如果要“只改顯示、不動幾何”最安全的方式是操作Annotation的顯示屬性而非直接改尺寸文字。這需要根據(jù)業(yè)務(wù)需求仔細判斷。5.2 自動創(chuàng)建標(biāo)注注釋的實際操作除了改尺寸有時候還需要在圖紙上自動添加文字注釋比如在圖紙角落寫入“設(shè)計者XXX 審核YYY 日期ZZZ”。通過API添加注釋的方式public bool AddNoteToSheet(ModelDoc2 doc, string sheetName, string noteText, double x, double y) { bool ok doc.ActivateSheet(sheetName); if (!ok) return false; // 在指定坐標(biāo)插入文字注釋 bool created doc.InsertNote(noteText, x, y); if (!created) return false; // 獲取最后一個注釋對象剛創(chuàng)建的 Note swNote doc.GetLastFeatureManagerData() as Note; return swNote ! null; }注意坐標(biāo)系統(tǒng)SolidWorks的插入坐標(biāo)默認是圖紙坐標(biāo)系單位是米沒錯API內(nèi)部一律用SI單位哪怕你界面設(shè)置是毫米。我第一版程序在毫米單位下輸入了10, 10結(jié)果注釋跑到了圖紙外找半天沒找著。換算很簡單需要處理的數(shù)值直接除以1000即可。關(guān)于定位注釋更穩(wěn)的做法是給注釋添加“定位點”和“引線”而不是用固定坐標(biāo)。因為工程圖設(shè)計變量多固定坐標(biāo)在不同比例的視圖里顯示位置不理想。SolidWorks的Note.AddLeader方法可以給注釋添加引線到某個幾何實體上這樣注釋跟著特征走位置更靈活。不過引線相關(guān)API在文檔里描述不夠詳細需要用試驗法摸索。我的建議是先用錄制宏的方式記錄一次手動添加注釋的操作然后打開VBA編輯窗口查看生成的宏代碼這是了解API調(diào)用最好的學(xué)習(xí)資料。5.3 標(biāo)注樣式的統(tǒng)一性維護另外一個實用場景是統(tǒng)一標(biāo)注樣式。團隊協(xié)作中經(jīng)常出現(xiàn)各人的標(biāo)注字體、箭頭樣式、文字高度不一致。通過C#可以批量對齊這些屬性標(biāo)注文字高度dim.GetTextHeight()通過dim.SetTextHeight()調(diào)整單位是米。箭頭類型遍歷尺寸通過dim.ArrowHeadStyle相關(guān)屬性調(diào)整。公差顯示方式dim.GetDimTolerance/SetDimTolerance相關(guān)方法可以統(tǒng)一改為“雙邊公差”或“對稱公差”。這些操作對API枚舉值比如箭頭樣式用起來有點繁瑣因為文檔不直觀。我實際做法是先用宏錄制一遍找到枚舉值的真實數(shù)值再在C#里用對應(yīng)枚舉硬編碼或定義常量。比如箭頭樣式在swArrowheadStyle_e里取值有實心、空心、建筑制圖等好幾種錄一遍宏就知道當(dāng)前命令默認用的是哪個數(shù)值。6. 常見問題與排查技巧實錄6.1 COM對象泄漏SolidWorks越來越卡這個問題我說過幾乎是所有COM二次開發(fā)都繞不過去的坎。SolidWorks的API對象大多派生自COMC#里的GC不會自動釋放COM引用必須手動調(diào)用Marshal.FinalReleaseComObject。實際經(jīng)驗是try { // 使用對象 } finally { Marshal.FinalReleaseComObject(dim); Marshal.FinalReleaseComObject(view); Marshal.FinalReleaseComObject(doc); }這個做法在遍歷嵌套對象時要注意順序先釋放子對象再釋放父對象。而且API文檔明確提示不要對同一個COM指針調(diào)用FinalReleaseComObject兩次否則會拋異常。還有一種更隱蔽的泄漏object類型的中間變量。很多API方法返回的是object你把它賦給具體類型后原來的object被丟棄了但COM引用計數(shù)還掛在那邊。穩(wěn)妥做法是用try-finally包住或者啟動一個定時GC兜底。最實在的技巧是批量處理大量圖紙時每處理完一張圖紙就調(diào)用一次GC.Collect()配合GC.WaitForPendingFinalizers()雖然粗暴但有效。6.2 后臺運行與界面刷新的取舍處理過程要不要顯示SolidWorks界面長期是個雙難選擇顯示界面速度慢但你能實時看到程序在干什么調(diào)試方便。后臺運行_swApp.Visible false速度快出問題時難以定位。我的建議是開發(fā)階段用Visible true真實批量跑時用false。另外隱藏界面時SolidWorks可能不刷新視圖導(dǎo)致你讀取的標(biāo)注狀態(tài)是“邏輯上的最新值”而不是“界面顯示值”。補救辦法是在關(guān)鍵操作后調(diào)用doc.ForceRebuild3(true)之類的重繪方式強制刷新。實際上在批處理流程中我遇到過一個罕見情況隱藏界面時GetCurrentSheet()返回的不是當(dāng)前激活圖紙因為界面沒有實時同步激活狀態(tài)。這個坑的解法是不要用GetCurrentSheet老老實實用GetSheet(i)按索引遍歷。6.3 版本兼容性不同SolidWorks版本的API差異C#加SolidWorks二次開發(fā)的另一個常見煩惱是版本差異。SolidWorks的API在2016、2018、2020等大版本切換時部分方法的簽名和枚舉定義會變化。最典型的是swAnnotationType_e枚舉的值在不同版本間有微小偏移導(dǎo)致在新版本上篩選類型時意外漏掉某些對象。應(yīng)對辦法有兩條鎖版本——只在目標(biāo)版本的SolidWorks上運行反正是內(nèi)部工具。類型泛化——不依賴具體枚舉判斷而是通過“對象是否能轉(zhuǎn)換為Dimension”來篩選。后者的寫法就算枚舉變了程序也能正常工作if (ann is Dimension dim) { // 這里直接處理 Dimension }這個寫法明顯更穩(wěn)健。6.4 程序偶發(fā)崩潰與異常處理批量運行時最怕的是跑到第37張圖紙時程序崩潰前面36張白跑了。我的處理方案是把異常捕獲放在單張圖紙粒度for (int i 1; i doc.SheetCount; i) { try { ProcessSheet(doc, i); } catch (Exception ex) { Log($Sheet {i} 處理失敗: {ex.Message}); continue; // 跳過繼續(xù) } }另外別忘了給AppDomain.CurrentDomain.UnhandledException掛一個全局處理器把棧信息寫進日志。COM異常在CLR里往往被包裝成COMException你需要讀取ErrorCode和Source字段才能看到有效信息。我排查過一個問題表面報錯在SetText實際根源是前面某次SelectByID2傳入的參數(shù)類型不對對象根本沒被選中后續(xù)操作全部建立在無效對象上。這就是“錯誤出現(xiàn)在下游根源在上游”的典型COM二次開發(fā)案例。6.5 調(diào)試技巧用好SolidWorks的宏錄制器最后一個技巧是二次開發(fā)新人最容易忽略的先錄宏再翻譯成C#。在SolidWorks里打開“宏錄制”工具→宏→錄制/暫停手動操作一遍你要自動化的動作比如改一個標(biāo)注文字、添加一個注釋、設(shè)置一個公差錄制出來的VBA代碼就是API調(diào)用的標(biāo)準(zhǔn)答案。這個思路的價值在于錄制宏學(xué)API的效率比翻文檔高太多很多對象屬性和方法在官方幫助文檔里寫得很晦澀但錄制出來的VBA代碼直白易懂。我基本已經(jīng)把“錄宏→轉(zhuǎn)C#”當(dāng)作SolidWorks二次開發(fā)的第一方法論。7. 我的一些實戰(zhàn)心得做C#加SolidWorks標(biāo)注自動化這個方向前后跑了不少項目最深的體會有幾點。第一理解業(yè)務(wù)比理解API更重要。標(biāo)注的自動化處理表面上是操作對象實際是要搞清楚圖紙上的標(biāo)注承擔(dān)了什么信息角色——是工藝標(biāo)識、尺寸公差還是表注文本。不同的信息角色修改的策略完全不一樣。程序把“該改的漏了不該改的改了”比手工改錯還要棘手。第二SolidWorks二次開發(fā)本質(zhì)上是一種“講故事”式的編程。你要先連接、再有文檔、再選視圖、再挑標(biāo)注每一層的對象獲取都依賴上一層的正確狀態(tài)。任何一步出錯后面全是空的。所以我的程序里每一步都做了嚴格的對象非空檢查寧可多寫幾十行判斷也不愿在運行時才看到“對象未引用”的報錯。第三批量化標(biāo)注處理特別適合做成“工具鏈”而不是單一腳本。從圖紙批量打開、標(biāo)注提取、Excel導(dǎo)出、錯誤標(biāo)記、修改回寫這個鏈路里的每一環(huán)都可以獨立復(fù)用到其他任務(wù)上。我后來把核心的DimensionReader和NoteWriter類抽象出來新項目直接復(fù)用省下大量重復(fù)編碼的時間。如果你也想做這類開發(fā)建議從最小的場景入手——先寫一個能讀取當(dāng)前工程圖里所有標(biāo)注并輸出Name和Text的小工具跑通了再逐步加功能。把宏錄制、Interop引用、COM釋放這幾個基礎(chǔ)問題先弄清楚后面自然就順了。關(guān)于標(biāo)注的自動化處理還有個延伸方向值得提一下與PLM/ERP系統(tǒng)對接。通過C#讀取SolidWorks標(biāo)注信息后直接寫入制造執(zhí)行系統(tǒng)的數(shù)據(jù)庫實現(xiàn)設(shè)計到生產(chǎn)的“圖紙標(biāo)注數(shù)據(jù)化”。這個方向在很多企業(yè)里還處于空白但需求非常明確做出來價值不小。最后分享一個小技巧批量處理前先對單張圖紙做一次完整走查確認你的程序在“標(biāo)準(zhǔn)圖紙”上跑出來的結(jié)果和你手工核查的結(jié)果完全一致再上量。我在早期項目里就是跳過這個環(huán)節(jié)直接跑50張圖紙后來發(fā)現(xiàn)遍歷視圖時漏了“圖紙格式”里的注釋導(dǎo)致一批標(biāo)注臺賬少了幾十行數(shù)據(jù)返工成本比預(yù)想高得多。磨刀不誤砍柴工先驗證再鋪開這個習(xí)慣比任何API技巧都更值得養(yǎng)成。