【上級プロ】クラスモジュールを活用した「アセンブリ合致ビルダー」のオブジェクト指向設計:保守性の高い再利用可能なVBAライブラリの構築
SolidWorks VBAによる自動化の現場において、アセンブリの構築ほどエンジニアの技量が露呈する領域はない。
「何百行にも及ぶ手続き型コードの羅列」「`SelectByID2` に依存した脆弱なコンテキスト操作」「エラーハンドリングの欠落によるCOMオブジェクトのメモリリーク」。これらはレガシーなVBA開発における悪しき伝統であり、少しでもモデルの階層構造が変われば即座に破綻する。
本稿では、SolidWorks APIの深層を理解し、クラスモジュールを用いたオブジェクト指向設計によって、保守性が高く、かつ堅牢な「アセンブリ合致ビルダー」を構築する極限の知見を公開する。
—
1. 散在する手続き型コードの限界と「合致ビルダー」の概念
従来の合致(Mate)定義コードは、以下のような手続きの連続であった。
1. コンポーネントをアセンブリに挿入する。
2. 画面上のエンティティを名前や型で選択(`SelectByID2`)する。
3. `AddMate5` などの巨大な引数を持つメソッドを叩く。
4. 選択解除を忘れて次の処理が暴走する。
これでは、大規模アセンブリの自動化においてコードベースが肥大化し、デバッグ不能に陥る。
我々が目指すべきは、以下のような流れるようなインターフェース(メソッドチェーン)による直感的なカプセル化である。
‘ 理想とする利用コードのイメージ
Dim builder As SldWorksAssemblyBuilder
Set builder = New SldWorksAssemblyBuilder
builder.Initialize “C:\Assemblies\TopAssy.sldasm”
builder.AddPart(“Base_Plate”, “C:\Parts\Plate.sldprt”, 0, 0, 0) _
.AddPart(“Bolt_M8”, “C:\Parts\Bolt.sldprt”, 0, 0, 10) _
.CreateMate(swMateCOINCIDENT, “Base_Plate”, “Face@Plate”, “Bolt_M8”, “Face@Bolt”) _
.Commit()
この思想を実現するため、SolidWorks APIのライフサイクルとCOMのメモリ管理を完全に制御したクラス設計を構築する。
—
2. アーキテクチャ設計とオブジェクトのライフサイクル
SolidWorks VBAにおける最大の罠は、COMオブジェクトの参照カウントと暗黙の選択セット(SelectionMgr)の汚染である。
クラスモジュール(例: `MateBuilder`)を設計するにあたり、以下の原則を厳守する。
- カプセル化: `SldWorks.ModelDoc2` や `SldWorks.AssemblyDoc` への参照をクラス内部に隠蔽し、外部に露出させない。
- ステート管理: 合致の定義情報を内部コレクションに蓄積し、最後の `.Commit()` 実行時にトランザクションとして一括処理する。
- メモリの明示的解放: クラスの終了時(`Terminate` イベント)や例外発生時に、内部保持しているCOMオブジェクトの参照を確実に `Nothing` に設定し、VBA特有のメモリリーク(SolidWorksプロセスのゾンビ化)を防ぐ。
—
3. 実装コード:堅牢な合致ビルダーライブラリ
以下に、実務で使用に耐えうる「アセンブリ合致ビルダー」のコア実装を示す。
※プロジェクト内にクラスモジュールを `AssemblyBuilder` という名前で作成し、以下のコードを記述してほしい。
クラスモジュール: `AssemblyBuilder`
Option Explicit
‘ ==============================================================================
‘ 致命的なCOMリークを防ぐためのプライベートフィールド
‘ ==============================================================================
Private m_swApp As SldWorks.SldWorks
Private m_swModel As SldWorks.ModelDoc2
Private m_swAssy As SldWorks.AssemblyDoc
‘ 合致定義を保持する構造体
Private Type MateDefinition
MateType As Long
ComponentName1 As String
EntityName1 As String
ComponentName2 As String
EntityName2 As String
Alignment As Long
End Type
Private m_MateQueue() As MateDefinition
Private m_MateCount As Long
‘ ==============================================================================
‘ クラス初期化・終了処理 (ライフサイクル管理)
‘ ==============================================================================
Private Sub Class_Initialize()
‘ アプリケーションインスタンスの取得
Set m_swApp = Application.SldWorks
m_MateCount = 0
ReDim m_MateQueue(0)
End Sub
Private Sub Class_Terminate()
‘ 確実にCOM参照を解放し、SolidWorksのメモリ残存を防ぐ
Set m_swAssy = Nothing
Set m_swModel = Nothing
Set m_swApp = Nothing
Erase m_MateQueue
End Sub
‘ ==============================================================================
‘ 初期化メソッド
‘ ==============================================================================
Public Function Initialize(ByVal assemblyPath As String) As AssemblyBuilder
Dim errors As Long
Dim warnings As Long
Set m_swModel = m_swApp.OpenDoc6(assemblyPath, swDocASSEMBLY, swOpenDocOptions_Silent, “”, errors, warnings)
If m_swModel Is Nothing Then
Err.Raise 9999, “AssemblyBuilder”, “アセンブリのオープンに失敗しました: ” & assemblyPath
End If
Set m_swAssy = m_swModel
Set Initialize = Me ‘ メソッドチェーンを実現するためのインスタンス返却
End Function
‘ ==============================================================================
‘ コンポーネント追加メソッド
‘ ==============================================================================
Public Function AddComponent(ByVal componentName As String, ByVal filePath As String, _
ByVal x As Double, ByVal y As Double, ByVal z As Double) As AssemblyBuilder
Dim swComp As SldWorks.Component2
‘ 単位系はメートル法(API標準)を前提とする
Set swComp = m_swAssy.AddComponents3(filePath, sw唤起_XYZ, x, y, z)
If swComp Is Nothing Then
Err.Raise 9998, “AssemblyBuilder”, “コンポーネントの挿入に失敗しました: ” & filePath
End If
‘ 必要に応じてコンポーネント名を変更する処理をここに記述
Set AddComponent = Me
End Function
‘ ==============================================================================
‘ 合致定義のキューイング(遅延実行パターン)
‘ ==============================================================================
Public Function QueueMate(ByVal mateType As Long, _
ByVal compName1 As String, ByVal entityName1 As String, _
ByVal compName2 As String, ByVal entityName2 As String, _
Optional ByVal alignment As Long = swMateAlign_ALIGNED) As AssemblyBuilder
If m_MateCount > 0 Then
ReDim Preserve m_MateQueue(m_MateCount)
End If
With m_MateQueue(m_MateCount)
.MateType = mateType
.ComponentName1 = compName1
.EntityName1 = entityName1
.ComponentName2 = compName2
.EntityName2 = entityName2
.Alignment = alignment
End With
m_MateCount = m_MateCount + 1
Set QueueMate = Me
End Function
‘ ==============================================================================
‘ コミット(トランザクション実行)
‘ ==============================================================================
Public Sub Commit()
Dim i As Long
Dim swSelMgr As SldWorks.SelectionMgr
Dim swMateRet As SldWorks.Mate2
Dim errCode As Long
Set swSelMgr = m_swModel.SelectionManager
‘ 画面描画を停止し、処理速度を極限まで高める
m_swModel.Extension.EnableSounds = False
m_swApp.SetUserPreferenceToggle swUserPreferenceToggle_e.swViewDisplayHideAllTypes, True
On Error GoTo ErrorHandler
For i = 0 To m_MateCount – 1
m_swModel.ClearSelection2 True
‘ エンティティの選択 (SelectByID2のラッパー)
If Not SelectEntity(m_MateQueue(i).ComponentName1, m_MateQueue(i).EntityName1, 1) Then
Err.Raise 9997, “AssemblyBuilder”, “エンティティ1の選択に失敗: ” & m_MateQueue(i).EntityName1
End If
If Not SelectEntity(m_MateQueue(i).ComponentName2, m_MateQueue(i).EntityName2, 2) Then
Err.Raise 9997, “AssemblyBuilder”, “エンティティ2の選択に失敗: ” & m_MateQueue(i).EntityName2
End If
‘ 合致の実行 (AddMate5)
Set swMateRet = m_swAssy.AddMate5( _
m_MateQueue(i).MateType, _
m_MateQueue(i).Alignment, _
False, _
0, 0, 0, 0, 0, 0, 0, 0, _
False, errCode)
If errCode <> 0 Then
Err.Raise 9996, “AssemblyBuilder”, “合致の作成に失敗しました。エラーコード: ” & errCode
End If
Next i
‘ 変更の保存とリビルド
m_swModel.ForceRebuild3 False
m_swModel.Save3 swSaveAsOptions_Silent, 0, 0
CleanUp:
‘ 描画設定の復元
m_swModel.Extension.EnableSounds = True
m_swApp.SetUserPreferenceToggle swUserPreferenceToggle_e.swViewDisplayHideAllTypes, False
Exit Sub
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
‘ ==============================================================================
‘ プライベートヘルパー: 脆弱なSelectByID2を隠蔽する
‘ ==============================================================================
Private Function SelectEntity(ByVal componentName As String, ByVal entityName As String, ByVal mark As Long) As Boolean
Dim selString As String
‘ SolidWorks特有の選択構文( 面@コンポーネント名@アセンブリ名 )を構築
selString = entityName & “@” & componentName & “@” & m_swModel.GetTitle()
‘ AppendFlag = True (複数選択)
SelectEntity = m_swModel.Extension.SelectByID2(selString, “FACE”, 0, 0, 0, True, mark, Nothing, swSelectOption_Default)
End Function
—
4. チーフアーキテクトが教える:実運用における極限の最適化テクニック
上記のクラスライブラリを実際のプロダクション環境やPLM/PDMシステム連携に投入する際、知っておくべき実務上の知見を授ける。
1. `EnableSounds` と画面描画抑制の真の効果
大規模アセンブリにおいて、合致を1つ追加するたびにSolidWorksがビューを再描画していると、処理時間が何倍にも膨れ上がる。
上記のコード内で行っている `EnableSounds = False` および表示タイプの非表示化は単なる化粧ではない。Windowsメッセージキューの溢れを防ぎ、COM通信のオーバーヘッドを劇的に削減するための必須の儀式である。
2. `SelectByID2` の文字列構築リスクへの対策
APIにおけるエンティティ名(例: `Face<1>` や `
高精度なシステム連携を行う場合、文字列によるハードコーディングを避け、`IComponent2::GetComponentByName` や `IBody2` / `IFace2` のポインターを直接トラバースして選択状態(`ISelectionMgr::AddSelectionListObject`)に渡す設計へと昇華させるべきだ。今回のサンプルは可読性を考慮して文字列選択(`SelectByID2`)を採用しているが、実務の堅牢性を極めるならオブジェクトポインター直交型へ移行せよ。
3. レガシー環境とメモリガベージコレクション
VBAはCOMオブジェクトの参照カウントを自動管理するが、多重ループや循環参照が発生した瞬間にメモリリークを起こす。
特にExcelマクロからSolidWorksを操作するような外部プロセス連携(Out-of-Process)の場合、VBA側でインスタンスを破棄し忘れると、タスクマネージャーに `SLDWORKS.exe` の残骸が残り続け、ライセンスを圧迫する。
クラスの終了時には必ずすべてのオブジェクト変数に `Nothing` を代入し、VBAのガベージコレクターを強制的にアシストする習慣をつけなければならない。
—
5. 総括
VBAはレガシーな言語と揶揄されがちだが、APIの仕様とメモリモデルを完全に掌握したプロフェッショナルが書くコードは、C#やC++製のアプリケーションに匹敵する堅牢性とパフォーマンスを発揮する。
今回紹介した「アセンブリ合致ビルダー」のオブジェクト指向設計を取り入れることで、あなたの書くマクロは「動くだけのスパゲッティコード」から「拡張可能で美しいエンタープライズライブラリ」へと進化する。
現場の自動化を極限まで押し上げ、真のエンジニアリングに集中してほしい。
