AutoCAD VBAを掌握する極限の知見:FSO連携による図面事前バリデーションの極意
開発プロジェクトのリーダーである私のもとに、よくこんな悲鳴が届く。
> 「バッチ処理で数千枚のDWGを開いて処理していたら、途中でAutoCADがフリーズした」
> 「壊れたファイルや、すでに最新の処理が済んでいるファイルを何度も開いてしまい、時間が溶けていく」
お前らは、AutoCADのオブジェクトモデルの「重さ」を舐めている。
`Workbooks.Open` のようなノリで `Documents.Open` を連発していれば、COMのプロセス間通信のオーバーヘッドでメモリはリークし、AutoCADは確実に音を上げる。さらに言えば、「AutoCADにファイルを開かせる」という行為自体が、異常系ファイルに対して極めて無防備なのだ。
今回は、AutoCAD VBAの限界を突破し、ファイルIOのボトルネックを消し去るための実務的アプローチ――「FileSystemObject(FSO)を用いた、開く前の事前バリデーション戦略」を授けよう。
—
1. なぜ AutoCAD API だけでは不十分なのか?
AutoCADのAPI(`Documents.Open`)は、いわば「刃物」だ。正しく使えば強力だが、何も考えずに振り回すと自分を傷つける。
実務において、大量の図面を処理する際によくある悪手はこれだ:
1. フォルダ内のDWGを全取得する。
2. 1つずつ `Documents.Open` する。
3. 図面内のメタデータやタイムスタンプを見て、処理が必要か判定する。
4. 不要なら `Close` する。
言語道断。
ファイルを開くという処理は、グラフィックスエンジンを初期化し、フォントを読み込み、外部参照を解決しようとするため、莫大なCPUリソースと時間を消費する。開いた瞬間に致命的なエラー(Fatal Error)でAutoCADごと落ちるリスクすらある。
解決策:レイヤーの分離
ファイルシステム層の情報(サイズ、更新日時、属性)は、AutoCADを起動する前(=COMの網をくぐる前)に、WindowsのネイティブAPIに近い `Scripting.FileSystemObject` で完全にフィルタリングしなければならない。これが大規模バッチを安定稼働させるための絶対原則だ。
—
2. 堅牢な設計:FSO連携バリデーションの3原則
プロダクション環境に耐えうるコードを書くためには、以下の3点を死守しろ。
1. ゼロ・オーバーヘッド判定
AutoCADのプロセスを汚染しないよう、ファイルが存在するか、サイズが0バイトではないか、排他制御(他人が開いていないか)をFSOとWindows APIの例外処理で事前に弾く。
2. キャッシュ・タイムスタンプによる差分同期
「前回処理した日時」と「ファイルの最終更新日時(`DateLastModified`)」を比較し、更新されていない図面は一歩も中に入らせない。
3. トランザクション的ロギング
どのファイルがなぜスキップされたのか、エラーの理由は何かを、ファイルシステムレベルで確実にトレースできるようにする。
—
3. 【プロダクションコード】コピペで使える堅牢な事前バリデーションエンジン
以下のコードは、単なるリファレンスの引き写しではない。実務の修羅場をくぐり抜けてきた私が構築した、実戦投入可能なモジュールだ。
Option Explicit
‘ ==============================================================================
‘ モジュール名: Mdl_DrawingValidator
‘ 概要: FSOを活用し、AutoCADで図面を開く前にファイルの妥当性と差分を検証する
‘ ==============================================================================
Public Sub RunBatchProcessingWithValidation()
Dim targetFolder As String
targetFolder = “C:\Projects\AutoCAD_Batch\TargetDWGs” ‘ 処理対象フォルダ
‘ FSOのインスタンス生成
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FolderExists(targetFolder) Then
MsgBox “指定されたフォルダが存在しません: ” & targetFolder, vbCritical, “パスエラー”
Exit Sub
End If
Dim folder As Object, file As Object
Set folder = fso.GetFolder(targetFolder)
Dim processedCount As Long, skippedCount As Long, errorCount As Long
processedCount = 0: skippedCount = 0: errorCount = 0
‘ 処理開始のログ(イミディエイトウィンドウ出力)
Debug.Print “=== バッチ処理開始: ” & Now & ” ===”
‘ フォルダ内の全ファイルを走査
For Each file In folder.Files
‘ 1. 拡張子フィルタリング (.dwg のみ)
If LCase(fso.GetExtensionName(file.Name)) = “dwg” Then
‘ 2. FSOによる事前バリデーションの実行
Select Case ValidateDrawingFile(file)
Case 0 ‘ 正常:処理対象
If ProcessDrawing(file.Path) Then
processedCount = processedCount + 1
Else
errorCount = errorCount + 1
End If
Case 1 ‘ スキップ(前回処理から更新なし、または一時ファイル)
skippedCount = skippedCount + 1
Debug.Print “[SKIP] 更新なし/一時ファイル: ” & file.Name
Case -1 ‘ 異常:破損リスク・アクセス不可
errorCount = errorCount + 1
Debug.Print “[ERROR] 読み込み不可または異常ファイル: ” & file.Name
End Select
End If
Next file
Debug.Print “=== バッチ処理終了 ===”
Debug.Print “処理成功: ” & processedCount & “件 / スキップ: ” & skippedCount & “件 / エラー: ” & errorCount & “件”
‘ クリーンアップ
Set file = Nothing
Set folder = Nothing
Set fso = Nothing
End Sub
‘ ==============================================================================
‘ 関数名: ValidateDrawingFile
‘ 概要 : ファイルを開く前に、FSO情報とメタデータから処理要否を判定する
‘ 戻り値: 0 = 処理実行, 1 = スキップ, -1 = 異常終了
‘ ==============================================================================
Private Function ValidateDrawingFile(ByVal fileObj As Object) As Long
On Error GoTo ErrorHandler
‘ A. ゼロバイトファイルのチェック(極稀に壊れた保存で発生)
If fileObj.Size = 0 Then
ValidateDrawingFile = -1
Exit Function
End If
‘ B. AutoCADの自動バックアップ・ロックファイル(.bak, .ac$)の除外
‘ ※拡張子は既にdwgで絞っているが、念のためファイル名の先頭文字($や~)もチェック
Dim leadChar As String
leadChar = Left(fileObj.Name, 1)
If leadChar = “~” Or leadChar = “$” Then
ValidateDrawingFile = 1
Exit Function
End If
‘ C. 排他制御(他のプロセスやユーザーが排他ロックしていないかテストオープン)
If IsFileLocked(fileObj.Path) Then
Debug.Print “[WARN] ファイルがロックされています(編集中?): ” & fileObj.Name
ValidateDrawingFile = -1
Exit Function
End If
‘ D. 差分同期チェック(例:前回の処理ログやカスタムプロパティ、タイムスタンプ比較)
‘ ここでは「最終更新日時」と、前回バッチ実行時の基準時刻を比較するロジックを想定
Dim lastModified As Date
lastModified = fileObj.DateLastModified
‘ 【実務的テクニック】
‘ 前回の処理完了スタンプを保持する外部INIやDB、またはファイル自身のカスタムプロパティと比較する
If Not NeedsUpdate(fileObj.Path, lastModified) Then
ValidateDrawingFile = 1 ‘ 更新されていないのでスキップ
Exit Function
End If
‘ すべての関門を突破
ValidateDrawingFile = 0
Exit Function
ErrorHandler:
‘ アクセス権限がない等の予期せぬIOエラー
ValidateDrawingFile = -1
End Function
‘ ==============================================================================
‘ 関数名: IsFileLocked
‘ 概要 : ファイルが他者によって占有(ロック)されているかを判定する
‘ ==============================================================================
Private Function IsFileLocked(ByVal filePath As String) As Boolean
Dim ff As Long
ff = FreeFile
On Error GoTo Locked
‘ 排他モード(Binary Read)で開いてみる。失敗すればロックされている。
Open filePath For Binary Access Read Lock Read Write As #ff
Close #ff
IsFileLocked = False
Exit Function
Locked:
IsFileLocked = True
On Error Resume Next
Close #ff
End Function
‘ ==============================================================================
‘ 関数名: NeedsUpdate
‘ 概要 : 最終更新日時に基づく差分判定(簡易実装:テキストログとの比較例)
‘ ==============================================================================
Private Function NeedsUpdate(ByVal filePath As String, ByVal currentModified As Date) As DXF
‘ ※実務ではここでINIファイルやSQLite、あるいはDWG自体のDB概要プロパティを参照する
‘ 今回は常に真(要更新)を返すスタブ、必要に応じて拡張せよ
NeedsUpdate = True
End Function
‘ ==============================================================================
‘ 関数名: ProcessDrawing
‘ 概要 : 厳格なバリデーションを通過した安全な図面のみを開いて処理する
‘ ==============================================================================
Private Function ProcessDrawing(ByVal filePath As String) As Boolean
On Error GoTo ErrorHandler
Dim doc As Object
‘ ここで初めてAutoCADのCOMオブジェクトに負荷をかける
Set doc = ThisDrawing.Application.Documents.Open(filePath, ReadOnly:=True)
‘ — 実際の図面処理(例:画層の確認やデータの抽出) —
‘ Debug.Print doc.Name & ” を処理中…”
‘ 安全に閉じる(変更を破棄)
doc.Close False
Set doc = Nothing
ProcessDrawing = True
Exit Function
ErrorHandler:
Debug.Print “[FATAL] 図面オープン時エラー: ” & filePath & ” (Error: ” & Err.Description & “)”
If Not doc Is Nothing Then
On Error Resume Next
doc.Close False
Set doc = Nothing
End If
ProcessDrawing = False
End Function
—
4. チーフアーキテクトからの実践的アドバイス
このコードを組み込むことで、お前のバッチ処理プログラムは劇的に洗練されるはずだ。特に以下の2点に注目してほしい。
1. `IsFileLocked` 関数の存在意義
ネットワークドライブ上で他の設計者が現在作業中の図面を誤ってバッチプログラムが掴んでしまうと、AutoCADがダイアログを出して完全停止(ハング)する。この関数をFSOチェックの段階で挟むことにより、「UIスレッドをブロックする幽霊ダイアログ」を完全に根絶できる。
2. メモリリークの防止
`ProcessDrawing` 内のエラーハンドリングを見ろ。万が一 `Documents.Open` の前後で予期せぬエラーが起きた場合でも、必ず `doc.Close False` を通し、オブジェクト変数を `Nothing` に明示的に解放している。COMの参照カウンタを適切に管理しないVBAは、100枚も開けば確実にメモリが枯渇する。
既製品のコードをただ貼り付けるだけのステージはもう終わりだ。ファイルシステム層の制御(FSO)と、アプリケーション層の制御(AutoCAD COM)を明確に分離し、「泥臭いが絶対に止まらない堅牢なシステム」を構築してこそ、一流の業務自動化エンジニアと言える。
現場の信頼を勝ち取るコードを、今日から実装してくれ。
