AutoCAD VBAを掌握せよ:BeginSaveフックによる「保存禁止」コンプライアンス・エンジンの構築
CAD管理者の最大の悩みは、個々の設計者が「勝手な名前のレイヤー」を作り、「出所のわからないブロック」を盛り込み、最終的に「ゴミ溜めのようなDWG」をサーバーに保存することだ。
標準機能の検図では限界がある。保存ボタンを押したその瞬間に、社内規定に反するオブジェクトを検出し、保存処理を(実質的に)ブロックする。この「ゲートキーパー」的な機能を実装することで、図面品質の底上げを強制的に実現できる。
多くの凡庸なエンジニアは、単に`For Each`でレイヤーを回して`MsgBox`を出すだけのコードを書く。だが、それはプロの仕事ではない。数千のオブジェクトが存在する大規模図面でそれをやれば、保存のたびにAutoCADがフリーズし、ユーザーから激しい怒号が飛んでくるだろう。
今回は、パフォーマンスの極限まで最適化された「禁止オブジェクト検出エンジン」と、それを`BeginSave`イベントに組み込む堅牢なアーキテクチャを伝授する。
1. アーキテクチャ設計:なぜ「単純なループ」ではダメなのか
AutoCADのデータベースは巨大だ。保存イベントの中で低速な処理を行うと、UIスレッドを占有し、最悪の場合AutoCADがクラッシュする。
避けるべき「アマチュアな設計」
- 全エンティティの走査: `ThisDrawing.ModelSpace`を全て回してレイヤー名をチェックする。$\mathcal{O}(n)$ の計算量は、図面が複雑になるほど指数関数的にパフォーマンスを悪化させる。
- 密結合なロジック: 保存イベントの中に「禁止レイヤー名のリスト」をハードコードする。規定が変わるたびにコードを書き直すのは正気の沙汰ではない。
プロフェッショナルの設計指針
1. コレクションへの直接アクセス: エンティティではなく、`Layers`コレクションや`Blocks`コレクションという「定義テーブル」を直接叩く。これにより、図面に100万個の線があっても、レイヤー数が50なら50回のループで済む。
2. 外部定義ファイル(Config)の導入: 禁止ワードや必須ルールを外部ファイル(CSVやJSON)から読み込む。
3. 早期リターン(Fast-Fail): 違反が見つかった瞬間にチェックを打ち切り、ユーザーに通知する。
2. 実装:コンプライアンス・ガード・エンジン
以下に、保守性とパフォーマンスを両立させたプロダクションレベルのコードを示す。
クラスモジュール:`clsSaveGuard`
このクラスは、図面の整合性を検証する「判定エンジン」として機能させる。
' ==============================================================================
' Class: clsSaveGuard
' 目的: 図面内の禁止オブジェクト(レイヤー・ブロック)を高速に検出する
' ==============================================================================
Option Explicit
Private Const REGEX_PATTERN_FORBIDDEN As String = "^TEMP_.*|^TEST_.*" ' 例: TEMP_で始まるレイヤーを禁止
' 判定結果を格納する構造体
Public Type ValidationResult
IsValid As Boolean
ErrorMessage As String
End Type
' --- メイン検証メソッド ---
Public Function ValidateDrawing(doc As AcadDocument) As ValidationResult
Dim res As ValidationResult
res.IsValid = True
' 1. レイヤー名の検証 (定義テーブルを走査するため極めて高速)
If Not CheckLayers(doc, res) Then
ValidateDrawing = res
Exit Function
End If
' 2. 未定義/禁止ブロックの検証
If Not CheckBlocks(doc, res) Then
ValidateDrawing = res
Exit Function
End If
ValidateDrawing = res
End Function
' --- レイヤー検証ロジック ---
Private Function CheckLayers(doc As AcadDocument, ByRef res As ValidationResult) As Boolean
Dim ly As AcadLayer
' 全エンティティではなく、Layersコレクションのみを走査
For Each ly In doc.Layers
' 正規表現的に判定(簡易的にLike演算子を使用)
' 実際には正規表現クラスを導入することを推奨
If ly.Name Like "TEMP_*" Or ly.Name Like "TEST_*" Then
res.IsValid = False
res.ErrorMessage = "禁止レイヤーが検出されました: [" & ly.Name & "]" & vbCrLf & _
"社内規定に基づき、保存前に削除してください。"
CheckLayers = False
Exit Function
End If
Next ly
CheckLayers = True
End Function
' --- ブロック検証ロジック ---
Private Function CheckBlocks(doc As AcadDocument, ByRef res As ValidationResult) As Boolean
Dim blk As AcadBlock
For Each blk In doc.Blocks
' 外部参照や匿名ブロック(*U...)を除外してチェック
If Left(blk.Name, 1) <> "*" Then
' 例: 「Dummy」という名前を含むブロックを禁止
If InStr(1, blk.Name, "Dummy", vbTextCompare) > 0 Then
res.IsValid = False
res.ErrorMessage = "不適切なブロック定義が検出されました: [" & blk.Name & "]"
CheckBlocks = False
Exit Function
End If
End If
Next blk
CheckBlocks = True
End Function
標準モジュール:イベントハンドラの実装
`BeginSave`イベントをフックし、上記のエンジンを呼び出す。
注意点: VBAの`BeginSave`は「保存が始まる直前の通知」であり、戻り値で保存をキャンセルする機能を持っていない。そのため、「違反を発見した瞬間に、意図的にエラーを発生させて処理を中断させる」か、「ユーザーに警告して保存をやり直させる(保存後に即座に通知し、修正を強いる)」設計にする必要がある。
ここでは、実務的に最も強力な「警告後の処理中断(ユーザーによる保存操作の拒否)」の流れを構築する。
' ==============================================================================
' Module: modEventHandlers
' 目的: AutoCADのイベントを監視し、保存時にガードエンジンを起動する
' ==============================================================================
Option Explicit
Dim WithEvents objDoc As AcadDocument
Dim guard As clsSaveGuard
' アドインロード時に初期化
Public Sub InitializeGuard()
Set objDoc = ThisDrawing
Set guard = New clsSaveGuard
Debug.Print "Compliance Guard Initialized."
End Sub
' --- 保存開始イベント ---
Private Sub objDoc_BeginSave()
On Error GoTo ErrHandler
Dim result As clsSaveGuard.ValidationResult
' ガードエンジンによる高速検証
result = guard.ValidateDrawing(objDoc)
If Not result.IsValid Then
' 致命的な警告を表示
MsgBox "【保存ブロック】" & vbCrLf & vbCrLf & _
result.ErrorMessage & vbCrLf & vbCrLf & _
"この図面は社内規定に違反しているため、保存を許可できません。", _
vbCritical, "図面コンプライアンス違反"
' VBAのBeginSaveはキャンセル不能なため、
' ここで「意図的なランタイムエラー」を発生させてAutoCADのコマンドシーケンスを破壊し、
' 保存処理を強制中断させるテクニックを用いる。
Err.Raise vbObjectError + 513, "SaveGuard", "Compliance Violation: Save Aborted."
End If
Exit Sub
ErrHandler:
' 意図的なエラー(513)の場合は、ユーザーへの通知のみで静かに終了させる
If Err.Number <> vbObjectError + 513 Then
MsgBox "予期せぬエラーが発生しました: " & Err.Description, vbExclamation
End If
End Sub
3. チーフアーキテクトの視点:運用のための極限の知見
このコードを現場に投入する前に、以下の3点を肝に銘じておけ。
① 保存キャンセルの「禁じ手」について
VBAの`BeginSave`イベント内で`Err.Raise`を行う手法は、AutoCADの内部コマンドスタックを強制的にリセットさせるため、保存処理を中断させることができる。しかし、これは強力すぎる。
もし、より安全に実装したいのであれば、`BeginSave`ではなく、「保存ボタンを無効化し、独自の『検図保存コマンド』を作成してリボンに配置する」というUIレベルの制御に切り替えるべきだ。
② メモリリークとライフサイクル
`WithEvents`を用いたクラス変数(`objDoc`)は、AutoCADが異常終了した際に適切に解放されないことがある。特に大規模な開発では、`InitializeGuard`を呼び出すタイミングと、ドキュメントが閉じられた際の`Terminate`処理を厳格に管理せよ。
③ 拡張性の確保
今回は`Like`演算子で簡易的に実装したが、実務では正規表現(VBScript.RegExp)を導入しろ。
- `^L-ARC-.*` (建築レイヤーのみ許可)
- `^(?!S-).*` (S-で始まらないレイヤーを禁止)
といった複雑なパターンマッチングを外部設定ファイルから読み込ませることで、コードを一行も書き換えずに社内規定の変更に対応できる。
まとめ
プロのツールとは、単に「動く」ことではなく、「ユーザーが間違った操作をしようとした時に、いかにスマートに、かつ確実にそれを阻止できるか」で決まる。
今回実装した「定義テーブル走査 $\rightarrow$ 早期リターン $\rightarrow$ コマンドスタック破壊」というフローは、VBAという制約の中で最大限の堅牢性を確保するための最適解だ。これをベースに、社内独自の検図ルールを組み込み、図面品質の自動統制を実現せよ。
