【極限の知見】SolidWorks 図面における Sketch Block の制御:一括配置と安全な一括削除のアーキテクチャ
多くのエンジニアは、SolidWorks APIを「操作を自動化するためのツール」と考えている。しかし、エンタープライズレベルの設計環境において、APIは「設計資産を統制するためのインフラ」でなければならない。
特に図面(Drawing)における「ユーザー定義記号(Sketch Block)」の扱いは、その典型だ。社内標準の注意記号、品質管理マーク、あるいは特殊な検図印。これらをプログラムで正確な座標に配置し、かつ、旧規格となった古いシンボルを、図面データの整合性を破壊せずに一括削除する。この一見単純なタスクの裏には、COMオブジェクトのライフサイクル管理、コレクションの反復におけるインデックスの動的変化、そしてメモリリークの回避という、シニアエンジニアが直面する深淵な問題が潜んでいる。
本稿では、単なる「動くコード」ではなく、大規模な図面データに対しても堅牢に動作する、プロフェッショナルな実装手法を詳解する。
1. Sketch Block の正体:階層構造の理解
まず、誤解を解いておかなければならない。SolidWorksの図面における「Sketch Block」は、単なる図形ではない。それは `SketchBlockInstance` というオブジェクトとして、特定の `Sheet` または `View` のコンテキストに紐付けられた、厳格な階層構造を持つエンティティである。
初心者が陥る最大の罠は、「図面全体からブロックを探そうとして、全エンティティを全スキャンする」という、計算量 $O(n)$ の非効率なアプローチだ。大規模なアセンブリ図面において、これはアプリケーションのフリーズを招く。
効率的な制御のためには、以下の階層を意識せよ。
1. DrawingDoc (Root)
2. Sheet (コンテキスト)
3. View (注釈のコンテキスト)
4. SketchBlockInstance (実体)
2. 破壊的削除における「インデックスの罠」
「特定の名前を持つブロックをすべて削除する」という要件に対し、多くの者が書くコードは以下の通りだ。
' --- 典型的な失敗例 ---
For i = 1 To blockCount
If blocks(i).Name = "Old_Symbol" Then
blocks(i).Delete
End If
Next i
これは致命的なバグを内包している。`Delete` メソッドを呼び出した瞬間、コレクション内の要素が詰められ、インデックスが繰り上がる。その結果、削除対象の次の要素がスキップされるという現象が発生する。
これを回避するプロフェッショナルな戦略は二つある。
1. 逆順ループ (Reverse Loop): インデックスを末尾からデクリメントしていく。
2. コレクションへの集約 (Collection Gathering): 削除対象のオブジェクト参照のみを一旦別の `Collection` オブジェクトに格納し、後から一括処理する。
後者の方が、メモリ管理の観点から予測可能性が高い。
3. 実装:極限の堅牢性を備えた制御モジュール
以下に、配置と削除の双方をカバーする、実戦的なクラスモジュール級のロジックを示す。
Option Explicit
' ==============================================================================
' Module: SymbolManager
' Description: 図面内におけるSketch Blockの精密な配置および一括管理
' ==============================================================================
''' <summary>
''' 指定したシート上の指定座標に、標準シンボルを配置する
''' </summary>
''' <param name="swDraw">SolidWorks Drawing Document</param>
''' <param name="blockName">配置するブロックの定義名</param>
''' <param name="posX">X座標 (m)</param>
''' <param name="posY">Y座標 (m)</param>
Public Sub InsertStandardSymbol(ByRef swDraw As SldWorks.DrawingDoc, _
ByVal blockName As String, _
ByVal posX As Double, _
ByVal posY As Double)
Dim swSheet As SldWorks.Sheet
Dim swModel As SldWorks.ModelDoc2
Dim swSketchBlock As SldWorks.SketchBlock
Dim swBlockInst As SldWorks.SketchBlockInstance
Dim swAnnotation As SldWorks.Annotation
Set swModel = swDraw
Set swSheet = swDraw.GetCurrentSheet
' 1. ブロック定義の取得 (既存の定義から検索)
' ※実際の実装では、ライブラリファイルから読み込む処理が必要
Set swSketchBlock = swModel.GetSketchBlock(blockName)
If swSketchBlock Is Nothing Then
Err.Raise vbObjectError + 513, "SymbolManager", "指定されたブロック定義が見つかりません: " & blockName
Exit Sub
End If
' 2. 配置処理
' InsertSketchBlockは、現在のシートのコンテキストにインスタンスを作成する
Set swBlockInst = swModel.InsertSketchBlock(swSketchBlock, posX, posY, 0)
If swBlockInst Is Nothing Then
Debug.Print "配置失敗: " & blockName
Exit Sub
End If
' 3. オブジェクトの明示的解放 (メモリ最適化)
' COMオブジェクトの参照カウントを適切に管理する
Set swBlockInst = Nothing
Set swSketchBlock = Nothing
Set swSheet = Nothing
Set swModel = Nothing
End Sub
''' <summary>
''' 特定の名称を持つ古いブロックを、図面全体から安全に一括削除する
''' </summary>
''' <param name="swDraw">SolidWorks Drawing Document</param>
''' <param name="targetName">削除対象のブロック名(部分一致)</param>
Public Sub PurgeOldSymbols(ByRef swDraw As SldWorks.DrawingDoc, ByVal targetName As String)
Dim swModel As SldWorks.ModelDoc2
Dim swView As SldWorks.View
Dim swBlockInst As SldWorks.SketchBlockInstance
Dim delCollection As New Collection
Dim i As Long
Set swModel = swDraw
' --- Step 1: 削除対象の収集 (Scanning Phase) ---
' 全ビューを走査し、条件に合致するインスタンスをCollectionに集める
' これにより、削除中のインデックス変動問題を完全に回避する
Set swView = swModel.GetFirstView ' Sheet自体が最初のビュー
Do While Not swView Is Nothing
' ビュー内のすべてのエンティティを走査
' Note: 実際にはView.GetFirstSketchBlock を使用するのが高速
Set swBlockInst = swView.GetFirstSketchBlock
Do While Not swBlockInst Is Nothing
' ブロック名の判定 (大文字小文字を区別しない)
If InStr(1, swBlockInst.Name, targetName, vbTextCompare) > 0 Then
delCollection.Add swBlockInst
End If
Set swBlockInst = swBlockInst.GetNext
Loop
Set swView = swView.GetNextView
Loop
' --- Step 2: 実行フェーズ (Execution Phase) ---
If delCollection.Count = 0 Then
Debug.Print "削除対象のシンボルは見つかりませんでした。"
Exit Sub
End If
' Undoを一つのトランザクションとして扱うため、画面更新を一時停止
swModel.ActiveView.EnableGraphicsUpdate = False
On Error Resume Next ' 削除中の予期せぬエラーでプロセスを止めないための防御
For i = 1 To delCollection.Count
Set swBlockInst = delCollection(i)
If Not swBlockInst Is Nothing Then
swBlockInst.Select2 False, 0
swModel.EditDelete
End If
' インスタンスへの参照を切り離す
Set swBlockInst = Nothing
Next i
On Error GoTo 0
' 3. 描画の再開とクリーンアップ
swModel.ActiveView.EnableGraphicsUpdate = True
swModel.ForceRebuild3 True
Debug.Print delCollection.Count & " 個のシンボルを削除しました。"
' 参照の完全な破棄
Set delCollection = Nothing
Set swModel = Nothing
End Sub
4. アーキテクトの視点:パフォーマンスとメモリ管理の極意
このコードを実運用に投入する際、シニアエンジニアが意識すべき「極限の知見」を3点付け加える。
I. COM参照の明示的解放(Explicit Release)
VBAはガベージコレクション(GC)を持っているように見えるが、それはあくまでVBA内部の挙動だ。SolidWorksのような巨大なCOMサーバーを操作する場合、`Set obj = Nothing` を怠ると、SolidWorksのプロセスがメモリ上に残り続け、あるいはメモリリークを引き起こして、数時間後の「謎のクラッシュ」を招く。特にループ内でオブジェクトを生成する場合は、必ずスコープの終わりに明示的な解放を行え。
II. Windows API による UI フリーズの抑制
大規模な削除処理を行う際、SolidWorksのGUIは応答なし(Not Responding)状態になる。これを防ぐには、`swModel.ActiveView.EnableGraphicsUpdate = False` を活用するのは当然として、もしより高度なシステム連携(C#等からの呼び出し)を行うのであれば、`SendMessage` API を用いて、処理中に「処理中…」というメッセージをOSのメッセージキューに流し込み、ユーザーのストレスを軽減する設計が必要だ。
III. トランザクションの整合性
`EditDelete` を繰り返すと、Undoスタックが膨大な量になり、メモリを圧迫する。大量の削除を行う場合は、処理の前後で `swModel.Extension.SelectByID2` 等を用いて選択状態を適切に管理し、必要であれば、処理後に一度 `ForceRebuild3` を呼び出して、内部のグラフ構造(Feature Tree)を再構築させることが、データ破損を防ぐための鉄則である。
結論
図面自動化の真髄は、「動くものを作る」ことではない。「どのような異常系においても、設計データの整合性を保ち、システムを安定して稼働させ続けること」にある。
今回提示した「Collectionへの集約による削除手法」と「厳格な参照管理」は、そのための最小限かつ強力な武器となるだろう。これらを使いこなし、単なるスクリプターから、真のCADオートメーション・アーキテクトへと昇華してほしい。
