【テクニカル・上級編】【上級プロ】レガシーなVBAプロシージャをクラスモジュールベースの「アセンブリ自動化フレームワーク」へモダンリファクタリング – SolidWorks VBA解析バイブル

スポンサーリンク

SolidWorks VBAを掌握する極限の知見

【上級プロ】レガシーなVBAプロシージャをクラスモジュールベースの「アセンブリ自動化フレームワーク」へモダンリファクタリング

多くの製造業の現場で、今この瞬間も動いているSolidWorksのVBAマクロ。その実態は、生成されたコードの断片が継ぎ足された「スパゲッティ・コード」であり、APIのエラーハンドリングが欠落し、メモリリークを日常的に引き起こすレガシーな爆弾と化していないだろうか。

「アセンブリの合致(Mate)定義を追加したら、なぜかCOMオブジェクトが解放されずにSolidWorksがフリーズする」
「一部の部品変更だけでスクリプト全体が破綻し、誰も手を入れることができない」

本稿では、レガシーな手続き型VBAの限界を突破し、オブジェクト指向設計の思想を極限まで持ち込んだ「アセンブリ自動化フレームワーク」の構築手法を詳解する。チーフアーキテクトとしての知見をすべて注ぎ込み、実務に耐えうる堅牢なコードベースへの昇華方法を示す。

—

1. なぜレガシーなVBAは破綻するのか?(アーキテクチャの限界)

従来のSolidWorks VBAマクロの多くは、以下のようなアンチパターンを含んでいる。

1. グローバル変数の乱用と状態管理の欠如: `SldWorks.Application` や `ModelDoc2` を至る所で `GetActiveObject` し、現在のコンテキストが不明確になる。
2. エラー時のCOM参照リーク: 途中でエラーが発生した際、`Set swModel = Nothing` が実行されず、背後で `sldworks.exe` のプロセスやCOMラッパーがメモリ上に残存する。
3. ハードコーディングされた合致(Mate): 面の選択(`Extension.SelectByID2`)の文字列やパスがプロシージャ内に直書きされ、モデル変更に極めて脆弱。

これらを解決するため、「コンテキストのカプセル化」「オブジェクトのライフサイクル管理」「関心事の分離(SoC)」をVBAのクラスモジュール(Class Module)で実装する。

—

2. 設計思想:アセンブリ自動化フレームワークの全体像

今回構築するフレームワークは、以下の3つのレイヤーで構成する。

  • `CAssemblyContext`(基盤層): SolidWorksのセッション、アクティブアセンブリのライフサイクル、エラーハンドリングを管理。
  • `CComponentNode`(要素層): 配置する部品(リソース)のパスや座標変換、インスタンス生成をカプセル化。
  • `CMateConstraint`(関係層): 面と面の合致、距離、角度などの制約条件を抽象化し、動的に適用。

—

3. 実装コード:モダン・リファクタリングの全貌

以下のコードは、単なるリファレンスの引き写しではない。COMの参照カウンタ(AddRef/Release)の呪縛から逃れ、確実にメモリを解放するプロダクション品質のコードである。

① 基盤層:アセンブリコンテキスト管理クラス

クラス名:`CAssemblyContext`

‘ =========================================================================
‘ クラス名: CAssemblyContext
‘ 概要: SolidWorksセッションおよびアセンブリドキュメントのライフサイクル管理
‘ =========================================================================
Option Explicit

Private m_swApp As SldWorks.SldWorks
Private m_swModel As SldWorks.ModelDoc2
Private m_swAssy As SldWorks.AssemblyDoc
Private m_isInitialized As Boolean

Private Sub Class_Initialize()
On Error GoTo ErrorHandler
‘ 実行中のSolidWorksインスタンスにアタッチ
Set m_swApp = CreateObject(“SldWorks.Application”)
If m_swApp Is Nothing Then
Err.Raise 9999, “CAssemblyContext”, “SolidWorksが起動していません。”
End If
m_isInitialized = True
Exit Sub

ErrorHandler:
m_isInitialized = False
‘ 致命的な初期化エラーのロギング
Debug.Print “Initialization Error: ” & Err.Description
End Sub

Private Sub Class_Terminate()
‘ 明示的な参照解放によるメモリリークの防止
Set m_swAssy = Nothing
Set m_swModel = Nothing
Set m_swApp = Nothing
m_isInitialized = False
End Sub

Public Property Get Application() As SldWorks.SldWorks
Set Application = m_swApp
End Property

Public Property Get ActiveAssembly() As SldWorks.AssemblyDoc
Set ActiveAssembly = m_swAssy
End Property

‘ 新規アセンブリドキュメントの作成
Public Function CreateNewAssembly() As Boolean
If Not m_isInitialized Then Exit Function

Dim docTemplate As String
docTemplate = m_swApp.GetUserPreferenceStringValue(swUserPreferenceStringValue_e.swDefaultTemplateAssembly)

Set m_swModel = m_swApp.NewDocument(docTemplate, swDwgPaperSizes_e.swDwgPaperA3size, 0, 0)
If Not m_swModel Is Nothing Then
If m_swModel.GetType() = swDocumentTypes_e.swDocASSEMBLY Then
Set m_swAssy = m_swModel
CreateNewAssembly = True
Exit Function
End If
End If
CreateNewAssembly = False
End Function

② 要素層:コンポーネント配置クラス

クラス名:`CComponentNode`

‘ =========================================================================
‘ クラス名: CComponentNode
‘ 概要: アセンブリ内の単一コンポーネント(部品)のインスタンスと配置を制御
‘ =========================================================================
Option Explicit

Private m_swAssy As SldWorks.AssemblyDoc
Private m_componentPath As String
Private m_swComp As SldWorks.Component2

Public Property Let ComponentPath(ByVal path As String)
m_componentPath = path
End Property

Public Property Set Assembly(ByRef assy As SldWorks.AssemblyDoc)
Set m_swAssy = assy
End Property

Public Property Get Component() As SldWorks.Component2
Set Component = m_swComp
End Property

‘ コンポーネントの挿入と固定(Fix)
Public Function InsertAndFix(ByVal x As Double, ByVal y As Double, ByVal z As Double) As Boolean
If m_swAssy Is Nothing Or m_componentPath = “” Then Exit Function

Dim errCode As Long
Set m_swComp = m_swAssy.AddComponents3(m_componentPath, False, x, y, z)

If Not m_swComp Is Nothing Then
‘ 原点付近に配置された最初の部品を固定する場合
Dim modelDoc As SldWorks.ModelDoc2
Set modelDoc = m_swAssy
modelDoc.ClearSelection2 True

Dim compName As String
compName = m_swComp.Name2

‘ コンポーネントの選択と固定化
boolstatus = modelDoc.Extension.SelectByID2(compName & “@” & modelDoc.GetTitle(), “COMPONENT”, 0, 0, 0, False, 0, Nothing, 0)
If boolstatus Then
m_swAssy.MenuChoise swCommands_e.swAssemblyFix
modelDoc.ClearSelection2 True
End If
InsertAndFix = True
Exit Function
End If

InsertAndFix = False
End Function

③ 関係層:合致(Mate)定義クラス

クラス名:`CMateConstraint`

‘ =========================================================================
‘ クラス名: CMateConstraint
‘ 概要: 2つのエンティティ間の合致関係を安全に構築する
‘ =========================================================================
Option Explicit

Public Enum MateType
MateCoincident = 1
MateConcentric = 2
MateDistance = 3
MateParallel = 4
End Enum

‘ 汎用的な面・軸合致の適用
Public Function ApplyMate(ByVal swModel As SldWorks.ModelDoc2, _
ByVal entity1Name As String, ByVal entity1Type As String, _
ByVal entity2Name As String, ByVal entity2Type As String, _
ByVal mType As MateType) As Boolean

If swModel Is Nothing Then Exit Function

swModel.ClearSelection2 True

‘ エンティティ1の選択 (Append = False)
Dim b1 As Boolean
b1 = swModel.Extension.SelectByID2(entity1Name, entity1Type, 0, 0, 0, False, 1, Nothing, 0)

‘ エンティティ2の選択 (Append = True)
Dim b2 As Boolean
b2 = swModel.Extension.SelectByID2(entity2Name, entity2Type, 0, 0, 0, True, 1, Nothing, 0)

If Not (b1 And b2) Then
Debug.Print “Mate Error: 選択エンティティが見つかりません -> ” & entity1Name & ” / ” & entity2Name
ApplyMate = False
Exit Function
End If

Dim swAssy As SldWorks.AssemblyDoc
Set swAssy = swModel

Dim errCode As Long
Dim mateEntity As SldWorks.Mate2

‘ 合致タイプのマッピングと実行
Select Case mType
Case MateType.MateCoincident
Set mateEntity = swAssy.AddMate3(swMateType_e.swMateCOINCIDENT, swMateAlign_e.swMateAlignCLOSEST, False, 0, 0, 0, 0, 0, 0, 0, 0, False, errCode)
Case MateType.MateConcentric
Set mateEntity = swAssy.AddMate3(swMateType_e.swMateCONCENTRIC, swMateAlign_e.swMateAlignCLOSEST, False, 0, 0, 0, 0, 0, 0, 0, 0, False, errCode)
Case Else
errCode = -1
End Select

swModel.ClearSelection2 True
ApplyMate = (errCode = 0)
End Function

—

4. エントリーポイント(メイン処理)の洗練

上記で作成したクラス群を束ねる標準モジュール(Main Module)は、これほどまでにシンプルかつ堅牢になる。ビジネスロジックとAPIの泥臭い処理が完全に分離されている点に注目してほしい。

‘ =========================================================================
‘ 標準モジュール: MainModule
‘ 概要: フレームワークのエントリーポイント
‘ =========================================================================
Option Explicit

Sub ExecuteAssemblyAutomation()
‘ パフォーマンス向上のためのUI画面描画ロック
Dim swApp As SldWorks.SldWorks
Set swApp = CreateObject(“SldWorks.Application”)
swApp.CommandInProgress = True

On Error GoTo ErrorHandler

‘ 1. コンテキストの初期化
Dim context As CAssemblyContext
Set context = New CAssemblyContext

If context.CreateNewAssembly() Then
Debug.Print “アセンブリの新規作成に成功しました。”

‘ 2. ベース部品の配置
Dim baseComp As CComponentNode
Set baseComp = New CComponentNode
Set baseComp.Assembly = context.ActiveAssembly
baseComp.ComponentPath = “C:\Parts\BasePlate.sldprt”

If baseComp.InsertAndFix(0, 0, 0) Then
Debug.Print “ベース部品の配置と固定完了。”
End If

‘ 3. ボス部品の配置と合致適用
Dim bossComp As CComponentNode
Set bossComp = New CComponentNode
Set bossComp.Assembly = context.ActiveAssembly
bossComp.ComponentPath = “C:\Parts\BossPin.sldprt”

If bossComp.InsertAndFix(0.1, 0, 0) Then
Dim mateEngine As CMateConstraint
Set mateEngine = New CMateConstraint

‘ 同心円合致の適用(例:円筒面同士の選択名)
Dim model As SldWorks.ModelDoc2
Set model = context.ActiveAssembly

Dim success As Boolean
success = mateEngine.ApplyMate(model, _
“Cylinder1@BossPin-1@Assem1”, “FACE”, _
“Cylinder2@BasePlate-1@Assem1”, “FACE”, _
MateConcentric)

If success Then
Debug.Print “合致の定義に成功しました。”
End If
End If
End If

CleanUp:
‘ 確実にUIロックを解除
swApp.CommandInProgress = False
Set context = Nothing
Set swApp = Nothing
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “SolidWorks Automation”
Resume CleanUp
End Sub

—

5. シニアエンジニアが押さえるべき極限の最適化ポイント

1. `swApp.CommandInProgress = True` の活用:
アセンブリ構築時のバックグラウンドグラフィック再描画(Rebuild)を抑制する。数千パーツ規模のアセンブリでは処理速度が最大で10倍以上向上する。エラー発生時や終了時に必ず `False` に戻す例外処理を組み込むことが鉄則。
2. コンポーネント名の動的解決:
アセンブリ内でのインスタンス名(例: `BossPin-1@Assem1`)は、アセンブリがネストしたり複数挿入されると一意でなくなる。大規模システム連携では、名前による文字列検索ではなく、`Component2` オブジェクトのポインタ(`IComponent2` インターフェース)を直接メモリ上でバインドする設計に拡張すべきである。
3. COM参照の厳密なスコープ管理:
VBAのガベージコレクションは頼りにならない。ローカル変数として宣言されたSolidWorksのAPIオブジェクトは、プロシージャの終了、あるいは明示的な `Set x = Nothing` によって参照カウントをデクリメントしなければ、SolidWorksのプロセスがゾンビ化し、バックグラウンドでメモリを食い潰し続ける。

—

結び:レガシーからの脱却

「動けばいい」で作られたスパゲッティVBAは、設計変更の嵐の前には無力である。今回示したクラスモジュールベースのアーキテクチャを導入することで、あなたのマクロは単なる「その場しのぎのスクリプト」から、拡張性に満ちた「社内CAD基幹システム」へと進化する。

真のエンジニアリングとは、コードの複雑性を飼い慣らし、美しさと強靭さを両立させることにある。明日からのリファクタリングに、ぜひこの知見を役立ててほしい。

タイトルとURLをコピーしました