【アクティブ文書の罠】ActiveDocとDocumentCollectionの挙動の違いと意図したモデルを確実に掴む方法
開発現場でよく耳にするトラブルがある。「1台のPCで複数の図面やアセンブリを開きながらバッチ処理マクロを走らせたら、全然関係ない図面に寸法が書き込まれた」「図面からデータを吸い出すはずが、裏で開いていた別のアセンブリを処理してクラッシュした」。
原因は決まっている。`SldWorks.ActiveDoc`という甘美な毒に依存した設計だ。
プログラミングの初期段階では、現在アクティブなドキュメントを取得する `swApp.ActiveDoc` は非常に手軽に見える。しかし、実務の現場――すなわち複数ドキュメントが同時に開き、ユーザーがウィンドウを切り替え、バックグラウンドでPDBやPDMとの連携処理が走るカオスな環境において、このプロパティは「時限爆弾」と化す。
今回は、SolidWorks VBAの根幹をなす `SldWorks` と `ModelDoc2` のオブジェクトモデルの挙動を解き明かし、意図したモデルを確実に、かつミリ秒単位の無駄なく掴み取るための堅牢なアーキテクチャを伝授する。
—
1. なぜ `ActiveDoc` は実務で使ってはいけないのか?
`SldWorks.ActiveDoc` は、文字通り「現在SolidWorksの画面上でアクティブ(最前面でフォーカス当たっている)になっているドキュメント」を返す。
ここにある最大の罠は、「アクティブ」という状態はユーザーの操作やWindowsのフォーカス、さらにはAPI内部の処理順序によって刻一刻と変化するという点だ。
- ユーザーの割り込み: マクロ実行中にユーザーが別のウィンドウをクリックしたら? ターゲットが瞬時にすり替わる。
- バックグラウンド処理: 非表示(Silent)でアセンブリを開閉する際、暗黙的にアクティブドキュメントのコンテキストが書き換わる。
- マルチドキュメントの非同期性: どのドキュメントがアクティブであるかは、OSのメッセージループやSolidWorksのUIスレッドに依存しており、コードの実行意図と完全に切り離されている。
プロフェッショナルな自動化エンジニアであれば、「今たまたま開いているから」という曖昧な状態に業務ロジックを依存させてはならない。ドキュメントの取得は、「名前(パス)」または「インスタンスの意図的な列挙」によって確実に担保されなければならないのだ。
—
2. SolidWorks APIオブジェクトモデルの基本構造
SolidWorksのAPIツリーの頂点には、常にアプリケーションの根幹である `SldWorks` オブジェクトが存在する。
[SldWorks プレースホルダー (swApp)]
┣━━ GetFirstDocument() / GetDocuments() ──> [DocumentCollection (ModelDoc2)]
┗━━ ActiveDoc ───────────────────────────> [現在のアクティブ文書 (危険な罠)]
ドキュメントを安全に制御するためのアプローチは主に2つある。
1. コレクション走査によるパス一致(GetDocuments):
現在メモリ上にロードされているすべてのドキュメントの配列を取得し、フルパス(絶対パス)で厳密にマッチングさせる方法。
2. 明示的なオープンとハンドリング(OpenDoc7):
マクロ自身でファイルを開き、その戻り値(`ModelDoc2`)を変数にバインドして最後までスコープ内で維持する方法。
実務で最も事故が起きるのは「すでに開いている、あるいは開いているかもしれないドキュメントを処理する」ケースである。この要件に対して、`ActiveDoc` を使うのではなく、メモリ上の全ドキュメントを走査するロジックを標準装備すべきだ。
—
3. 【プロダクションコード】意図したモデルを確実に掴む安全な関数
以下のコードは、指定したファイル名(またはパス)を持つドキュメントがSolidWorksのメモリ上に存在するかを走査し、存在すればその `ModelDoc2` インスタンスを安全に取得、なければ必要に応じて開く(あるいはエラーとする)堅牢なプロシージャである。
コピペしてそのままプロジェクトの標準モジュールに組み込んでほしい。
Option Explicit
‘ =========================================================================
‘ 模範的なドキュメント取得・制御モジュール
‘ 開発現場のロバストネス(堅牢性)を極限まで高めた実装例
‘ =========================================================================
Public Sub ExecuteRobustModelProcessing()
Dim swApp As SldWorks.SldWorks
Set swApp = Application.SldWorks
If swApp is Nothing Then
MsgBox “SolidWorksが起動していません。”, vbCritical
Exit Sub
End If
‘ 処理対象の完全パス(実務では設定ファイルや引数から動的に渡す)
Dim targetPath As String
targetPath = “C:\Data\Projects\202X\Assembly001.sldasm”
‘ 【重要】ActiveDocを使わず、意図したモデルを確実に掴む
Dim swModel As SldWorks.ModelDoc2
Set swModel = GetOrOpenDocument(swApp, targetPath)
If swModel Is Nothing Then
MsgBox “ターゲットモデルの取得に失敗しました: ” & targetPath, vbCritical
Exit Sub
End If
‘ — ここから先は確実に意図したモデルに対する処理が保証される —
Call ProcessModel(swModel)
End Sub
‘ ————————————————————————-
‘ メモリ上のドキュメントコレクションから意図したパスのモデルを安全に取得する関数
‘ ————————————————————————-
Private Function GetOrOpenDocument(ByVal swApp As SldWorks.SldWorks, ByVal filePath As String) As SldWorks.ModelDoc2
Dim vDocs As Variant
Dim swModel As SldWorks.ModelDoc2
Dim i As Long
‘ 1. まず、すでにメモリ上にロードされているドキュメント群を走査する
vDocs = swApp.GetDocuments
If Not IsEmpty(vDocs) Then
For i = LBound(vDocs) To UBound(vDocs)
Set swModel = vDocs(i)
‘ PathNameは大文字小文字を区別しない比較を行うのが安全
If StrComp(swModel.GetPathName(), filePath, vbTextCompare) = 0 Then
‘ 既に開かれているため、そのインスタンスをそのまま返す
Set GetOrOpenDocument = swModel
Exit Function
End If
Next i
End If
‘ 2. メモリ上に存在しない場合は、新規に開く(サイレントモード等、要件に応じ調整)
Dim docType As Long
Dim errors As Long
Dim warnings As Long
‘ 拡張子からドキュメントタイプを推測
docType = GetDocumentTypeFromPath(filePath)
‘ 確実にバックグラウンドあるいは通常のドキュメントとして開く
Set swModel = swApp.OpenDoc6(filePath, docType, swOpenDocOptions_Silent, “”, errors, warnings)
If swModel Is Nothing Then
‘ ログ出力やエラーハンドリングをここに記述
Debug.Print “Error: ファイルのオープンに失敗しました. 終了コード: ” & errors
Set GetOrOpenDocument = Nothing
Exit Function
End If
Set GetOrOpenDocument = swModel
End Function
‘ ————————————————————————-
‘ 拡張子に基づくドキュメントタイプの判定
‘ ————————————————————————-
Private Function GetDocumentTypeFromPath(ByVal filePath As String) As Long
Dim ext As String
ext = LCase$(Mid$(filePath, InStrRev(filePath, “.”) + 1))
Select Case ext
Case “sldprt”: GetDocumentTypeFromPath = swDocPART
Case “sldasm”: GetDocumentTypeFromPath = swDocASSEM
Case “slddrw”: GetDocumentTypeFromPath = swDocDRAWING
Case Else: GetDocumentTypeFromPath = swDocNONE
End Select
End Function
‘ ————————————————————————-
‘ 実務ロジックのプレースホルダー
‘ ————————————————————————-
Private Sub ProcessModel(ByVal swModel As SldWorks.ModelDoc2)
‘ 例: タイトルの取得とリビルド
Debug.Print “処理中モデル: ” & swModel.GetTitle()
‘ 意図したドキュメントコンテキストでの強制リビルド
Dim bRet As Boolean
bRet = swModel.Extension.ForceRebuildAll()
If bRet Then
Debug.Print “リビルド成功: ” & swModel.GetPathName()
Else
Debug.Print “リビルド警告/失敗: ” & swModel.GetPathName()
End If
End Sub
—
4. コードの解説:なぜこの設計が「プロフェッショナル」なのか?
上記のコードには、現場でシステムを破綻させないための重要な設計思想が凝縮されている。
1. `swApp.GetDocuments` によるコレクションの直接走査
SolidWorksは開かれている全ドキュメントの配列を返す `GetDocuments` メソッドを持っている。これを活用することで、OSやUIの「アクティブ状態」を完全に無視して、メモリ上の実体(インスタンス)とパスを直接突合できる。
2. `StrComp` による厳密なパス比較
Windows環境ではファイルパスの大文字小文字は同一視されるが、VBAの通常の比較演算子や文字列処理で思わぬバグを生むことがある。`vbTextCompare` を指定した `StrComp` により、パスの不一致による二重オープンや誤認を防いでいる。
3. スコープの局所化とポインタの保持
取得した `ModelDoc2` をグローバル変数に逃がすのではなく、処理関数のスコープ内で確実に引き回すことで、メモリリークや意図しない参照切れ(Object variable not setエラー)を根絶している。
—
5. ファイル連携・データベース連携における応用と注意点
社内の生産管理システムやPDM(Product Data Management)データベースから取得したファイルパスリストを元に、大量の図面やアセンブリをバッチ処理する自動化ツールを構築する場合、この「ActiveDocに依存しない設計」は生命線となる。
- メモリプレッシャーの管理:
数千件の図面を次々と `OpenDoc6` で開き続けると、SolidWorksのメモリ消費量が限界に達し、VBAの実行が突然クラッシュ(Fatal Error)する。一定数(例: 50ファイルごと)処理したら、明示的にドキュメントを閉じる(`swApp.CloseDoc`)処理をループ内に組み込むこと。
- 読取専用(Read-Only)の強制:
データ収集や自動PDF出力などの「書き込みを行わない」プロセスでは、必ず `OpenDoc6` のオプションで読み取り専用を指定し、意図しないファイルのチェックアウトや上書き保存を防がなければならない。
—
最後に:チーフアーキテクトからの提言
「動けばいい」というマインドで作られたVBAマクロは、開発者の手元を離れた瞬間に「爆弾」に変わる。特にCADという重厚なデスクトップアプリケーションを操作するAPIにおいて、UIの「アクティブ状態」に頼るコーディングはプロの仕事ではない。
常に「今、どのインスタンスを掴んでいて、どこに向かって命令を発行しているのか」をコードの構造で完全にコントロールすること。その規律を守るだけで、あなたの作る自動化ツールは、現場のエンジニアたちから絶大な信頼を得る「強固なインフラ」へと昇華するだろう。
