【上級】AcadDocument.Databaseオブジェクト深掘り:Undo/Redoスタックを考慮したトランザクション的な処理の実装
AutoCAD VBAにおける真のパフォーマンスチューニングと、堅牢なエンタープライズ・システムの構築において、避けて通れない領域がある。それが `AcadDocument.Database` オブジェクトの深層と、Undo/Redoスタックの制御だ。
数万件におよぶ図形を一括生成・改修するバッチ処理をVBAで組んだ際、ユーザーが「元に戻す(Ctrl + Z)」を実行した瞬間、AutoCADがフリーズしたり、オブジェクトが中途半端な状態で残されたりした経験はないだろうか。あるいは、Undoの粒度が細かすぎて、1つのコマンドに対して数回Undoを押さなければ元の状態に戻らないという悪夢に直面したことは?
本稿では、AutoCADのオブジェクトモデルの裏側にあるトランザクションの概念を暴き、`StartUndoMark` と `EndUndoMark` を駆使して、数万の図形操作を「単一の不可分なトランザクション」としてアトミックに処理するための極限の知見を授ける。
—
1. AutoCADのUndo/RedoアーキテクチャとVBAの罠
AutoCADの内部データベース(`Database`)は、すべての図形エンティティやシンボルテーブルを厳密なトランザクション管理下で保持している。.NET API(ObjectARX)の世界では `Transaction` オブジェクトを明示的に生成・コミット・破棄するが、VBAの世界ではこのトランザクションの境界が暗黙的に処理されることが多い。
VBAでループ処理を回し、数千個の `AcadLine` や `AcadBlockReference` を連続生成した場合、AutoCADはデフォルトで「1つのメソッド呼び出し、あるいはVBAの実行コンテキスト内の各操作」を個別のUndoイベントとしてスタックに積み上げる。
これが何を意味するか。
1. メモリの爆発的消費: Undoスタックに膨大な数のコマンド履歴が保持され、メモリフットプリントが跳ね上がる。
2. UXの崩壊: ユーザーが「元に戻す」を押すと、数千回分の処理を1回ずつ逆再生しようとしてAutoCADが応答不可に陥る。
3. データ不整合: 処理の途中でエラーハンドリングが発生した場合、データベースが半端な状態でロックされる。
これを解決するのが、`Database.StartUndoMark` と `Database.EndUndoMark` による「Undoマークの明示的なグループ化」である。
—
2. 実装パターン:アトミック・バッチ処理の設計
以下のコードは、数万個のオブジェクトを一括生成する際、Undo/Redoスタックを単一のブロックにまとめ、さらにパフォーマンスを極限まで引き上げるための実践的なモジュールである。
Option Explicit
‘ ==============================================================================
‘ 致命的な一括処理をアトミックに実行するプロシージャ
‘ ==============================================================================
Public Sub ExecuteAtomicBatchOperation()
Dim doc As AcadDocument
Set doc = ThisDrawing
Dim db As AcadDatabase
Set db = doc.Database
Dim undoMarkId As Long
undoMarkId = -1
‘ 堅牢なエラーハンドリングの確立(トランザクションのスタック崩壊を防ぐ)
On Error GoTo ErrorHandler
‘ ————————————————————————–
‘ 1. パフォーマンス最適化の極意:画面描画とイベントの凍結
‘ ————————————————————————–
‘ ※注意: 内部APIやシステム変数の操作は極限の速度を得るための定石
doc.Application.ScreenUpdating = False
‘ ————————————————————————–
‘ 2. Undoマークの開始(トランザクションの扉を開く)
‘ ————————————————————————–
‘ このメソッドは内部的にUNDOコマンドの「Mark」を発行する。
undoMarkId = db.StartUndoMark()
Dim i As Long
Dim lineObj As AcadLine
Dim startPoint(0 to 2) As Double
Dim endPoint(0 to 2) As Double
‘ ————————————————————————–
‘ 3. 大量オブジェクトの生成シミュレーション(例:10,000本の線分生成)
‘ ————————————————————————–
For i = 1 To 10000
startPoint(0) = i 10: startPoint(1) = 0: startPoint(2) = 0
endPoint(0) = i 10: endPoint(1) = 1000: endPoint(2) = 0
‘ ModelSpaceへの直接追加は重いため、データベースの整合性を保ちつつ追加
Set lineObj = doc.ModelSpace.AddLine(startPoint, endPoint)
‘ 定期的なメモリ解放のトリガー(VBAのCOMラッパー肥大化対策)
If i Mod 1000 = 0 Then
Set lineObj = Nothing
DoEvents ‘ UIスレッドのフリーズ防止(必要に応じて)
End If
Next i
‘ ————————————————————————–
‘ 4. Undoマークの正常終了(コミットに相当)
‘ ————————————————————————–
db.EndUndoMark
‘ 画面描画の復元
doc.Application.ScreenUpdating = True
MsgBox “バッチ処理が正常に完了しました。Undoスタックは1つに統合されています。”, vbInformation
Exit Sub
ErrorHandler:
‘ ————————————————————————–
‘ 5. 異常系:トランザクションのロールバック(Undoマークまで巻き戻す)
‘ ————————————————————————–
doc.Application.ScreenUpdating = True
If undoMarkId >= 0 Then
‘ エラー発生時は、作成したUndoマークの位置まで強制的に状態を巻き戻す(Rollback相当)
‘ AutoCADのCOMには直接的な Rollback メソッドが存在しないため、
‘ UndoコマンドのBackオプションをSendCommandで実行するか、マークまで遡る制御を行う。
Call RollbackUndoMark(doc, db)
End If
MsgBox “致命的なエラーが発生しました。処理をロールバックします。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical
End Sub
‘ ==============================================================================
‘ Undoマークまでを一括で巻き戻す(ロールバック)ヘルパー関数
‘ ==============================================================================
Private Sub RollbackUndoMark(doc As AcadDocument, db As AcadDatabase)
On Error Resume Next
‘ EndUndoMarkが呼ばれていない状態でUndoのBackを実行すると、
‘ StartUndoMarkの位置まで一気に状態が復元される
‘ 注意: SendCommandの実行タイミングに依存するため、実運用では
‘ グローバルなUndoコントロールの状態を厳密に監視する必要がある。
doc.SendCommand “_.UNDO _Back ”
‘ 念のためUndoマークを明示的に閉じる試行
db.EndUndoMark
On Error GoTo 0
End Sub
—
3. チーフアーキテクトが教える:実運用における深層の注意点
メモリリークとCOMラッパーの寿命
VBAからAutoCADを操作する場合、背後でCOM(Component Object Model)のラッパーが生成されている。ループ内で `AcadEntity` を変数に格納し続けると、VBAのガベージコレクションが追いつかず、メモリリーク(メモリフットプリントの肥大化)を引き起こす。
上記のコード例にあるように、一定の周期でオブジェクト変数に `Nothing` を代入し、参照カウントを意図的に解放することが、長時間のバッチ処理を安定稼働させるための極意である。
StartUndoMarkのネスト(入れ子構造)に関する仕様
`StartUndoMark` と `EndUndoMark` は、必ずペアで呼び出されなければならない。もし `StartUndoMark` を呼んだ後に `EndUndoMark` を呼び忘れると、AutoCADの内部Undoスタックがオープン状態のままとなり、そのセッション中に行われる後続のすべてのユーザー操作が1つの巨大なUndoブロックに呑み込まれるという致命的な不具合を引き起こす。
そのため、本稿のコードのように `On Error GoTo` を必ず配置し、異常終了時であってもスタックの整合性を保つ防衛的プログラミングが不可欠となる。
パフォーマンスの真のボトルネック
`AddLine` や `AddCustomEntity` などのメソッドを呼び出すたびに、AutoCADはグラフィックスシステムのインデックス更新とデータベースのインデックス再構築を行う。
前述のコードで `ScreenUpdating = False` を挟んでいるが、さらに極限を求めるならば、一時的にレイヤーの非表示化や、自動スナップ・Osnapの無効化(システム変数 `OSMODE` の制御)を前後に挟むことで、処理速度を数倍から数十倍に跳ね上げることが可能だ。
—
総括
VBAはレガシーな言語として片付けられがちだが、その背後にあるAutoCADの `Database` および `Undo` アーキテクチャの本質を理解していれば、.NET APIに匹敵する堅牢で高速な自動化システムを構築できる。
「ただ動くコード」を書くフェーズは終わりだ。
トランザクションの境界を支配し、メモリのライフサイクルをコントロールする者だけが、真に信頼されるAutoCAD自動化のアーキテクトの名を手にすることができる。
