【実務・中級編】【安全なドキュメント操作】ActiveDocへの依存を脱却しSldWorksとDocumentCollectionで文書を確実に制御する方法 – SolidWorks VBA解析バイブル

スポンサーリンク

1. `ActiveDoc`という時限爆弾:なぜあなたのマクロは現場で落とすのか?

SolidWorks VBAで自動化ツールを開発する際、誰もが最初に書くのが次のようなコードです。

Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2

Set swApp = Application.SldWorks
Set swModel = swApp.ActiveDoc ‘ ← 諸悪の根源

開発端末で、1つの部品だけを開いてテストしている間は、このコードは何の問題もなく動作します。しかし、このマクロを現場の運用に投入した瞬間から、不具合の報告が相次ぐことになります。

  • 「アセンブリを処理している最中に、隣の画面で図面を開いたら誤動作した」
  • 「バックグラウンド処理のハズなのに、アクティブ画面がコロコロ入れ替わって描画が追いつかずフリーズした」
  • 「マクロ実行中にユーザーがマウスで別のウィンドウをクリックしたら、全く関係ない部品のプロパティが書き換わった」

原因は明確です。`swApp.ActiveDoc` に依存しているからです。

`ActiveDoc` は「現在、画面の最前面でユーザーのフォーカスを得ているドキュメント」を返すプロパティに過ぎません。それは処理対象のデータモデルの参照ではなく、単なる「UIの状態」です。自動化処理という非同期なロジックを、ユーザーの気まぐれなUI操作と同期させてはいけません。

本記事では、UIのフォーカスに一切依存せず、SolidWorksメモリ空間にロードされているドキュメントを直接かつ確実に補獲・制御する「プロダクションレベルのドキュメント管理設計」を伝授します。

2. SolidWorksオブジェクトモデルの真実:ドキュメントツリーの走査メカニズム

`ActiveDoc` から脱却するためには、SolidWorksアプリケーション(`SldWorks`)内部でドキュメントがどのように保持・管理されているか理解する必要があります。

SolidWorksメモリ空間内では、開かれたすべてのファイル(明示的に開いたもの、アセンブリによってバックグラウンドで参照ロードされたもの含む)が内部コレクションとして保持されています。

[ SldWorks (App) ]

├─ GetFirstDocument() ──> [ ModelDoc2 (Doc A) ]
│ │
│ └─ GetNext() ──> [ ModelDoc2 (Doc B) ]
│ │
│ └─ GetNext() ──> …

このコレクションを安全にたどるAPIが `SldWorks.GetFirstDocument` と `ModelDoc2.GetNext` です。

なぜアクティブ化(`ActivateDoc3`)せずに操作すべきなのか?

ドキュメントを操作する際、わざわざ `swApp.ActivateDoc3` を呼んで画面の最前面に切り替えるコードをよく見かけますが、これは二重の意味で悪手です。

1. 圧倒的なパフォーマンス低下: ウィンドウの再描画、グラフィックスキャッシュの再構築、UIリボンの再生成が発生し、処理時間が数倍から数十分の1に劣化します。
2. 不確定要素の介入: ウィンドウアクティブ化イベントが発火するため、他のアドインやSolidWorks内部のイベントハンドラが予期せぬ挙動を引き起こすリスクが高まります。

SolidWorks APIの設計思想として、`ModelDoc2` ポインタさえ保持していれば、画面上に表示されていなくても(あるいは最前面になくても)、ほぼ全てのデータ操作(カスタムプロパティの書き換え、構成の変更、フィーチャー情報の取得など)が可能です。

3. アンチパターン vs 堅牢な設計パターン

違いを明確にするため、アンチパターンと本番運用に耐えうる堅牢な設計パターンを比較してみましょう。

✕ 危険なアンチパターン

ユーザーの操作や実行タイミングによって参照先が簡単にブレます。

‘ ✕ 画面に依存した危険な実装
Sub DangerProcess()
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2

Set swApp = Application.SldWorks
Set swModel = swApp.ActiveDoc ‘ ユーザーが別ウィンドウをクリックしたら即アウト

‘ ここでswModelを使った処理…
Debug.Print swModel.GetTitle
End Sub

◯ 堅牢なプロフェッショナル設計

UIを無視し、メモリ上のポインタをファイルパスや名前で正確に一致させて捕捉します。

‘ ◯ ドキュメントコレクションから直接ポインタを補獲する実装
Sub RobustProcess()
Dim swApp As SldWorks.SldWorks
Dim targetDoc As SldWorks.ModelDoc2
Dim targetPath As String

Set swApp = Application.SldWorks
targetPath = “C:\Projects\ComponentA.sldprt”

‘ 目的のドキュメントをコレクションから完全特定
Set targetDoc = GetDocumentByPath(swApp, targetPath)

If targetDoc Is Nothing Then
MsgBox “対象のドキュメントがメモリ上に存在しません。”, vbExclamation
Exit Sub
End If

‘ アクティブ化することなく、そのまま安全に操作を実行
‘ (画面のチラつき zero, 割り込み不具合 zero)
Call UpdateCustomProperties(targetDoc)
End Sub

4. プロダクションコード:コピペで使える安全なドキュメント制御モジュール

以下は、実際の業務ツール開発でそのまま組み込んで利用できる、安全かつ堅牢なドキュメント検索・管理の標準モジュール例です。

モジュール名: `mod_DocumentManager.bas`

Attribute VB_Name = “mod_DocumentManager”
Option Explicit

‘ ==============================================================================
‘ 関数名: GetDocumentByPath
‘ 概要 : 開かれている全ドキュメントから、完全パス指定でModelDoc2を取得する
‘ 引数 : swApp – SldWorksアプリケーションオブジェクト
‘ targetPath – 検索対象のファイル完全パス
‘ 戻り値: 該当するModelDoc2 (見つからない場合はNothing)
‘ ==============================================================================
Public Function GetDocumentByPath(ByVal swApp As SldWorks.SldWorks, ByVal targetPath As String) As SldWorks.ModelDoc2
On Error GoTo ErrorHandler

Dim currentDoc As SldWorks.ModelDoc2
Dim normalizedTargetPath As String

‘ パスの正規化(小文字化・トリム)
normalizedTargetPath = LCase$(Trim$(targetPath))

If swApp Is Nothing Or Len(normalizedTargetPath) = 0 Then
Set GetDocumentByPath = Nothing
Exit Function
End If

‘ メモリ上の最初のドキュメントポインタを取得
Set currentDoc = swApp.GetFirstDocument()

‘ ドキュメントチェーンを走査
Do While Not currentDoc Is Nothing
‘ パスが存在し、一致するか判定(保存済みのファイル)
If Len(currentDoc.GetPathName()) > 0 Then
If LCase$(Trim$(currentDoc.GetPathName())) = normalizedTargetPath Then
Set GetDocumentByPath = currentDoc
Exit Function
End If
End If

‘ 次のドキュメントポインタへ移動
Set currentDoc = currentDoc.GetNext()
Loop

‘ 見つからなかった場合
Set GetDocumentByPath = Nothing
Exit Function

ErrorHandler:
‘ ログ等を出力(実務ではログライブラリへ転送)
Debug.Print “[ERROR] GetDocumentByPath: ” & Err.Description
Set GetDocumentByPath = Nothing
End Function

‘ ==============================================================================
‘ 関数名: SafeExecuteOperation
‘ 概要 : 対象ドキュメントを非アクティブのまま安全に操作する実行サンプラー
‘ 引数 : swApp – SldWorksアプリケーションオブジェクト
‘ filePath – 処理対象のファイルパス
‘ ==============================================================================
Public Sub SafeExecuteOperation(ByVal swApp As SldWorks.SldWorks, ByVal filePath As String)
On Error GoTo ErrorHandler

Dim targetDoc As SldWorks.ModelDoc2
Dim cusPropMgr As SldWorks.CustomPropertyManager
Dim boolStatus As Boolean

‘ 1. コレクションからドキュメントを捕捉(ActiveDocは絶対に使わない)
Set targetDoc = GetDocumentByPath(swApp, filePath)

‘ 2. メモリ上にない場合は、安全にバッググラウンドロード(非表示で開く例)
If targetDoc Is Nothing Then
Dim docErrors As Long
Dim docWarnings As Long

‘ UI描画を伴わずに開くためのオプション設定
swApp.DocumentVisible False, swswDocumentTypes_e.swDocPART

Set targetDoc = swApp.OpenDoc6(filePath, _
swswDocumentTypes_e.swDocPART, _
swOpenDocOptions_e.swOpenDocOptions_Silent, _
“”, docErrors, docWarnings)

‘ 描画設定の即時復元(必須)
swApp.DocumentVisible True, swswDocumentTypes_e.swDocPART
End If

If targetDoc Is Nothing Then
Err.Raise vbObjectError + 1001, , “ドキュメントのロードに失敗しました: ” & filePath
End If

‘ 3. バックグラウンドでのデータ操作(例:カスタムプロパティの書き込み)
Set cusPropMgr = targetDoc.Extension.CustomPropertyManager(“”)

‘ 画面切り替えなしで直接データを変更
cusPropMgr.Add3 “管理番号”, swCustomInfoType_e.swCustomInfoText, “REV-2023-A”, swCustomPropertyAddOption_e.swCustomPropertyReplaceValue

‘ 4. 必要に応じて静かに保存
‘ ※画面がアクティブでなくてもModelDoc2経由で安全に保存可能
boolStatus = targetDoc.Save3(swSaveAsOptions_e.swSaveAsOptions_Silent, docErrors, docWarnings)

Debug.Print “処理成功: ” & targetDoc.GetTitle()
Exit Sub

ErrorHandler:
‘ 例外発生時のクリーンアップ処理
MsgBox “処理中にエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
End Sub

5. データベース/ファイルシステム連携における高度な注意点

実務でバッチ処理ツールや基幹システム(基幹DB/PDM)連携ツールを構築する場合、以下のエッジケースに配慮する必要があります。

1. 保存前ドキュメント(`GetPathName` が空)の扱い

新規作成された一度も保存されていないドキュメントは、`GetPathName()` が空文字列 `””` を返します。これをパスで検索しようとするとすり抜けてしまいます。タイトル(`GetTitle()`)でのフォールバックロジックを用意しておくか、保存済みであることを前提とするガード文(Guard Clause)を設けるのが鉄則です。

2. パス比較時の「大文字・小文字」と「ロングパス/ショートパス」

Windowsファイルシステムは大文字・小文字を区別しませんが、VBAの文字列比較 `=` はデフォルトで区別します(`Option Compare Binary` の場合)。必ず `LCase$` や `UCase$` で正規化して比較してください。

また、ネットワークドライブ(`\\Server\Share`)と割り当てドライブ(`Z:\`)の表記揺れにも注意が必要です。DBにはUNCパス(`\\Server\…`)で保存し、比較前に共通化する設計を推奨します。

3. メモリリークの防止と参照解放

`GetNext()` で辿る際、ループの終端でオブジェクト変数が確実に解放されるように記述してください。大規模バッチ処理(数千点の部品走査)では、変数の再代入時に古い参照が解放されずSolidWorksのメモリを圧迫することがあります。

‘ 走査ループでの正しい変数使用パターン
Do While Not currentDoc Is Nothing
‘ 処理…

‘ 次のドキュメントを取得する前に一時保持を制御
Set nextDoc = currentDoc.GetNext()
Set currentDoc = nextDoc
Loop

6. アーキテクトからのメッセージ:「動くコード」から「倒れないシステム」へ

初心者エンジニアとプロの業務自動化エンジニアを隔てる壁は、「正常系で動くコードが書けるか」ではなく「異常系やUIの不確定要素を考慮した堅牢な設計ができるか」 にあります。

  • `ActiveDoc` に頼るコードは、ユーザーの動作ひとつで崩壊する「脆いマクロ」です。
  • `SldWorks.GetFirstDocument` から走査し、`ModelDoc2` のインスタンスを直接捕捉して操作するコードは、環境を選ばずバックグラウンドで黙々と正確な仕事を行う「堅牢なシステム」です。

開発プロジェクトのリーダーとして、チームのVBAコードから `ActiveDoc` を追放してください。オブジェクトのライフサイクルとメモリ空間上のポインタを完全に制御することこそが、SolidWorks APIを完全に掌握する第一歩です。

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