【実務・中級編】【上級プロフェッショナル向け】図面保存イベント「BeginSave」をフックした、禁止オブジェクトの自動検出と保存ブロック – AutoCAD VBA解析バイブル

スポンサーリンク

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という制約の中で最大限の堅牢性を確保するための最適解だ。これをベースに、社内独自の検図ルールを組み込み、図面品質の自動統制を実現せよ。

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