AutoCAD VBAを掌握する極限の知見:クロスヘア動的制御によるCAD UI拡張の極意
AutoCAD VBAの開発現場において、私たちは常に「標準機能の壁」と対峙している。
オブジェクトモデルは洗練されているように見えて、その実、COMラッパーの背後にはC++で書かれた巨大なネイティブコアが鎮座している。この境界線を正確に理解せずして、真に実用的なUI拡張ツールを作ることはできない。
今回は、広域図面(メガ座標系を持つ土木図面やプラント設計図など)における「位置合わせ」の地獄を解決する、極めて実践的なテーマを取り上げる。
`AcadApplication.Preferences.Display.CursorSize` を動的に操作し、特定の配置・移動コマンド実行時のみクロスヘアカーソルを画面いっぱいの「100%」に拡張、完了時(あるいは異常終了時)に元のサイズへと確実に復元するUI補助メカニズムの全貌を解説する。
—
1. 核心課題:なぜ「単純なプロパティ書換」では実用に耐えないのか?
`CursorSize` プロパティ自体は、以下のように極めてシンプルに見える。
‘ カーソルを100%にする
ThisDrawing.Application.Preferences.Display.CursorSize = 100
しかし、シニアエンジニアであればこのコードを見た瞬間にいくつかの致命的な懸念を抱くはずだ。
1. 状態のロスト(ゾンビ設定):マクロの実行中にエラーが発生したり、ユーザーが `Esc` キーで中断したりした場合、カーソルが100%のまま放置される。
2. レジストリ・プロファイルへの負荷:Preferencesオブジェクト経由の変更は、内部でレジストリや現在のプロファイルに書き込みを行っている場合がある。高頻度な書き込みはパフォーマンス低下を招く。
3. トランザクションと画面描画の非同期:VBAの実行スレッドとAutoCADのメインUIスレッドの同期ズレにより、カーソルサイズ変更が画面描画に即座に反映されない現象(レイテンシ)が発生する。
これらを完全に制御し、プロダクション環境に耐えうる堅牢なツールに仕立て上げるには、「RAII(Resource Acquisition Is Initialization)」の概念をVBAに持ち込む設計が必要となる。
—
2. アーキテクチャ設計:安全な状態管理と例外フック
今回のツールの要件は以下の通りだ。
- ユーザー定義のコマンド(例:`AlignWithFullCursor`)実行時に、現在のCursorSizeを退避する。
- カーソルサイズを100に変更する。
- ターゲットとなる図形選択・配置処理を実行する。
- いかなる場合(正常終了、エラー終了、ユーザーキャンセル)でも、確実に元のCursorSizeに復元する。
これをVBAのクラスモジュールと標準モジュールを組み合わせて実装する。
実装コード:堅牢なカーソル制御クラス (`CursorManager.cls`)
まずは、オブジェクトのライフサイクル(Class_Initialize と Class_Terminate)を利用して、確実に状態を復元するクラスを作成する。
‘ =================================================================メイジ
‘ クラスモジュール名: CursorManager
‘ 概要: カーソルサイズの一時変更と確実な復元を保証するRAIIパターン実装
‘ =================================================================
Option Explicit
Private m_App As AcadApplication
Private m_OriginalSize As Integer
Private m_IsInitialized As Boolean
Private Sub Class_Initialize()
On Error GoTo ErrorHandler
‘ Application オブジェクトの取得と参照の保持
Set m_App = ThisDrawing.Application
‘ 現在のカーソルサイズを退避
m_OriginalSize = m_App.Preferences.Display.CursorSize
‘ 100%に拡張
m_App.Preferences.Display.CursorSize = 100
m_IsInitialized = True
Exit Sub
ErrorHandler:
m_IsInitialized = False
MsgBox “CursorManager 初期化エラー: ” & Err.Description, vbCritical
End Sub
Private Sub Class_Terminate()
‘ 確実に元のサイズに復元する(デストラクタの強制実行)
If m_IsInitialized Then
On Error Resume Next
m_App.Preferences.Display.CursorSize = m_OriginalSize
On Error GoTo 0
End If
‘ メモリリーク防護:COMオブジェクトの明示的解放
Set m_App = Nothing
‘ Debug.Print “CursorManager Destructor: Restored to ” & m_OriginalSize
End Sub
実装コード:広域図面支援メインプロシージャ (`AlignTools.bas`)
次に、標準モジュールから上記のクラスをインスタンス化し、実際の配置・移動処理を挟み込む。エラーハンドリングブロック(`On Error GoTo`)の外側でクラスのスコープを抜けるため、VBAの仕様上、プロシージャを抜けた瞬間に `Class_Terminate` が走り、カーソルが復元される仕組みだ。
‘ =================================================================
‘ 標準モジュール名: AlignTools
‘ 概要: 広域図面での位置合わせ支援エントリポイント
‘ =================================================================
Option Explicit
Public Sub ExecuteAlignedPlacement()
‘ 1. CursorManagerのインスタンス生成(この瞬間にカーソルが100%になる)
Dim cursorMgr As New CursorManager
‘ 2. エラーハンドリングの有効化
On Error GoTo ErrorHandler
Dim sSet As AcadSelectionSet
Set sSet = CreateSelectionSet(“TempAlignSet”)
MsgBox “クロスヘアが100%に拡張されました。” & vbCrLf & _
“遠くの基準点との位置合わせを行ってください。”, vbInformation, “位置合わせ支援”
‘ — ここに実際の図形選択・移動・コピー処理を記述 —
‘ 例: ユーザーに対象図形を選ばせ、基点を指定して移動させる実務ロジック
Dim basePoint As Variant
Dim secondPoint As Variant
‘ 例としてオブジェクト選択プロンプトを投げる(簡略化のため選択のみ)
sSet.SelectOnScreen
If sSet.Count > 0 Then
basePoint = ThisDrawing.Utility.GetPoint(, vbCrLf & “基点を指定: “)
secondPoint = ThisDrawing.Utility.GetPoint(basePoint, vbCrLf & “目的点を指定: “)
Dim i As Long
For i = 0 To sSet.Count – 1
sSet.Item(i).Move basePoint, secondPoint
Next i
End If
CleanUp:
‘ 選択セットのクリーンアップ(メモリ最適化の基本)
Call DeleteSelectionSet(“TempAlignSet”)
‘ cursorMgr はここでスコープを抜けるため、自動的に Class_Terminate が呼ばれカーソルが復元される
Exit Sub
ErrorHandler:
If Err.Number <> -2147352567 Then ‘ ユーザーによるEscキャンセル(E_FAIL等)を除外
MsgBox “エラー番号: ” & Err.Number & vbCrLf & “内容: ” & Err.Description, vbExclamation
End If
Resume CleanUp
End Sub
‘ —————————————————————–
‘ ヘルパー関数: 選択セットの安全な生成
‘ —————————————————————–
Private Function CreateSelectionSet(ByVal setName As String) As AcadSelectionSet
On Error Resume Next
Set CreateSelectionSet = ThisDrawing.SelectionSets.Item(setName)
If CreateSelectionSet Is Nothing Then
Set CreateSelectionSet = ThisDrawing.SelectionSets.Add(setName)
Else
CreateSelectionSet.Clear
End If
On Error GoTo 0
End Function
‘ —————————————————————–
‘ ヘルパー関数: 選択セットの安全な破棄
‘ —————————————————————–
Private Sub DeleteSelectionSet(ByVal setName As String)
On Error Resume Next
Dim sSet As AcadSelectionSet
Set sSet = ThisDrawing.SelectionSets.Item(setName)
If Not sSet Is Nothing Then
sSet.Delete
End If
On Error GoTo 0
End Sub
—
3. シニアエンジニアが押さえるべき「メモリ最適化」と「COMの寿命管理」
AutoCAD VBA開発において最も恐ろしいのは、「見えないメモリリーク」と「COMポインタの解放漏れ」である。特に `AcadSelectionSet` や `AcadApplication` のようなクロスプロセス/インプロセスCOMオブジェクトは、明示的に参照を断ち切らないと、AutoCADのプロセス終了時にメモリが解放されず、ゴーストプロセスが残る原因となる。
今回のコードでは以下の配慮を徹底している:
1. クラスによるスコープカプセル化:
プロシージャ内で完結するリソース管理をクラス(`CursorManager`)に委譲することで、エラー発生時の巻き戻し(Unwind)を確実なものにしている。VBAはC++のような本格的な例外機構(try-catch-finally)を持たないため、「オブジェクトのスコープアウトによるデストラクタ発火」はVBAにおける最強のfinallyブロック代用手法である。
2. 選択セットの即時破棄:
AutoCADの図面データベース内に作成されたSelectionSetは、明示的に `.Delete` しない限りドキュメントが開いている間メモリ上に残り続ける。これを毎回確実に掃除するルーチンを挟むことで、何千回とコマンドを叩くヘビーユーザーの環境でもメモリフットプリントを一定に保つ。
—
4. レガシー環境・マルチバージョン運用への備え
AutoCADはバージョン(2015から最新の2025以降まで)によって、リボンUIや内部のCOMインターフェースの挙動に微妙な差異が存在する。
特に `Preferences.Display` 系のプロパティは、マルチドキュメント環境(SDI/MDI)において、どのドキュメントのアクティブセッションから書き換えるかによって挙動が揺らぐことがある。
- `ThisDrawing.Application` の明示的参照:
グローバルな `Application` オブジェクトを直接叩くのではなく、必ず `ThisDrawing.Application` を経由することで、現在フォーカス当たりの正しいインスタンスコンテキストを維持する。
- 画面の強制再描画(必要に応じた Redraw):
カーソルサイズ変更直後に画面の即時反映が必要な場合は、`ThisDrawing.Regen acAllViewports` を挟むことも検討価値があるが、パフォーマンスコストを考慮し、ユーザーのマウス移動(イベント)による自然な描画更新に委ねる設計としている。
—
総括
たかが「カーソルサイズを100%にする」という単純なUI補助機能であっても、製品レベルの品質に昇華させるためには、ライフサイクル管理、例外耐性、メモリ最適化の全知見を注ぎ込む必要がある。
泥臭いコードの積み重ねで動いている現場のシステムこそ、こうしたアーキテクチャの導入によって「絶対に落ちない、信頼できるインフラ」へと生まれ変わる。
型にはまったリファレンスを脱却し、AutoCADのCOMアーキテクチャの奥底を掌握した者だけが到達できるコードを、あなたの現場でも実装してほしい。
