AutoCAD VBAを掌握する極限の知見:図面オープン・セーブ時におけるトランザクション管理とロールバックの極意
フリマアプリのトランザクション制御や、RDBのACID特性について語るエンジニアは数あれど、AutoCADのDWGファイル入出力において「完全なロールバック」をVBAレベルで実装し、プロダクション環境で運用している人間はどれほどいるだろうか。
AutoCAD VBAは、単なるマクロの延長ではない。背後には巨大なObjectARXのC++コアが鎮座し、COM(Component Object Model)の境界を越えてメモリ空間を共有している。このアーキテクチャの特性を理解せず、`ThisDrawing.SaveAs`を無造作にコードの海へ放り込む行為は、時限爆弾を抱えてCADを運用するに等しい。
今回は、数々のレガシーシステムと巨大プラント図面の自動化地獄をくぐり抜けてきたシニアエンジニアたちに向けて、図面オープン・セーブ時におけるトランザクション的思考と、予期せぬクラッシュからデータを守るための「堅牢なロールバック機構」の全貌を解説する。
—
1. AutoCADにおける「トランザクション」の正体とVBAの限界
データベースの世界におけるトランザクションとは異なり、AutoCADの標準COM APIには、明示的な `BeginTrans` / `Commit` / `Rollback` のような汎用トランザクション機構は存在しない。(※.NET APIの `TransactionManager` にはそれに近い概念があるが、VBAのCOMラッパーからは直接制御できない)。
VBAから操作する場合、すべての変更は `Database` オブジェクトに直結しており、メソッドが実行された瞬間からメモリ上の図面データ(DwgDatabase)が書き換わる。もしスクリプトの途中でエラーが発生した場合、以下のような惨劇が引き起こされる。
- 途中でアタッチされた外部参照(Xref)や画層(Layer)のゴミデータが残留する。
- 不完全な状態で `Save` が走ることで、数ギガバイトの重要図面が破損(Corruption)する。
- メモリリークを引き起こし、AutoCADプロセス自体がゾンビ化する。
これを防ぐためには、「操作対象の図面を非表示のサンドボックス(別プロセス、あるいはメモリ上のクローン)で処理し、完全な成功を確認してから本番ストレージにコミットする」 という、ファイルシステムレベルのトランザクション管理を自前で実装する必要がある。
—
2. 実装:安全な「セーフ・セーブ&ロールバック」アーキテクチャ
以下のコードは、単なるエラーハンドリングの域を超え、ファイルロック、一時退避、アトミック(不可分)なファイル置き換えをVBAで実現するプロダクションコードである。
Windows Script Host (WSH) や FileSystemObject (FSO) を駆使し、ディスクI/Oの安全性を担保している点に注目してほしい。
Option Explicit
‘ Win32 API: ファイルの強制削除や排他制御の確認等に必要に応じて使用
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Public Sub ExecuteTransactionalDrawingUpdate(ByVal targetFilePath As String)
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ 0. 存在チェックとパスの正規化
If Not fso.FileExists(targetFilePath) Then
Err.Raise vbObjectError + 1000, “TransactionalSave”, “対象の図面ファイルが存在しません: ” & targetFilePath
End If
Dim backupPath As String
backupPath = targetFilePath & “.bak_” & Format(Now, “yyyymmdd_hhmmss”)
Dim tempWorkPath As String
tempWorkPath = fso.GetSpecialFolder(2) & “\ACAD_Temp_” & Int(Rnd 1000000) & “.dwg”
On Error GoTo ErrorHandler
‘ ==========================================
Phase 1: 隔離環境(バックアップとワークスペース)の構築
‘ ==========================================
‘ 堅牢性のため、元のファイルをバックアップへ退避
fso.CopyFile targetFilePath, backupPath, True
‘ 作業用の一時ファイルを作成(元ファイルを直接イジらないためのサンドボックス)
fso.CopyFile targetFilePath, tempWorkPath, True
‘ ==========================================
Phase 2: AutoCADエンジンによるトランザクション処理
‘ ==========================================
Dim targetDoc As Document
Dim isDocOpened As Boolean
isDocOpened = False
‘ AutoCADのバックグラウンド、あるいはアクティブセッションで一時ファイルをオープン
‘ ここでは視覚的なチラつきを防ぐため、可能な限りサイレントに処理
Set targetDoc = Application.Documents.Open(tempWorkPath)
isDocOpened = True
‘ — 【ここに実際の図面加工ロジックを記述】 —
‘ 例:全画層の色を強制的に変更する、カスタムプロパティを埋め込む等
Call MutateDrawingData(targetDoc)
‘ ——————————————
‘ 一時ファイルに対してセーブを実行(ここではまだ本番ファイルは安全)
targetDoc.Save
‘ ドキュメントを安全に閉じる(COM参照の解放を確実に行う)
targetDoc.Close False
isDocOpened = False
‘ ==========================================
Phase 3: アトミック・コミット(本番反映)
‘ ==========================================
‘ ファイルの置き換え(Windowsファイルシステムの原子的操作に近い挙動)
If fso.FileExists(targetFilePath) Then
fso.DeleteFile targetFilePath, True
End If
fso.MoveFile tempWorkPath, targetFilePath
‘ 正常終了時はバックアップを削除(残しておきたければ残す)
If fso.FileExists(backupPath) Then
fso.DeleteFile backupPath, True
End If
‘ クリーンアップ
Set fso = Nothing
MsgBox “トランザクションは正常にコミットされました。”, vbInformation, “成功”
Exit Sub
ErrorHandler:
‘ ==========================================
Phase 4: ロールバック(例外発生時の復旧処理)
‘ ==========================================
Dim errDesc As String
errDesc = Err.Description
‘ 開いたままのドキュメントがあれば強制クローズ(変更破棄)
On Error Resume Next
If isDocOpened And Not (targetDoc Is Nothing) Then
targetDoc.Close False
End If
‘ 本番ファイルが破損している、あるいは消失している場合の緊急復旧
If fso.FileExists(backupPath) Then
If fso.FileExists(targetFilePath) Then
fso.DeleteFile targetFilePath, True
End If
fso.MoveFile backupPath, targetFilePath
End If
‘ 一時ファイルの残骸掃除
If fso.FileExists(tempWorkPath) Then
fso.DeleteFile tempWorkPath, True
End If
Set fso = Nothing
‘ エラーの再送出
Err.Raise vbObjectError + 1001, “TransactionalSave”, _
“トランザクション中に致命的なエラーが発生しました。システムはロールバックされました。” & vbCrLf & “詳細: ” & errDesc
End Sub
Private Sub MutateDrawingData(ByRef doc As Document)
‘ サンプルとしての図面操作ロジック
Dim acadLayer As AcadLayer
For Each acadLayer In doc.Layers
‘ 例: 特定の画層のロックを解除するなど
If acadLayer.Lock Then acadLayer.Lock = False
Next acadLayer
‘ あえてエラーを誘発してテストしたい場合はここで Err.Raise を発行する
End Sub
—
3. レガシー環境とメモリ管理の罠(COMの参照解放とガベージコレクション)
AutoCAD VBAで最も恐ろしいのは、「コード上では正しく書かれているのに、背後のC++オブジェクトがメモリ上に残り続け、AutoCADのプロセス(`acad.exe`)がメモリリークで徐々に肥大化する現象」である。
特に、`Documents.Open` や `ModelSpace.Add…` といったメソッドの戻り値を受け取る際、不適切な変数スコープやオブジェクトの放置は致命傷になる。
メモリ最適化の鉄則
1. ループ内でのCOMオブジェクト生成の排除
`For Each` や `For` ループ内で `Layers` や `Blocks` などのコレクションを漫然と呼び出すと、内部で一時的なCOMラッパーが生成され、VBAのガベージコレクタが追いつかなくなる。
2. 明示的な `Nothing` 代入
処理が終わったオブジェクト変数には、速やかに `Set variable = Nothing` を明示し、インターフェースの参照カウント(Reference Count)を即座にデクリメントさせること。
3. 多重ループの参照リーク対策
複雑なネスト構造を持つ図面要素(例:ブロック参照内の属性など)を走査する際は、必ずローカル変数に落とし込み、使い終わったら即座に解放するルーティンを徹底する。
—
4. シニアエンジニアが知るべき「AutoCAD固有の排他制御」
ネットワークドライブ(NASや共有サーバー)上でDWGファイルを操作する場合、AutoCAD特有の「`.LCK` ファイル」や「`.SV$ (自動保存)` ファイル」の挙動が、トランザクションの邪魔をすることがある。
他のユーザーが既にファイルを開いている場合、AutoCADの `Documents.Open` は単にエラーを吐くだけでなく、ダイアログを表示してスクリプトの実行を完全にハング(フリーズ)させる。
これを回避するためには、ファイルをオープンする前に、VBA側でファイルが排他ロックされていないかを事前に検査するロジック(あるいはWindows APIによるファイル共有モードのチェック)を挟むのが、真にプロフェッショナルなシステム設計というものだ。
—
結びにかえて
自動化スクリプトの価値は、「うまく動いている時」ではなく、「予期せぬエラーが起きた瞬間」にどれだけシステムを安全な状態に保てるか(レジリエンスの高さ)で決まる。
`On Error Resume Next` でエラーを握りつぶすような幼稚なコードは今日で終わりにしよう。アトミックなファイル操作と厳格なライフサイクル管理を身につけたとき、あなたのAutoCAD VBAは、単なる「お絵描きマクロ」から、ミッションクリティカルな「エンタープライズCADインフラ」へと昇華する。
