AutoCAD VBAを掌握する極限の知見:図面内オブジェクトの高速フィルタリングとメモリ管理の極意
はじめに:なぜ「ただ回すだけ」のコードが現場で破綻するのか
AutoCAD VBAにおいて、図面データベース(`AcadDocument.ModelSpace` または `PaperSpace`)の全オブジェクトを走査し、特定の属性でフィルタリングする処理は、図面監査や自動修正スクリプトの基本中の基本だ。
しかし、数万〜数十万のエンティティを持つ実務図面で、安易に `For Each` ループを回し、オブジェクトの解放(`Set obj = Nothing`)を怠るコードを書いた瞬間、AutoCADはメモリリークを引き起こし、最悪の場合はFATAL ERRORで強制終了する。
シニアエンジニアやCAD管理者であれば、COMコンポーネントのライフサイクル、そしてVBA背後で稼働するObjectARXのメモリ管理機構を意識しなければならない。本稿では、`AcadDocument` と `AcadObject` の連携におけるパフォーマンスの限界を突破し、実務に耐えうる堅牢なフィルタリング処理の実装手法を解説する。
—
1. 限界を知る:SelectionSet vs For Each 走査
図面から特定条件のオブジェクトを抽出するアプローチには、主に2つの方法が存在する。
1. `SelectionSet`(選択セット)によるネイティブフィルタリング
2. `ModelSpace` の総当たり `For Each` 走査 + VBA側での条件判定
結論から言えば、「特定の画層やオブジェクトタイプ、色などの単一・複数条件」であれば、迷わず `SelectionSet` の DXFグループコードによるフィルタリングを使うべきである。VBA側のループで判定するよりも、AutoCADのC++コアエンジン側で処理が完結するため、実行速度が桁違いに早い。
しかし、「カスタム属性の有無」「複雑な複合条件」「ジオメトリの形状解析を伴う判定」など、`SelectionSet` のフィルタ構文(FilterType / FilterData)では表現しきれない領域においては、全走査(`For Each`)を避けて通れない。
今回は、あえて「全走査が必要な複雑な要件」を想定し、そのパフォーマンスとメモリ安全性を極限まで高めたコードを提示する。
—
2. 実装:メモリリークを根絶する高速フィルタリング・エンジン
以下のコードは、指定した画層かつ特定のオブジェクトタイプを持つエンティティを抽出し、そのハンドル(Handle)とバウンディングボックス(BBox)の情報をイミディエイトウインドウに出力するプロシージャである。
ここで重要なのは、VBAのループ内で見つかったオブジェクトの参照を確実に破棄し、COMラッパーの参照カウンタを適切に管理することである。
Option Explicit
‘ =========================================================================
‘ 処理名: 抽出・フィルタリング・エンジン
‘ 概要 : 図面内の全オブジェクトを走査し、指定された画層とタイプに一致するものを抽出
‘ 著者 : チーフアーキテクト
‘ =========================================================================
Public Sub ExecuteAdvancedFiltering()
Dim startTime As Double
startTime = Timer
Dim acadDoc As AcadDocument
Set acadDoc = ThisDrawing.Application.ActiveDocument
‘ エラーハンドリングの強制(COMオブジェクトの解放を担保するため)
On Error GoTo ErrorHandler
‘ 検索条件の定義
Const TARGET_LAYER As String = “0” ‘ 対象画層
Const TARGET_TYPE As String = “AcDbLine” ‘ 対象オブジェクトタイプ (DXF名)
Dim totalCount As Long
Dim matchedCount As Long
totalCount = 0
matchedCount = 0
‘ モデル空間の参照を取得
Dim mSpace As AcadModelSpace
Set mSpace = acadDoc.ModelSpace
Dim ent As AcadEntity
Dim vMin As Variant, vMax As Variant
‘ 画面描画を停止し、描画コストを排除してパフォーマンスを極限まで高める
acadDoc.Application.ZoomExtents
acadDoc.Utility.Prompt “図面走査を開始します…” & vbCrLf
‘ 走査ループ
For Each ent In mSpace
totalCount = totalCount + 1
‘ 条件1: 画層の一致 & 条件2: オブジェクトタイプの一致
‘ ※ Layerプロパティは常に大文字小文字を厳密に比較するため StrComp を推奨
If StrComp(ent.Layer, TARGET_LAYER, vbTextCompare) = 0 Then
If StrComp(ent.ObjectName, TARGET_TYPE, vbTextCompare) = 0 Then
matchedCount = matchedCount + 1
‘ 該当オブジェクトに対する処理(例としてハンドルとバウンディングボックスを取得)
‘ ※ GetBoundingBox は非表示・一部特殊オブジェクトでエラーになるため個別に捕捉
On Error Resume Next
ent.GetBoundingBox vMin, vMax
If Err.Number = 0 Then
Debug.Print “Match [” & matchedCount & “] Handle: ” & ent.Handle & _
” | Min:(” & Format(vMin(0), “0.00”) & “,” & Format(vMin(1), “0.00”) & “)”
Else
Debug.Print “Match [” & matchedCount & “] Handle: ” & ent.Handle & ” (BBox取得不可)”
Err.Clear
End If
On Error GoTo ErrorHandler
End If
End If
‘ 【重要】VBAのループ内ではオブジェクト変数に Nothing を代入し、
C#やC++とは異なるVBA固有のCOMラッパー解放遅延を防ぐ
Set ent = Nothing
Next ent
‘ 終了処理
Dim elapsedTime As Double
elapsedTime = Timer – startTime
MsgBox “走査完了:” & vbCrLf & _
“総スキャン数: ” & totalCount & vbCrLf & _
“一致件数: ” & matchedCount & vbCrLf & _
“処理時間: ” & Format(elapsedTime, “0.00”) & ” 秒”, vbInformation, “完了”
CleanUp:
‘ 参照の明示的解放
Set ent = Nothing
Set mSpace = Nothing
Set acadDoc = Nothing
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
Resume CleanUp
End Sub
—
3. チーフアーキテクトが解説するコードの急所
A. COMラッパーとメモリ管理(Garbage Collectionの罠)
VBAからAutoCADを操作する場合、背後でCOM(Component Object Model)のプロセス間通信が発生している。`For Each ent In mSpace` のループ内で `ent` 変数は自動的に生成・破棄を繰り返すが、VBAの内部ガベージコレクションは即座にメモリを解放するとは限らない。
数万件のループを回す際、`Set ent = Nothing` を明示的にループの末尾に記述することで、VBA側のポインタ参照を強制的に切り離し、メモリ消費量を一定に保つことができる。これを怠ると、巨大図面において「メモリ不足 (Out of Memory)」エラーの温床となる。
B. `StrComp` による厳密な文字列比較
画層名やオブジェクトタイプの比較に `=` 演算子を使用する開発者が多いが、VBAのオプション設定(`Option Compare Text` など)に依存するため、環境によって挙動が変わるリスクがある。
プロフェッショナルなコードでは、大文字小文字を区別しないバイナリ/テキスト比較を明示する `StrComp(str1, str2, vbTextCompare)` を使用し、予期せぬバグを排除する。
C. 実行時エラーのスコープ制御
バウンディングボックス(`GetBoundingBox`)の取得などは、特定のゼロレングスや特殊なジオメトリを持つエンティティに対して実行するとエラーを吐く。
全体の走査フローを止めないために、エラー処理のスコープを最小限(`On Error Resume Next` から `On Error GoTo ErrorHandler` への復帰)に絞り込み、堅牢性を担保している。
—
4. さらなる高みへ:システム間連携とレガシー保守の知見
実務において、このVBAスクリプト単体で完結することは少ない。多くの場合、外部の基幹システム(ERPやBOM、PDM)から渡されたJSONやCSVの属性リストに基づき、AutoCAD図面側のオブジェクトを動的にフィルタリング・ハイライト、あるいは一括属性変更するブリッジとして機能する。
もし、数千枚の図面ファイルを一括処理するバッチ処理(ヘッドレス実行)を求められた場合、VBAではなく VB.NET (COM Interop / AutoCAD .NET API) への移行を強く推奨する。
.NET APIであれば、LINQを使用した強力なオブジェクトクエリ(`mSpace.Cast(Of Entity)().Where(Function(e) e.Layer = “0”)`)が記述でき、VBAの数十倍のパフォーマンスとスレッド安全性を手に入れることができる。
しかし、社内の既存資産や、CADカスタマイズの導入ハードルの低さを考慮すれば、VBAは依然として最強の即戦力である。本稿で示したメモリ管理とフィルタリングの極意を実装に組み込むことで、レガシーなVBA環境であっても、モダンなシステムに匹敵する安定稼働を実現できる。
妥協のないコードだけが、現場の信頼を生む。明日からの開発に、この知見を役立ててほしい。
