【AutoCAD VBA】AcadDocument.Blocks完全攻略:重複なしでブロック定義名を抽出する堅牢な実装設計
AutoCADの業務自動化において、図面内にどのような「ブロック定義(AcadBlock)」が存在するかを正確に把握することは、すべての自動処理の起点だ。
属性情報の集計、特定ブロックの存在チェック、レイアウトの自動化、あるいはBIM/CIMデータへの変換——どんな高度な処理を描くとしても、最初に立ちはだかるのが「図面内のブロック名を重複なく、漏れなく、かつ高速に抽出する」というタスクである。
しかし、市販の入門書にあるような数行のサンプルコードをそのまま実務で使うと、ほぼ確実に壁にぶつかる。なぜならAutoCADの内部構造において、「モデル空間」や「寸法線」すらもブロックの一種として扱われているからだ。
本稿では、AutoCAD VBAのオブジェクトモデルの本質を解き明かしながら、実務の現場で決して落ちない、堅牢で保守性の高いプロダクションコードを提供する。
—
1. なぜ安易なループ処理は実務で破綻するのか?
まず、AutoCADにおける`AcadDocument.Blocks`コレクションの正体を正しく理解しよう。
多くのビギナーは「`Blocks`コレクション=ユーザーが作成した図形ブロックのリスト」だと勘違いしている。だが、これは大きな間違いだ。AutoCADのデータベース構造において、`Blocks`には以下の要素がすべて同列に格納されている。
1. ユーザー定義ブロック(例: `デスク_1400`, `バルブ_DN50`)
2. モデル空間・ペーパー空間自体(`Model_Space`, `Paper_Space`)
3. 匿名ブロック(`A1`, `D2` など、寸法線、ハッチング、ダイナミックブロックの参照状態)
4. 外部参照(Xref)およびアタッチされたオーバーレイ
何も考えずに`For Each objBlock In ThisDrawing.Blocks`を回せば、システム内部で使用されているアスタリスク付きの特殊な名前(“で始まる名前)まで大量に取得され、実業務の集計データとしてはノイズだらけになってしまう。
プロのエンジニアが設計すべきは、「特殊なシステムブロックを適切にフィルタリングし、必要な純粋定義名だけを重複なく高速に抽出するロジック」である。
—
2. アーキテクチャ設計:高速化と重複排除の鍵「Dictionary」
VBAで重複を排除したリストを作成する際、配列を使って `InStr` やネストしたループで重複チェックを行うコードを時折見かけるが、絶対にやめてほしい。要素数が増えた際に計算量が $O(N^2)$ で増大し、巨大な図面で著しくパフォーマンスが低下するからだ。
ここでは、ハッシュテーブル構造を持つ `Scripting.Dictionary` を採用する。
Dictionaryのキー(Key)にブロック名を設定すれば、探索コストは $O(1)$ となり、登録処理自体が自動的に重複を排除してくれる。
フィルタリングの要件定義
実務で「有効なブロック名リスト」を作成する場合、以下の条件でフィルタをかけるのが鉄則だ。
- アスタリスク(“)から始まる名前をスキップする
- `Model_Space`, `Paper_Space` などの空間定義を除外。
- `A…` などの匿名ブロック(寸法、ハッチング等)を除外。
- 外部参照(Xref)の扱いを明示的に制御する
- `IsXRef` プロパティを評価し、図面内部のブロック定義のみに絞り込む(用途に応じてフラグで切り替え可能にする)。
—
3. プロダクションコード:ブロック名一覧を取得する極限のVBA
以下に、そのまま実務ツールへ組み込み可能なプロダクションコードを示す。環境依存を減らすため、`Scripting.Dictionary` はレイトバインディング(`CreateObject`)で呼び出している。
Option Explicit
”
‘ 図面内の全ブロック定義名を取得し、重複なくイミディエイトウィンドウに出力する
‘
Public Sub ListUpUniqueBlockNames()
Dim blockNames As Variant
Dim i As Long
‘ 時間計測開始(パフォーマンス計測の癖をつけよう)
Dim startTime As Double
startTime = Timer
‘ ブロック名取得ロジックの呼び出し
blockNames = GetUniqueBlockNames(ThisDrawing, IncludeXrefs:=False)
‘ 結果の出力
Debug.Print “========================================”
Debug.Print ” 抽出完了: ” & (UBound(blockNames) – LBound(blockNames) + 1) & ” 件”
Debug.Print ” 処理時間: ” & Format(Timer – startTime, “0.000”) & ” 秒”
Debug.Print “========================================”
For i = LBound(blockNames) To UBound(blockNames)
Debug.Print “[” & i + 1 & “] ” & blockNames(i)
Next i
End Sub
”
‘ 指定された AcadDocument から有効なブロック定義名を重複なく配列として返す関数
‘
‘ @param doc 対象の AcadDocument オブジェクト
‘ @param IncludeXrefs 外部参照ブロックを含めるかどうか (デフォルト: False)
‘ @return 抽出されたブロック名の文字列配列(該当なしの場合は空配列)
‘
Public Function GetUniqueBlockNames(ByRef doc As AcadDocument, Optional ByVal IncludeXrefs As Boolean = False) As Variant
On Error GoTo ErrorHandler
‘ ハッシュテーブル(Dictionary)の初期化
Dim dict As Object
Set dict = CreateObject(“Scripting.Dictionary”)
dict.CompareMode = 1 ‘ vbTextCompare: 大文字・小文字を区別しない(AutoCADの仕様に合わせる)
Dim blk As AcadBlock
‘ Blocksコレクションの走査
For Each blk In doc.Blocks
‘ ——————————————————————-
‘ 1. システム内部ブロック(Model_Space, A123 等)を除外
‘ ——————————————————————-
If Left$(blk.Name, 1) <> “” Then
‘ ——————————————————————-
‘ 2. 外部参照(Xref)のフィルタリング
‘ ——————————————————————-
Dim isTarget As Boolean
isTarget = True
If Not IncludeXrefs Then
‘ XRefブロックである場合は除外
If blk.IsXRef Then
isTarget = False
End If
End If
‘ ——————————————————————-
‘ 3. 辞書へ登録(Keyの重複は自動的に無視/上書き)
‘ ——————————————————————-
If isTarget Then
If Not dict.Exists(blk.Name) Then
dict.Add blk.Name, True
End If
End If
End If
Next blk
‘ 結果を一次元配列に変換して返す
If dict.Count > 0 Then
GetUniqueBlockNames = dict.Keys
Else
GetUniqueBlockNames = Array()
End If
‘ メモリ解放
Set dict = Nothing
Exit Function
ErrorHandler:
‘ 堅牢なエラーハンドリング
MsgBox “ブロック名の抽出中にエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“内容: ” & Err.Description, vbCritical, “システムエラー”
GetUniqueBlockNames = Array()
Set dict = Nothing
End Function
—
4. プロのチーフアーキテクトが教える「運用の落とし穴」
このコードで基礎は完璧だが、実際の開発現場で遭遇する「一歩先」の技術的知見を共有しておく。これを知っているかどうかが、プロとアマチュアの分かれ目だ。
① ダイナミックブロックの「定義名」と「参照名」の違い
ダイナミックブロックが図面内でグリップ操作によって変更されると、AutoCADは内部的に `U…` という匿名ブロック参照を生成する。
本記事のコード(`AcadBlock.Name`を走査する方法)は「図面に登録されている元のブロック定義(親)」をリストアップするため、この問題の影響を受けず、正しく元のブロック名(例: `可変テーブル`)を取得できる。
もし「図面上に配置されている個々のブロック参照(`AcadBlockReference`)」から名前を拾うコードを書く場合は、`blkRef.Name` ではなく `blkRef.EffectiveName` を参照しなければならない。これを混同すると、集計ツールは即座にバグを引き起こす。
② 大文字・小文字の区別(CompareMode)
AutoCADの内部データベースにおいて、ブロック名は大文字と小文字を区別しない(`DOOR` と `door` は同一のブロック定義として扱われる)。
そのため、Dictionaryを作成する際に `dict.CompareMode = 1` (`vbTextCompare`) を設定しておくことが極めて重要だ。この設定を怠ると、外部連携(CSVやExcel、外部DB)時に微妙な表記揺れによってデータが重複するリスクが生じる。
③ データベース連携時のトランザクション設計
抽出したブロック名をExcelに出力したり、SQL Server / PostgreSQL などのデータベースへ流し込む場合、ループ内で都度I/O(ファイル書き込みやクエリ発行)を行うのは最悪の設計だ。
必ず今回のように 「VBA内部で完結した配列を作り、メモリ上で処理を終えてから一括書き込み(Bulk Insert / Range一括代入)」 を行うこと。これにより、ミリ秒単位でツールが高速化する。
—
5. まとめ
AutoCAD VBAにおける `AcadDocument.Blocks` コレクションの操作は、一見シンプルに見えてAutoCADのデータベース思想が凝縮された深い領域である。
- 単純なループではなく、“(アスタリスク)によるシステムブロックの除外を行う。
- `Scripting.Dictionary` を活用し、計算量 $O(1)$ で重複排除と高速化を両立する。
- 将来的な拡張性(Xrefの扱い、Excel/DB連携)を見越した関数(Function)分割設計を行う。
この3原則を徹底するだけで、君が書くVBAコードの品質と堅牢性は、現場の他の開発者を圧倒するレベルに到達する。まずは提供したコードをイミディエイトウィンドウで走らせ、その速度と正確性を体感してほしい。業務自動化の真価は、こうした美しい基礎設計の上にしか実らないのだ。
