【実務・中級編】【厳密なドキュメント制御】ActiveDocに頼らないSldWorks.ActivateDoc3とDocumentCollectionによる安全な文書切り替え – SolidWorks VBA解析バイブル

スポンサーリンク

【厳密なドキュメント制御】ActiveDocに頼らないSldWorks.ActivateDoc3とDocumentCollectionによる安全な文書切り替え

SolidWorksのアドインやマクロを開発する際、多くのエンジニアが最初に通る、そして最も頻繁に踏み抜く致命的な罠があります。それが「現在開いているドキュメントを `SldWorks.ActiveDoc` で取得して操作する」という設計パターンです。

単一の部品図を開いて単純な処理を行うだけなら、この方法でも問題は顕在化しません。しかし、以下のような実務のマルチドキュメント環境に置かれた瞬間、システムは容易に崩壊します。

  • アセンブリから子部品を順次開き、裏でデータを書き換えて保存するバッチ処理
  • PDM(Product Data Management)やExcelの構成表から自動で図面を生成・出力するバッチ処理
  • 処理の実行中に、ユーザーが別のSolidWorksウィンドウをクリックしてアクティブドキュメントを切り替えてしまった場合

本稿では、なぜ `ActiveDoc` が「諸悪の根源」となるのかをロジカルに解き明かし、`SldWorks.GetDocuments`(または `GetFirstDocument` / `GetNextDoc`)による厳密なドキュメント特定と、`SldWorks.ActivateDoc3` を用いた安全な文書切り替えの極意を伝授します。

1. なぜ `ActiveDoc` を使ってはいけないのか?

理由①:ユーザー操作との非同期性

`ActiveDoc` は、「その瞬間にSolidWorksのGUI上で最前面にあるドキュメント」を返します。
マクロの実行処理には数秒から数分かかることがあります。この実行中に、ユーザーが「進捗はどうかな?」と別のモデルのウィンドウをクリックした瞬間、`ActiveDoc` が指す参照先は書き換わります。結果として、全く関係のない別のアセンブリや部品に対してフィーチャーの削除やプロパティの書き込みが実行され、最悪の場合は設計データを破壊します。

理由②:バックグラウンド処理での破綻

パフォーマンス向上のため、`OpenDoc6` のオプションで `swOpenDocOptions_Silent` を指定し、画面上に非表示(バックグラウンド)でドキュメントを開いて処理を行うケースは多々あります。このとき、バックグラウンドで開かれたドキュメントはアクティブ化されません。したがって、`ActiveDoc` を参照しても、そのドキュメントを操作することはできません。

理由③:暗黙的な型変換によるバグの誘発

`ActiveDoc` は `ModelDoc2` オブジェクトを返しますが、これが「パーツ(`PartDoc`)」なのか「アセンブリ(`AssemblyDoc`)」なのか「図面(`DrawingDoc`)」なのかを厳密に検証せずにキャストすると、実行時エラー(Type Mismatch)を誘発します。

2. 堅牢なドキュメント制御の三原則

マルチドキュメントを安全に制御するための鉄則は以下の3つです。

1. 「アクティブな文書」ではなく「オブジェクトのポインタ(参照)」を直接保持して操作する。
2. ドキュメントを特定する際は、ドキュメント名(Title)やフルパス(PathName)を用いて、ロード済みのドキュメントコレクションから厳密に走査(スキャン)する。
3. GUIの描画更新やユーザー操作が必要な場合のみ、最小限の範囲で `ActivateDoc3` を使用して安全にアクティブ化する。

💡 API仕様の裏側:`ActivateDoc3` の正しい理解

ドキュメントをアクティブ化するAPIにはいくつか歴史がありますが、現代のSolidWorks API開発においては `ActivateDoc3` 一択です。

Set ModelDoc = SldWorks.ActivateDoc3( _
Name, _
Rebuild, _
Option, _
ByRef Errors _
)

  • `Name`: アクティブにしたいドキュメントの「タイトル(拡張子を含むファイル名、例: `part1.sldprt`)」を指定します。フルパスではありません。 ここを誤るとアクティブ化に失敗します。
  • `Rebuild`: アクティブ化と同時に再構築を行うかを指定します。パフォーマンスを最優先する場合、不要な再構築(`swUserDecision` や `True`)を避け、`swRebuildOnActivation_e.swDontRebuildActiveDoc` を指定するのが定石です。
  • `Option`: 通常は `swActivateDocOptions_e.swActivateDoc_NoError` を指定します。
  • `Errors`: エラー情報を格納する変数(`Long`)を渡します。戻り値の評価は必須です。

3. 実践:安全なドキュメント切り替え・取得クラス

以下に、実務でそのまま利用できる堅牢なVBAコードを示します。このコードは、指定されたファイルパス(またはファイル名)のドキュメントが既にSolidWorks上で開かれているかを安全に走査し、必要に応じてアクティブ化して `ModelDoc2` を返します。

標準モジュール:`MDocumentController`

Option Explicit

‘ ==============================================================================
‘ 業務自動化プロフェッショナル向け:安全なドキュメント制御モジュール
‘ ==============================================================================

”’

”’ 指定されたフルパスのドキュメントがSolidWorks内に既に開かれているか走査し、
”’ 安全にそのModelDoc2オブジェクトの参照を返します(アクティブ化は行いません)。
”’

Public Function GetOpenedDocument(ByVal app As SldWorks.SldWorks, ByVal targetFullPath As String) As SldWorks.ModelDoc2
Dim doc As SldWorks.ModelDoc2
Dim currentPath As String

Set GetOpenedDocument = Nothing

‘ 引数のサニタイズ
targetFullPath = Trim$(targetFullPath)
If targetFullPath = “” Then Exit Function

‘ 現在SolidWorksにロードされている全ドキュメントを走査
Set doc = app.GetFirstDocument()
Do While Not doc Is Nothing
currentPath = doc.GetPathName()

‘ パスの一致を確認(Windows環境を考慮し大文字小文字を区別しない)
If StrComp(currentPath, targetFullPath, vbTextCompare) = 0 Then
Set GetOpenedDocument = doc
Exit Function
End If

‘ 次のドキュメントへ
Set doc = doc.GetNextDoc()
Loop
End Function

”’

”’ 指定されたドキュメントを安全にアクティブ化します。
”’

”’ SolidWorks アプリケーションインスタンス ”’ アクティブ化対象のModelDoc2オブジェクト ”’ アクティブ化に成功した場合はTrue
Public Function SafeActivateDocument(ByVal app As SldWorks.SldWorks, ByVal doc As SldWorks.ModelDoc2) As Boolean
Dim targetTitle As String
Dim activatedDoc As SldWorks.ModelDoc2
Dim errors As Long

SafeActivateDocument = False
If app Is Nothing Or doc Is Nothing Then Exit Function

‘ すでにアクティブである場合は無駄な処理をスキップ
Dim activeDoc As SldWorks.ModelDoc2
Set activeDoc = app.ActiveDoc
If Not activeDoc Is Nothing Then
If activeDoc Is doc Then
SafeActivateDocument = True
Exit Function
End If
End If

‘ ActivateDoc3の引数には「タイトル(ファイル名)」が必要(フルパスではない)
targetTitle = doc.GetTitle()

‘ タイトルから拡張子が抜けているケースを考慮(Windowsの設定による挙動差の吸収)
‘ ※通常GetTitleは拡張子を含みますが、新規保存前のドキュメントや環境に依存する場合があります。

‘ ドキュメントを安全にアクティブ化(無駄な再構築を徹底的に避けるオプションを指定)
Set activatedDoc = app.ActivateDoc3( _
targetTitle, _
swRebuildOnActivation_e.swDontRebuildActiveDoc, _
swActivateDocOptions_e.swActivateDoc_NoError, _
errors _
)

‘ 結果の検証
If Not activatedDoc Is Nothing And errors = 0 Then
SafeActivateDocument = True
Else
‘ エラーハンドリング(ログ出力など)
Debug.Print “Error: Document activation failed. Title: ” & targetTitle & ” / Error Code: ” & errors
End If
End Function

ユースケース(呼び出し側での実装例)

上記の関数群を使い、バックグラウンド処理とGUI操作を明確に分離した堅牢なメイン処理のテンプレートです。

Public Sub BatchProcessExample()
Dim app As SldWorks.SldWorks
Set app = Application.SldWorks

‘ 処理対象のファイルリスト(本来はDBやExcelから動的に取得)
Dim filePaths(1) As String
filePaths(0) = “C:\EngineeringData\PartA.sldprt”
filePaths(1) = “C:\EngineeringData\AssemblyB.sldasm”

Dim i As Integer
For i = LBound(filePaths) To UBound(filePaths)
Dim targetPath As String
targetPath = filePaths(i)

‘ 1. すでに開かれているか確認
Dim targetDoc As SldWorks.ModelDoc2
Set targetDoc = GetOpenedDocument(app, targetPath)

‘ 2. 開かれていなければ、サイレントモードで開く(ユーザーの画面を専有しない)
If targetDoc Is Nothing Then
Dim fileType As Long
fileType = DetermineDocType(targetPath) ‘ 拡張子からタイプを判定するヘルパー関数(省略)

Dim openErrors As Long
Dim openWarnings As Long

Set targetDoc = app.OpenDoc6( _
targetPath, _
fileType, _
swOpenDocOptions_e.swOpenDocOptions_Silent, _
“”, _
openErrors, _
openWarnings _
)
End If

‘ 3. 確実なポインタを保持した状態で処理を実行(ActiveDocは一切使用しない)
If Not targetDoc Is Nothing Then
‘ — 安全なデータ書き換え領域 —
‘ 例: カスタムプロパティの書き込み(画面に表示させずに裏で高速に処理)
Dim customPropMgr As SldWorks.CustomPropertyManager
Set customPropMgr = targetDoc.Extension.CustomPropertyManager(“”)
customPropMgr.Add3 “CheckedBy”, swCustomInfoType_e.swCustomInfoText, “SystemAutomator”, swCustomPropertyAddOption_e.swCustomPropertyReplaceValue

‘ 図面出力など、どうしても「アクティブ化」して描画更新が必要な場合のみ切り替える
If fileType = swDocumentTypes_e.swDocDRAWING Then
If SafeActivateDocument(app, targetDoc) Then
‘ アクティブ描画に依存する処理(PDFエクスポートなど)を実行
ExportToPDF targetDoc
End If
End If

‘ 保存処理(ポインタから直接実行)
Dim saveErrors As Long
Dim saveWarnings As Long
targetDoc.Save3 swSaveAsOptions_e.swSaveAsOptions_Silent, saveErrors, saveWarnings

‘ メモリとリソースの解放
Set targetDoc = Nothing
Else
Debug.Print “Failed to load document: ” & targetPath
End If
Next i
End Sub

4. PDMやデータベース連携時における極限の注意点

エンタープライズ環境(SOLIDWORKS PDMやERP、外部データベースとの連携)において、上記のドキュメント制御を組み込む際には、さらに一歩踏み込んだ考慮が必要です。

① PDMボルト内の一時ローカルキャッシュ問題

PDM環境では、ユーザーがファイルを「チェックアウト」せずに開いた場合、ファイルは読み取り専用(ReadOnly)としてローカルキャッシュから開かれます。
データベース(SQL Server等)から得たBOM情報をモデルのカスタムプロパティに書き戻す際、`ModelDoc2` のポインタを確保できていても、ファイル自体が読み取り専用であれば書き込みは失敗します。
処理を実行する前に、必ず `ModelDoc2.IsOpenedReadOnly` をチェックし、読み取り専用であれば処理をスキップするか、PDM APIを通じてチェックアウトを要求するロジックを挟んでください。

② ファイル名重複による `ActivateDoc3` の誤動作

大規模アセンブリなどで、別フォルダに存在する「同名の異なる部品」(例: `bracket.sldprt` が複数のプロジェクトフォルダに存在)が同時にメモリにロードされている場合、`ActivateDoc3` に「タイトル名」のみを渡すと、SolidWorksは最初に見つけた方のドキュメントをアクティブにします。
これを防ぐため、同名ファイルが複数開かれる可能性がある超大規模環境では、以下の戦略をとります。

  • アクティブ化を伴う処理を極限まで排除し、すべて `ModelDoc2` ポインタ経由でのバックグラウンド処理で完結させる。
  • どうしてもアクティブ化が必要な場合は、一旦すべての競合ドキュメントを閉じ、ターゲットとなるフルパスのドキュメントのみを排他的にロード・アクティブ化する。

5. まとめ:「動くコード」から「堅牢なシステム」へ

`SldWorks.ActiveDoc` に依存したコードは、言わば「いつ動かなくなるかわからない爆弾」を抱えた状態です。開発環境では完璧に動いていたマクロが、実務環境でユーザーが並行作業を始めた途端に牙を剥きます。

今回紹介した `GetDocuments`による走査`ActivateDoc3`による厳密なコンテキスト制御 を実装することで、あなたのVBAツールは「ユーザーの誤操作にビクともしない、極めてプロフェッショナルで信頼性の高いシステム」へと昇華します。

堅牢な設計は、コードの行数を増やすことではなく、曖昧な状態(ステート)を徹底的に排除することから生まれます。ぜひ、あなたのプロジェクトのコードベースを今日からリファクタリングしてください。

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