AutoCAD VBAを掌握する極限の知見:PickFirst制御による「プロの挙動」の実現
AutoCAD VBAによる開発において、多くのエンジニアが最初に直面し、そして見落としがちな罠がある。それが 「事前選択(PickFirst)オブジェクト」 のハンドリングだ。
標準コマンド(MOVEやERASEなど)を思い出してほしい。ユーザーが先に対象を選択してからコマンドを実行すれば、そのまま処理が走る。逆に、何も選択せずにコマンドを実行すれば、プロンプトが立ち上がり「オブジェクトを選択」を促す。
しかし、素朴に書いたVBAマクロはどうだろうか?
ユーザーが図形を選択した状態でマクロを実行した途端、選択状態が勝手に解除されたり、予期せぬエラーを吐いたり、あるいは意図しない図形を巻き込んで暴走したりする。これでは「素人が作ったオモチャ」と言われても仕方がない。プロフェッショナルなツールと呼ぶには、AutoCADのコマンドライフサイクルと完全に同期した選択セットの制御が不可欠だ。
今回は、`AcadApplication.Preferences.Selection.PickFirst` を軸に、事前選択を完璧に支配し、実務の現場で絶対に破綻しない堅牢なVBAアーキテクチャを伝授する。
—
1. なぜ「PickFirst」の制御がプロの必須要件なのか?
AutoCADの内部アーキテクチャにおいて、画面上でハイライトされている選択セット(PickFirstセット)と、VBAからコードで生成する選択セットは、本来別物である。
VBAの実行開始時、デフォルトの状態のままだと以下のような問題が発生する:
1. コンテキストの喪失: ユーザーが事前に選んだ文脈が無視される。
2. 思わぬ二度手間: 「選択してからボタンを押したのに、また選択させられる」という最悪のUX。
3. 未定義動作: 実行前の選択状態がメモリ上に残ったままコードが走ることで、後続の `SelectionSet.Select` メソッド等と衝突し、エラー `-2145386446 (複数回選択エラー等)` を誘発する。
これを解決するのが、アプリケーションレベルのプリファレンス、`AcadApplication.Preferences.Selection.PickFirst` の動的制御である。さらに、現在の図面空間にある「すでに選択されているエンティティ」をスマートにVBAへ取り込むテクニックを組み合わせることで、標準コマンドと全く同じ「手に馴染む挙動」を実現できる。
—
2. 堅牢な設計アプローチ:実装の黄金律
実務で使える堅牢なコードを書くための設計方針は以下の3点だ。
1. 状態の退避と復元: マクロ開始時の環境設定(PickFirstの値や、現在の選択状態)を必ず退避させ、処理終了時(エラー発生時を含む)に確実に復元する。
2. 選択セットのクリーンアップ: 処理開始時に既存の選択セット名を安全に削除・解放する。
3. デュアルモードのサポート:
- パターンA:事前選択があれば、それを対象に処理する。
- パターンB:事前選択がなければ、ユーザーに選択を促す(あるいは処理をスキップする)。
—
3. 【プロダクションコード】実務で使えるPickFirst制御モジュール
以下のコードは、実務の現場でそのままコピー&ペーストして活用できる、極めて堅牢な標準モジュールの実装例である。特定のレイヤ変更や一括処理を行うツールを想定している。
Option Explicit
‘ ==============================================================================
‘ モジュール名: modPickFirstController
‘ 概要: PickFirstを制御し、事前選択オブジェクトを安全に処理するテンプレート
‘ ==============================================================================
Public Sub ExecuteSmartSelectionProcess()
Dim acadApp As AcadApplication
Dim acadDoc As AcadDocument
Dim originalPickFirst As Integer
Dim ssName As String
Dim ss As AcadSelectionSet
‘ 1. アプリケーションとドキュメントの参照取得
Set acadApp = ThisDrawing.Application
Set acadDoc = ThisDrawing.ActiveDocument
‘ エラーハンドリングの有効化(異常終了時の環境復元を担保)
On Error GoTo ErrorHandler
‘ 2. 現在のPickFirst設定を退避し、VBAからの制御用に有効化(1)に設定
originalPickFirst = acadApp.Preferences.Selection.PickFirst
acadApp.Preferences.Selection.PickFirst = 1 ‘ 1 = 有効, 0 = 無効
‘ 3. 選択セット名の定義と一意なクリーンアップ
ssName = “ProVBA_ActiveSS_” & Format(Now, “hhmmss”)
Set ss = SafeCreateSelectionSet(acadDoc, ssName)
‘ 4. 事前選択(PickFirst)されているオブジェクトを取得
‘ 引数に acadSelectionSetPrevious を指定するのが極意
ss.Select acSelectionSetPrevious
If ss.Count > 0 = True Then
‘ —————————————————-
‘ 【ケースA】事前にオブジェクトが選択されていた場合
‘ —————————————————-
MsgBox “事前選択された ” & ss.Count & ” 個のオブジェクトを処理します。”, vbInformation, “スマート制御”
Call ProcessEntities(ss)
Else
‘ —————————————————-
‘ 【ケースB】何も選択されていなかった場合(対話型選択へフォールバック)
‘ —————————————————-
ss.SelectOnScreen ‘ ユーザーに対象選択を促す
If ss.Count > 0 Then
MsgBox “画面から選択された ” & ss.Count & ” 個のオブジェクトを処理します。”, vbInformation, “スマート制御”
Call ProcessEntities(ss)
Else
MsgBox “オブジェクトが選択されませんでした。処理を中断します。”, vbExclamation, “中断”
End If
End If
CleanUp:
‘ 5. 環境の復元とリソースの解放
acadApp.Preferences.Selection.PickFirst = originalPickFirst
Call SafeDeleteSelectionSet(acadDoc, ssName)
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
Resume CleanUp
End Sub
‘ ==============================================================================
‘ 補助関数: 既存の同名選択セットをパージして安全に新規作成する
‘ ==============================================================================
Private Function SafeCreateSelectionSet(doc As AcadDocument, ssName As String) As AcadSelectionSet
Dim targetSS As AcadSelectionSet
‘ 既に存在する場合は削除
On Error Resume Next
Set targetSS = doc.SelectionSets(ssName)
If Not targetSS Is Nothing Then
doc.SelectionSets.Item(ssName).Delete
End If
On Error GoTo 0
‘ 新規作成
Set SafeCreateSelectionSet = doc.SelectionSets.Add(ssName)
End Function
‘ ==============================================================================
‘ 補助関数: 選択セットの安全な削除
‘ ==============================================================================
Private Sub SafeDeleteSelectionSet(doc As AcadDocument, ssName As String)
Dim targetSS As AcadSelectionSet
On Error Resume Next
Set targetSS = doc.SelectionSets(ssName)
If Not targetSS Is Nothing Then
doc.SelectionSets.Item(ssName).Delete
End If
On Error GoTo 0
End Sub
‘ ==============================================================================
‘ 実処理をシミュレートするプロシージャ
‘ ==============================================================================
Private Sub ProcessEntities(targetSS As AcadSelectionSet)
Dim ent As AcadEntity
‘ 選択セット内の全エンティティをループ処理
For Each ent in targetSS
‘ 例として、選択された図形の画層名をイミディエイトウインドウに出力
Debug.Print “処理中オブジェクト: ” & ent.EntityName & ” / 画層: ” & ent.Layer
‘ — ここに実際の業務ロジック(プロパティ変更、座標計算など)を記述 —
‘ ent.Color = acRed ‘ 例: 赤色に変更する等
Next ent
End Sub
—
4. チーフアーキテクトが教える「現場の知見」と注意点
上記のコードをプロダクション環境に投入する際、エンジニアが知るべき実践的な注意点を挙げる。
① `acSelectionSetPrevious` の魔力
コード内で使用している `ss.Select acSelectionSetPrevious` は、AutoCADの内部バッファから「直前に選択されていた(または現在選択されている)オブジェクト群」をごっそり引き抜くための最強の定数だ。これを使うことで、VBA実行直前のユーザーの選択状態をノータイムでプログラムに取り込むことができる。
② メモリリーク(COMオブジェクトの蓄積)を防ぐ
AutoCAD VBAにおいて、`SelectionSets.Add` で作成したオブジェクトは、明示的に `.Delete` しない限りドキュメントのライフサイクル内に残り続ける。次回同名の選択セットを作ろうとした際に「すでに存在します」という致命的なエラーを踏む原因になる。前述のコードにあるような `SafeDeleteSelectionSet` のラップ処理を必ず実装せよ。
③ トランザクションとパフォーマンス
数千・数万個単位の図形を `For Each ent In targetSS` で回す場合、個別のプロパティアクセスはパフォーマンスを低下させる原因となる。もし本格的な高速化が必要な場合は、SelectionSet処理と合わせて、必要に応じてレイヤーやブロック定義の一括操作、あるいはObjectARX/.NET APIへの移行を視野に入れるべきだが、日常的な図面整理・属性書換マクロであれば、上記のVBA設計で十分実用的な速度が出る。
—
総括
AutoCAD VBAの価値は、「にいちゃん、このボタン押すだけで自動で終わるのか、助かるよ」と現場のオペレーターに言わせる点にある。そのためには、単にコードが動くだけではなく、CADの挙動と完全に調和したユーザー体験(UX)を提供しなければならない。
`PickFirst` の制御をマスターしたあなたなら、もう「選択が外れてイライラするマクロ」を作ることはないはずだ。プロフェッショナルな設計思想を武器に、最高峰の自動化ツールを構築してほしい。
