【テクニカル・上級編】【実務中級】AcadDocument.SelectionSets.Add時の「既に存在します」エラーを回避する汎用関数化 – AutoCAD VBA解析バイブル

スポンサーリンク

AutoCAD VBAを掌握する極限の知見:`SelectionSets.Add` の呪縛を断つ「Try-Get-Create」パターン

AutoCAD VBAによる自動化開発の現場において、図面内のエンティティを動的に操作するための基盤となるのが `AcadSelectionSets` コレクションである。
しかし、実務でこのオブジェクトを扱い始めたエンジニアが、ほぼ確実に直面する最初の壁が 「既に存在します」という実行時エラー(エラー番号:-2147467259 / 自動化エラー) である。

今回は、このレガシーなCOMコンポーネント特有の悪癖を完全に無力化し、実務の過酷なバッチ処理や連続図面処理にも耐えうる、極限まで洗練された選択セットの生成・取得メカニズムを解説する。

1. なぜ「既に存在します」エラーが頻発するのか?(オブジェクトのライフサイクル)

AutoCADのCOMインターフェースにおいて、`SelectionSets` は図面ドキュメント(`AcadDocument`)のライフサイクルと強固に結びついている。

一度作成された選択セット(例: `”MyActiveSS”`)は、明示的にコード側で削除(`Delete` メソッドの実行)を行わない限り、図面データベースのメモリ空間(あるいはCOMラッパーの参照カウンタ)に残り続ける。
そのため、マクロの強制終了、デバッグ中のブレークポイントでの中断、あるいは不完全なエラーハンドリングによって `Delete` がスキップされた瞬間、次にそのマクロを走らせた時に必ずあの忌々しいエラーが爆発する。

‘ 【アンチパターン】これでは実務の嵐を生き残れない
Dim ss As AcadSelectionSet
Set ss = ThisDrawing.SelectionSets.Add(“MyActiveSS”) ‘ ← 前回の残骸があるとここで即死する

「じゃあ、作る前に毎回消せばいいじゃないか」と思うかもしれないが、存在しない名前を削除しようとすれば、今度は「存在しません」という別のエラーが返ってくる。これがVBA開発者を無駄に疲弊させる原因だ。

2. 解決策:一撃で安全を担保する「Try-Get-Create」汎用関数

この問題に対するアーキテクチャ上の正解は、「存在チェックを行い、あれば取得・再利用(または安全に破棄して再作成)、なければ新規作成する」というカプセル化されたユーティリティ関数を用意することだ。

以下に、実務の現場で即座に採用できる最高峰の汎用関数を示す。

‘ =================================================================================聯
‘ 模块名: ModSelectionSetManager
‘ 概要 : AutoCAD選択セットの安全な取得・生成を行う極限のユーティリティ
‘ =================================================================================

Option Explicit


‘ 指定された名前の選択セットを安全に取得または新規作成する。
‘ 既に同名のセットが存在する場合は、既存のものを破棄してクリーンな状態で返す。

‘ @param targetDoc 対象となる AcadDocument
‘ @param ssName 取得・作成する選択セットの名前
‘ @return 初期化済みの AcadSelectionSet オブジェクト

Public Function GetOrCreateSelectionSet(ByRef targetDoc As AcadDocument, ByVal ssName As String) As AcadSelectionSet
Dim targetSS As AcadSelectionSet
Dim existingSS As AcadSelectionSet
Dim isFound As Boolean

isFound = False

‘ 1. コレクション走査による存在確認(エラーハンドリングに依存しない堅牢なアプローチ)
‘ ※ 厳密にはエラー監視型よりもメモリ・パフォーマンス上有利
For Each existingSS In targetDoc.SelectionSets
If StrComp(existingSS.Name, ssName, vbTextCompare) = 0 Then
Set targetSS = existingSS
isFound = True
Exit For
End If
Next existingSS

‘ 2. 既存が存在する場合は、安全のために一度削除してパージする
‘ (中身のエンティティ参照だけをクリアしたい場合は Clear メソッドを使用するが、
‘ 構造的なゴミを残さないためには一度 Delete するのが最も確実)
If isFound Then
On Error Resume Next
targetSS.Delete
On Error GoTo 0
End If

‘ 3. クリーンな状態から新規作成
Set targetSS = targetDoc.SelectionSets.Add(ssName)

Set GetOrCreateSelectionSet = targetSS

‘ オブジェクト参照のクリーンアップ(VBAのCOM参照リークを防ぐ鉄則)
Set targetSS = Nothing
Set existingSS = Nothing
End Function

3. チーフアーキテクトが解説するコードの急所とメモリ最適化

上記のコードは、単にエラーを回避しているだけではない。シニアエンジニアが意識すべき「COMコンポーネントのメモリ管理とパフォーマンス」の知見が組み込まれている。

① `For Each` による安全な走査

`targetDoc.SelectionSets(ssName)` のように直接インデックスアクセスを試みると、存在しない場合にCOMエラーが発生し、`On Error Resume Next` の乱用を招く。
コレクションを `For Each` でイテレートし、`StrComp`(大文字小文字を区別しない比較)で判定する手法は、エラーハンドリングのコンテキストスイッチが発生しないため、実行速度とコードの可読性の両面において優れている。

② 明示的なオブジェクト解放(`Nothing`代入)の哲学

VBAの裏側では、COMオブジェクト(AutoCADのC++コア)とVBAのポインタがリンクしている。
ローカル変数として宣言した `targetSS` や `existingSS` に参照を保持させたまま関数を抜けると、参照カウンタの解放タイミングがVBAのガベージコレクション任せになり、長大なループ処理においてメモリリークやAutoCAD自体の不安定化(最悪の場合はクラッシュ)を誘発する。
関数の最後で `Set targetSS = Nothing` を明示的に実行し、COM参照を即座に切り離すことが、プロフェッショナルなVBAコードの絶対条件である。

4. 実戦での活用例:バッチ処理におけるイディオム

この汎用関数を共通モジュール(例: `M_Utils`)に配置すれば、実際の図面処理コードは驚くほどシンプルかつ堅牢になる。

Sub ProcessDrawingEntities()
Dim ss As AcadSelectionSet

‘ どんなにマクロが途中で落ちていようとも、この1行で100%安全に選択セットが手に入る
Set ss = GetOrCreateSelectionSet(ThisDrawing, “Batch_Target_SS”)
ウト
‘ 画面上のレイヤ「0」以外のオブジェクトを一括選択する例
Dim filterType(0) As Integer
Dim filterData(0) As Variant
filterType(0) = 8 ‘ DXFグループコード:レイヤ名
filterData(0) = “~0” ‘ 「0」レイヤ以外

ss.Select acSelectionSetAll, , , filterType, filterData

MsgBox “選択されたエンティティ数: ” & ss.Count

‘ 処理終了後の後始末(次の処理への布石)
ss.Delete
Set ss = Nothing
End Sub

総括

AutoCAD VBA開発における最大の敵は、APIの仕様の古さでも、ドキュメントの少なさでもなく、「予期せぬ状態の持ち越し」である。

今回紹介した「Try-Get-Create」パターン、そしてCOMオブジェクトのライフサイクルをコントロールする厳格な参照管理は、単なる「エラー回避テクニック」の枠を超え、あなたの作るすべてのCAD自動化ソリューションの信頼性を担保する最強の基盤アーキテクチャとなるはずだ。

レガシーな技術であっても、エンジニアの知見と設計思想によって、モダンで堅牢なシステムへと昇華させることができる——それを証明するためのコードを、ぜひ今日の開発現場から役立ててほしい。

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