【上級プロ】クラスモジュールを活用した「アセンブリ合致ビルダー」のオブジェクト指向設計:保守性の高い再利用可能なVBAライブラリの構築
SolidWorks VBAでのアセンブリ自動化において、最も開発者を絶望させる瞬間は何か。それは、数千行に及ぶプロシージャの中で`AddMate5`メソッドが突如としてエラーを吐き、どの部品のどの面(Entity)が原因でコンフリクトを起こしたのか全くデバッグできない瞬間だ。
標準のAPIをそのままベタ書きしたコードは、部品の構成変更や面IDの変動に対して極めて脆弱である。実務に耐えうる堅牢な自動化ツールを構築するには、手続き型コードの呪縛を断ち切り、オブジェクト指向設計(OOP)を取り入れたカプセル化が不可欠となる。
今回は、SolidWorks VBAの限界を突破し、まるでモダンな専用APIのように直感的なメソッドチェーンでアセンブリを構築できる「アセンブリ合致ビルダー」の設計思想と実装コードを伝授する。
—
1. なぜ標準の `AddMate5` 直書きは破綻するのか
多くの開発者が最初に直面するアンチパターンを見てみよう。
‘ 【アンチパターン】手続き型のスパゲッティコード
Dim swMateFeat As SldWorks.Feature
Set swMateFeat = swAssembly.AddMate5(swMateType_e.swMateCoincident, _
swMateAlign_e.swMateAlignALigned, _
False, 0, 0, 0, 0, 0, 0, 0, 0, False, errCode)
このアプローチの何が問題か。
1. 引数の爆発: `AddMate5` は引数が多すぎて、何を指定しているのかコード単体では判読不能。
2. エラーハンドリングの欠如: `errCode` が返されても、どの面とどの面が失敗したのかコンテキストが失われている。
3. 再利用性の皆無: 似たような合致構造を作るたびに、同じような定型コードが乱立し、保守不能な「レガシー遺産」へと変貌する。
この課題を解決するため、「合致の意図(Intent)」をカプセル化するクラスモジュールを設計する。
—
2. アーキテクチャ設計:合致ビルダーの全体像
今回構築するライブラリは、以下の2つのコンポーネントで構成する。
1. `ClsAssemblyBuilder` (メインコーディネータークラス):
アセンブリドキュメントをラップし、構成部品の挿入とビルダーの起点を提供する。
2. `ClsMateBuilder` (フルーエントインターフェース/メソッドチェーンクラス):
合致の種類、面、方向を流れるような記述(Fluent Interface)で定義し、最後に実行する。
この設計により、クライアントコード(呼び出し側)は以下のように極めて直感的に記述できるようになる。
‘ 【理想的なクライアントコード】
Dim builder As New ClsAssemblyBuilder
builder.Init swApp, swAssyDoc
builder.AddPart(“Base_Plate”, “C:\Parts\Base.sldprt”) _
.AddPart(“Guide_Block”, “C:\Parts\Guide.sldprt”) _
.CreateMate() _
.Coincident() _
.Entities(“Base_Plate”, “TopFace”, “Guide_Block”, “BottomFace”) _
.Align(swMateAlign_e.swMateAlignALigned) _
.Build()
—
3. プロダクションコードの実装
それでは、実際のVBAプロジェクトにそのまま組み込める堅牢なコードを公開する。
実装1: クラスモジュール `ClsMateBuilder`
(※プロジェクト内のクラスモジュール名を `ClsMateBuilder` に設定してください)
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 ‘True
END
Attribute VB_Name = “ClsMateBuilder”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
‘ ==============================================================================
‘ クラス名: ClsMateBuilder
‘ 概要: Fluent Interfaceを用いた合致定義のカプセル化と実行
‘ ==============================================================================
Option Explicit
Private m_AssyDoc As SldWorks.AssemblyDoc
Private m_MateType As swMateType_e
Private m_Alignment As swMateAlign_e
Private m_EntityName1 As String
Private m_FaceName1 As String
Private m_EntityName2 As String
Private m_FaceName2 As String
Private m_ParentBuilder As ClsAssemblyBuilder
‘ 親ビルダーの設定
Public Sub SetContext(ByVal assyDoc As SldWorks.AssemblyDoc, ByVal parent As ClsAssemblyBuilder)
Set m_AssyDoc = assyDoc
Set m_ParentBuilder = parent
‘ デフォルト値
m_Alignment = swMateAlignALigned
End Sub
‘ — メソッドチェーン: 合致タイプの定義 —
Public Function Coincident() As ClsMateBuilder
m_MateType = swMateCoincident
Set Coincident = Me
End Function
Public Function Concentric() As ClsMateBuilder
m_MateType = swMateConcentric
Set Concentric = Me
End Function
Public Function Distance(ByVal dist As Double) As ClsMateBuilder
‘ ※拡張用:今回は簡略化のためタイプのみ
m_MateType = swMateDistance
Set Distance = Me
End Function
‘ — メソッドチェーン: 対象エンティティの指定 —
Public Function Entities(ByVal comp1 As String, ByVal face1 As String, ByVal comp2 As String, ByVal face2 As String) As ClsMateBuilder
m_EntityName1 = comp1
m_FaceName1 = face1
m_EntityName2 = comp2
m_FaceName2 = face2
Set Entities = Me
End Function
‘ — メソッドチェーン: アライメントの指定 —
Public Function Align(ByVal alignment As swMateAlign_e) As ClsMateBuilder
m_Alignment = alignment
Set Align = Me
End Function
‘ — ビルド実行&親ビルダーへの復帰 —
Public Function Build() As ClsAssemblyBuilder
Dim swModel As SldWorks.ModelDoc2
Set swModel = m_AssyDoc
‘ 1. コンポーネントと面(エンティティ)の選択
swModel.ClearSelection2 True
Dim bRet1 As Boolean
Dim bRet2 As Boolean
bRet1 = SelectEntity(m_EntityName1, m_FaceName1)
bRet2 = SelectEntity(m_EntityName2, m_FaceName2)
If Not (bRet1 And bRet2) Then
Err.Raise 9999, “ClsMateBuilder”, “指定された構成部品または面が見つかりませんでした: ” & m_EntityName1 & ” / ” & m_EntityName2
End If
‘ 2. 合致の実行
Dim swMateFeat As SldWorks.Feature
Dim errCode As Long
Set swMateFeat = m_AssyDoc.AddMate5( _
m_MateType, _
m_Alignment, _
False, _
0, 0, 0, 0, 0, 0, 0, 0, _
False, _
errCode)
If errCode <> 0 Then
Err.Raise 9998, “ClsMateBuilder”, “合致の作成に失敗しました。エラーコード: ” & errCode
End If
swModel.ClearSelection2 True
‘ 親ビルダーを返すことでチェーンを継続
Set Build = m_ParentBuilder
End Function
‘ 内部ヘルパー: コンポーネント名と面名からSolidWorks上のエンティティを選択状態にする
Private Function SelectEntity(ByVal compName As String, ByVal faceName As String) As Boolean
SelectEntity = False
Dim swModelDoc As SldWorks.ModelDoc2
Set swModelDoc = m_AssyDoc
‘ コンポーネントの走査と選択
Dim vComps As Variant
vComps = m_AssyDoc.GetComponents(False)
Dim i As Long
Dim targetComp As SldWorks.Component2
Dim found As Boolean
found = False
For i = LBound(vComps) To UBound(vComps)
Set targetComp = vComps(i)
‘ コンポーネント名(ルートからの相対名またはインスタンス名)のチェック
If InStr(1, targetComp.Name2, compName, vbTextCompare) > 0 Then
found = True
Exit For
End If
Next i
If Not found Then Exit Function
‘ 注意: 本番環境では面(Face)の特定にModelDocExtension::SelectByID2 または
‘ Body2/Face2を走査するロジックをここに実装します。
‘ ここでは簡略化のため、ID指定選択のラッパーを想定します。
Dim selString As String
selString = faceName & “@” & targetComp.Name2 & “@” & swModelDoc.GetTitle
SelectEntity = swModelDoc.Extension.SelectByID2(selString, “FACE”, 0, 0, 0, True, 1, Nothing, 0)
End Function
実装2: クラスモジュール `ClsAssemblyBuilder`
(※プロジェクト内のクラスモジュール名を `ClsAssemblyBuilder` に設定してください)
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 ‘True
END
Attribute VB_Name = “ClsAssemblyBuilder”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
‘ ==============================================================================
‘ クラス名: ClsAssemblyBuilder
‘ 概要: アセンブリの統括管理と部品配置を行うコーディネータクラス
‘ ==============================================================================
Option Explicit
Private swApp As SldWorks.SldWorks
Private swAssyDoc As SldWorks.AssemblyDoc
Private swModel As SldWorks.ModelDoc2
Public Sub Init(ByVal app As SldWorks.SldWorks, ByVal assyDoc As SldWorks.AssemblyDoc)
Set swApp = app
Set swAssyDoc = assyDoc
Set swModel = assyDoc
End Sub
‘ 構成部品の追加
Public Function AddPart(ByVal componentTag As String, ByVal filePath As String, Optional ByVal x As Double = 0, Optional ByVal y As Double = 0, Optional ByVal z As Double = 0) As ClsAssemblyBuilder
Dim errCode As Long
Dim swComp As SldWorks.Component2
Set swComp = swAssyDoc.AddComponents3(filePath, 0, x, y, z)
If swComp Is Nothing Then
Err.Raise 9997, “ClsAssemblyBuilder”, “部品の挿入に失敗しました: ” & filePath
End If
‘ 戻り値として自分自身を返し、メソッドチェーンを継続
Set AddPart = Me
End Function
‘ 合致ビルダーのインスタンスを生成して返す
Public Function CreateMate() As ClsMateBuilder
Dim mateBuilder As New ClsMateBuilder
mateBuilder.SetContext swAssyDoc, Me
Set CreateMate = mateBuilder
End Function
‘ アセンブリの保存とリビルド
Public Sub Complete()
swModel.EditRebuild3
swModel.Save3 swSaveAsOptions_Silent, 0, 0
End Sub
—
4. 実務におけるファイル・データベース連携の注意点
現場でこのライブラリを運用する際、ハードコーディングされたファイルパスや部品名はご法度である。以下のベストプラクティスを遵守してほしい。
1. PDM / データベースからのパス解決:
部品の配置パスは、Excelマスタや外部DB(SQL Server等)から動的に取得し、`AddPart` の引数に渡す設計にする。SolidWorks PDMを使用している場合は、最新のリビジョンがローカルキャッシュに正しく同期されていることを確認するAPI(`EdmLib` 等)を前段に挟むこと。
2. コンポーネント名の揺れ対策:
SolidWorksは、同じ部品を複数アセンブリに配置すると `Base_Plate-1`, `Base_Plate-2` のようにサフィックスが付与される。クラス内の選択ロジックでは、完全一致ではなく `InStr` による部分一致や、内部的なコンポーネントID(`GetID`)による管理を取り入れると、大規模アセンブリでも破綻しなくなる。
—
5. チーフアーキテクトからの総括
今回紹介した「アセンブリ合致ビルダー」は、単なるコードの綺麗さを競うものではない。
「VBAであっても、モダンな言語と同等の保守性と堅牢性を担保できる」という実務的な証明である。
手続き型のベタ書きコードは、書いた本人しか直せない「負債」を生む。しかし、オブジェクト指向のカプセル化を取り入れたライブラリを一度構築してしまえば、若手エンジニアでも安全に、かつ高速にアセンブリ自動化ツールを拡張していくことが可能になる。
あなたの現場のSolidWorks VBA開発を、次のステージへと引き上げてほしい。
