【アクティブ文書の罠】ActiveDocとDocumentCollectionの挙動の違いと意図したモデルを確実に掴む方法
SolidWorksマクロ開発において、最も多くのエンジニアが踏み抜く地雷がある。それが `SldWorks.ActiveDoc` という甘美なプロパティだ。
「現在画面に表示されているアクティブなドキュメントを取得する」——一見して直感的であり、単純な自動化スクリプトであればこれだけで動く。しかし、複数ウィンドウの並行運用、バックグラウンド処理、あるいはPDMや外部システム連携を伴うエンタープライズ環境において、この `ActiveDoc` への依存はシステム崩壊のトリガーとなる。
本稿では、COMオブジェクトのライフサイクル、SolidWorksの内部ドキュメント管理機構、そして意図したモデルを確実にかつ高速に掴むための実践的アーキテクチャを、チーフアーキテクトの視点から解き明かす。
—
1. なぜ `ActiveDoc` は「罠」なのか?
SolidWorks APIにおける `SldWorks` ルートオブジェクトは、アプリケーションインスタンスそのものを指す。その配下にある `ActiveDoc` は、OSのフォーカスやユーザーのマウス操作に完全に依存している。
ここに潜むリスクの本質は以下の通りである:
1. フォーカスの移転による誤作動:マクロ実行の瞬間にユーザーが別のウィンドウをクリックしたり、バックグラウンドでポップアップウィンドウが割り込んだりすると、全く意図しないモデルに対して処理が走る。
2. 図面(Drawing)と部品・アセンブリ(Part/Assembly)の混同:図面ビューがアクティブな場合、`ActiveDoc` が返すのは `DrawingDoc` であり、その背後にある `ModelDoc2`(部品やアセンブリ)を操作しようとした瞬間、型ミスマッチや予期せぬ挙動を引き起こす。
3. 非表示(Headless)実行時の不定性:将来的にVBAからVB.NETやC#、あるいは外部プロセスからのCOM経由へとスケールアップする際、UIを持たないコンテキストでは「アクティブ」という概念自体が破綻する。
レガシーな現場で見られる「とりあえず `ActiveDoc` を叩いてエラー処理を省く」という実装は、自動化の信頼性を自らドブに捨てる行為に他ならない。
—
2. `DocumentCollection` と内部キャッシュの真実
SolidWorksは、メモリ上にロードされたすべてのドキュメントをコレクションとして管理している。これにアクセスする代表的な手法が `SldWorks.GetDocuments` メソッドである。
`ActiveDoc` が「今、目に見えているもの」を返すのに対し、`GetDocuments` は「メモリ上に存在する全ドキュメントの配列」を返す。
ここで重要となるのが、SolidWorks APIのメモリモデルとCOM参照のライフサイクルである。配列として取得したドキュメント群を走査する際、適切な変数解放を行わないと、SolidWorksのプロセス内にゾンビ参照が残り、メモリリークや最悪の場合のSolidWorks本体のクラッシュ(Fatal Error)を誘発する。
—
3. 【実践】意図したモデルを確実に掴む堅牢なアーキテクチャ
現場で即座に採用できる、堅牢なドキュメント取得ロジックを提示する。
このコードでは、ファイル名(拡張子含む、またはパス)をキーにして、複数開かれているウィンドウの中から正確に意図したモデルインスタンスを特定し、取得する。さらに、VBAにおけるCOMオブジェクトの適切な解放作法も網羅している。
Option Explicit
‘ =================================================================================
‘
‘ 概要: 指定されたファイル名(または部分一致)を持つModelDoc2を、
‘ ActiveDocに依存せず確実かつ安全に取得するエンタープライズグレード関数
‘
‘ =================================================================================
Public Sub ExecuteRobustModelAccess()
Dim swApp As SldWorks.SldWorks
Set swApp = Application.SldWorks
If swApp is Nothing Then
MsgBox “SolidWorksが起動していません。”, vbCritical
Exit Sub
End If
‘ ターゲットとするファイル名(例: “C-1001.SLDPRT” または “C-1001″)
Dim targetFileName As String
targetFileName = “C-1001.SLDPRT”
Dim targetModel As SldWorks.ModelDoc2
Set targetModel = GetModelDocByName(swApp, targetFileName)
If Not targetModel Is Nothing Then
‘ — ターゲットモデルを確実に掴んだ状態での処理 —
MsgBox “対象モデルの取得に成功しました: ” & targetModel.GetPathName, vbInformation
‘ 例: ユーザー定義プロパティの取得など、安全な処理を記述
‘ Call ProcessModel(targetModel)
‘ オブジェクト変数の明示的解放(VBAメモリ管理の鉄則)
Set targetModel = Nothing
Else
MsgBox “指定されたモデルがメモリ上に存在しません: ” & targetFileName, vbExclamation
End If
Set swApp = Nothing
End Sub
‘ ——————————————————————————–
‘ 内部関数: 厳密な条件分岐によるドキュメント検索
‘ ——————————————————————————–
Private Function GetModelDocByName(ByRef app As SldWorks.SldWorks, ByVal docName As String) As SldWorks.ModelDoc2
Dim docs As Variant
docs = app.GetDocuments()
If IsEmpty(docs) Then
GetModelDocByName = Nothing
Exit Function
End If
Dim i As Long
Dim doc As SldWorks.ModelDoc2
Dim foundDoc As SldWorks.ModelDoc2
Set foundDoc = Nothing
‘ 配列をイテレート
For i = LBound(docs) To UBound(docs)
Set doc = docs(i)
If Not doc Is Nothing Then
‘ パス名またはタイトルで比較
‘ 厳密性を高めるため、GetPathName(フルパス)または 拡張子付きのTitleを評価
Dim currentTitle As String
currentTitle = doc.GetTitle()
‘ 必要に応じて完全一致(LCaseによる大文字小文字の吸収)に書き換えてください
If StrComp(LCase(currentTitle), LCase(docName), vbTextCompare) = 0 Or _
StrComp(LCase(doc.GetPathName), LCase(docName), vbTextCompare) = 0 Then
Set foundDoc = doc
Exit For ‘ 目的のドキュメントを発見したら即座に抜ける
End If
‘ ループ内でのローカル変数解放(COM参照カウンタの暴走を防ぐ)
Set doc = Nothing
End If
Next i
Set GetModelDocByName = foundDoc
End Function
—
4. チーフアーキテクトからの実践的提言
① ウィンドウハンドル(HWND)とWindows APIの併用(極限の制御)
もし「どうしても現在アクティブなウィンドウに関連づくモデルが欲しいが、ユーザーの誤操作を防ぎたい」というジレンマがある場合、Windows API (`FindWindowEx` や `GetForegroundWindow`) を用いてSolidWorksのメインフレームからアクティブなMDI(Multiple Document Interface)子ウィンドウのキャプションを直接取得し、それをキーにして `GetDocuments` から逆引きするという手法が極めて有効である。
レガシーなCAD環境において、APIの抽象化レイヤーをあえてバイパスし、OSレベルのウィンドウ構造と同期させるこの手法は、ミッションクリティカルな現場で最後の砦となる。
② COM参照の明示的破棄 (`Set obj = Nothing`) の徹底
VBAのガーベージェネレータは、.NETのそれとは異なり非常に原始的である。特にSolidWorks APIのコレクションをループで回す際、一時変数にオブジェクトを代入し続けると、COMの参照カウンタがインクリメントされたままメモリ上に残存し、マクロ終了後もSolidWorksプロセスがゾンビ化する原因になる。
上記のコード例の通り、ループの各イテレーションの最後、あるいはプロシージャの退出時には必ず `Set xxx = Nothing` を記述すること。これが大規模アセンブリを何百回とループ処理するバッチマクロを安定稼働させるための絶対条件である。
③ エラーハンドリングの標準化
単に `On Error Resume Next` でエラーを握り潰すのは、プロアマ問わず最も忌むべき悪習である。予期せぬNULL参照に対しては、明示的なガード節(`If Not obj Is Nothing Then`)を必ず挟み、ログ出力や適切なユーザー通知へルーティングする設計を義務づけよ。
—
総括
`ActiveDoc` はプロトタイピングの玩具としては便利だが、生産システムや実業務を支えるVBAマクロにおいては「百害あって一利なし」の危険なプロパティである。
SldWorksインスタンスのドキュメントコレクションを的確に把握し、メモリライフサイクルを完全にコントロールする。この基本原則を遵守する者だけが、SolidWorks VBAを真に掌握し、揺るぎない自動化基盤を築き上げることができる。
