境界線を越える自動化:SolidWorks図面生成における非同期制御とイベントハンドリングの極致
多くの開発者は、SolidWorksのAPIを叩き、図面を生成し、PDFを出力することに成功する。しかし、大規模なアセンブリを扱う実務環境において、彼らのコードは「現場」では通用しない。
なぜか。「UIのフリーズ」と「非決定的な処理完了」という、プロフェッショナルが最も忌避すべき問題に直面するからだ。
数千点の部品を持つアセンブリから図面を生成し、ビューを配置し、PDFへエクスポートする。この一連のプロセスは、SolidWorksのメインスレッドを長時間占有する。VBAの標準的な実行モデルでは、処理中にSolidWorksは「応答なし」となり、ユーザーは作業を中断せざるを得ない。さらに、PDF変換のような外部プロセスが絡む処理は、APIの戻り値が「成功」を返したとしても、実際にはファイルが書き込み中であるという「タイミングの罠」を孕んでいる。
本稿では、単なる「動くコード」ではない。システムの堅牢性と、ユーザー体験を両立させるための、アーキテクチャレベルでの実装パターンを解体する。
1. シングルスレッドの呪縛を解く:アーキテクチャの再考
まず、残酷な真実を認めなければならない。VBA自体に真のマルチスレッド機能は存在しない。 SolidWorks APIの呼び出しは、SolidWorks本体のメインスレッド(UIスレッド)と密接に結合している。
したがって、VBA単体で「バックグラウンド処理」を実現しようとして `DoEvents` を乱用するのは、設計思想として極めて貧弱だ。`DoEvents` はOSへの制御を一時的に戻すが、それはスタックの管理を不確実にし、再入(Re-entrancy)による予期せぬエラー(変数の書き換えや二重実行)を招く温床となる。
プロフェッショナルの戦略
真の自動化エンジニアは、以下の2つのアプローチを選択する。
1. 非同期的な「状態監視」モデル: 処理を分割し、イベントハンドリングによって「完了」を検知する。
2. プロセス分離モデル (Out-of-Process): VBAではなく、VB.NETやC#を用いて、SolidWorksをCOMサーバーとして制御する。これにより、UIスレッドとは完全に分離された制御が可能になる。
本稿では、VBAの枠組みの中で最大限の堅牢性を確保するための「イベント駆動型ステートマシン」の実装に焦点を当てる。
2. Event Handling: DrawingDocEventによる決定論的な完了検知
図面生成において、ビューの配置や更新が「いつ終わったか」を `Sleep` 関数で待機するのは、素人の仕事だ。我々は、SolidWorksが発行するイベントをフックし、システムが「準備完了」を宣言した瞬間に次のステップへ移行する設計をとるべきである。
`SwDocumentEvents` を実装し、図面ドキュメントの状態変化を監視するクラスモジュールを構築する。
実装例:EventWatcher クラス(Class Module)
' Class Module: clsDrawingEventWatcher
Option Explicit
' SolidWorksのイベントをキャッチするためのインターフェースを実装
Private WithEvents swApp As SldWorks.SldWorks
Private swModel As SldWorks.ModelDoc2
Private IsProcessing As Boolean
' 外部からイベントを購読するためのプロパティ
Public Property Let SetTarget(ByVal model As SldWorks.ModelDoc2)
Set swModel = model
End Property
Public Property Get Processing() As Boolean
Processing = IsProcessing
End Property
' 図面ドキュメントのイベントが発生した際のハンドラ
' ここでは、再生成(Regenerate)や保存が完了したタイミングを捕捉する
Private Sub swApp_DocumentRegenerate(ByVal Doc As SldWorks.ModelDoc2)
If Not swModel Is Nothing Then
If Doc.Equals(swModel) Then
Debug.Print "--- [Event] Document Regenerated: " & Doc.GetTitle
' ここで次なる処理(PDF出力など)のトリガーを引く
IsProcessing = False
End If
End If
End Sub
' ドキュメントが保存されたタイミングを捕捉
Private Sub swApp_DocumentSave(ByVal Doc As SldWorks.ModelDoc2)
If Not swModel Is Nothing Then
If Doc.Equals(swModel) Then
Debug.Print "--- [Event] Document Saved: " & Doc.GetTitle
IsProcessing = False
End If
End If
End Sub
' 初期化メソッド
Public Sub Initialize(ByVal app As SldWorks.SldWorks)
Set swApp = app
IsProcessing = True
End Sub
3. Windows APIによるメッセージループの制御とメモリの整合性
大規模な図面生成を行う際、メモリリークは致命的だ。COMオブジェクトの参照カウントが適切に管理されていないと、SolidWorksはプロセスを終了してもメモリ内に残存し、次回の自動化実行時に「インスタンスの重複」や「メモリ不足」を引き起こす。
メモリ管理の鉄則
1. 明示的解放: `Set obj = Nothing` を単なる慣習ではなく、オブジェクトのライフサイクル管理として徹底する。
2. Scopeの最小化: 変数のスコープを可能な限り狭め、関数を抜ける際に確実に解放されるようにする。
また、UIのフリーズを軽減するために、Windows APIの `PostMessage` を利用して、自作のメッセージをキューに送り、処理の合間にメッセージループを回す高度な手法もあるが、ここではより安全な「ステートマシンによる非同期風制御」を示す。
実装例:メインコントローラー
' Standard Module: modDrawingAutomation
Option Explicit
Private Watcher As clsDrawingEventWatcher
Private swApp As SldWorks.SldWorks
Public Sub ExecuteAdvancedDrawingTask()
Dim swModel As SldWorks.ModelDoc2
Dim swDraw As SldWorks.DrawingDoc
Dim targetFilePath As String
Set swApp = Application.SldWorks
targetFilePath = "C:\Temp\LargeAssembly_Drawing.slddrw"
' 1. 図面を開く
Set swModel = swApp.OpenDoc6(targetFilePath, swDocDRAWING, swOpenDocOptions_Silent, "", 0, 0)
If swModel Is Nothing Then Exit Sub
Set swDraw = swModel
' 2. イベント監視クラスの初期化
Set Watcher = New clsDrawingEventWatcher
Watcher.SetTarget swModel
Watcher.Initialize swApp
' 3. 重い処理の開始(ビューの更新、配置など)
Debug.Print "Starting Heavy Task..."
' ※ ここで実際の図面生成ロジックを呼び出す
' 理想的には、この処理自体を別プロセスに投げるべきだが、
' VBA内では、処理の「隙間」に状態をチェックする
PerformHeavyDrawingLogic swDraw
' 4. 非同期完了の待機(ポーリング・モデル)
' DoEventsを乱用せず、イベントハンドラがIsProcessingをFalseにするのを待つ
Do While Watcher.Processing
DoEvents ' OSに制御を戻し、イベントハンドラが動作する隙を作る
' タイムアウト処理を入れないと、無限ループのリスクがある
' (実務では、ここでSystem.DateTime等を用いたタイムアウト管理が必須)
Loop
Debug.Print "All Tasks Completed Successfully."
' 5. 徹底したクリーンアップ
' オブジェクトの解放順序は、参照の依存関係に注意する
Set Watcher = Nothing
Set swDraw = Nothing
Set swModel = Nothing
' swApp はアプリケーション本体なので、基本的には保持する
End Sub
Private Sub PerformHeavyDrawingLogic(ByRef swDraw As SldWorks.DrawingDoc)
' ここにビューの配置、スケール変更、注釈挿入などの重いAPI処理を記述
' 処理の途中で swDraw.ForceRebuild3 を呼ぶことで、
' Watcher内の Regenerate イベントを誘発させる
swDraw.ForceRebuild3 True
End Sub
4. 伝説的アーキテクトの知見:レガシー環境とシステム間連携
自動化システムを運用する際、最後に立ちはだかるのは「環境の不一致」だ。
PDF変換時の「ファイルロック」問題
図面生成の最終工程であるPDF変換(`SaveAs`)において、`SaveAs` メソッドが成功を返しても、ファイルシステム上ではまだファイルがロックされていることが多々ある。これは、SolidWorksの内部スレッドが書き込みを完了する前に、APIが制御を戻してしまうためだ。
これを防ぐには、Windows APIの `GetFileAttributes` や、ファイルハンドルをチェックするロジックを組み込み、「ファイルが完全に解放されるまで待機する」 プロトコルを実装せよ。
システム間連携の極意
もし、この自動化をERPやPLMシステムから呼び出す場合、VBAのUIスレッドに依存する設計は「ゴミ」に等しい。
- Headlessモードの検討: SolidWorksをGUIなしで起動するオプションを検討せよ。
- ログの外部出力: `Debug.Print` ではなく、Windows Event Logや、外部のSQLiteデータベース、あるいはログファイルに、タイムスタンプ付きで詳細なステートを書き出せ。エラーが発生した際、その原因が「APIの失敗」なのか「メモリ不足」なのか「イベントの未到達」なのかを、事後的に特定できる唯一の手段となる。
結論
SolidWorks VBAにおける真のプロフェッショナリズムとは、APIのメソッドを知っていることではない。APIが「いつ、どのように、どのような状態で」動作するか、そのライフサイクルを完全に掌握していることだ。
イベントハンドリングによる決定論的な制御、厳格なオブジェクトの解放、そしてOSレベルの挙動を考慮した待機ロジック。これらを組み合わせることで初めて、あなたのコードは「ツール」から「堅牢なシステム」へと昇華する。
エンジニアよ、単なる自動化で満足するな。制御せよ。
