【AutoCAD VBAを掌握する極限の知見】ThisDrawingとActiveDocumentの決定的な違いと使い分けの罠
AutoCAD VBAの開発において、最も頻繁に犯す致命的なミス、そしてデバッグ泣かせのバグの根源。それが `ThisDrawing` と `AcadApplication.ActiveDocument` の使い分けの誤認 だ。
「どちらを使っても同じ図面が取れるから、適当に書いても動く」――そう考えていないだろうか。
もしあなたが複数の図面を同時に開き、バックグラウンド処理やイベント連動型のマクロを組み込もうとしているなら、その甘い認識は遠からずシステムを崩壊させる。
今回は、AutoCADのオブジェクトモデルの深層を知り尽くしたチーフアーキテクトの視点から、この2つの参照が持つ「ライフサイクルの決定的な違い」と、実務で絶対にバグらせないための堅牢なコード設計を授けよう。
—
1. 概念の解剖:なぜ「同じ図面を指すもの」が2つあるのか?
まず、大前提としてオブジェクト階層を思い出してほしい。
AutoCADの頂点には `AcadApplication`(アプリ本体)があり、その配下に複数の `AcadDocument`(開いている図面群)がぶら下がっている。
この構造において、`ThisDrawing` と `AcadApplication.ActiveDocument` は、指し示す「対象(ポインタ)」の性質が根本的に異なる。
`ThisDrawing` とは何か?(暗黙のコンテキスト)
- 定義: コードが記述されている「そのプロジェクト(DVBまたはDWG内のVBA)」が属するドキュメントを静的に指す。
- 特徴: コードの生存期間がその図面と運命を共にする。他の図面をアクティブにしようが、`ThisDrawing` が指す先は絶対にブレない。
`AcadApplication.ActiveDocument` とは何か?(動的なポインタ)
- 定義: ユーザーが「現在画面上でアクティブ(最前面で操作中)にしている」ドキュメントを動的に指す。
- 特徴: ユーザーがマウスをクリックして別の図面に切り替えた瞬間、指し示すターゲットが切り替わる。
—
2. 実務を崩壊させる「使い分けの罠」
初心者が陥る最大の罠は、標準モジュールの中で安易に `ThisDrawing` を乱用する、あるいはマルチドキュメント環境で `ActiveDocument` を信用し切ることだ。
罠のシナリオ:複数図面バッチ処理の悲劇
例えば、あなたが「開いているすべての図面に対してレイヤを一括作成するツール」を作ったとする。処理の途中でユーザーが別の図面をクリックしたり、非同期で他の図面にフォーカスが移ったりしたとき、`ActiveDocument` をベースに構築されたコードはどうなるか?
- 意図: 処理対象Aの図面をイジっているつもり。
- 現実: ユーザーが図面Bをクリックしたため、`ActiveDocument` が図面Bに切り替わり、図面Bに対して破壊的な変更が走り、図面Aは未処理で放置される。
逆に、VBAプロジェクトを特定の図面(テンプレート等)に埋め込んでおきながら、別図面を操作させようと `ThisDrawing` を固定で使ってしまい、「なぜか元の図面が書き換わってしまう」という事故も後を絶たない。
—
3. 堅牢な設計:モジュールの性質に応じた使い分けの鉄則
プロフェッショナルなAutoCAD VBA開発における設計指針はこうだ。
1. イベントプロシージャや、特定の図面と完全に結合した処理: `ThisDrawing` を使う。
2. 汎用的なユーティリティ関数(標準モジュール): グローバルな暗黙の参照(`ThisDrawing`)を避け、呼び出し元からドキュメントオブジェクトを明示的に引数として渡す(Dependency Injection思想)。
3. 複数図面をまたぐバッチ処理: `AcadApplication.Documents` コレクションをイテレートし、個々のドキュメントを明示的な変数に格納して制御する。
—
4. 【実戦的プロダクションコード】バグを駆逐する堅牢な一括処理実装
以下に、複数図面を開いた状態で、意図しない図面を操作する事故を完全に防ぐための堅牢なコードを示す。標準モジュールに配置し、明示的なドキュメント制御を行う実装例だ。
Option Explicit
‘ ==============================================================================
‘ モジュール名: modBatchProcesser
‘ 概要: 複数図面を安全に走査し、意図したドキュメントに対してのみ処理を実行する
‘ ==============================================================================
Public Sub ExecuteBatchLayerCheck()
Dim acadApp As AcadApplication
Dim targetDoc As AcadDocument
Dim docCount As Long
Dim i As Long
‘ 1. アプリケーションインスタンスの確実な取得
On Error Resume Next
Set acadApp = Classes.Application ‘ または GetObject(, “AutoCAD.Application”)
If acadApp Is Nothing Then
‘ すでにAutoCAD内で実行されている場合は ThisDrawing.Application も可
Set acadApp = ThisDrawing.Application
End If
On Error GoTo 0
If acadApp Is Nothing Then
MsgBox “AutoCADアプリケーションが取得できません。”, vbCritical, “致命的エラー”
Exit Sub
End If
docCount = acadApp.Documents.Count
If docCount = 0 Then
MsgBox “開かれている図面がありません。”, vbExclamation, “警告”
Exit Sub
End If
‘ 2. ユーザーへの確認(意図しない一括処理の暴走を防ぐガード)
If MsgBox(“現在開いている ” & docCount & ” 件すべての図面に対して、標準レイヤの監査・追加を実行します。” & vbCrLf & “よろしいですか?”, _
vbYesNo + vbQuestion, “一括処理確認”) = vbNo Then
Exit Sub
End If
‘ 3. ActiveDocument に依存せず、Documents コレクションを明示的にインデックスで回す
‘ ※ For Each ドキュメントループ中に図面の開閉を行うとコレクションが崩れるため For 構文を使用
For i = 0 To docCount – 1
Set targetDoc = acadApp.Documents(i)
‘ 【重要】ここで ActiveDocument は一切使わず、targetDoc をスコープ内に完全に閉じ込める
Call ProcessSingleDocument(targetDoc)
Next i
MsgBox “すべての図面に対する処理が正常に完了しました。”, vbInformation, “完了”
End Sub
‘ ==============================================================================
‘ 関数名: ProcessSingleDocument
‘ 概要: 渡された特定のAcadDocumentに対してのみ処理を安全に実行する
‘ 引数: targetDoc (AcadDocument) – 操作対象のドキュメント(明示的指定)
‘ ==============================================================================
Private Sub ProcessSingleDocument(ByVal targetDoc As AcadDocument)
Dim targetLayer As AcadLayer
Const REQUIRED_LAYER_NAME As String = “PRJ_AUTO_CHECK”
‘ エラーハンドリングを局所化し、1つの図面のエラーが全体をクラッシュさせないようにする
On Error GoTo ErrorHandler
‘ 対象図面が読み取り専用、またはロックされていないかチェック
If targetDoc.ReadOnly Then
Debug.Print “スキップ (読み取り専用): ” & targetDoc.Name
Exit Sub
End If
‘ アクティブ化しなくても、背後でレイヤ操作オブジェクトモデルを叩くことは可能
‘ ※ビューポート等のUI操作を伴う場合は targetDoc.Activate が必要な場合もある
Set targetLayer = Nothing
On Error Resume Next
Set targetLayer = targetDoc.Layers.Item(REQUIRED_LAYER_NAME)
On Error GoTo ErrorHandler
If targetLayer Is Nothing Then
‘ レイヤが存在しない場合は新規作成
Set targetLayer = targetDoc.Layers.Add(REQUIRED_LAYER_NAME)
targetLayer.Color = acRed ‘ 赤色を指定
Debug.Print “レイヤ作成成功 [” & REQUIRED_LAYER_NAME & “] in ” & targetDoc.Name
Else
Debug.Print “レイヤ既存在 [” & REQUIRED_LAYER_NAME & “] in ” & targetDoc.Name
End If
‘ 必要に応じてここで図面の保存等を行う
‘ targetDoc.Save
Exit Sub
ErrorHandler:
‘ ログ出力またはイミディエイト窓への通知(実務ではファイルログ等へ拡張を推奨)
Debug.Print “【エラー発生】図面: ” & targetDoc.Name & ” / 内容: ” & Err.Description
Resume Next
End Sub
—
5. チーフアーキテクトからの提言
コードを見て気づいただろうか。
上記のコードでは、`ThisDrawing` や `ActiveDocument` といった曖昧なグローバル参照を極力排除し、`acadApp.Documents(i)` から取得した `targetDoc` というローカル変数にスコープを閉じ込めている。
これにより、ユーザーがマクロ実行中にマウスをガチャガチャ動かそうが、別の図面を開こうが、処理対象が勝手にすり替わることは物理的に不可能になる。
- 初心者: 動けばいいやと `ActiveDocument` や `ThisDrawing` をあちこちに散りばめ、マルチドキュメント環境で原因不明のバグに頭を抱える。
- プロフェッショナル: オブジェクトのスコープを完全に制御し、予測可能性の極めて高い堅牢なコードベースを構築する。
AutoCAD VBAを「おもちゃのマクロ」から「エンタープライズレベルの自動化ツール」へと昇華させるのは、こうした細部へのこだわりと、オブジェクトモデルの本質を見抜くアーキテクチャ設計にほかならない。
明日の開発から、ぜひこの設計思想を取り入れてほしい。
