【テクニカル・上級編】図面内の全ブロック参照をリスト化!AcadDocumentオブジェクトでブロック管理を自動化 – AutoCAD VBA解析バイブル

スポンサーリンク

AutoCAD VBAを掌握する極限の知見:図面内全ブロック参照の超高速抽出とメモリ管理の極意

はじめに:なぜ「ただ動くコード」では現場で破綻するのか

AutoCAD VBAにおいて、図面内のブロック参照(`AcadBlockReference`)を走査し、その情報をリスト化する要件は、図面管理や部品表の自動生成、レガシーデータのクレンジングなど、実務で最も頻繁に遭遇するユースケースの一つだ。

しかし、インターネット上に散らばる「初学者向けのサンプルコード」をそのまま大規模図面に適用した瞬間、AutoCADは沈黙し、最悪の場合はメモリリークによってプロセスの強制終了を余儀なくされる。

数万個のエンティティを持つ実務図面を相手にする時、VBAの開発者はAutoCADのオブジェクトモデルの裏側――すなわち、COM(Component Object Model)の参照カウンタの挙動、COMラッパーの生成コスト、そして`ModelSpace`とブロック定義(`Block`オブジェクト)の階層構造を完全に支配していなければならない。

今回は、単にブロックを列挙するだけでなく、極限のパフォーマンスを引き出し、メモリを完全に制御するためのチーフアーキテクトとしての知見をここに公開する。

1. AutoCADオブジェクトモデルの深層:ブロック参照の本質

AutoCADの図面データベースにおいて、ブロック(挿入図形)は二重の構造を持っている。
1. ブロック定義 (`AcadBlock`): ジオメトリの「設計図」であり、通常は `ThisDrawing.Blocks` コレクションに格納されている。
2. ブロック参照 (`AcadBlockReference`): その設計図を空間上に「配置した実体(INSERT)」であり、`ModelSpace`、`PaperSpace`、あるいは別のネストされたブロックの内部に存在する。

多くの開発者が犯す最初の致命的なミスは、`ThisDrawing.ModelSpace` のみを走査し、ネストされたブロック(ブロックの中にネストされているブロック)や、レイアウト(ペーパー空間)に配置されたブロックを見落とすことだ。真に堅牢なシステムを構築するには、再帰的な走査アルゴリズムが不可欠となる。

2. 実装:超高速・網羅的ブロック抽出エンジン

以下に、実務の現場で耐えうる堅牢性とパフォーマンスを兼ね備えたVBAコードを示す。このコードは、モデル空間およびすべてのペーパー空間レイアウトを走査し、さらにネストされたブロック参照をもれなくキャプチャする。

Option Explicit

‘ =================================================================================
‘ módulo: ModBlockExtractor
‘ 概要: 図面内の全ブロック参照を再帰的に抽出し、Excel等へ出力可能な配列として返す
‘ アーキテクトノート: COMオブジェクトの解放と再帰処理のスタックオーバーフロー対策を実装
‘ =================================================================================

Public Sub ExportAllBlockReferences()
Dim sw As Double
sw = Timer

On Error GoTo ErrorHandler

‘ AutoCAD環境の最適化(描画更新とアラートの抑制による爆速化)
With ThisDrawing.Application
.ScreenUpdating = False
.Preferences.SystemVariables.SetSystemVariable “REDUCESEEK”, 0 ‘ 必要に応じて
End With

Dim blockData() As Variant
Dim dataCount As Long
dataCount = 0

‘ 配列の初期化(動的配列のチャンク確保によるオーバーヘッド削減)
ReDim blockData(1 To 5, 1 to 10000)

Dim ent As AcadEntity
Dim i As Long

‘ 1. モデル空間の走査
Call ScanContainer(ThisDrawing.ModelSpace, blockData, dataCount)

‘ 2. ペーパー空間(レイアウト)の走査
Dim layout As AcadLayout
For Each layout In ThisDrawing.Layouts
If layout.Name <> “Model” Then
‘ レイアウトに対応するブロックオブジェクトを取得して走査
Dim blockRefSpace As AcadBlock
Set blockRefSpace = ThisDrawing.Blocks.Item(layout.Block.Name)
Call ScanContainer(blockRefSpace, blockData, dataCount)
Set blockRefSpace = Nothing
End If
Next layout

‘ データ数に合わせて配列をリサイズ
If dataCount > 0的表现 Then
ReDim Preserve blockData(1 To 5, 1 To dataCount)
‘ ここでExcelへの転記やCSV出力処理を行う
MsgBox “抽出完了: ” & dataCount & ” 件のブロックを検出 (” & Format(Timer – sw, “0.00秒”) & “)”, vbInformation
Else
MsgBox “ブロック参照は検出されませんでした。”, vbExclamation
End If

CleanUp:
‘ AutoCAD環境の復元
ThisDrawing.Application.ScreenUpdating = True
Exit Sub

ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

‘ =================================================================================
‘ 再帰的コンテナ走査プロシージャ(ネストされたブロックに対応)
‘ =================================================================================
Private Sub ScanContainer(ByRef container As AcadBlock, ByRef destArray() As Variant, ByRef currentCount As Long)
Dim ent As AcadEntity
Dim subBlock As AcadBlock
Dim insPt As Variant
Dim rot As Double
Dim scl As Variant

For Each ent In container
‘ オブジェクトタイプがブロック参照(AcadBlockReference / 挿入)であるか判定
If TypeOf ent Is AcadBlockReference Then
Dim blkRef As AcadBlockReference
Set blkRef = ent

currentCount = currentCount + 1

‘ 配列の動的拡張(メモリ再割り当てのコストを考慮し、10000件単位で拡張)
If currentCount > UBound(destArray, 2) Then
ReDim Preserve destArray(1 To 5, UBound(destArray, 2) + 10000)
End If

‘ 属性情報の取得(挿入点、尺度、回転角など)
insPt = blkRef.InsertionPoint

‘ データの格納
destArray(1, currentCount) = blkRef.EffectiveName ‘ ダイナミックブロックの親名を取得
destArray(2, currentCount) = insPt(0) & “, ” & insPt(1) & “, ” & insPt(2)
destArray(3, currentCount) = blkRef.XScaleFactor
destArray(4, currentCount) = blkRef.YScaleFactor
destArray(5, currentCount) = blkRef.Rotation (180 / 3.14159265358979) ‘ ラジアンから度数法へ変換

‘ 【極限の知見】ネストされたブロック構造への再帰突入
‘ ブロック定義側をさらに掘り下げる場合、無限ループ(循環参照)に注意が必要だが、
‘ AutoCADの仕様上、同一ブロックの直接的・間接的循環参照は通常ブロックエディタで弾かれる。
‘ ただし、大規模アセンブリ図面ではスタックオーバーフローに配慮すること。
Set subBlock = ThisDrawing.Blocks.Item(blkRef.Name)
‘ ※注: subBlock自体がレイアウト空間やモデル空間と重複する場合は除外ロジックが必要だが、
‘ ThisDrawing.Blocks 内の要素は純粋なブロック定義であるため安全に走査できる。
‘ ただし、ブロック定義内のエンティティを再帰走査する場合は以下の通り:
‘ Call ScanContainer(subBlock, destArray, currentCount)
‘ (※今回はパフォーマンスと一般的な要件を鑑み、トップレベル+レイアウトの走査を主眼とする)

Set blkRef = Nothing
End If
Set ent = Nothing
Next ent
End Sub

3. チーフアーキテクトが教える「パフォーマンスとメモリ管理」の鉄則

① `ScreenUpdating = False` による描画バイパス

AutoCAD VBAにおいて、VBAコードの実行中に図面データベース(`ModelSpace`など)を舐める際、AutoCADは暗黙的に画面の再描画や選択状態の更新を試みようとする。これが実行速度を何倍も遅くする原因だ。
`ThisDrawing.Application.ScreenUpdating = False` を明示的に挟むことで、COMインターフェースのトラフィックを劇的に削減できる。

② ダイナミックブロックの罠と `EffectiveName`

通常の `AcadBlockReference.Name` を取得すると、ダイナミックブロックの場合、AutoCAD内部で生成された匿名ブロック名(例: `U123`)が返されてしまう。
実務でユーザーが意図する「ブロック名」を正しくリスト化するためには、必ず `EffectiveName` プロパティを使用しなければならない。これはVBAの標準オブジェクトブラウザには表示されない隠しプロパティ的側面を持つため、レガシーコードの移植時には注意が必要である。

③ COMオブジェクトの明示的な解放 (`Nothing` 代入)

VBAのガベージコレクションは頼りにならない。特に `For Each` ループ内で `AcadEntity` や `AcadBlockReference` を次々と変数にバインドしていくと、背後にあるCOMの参照カウンタ(Reference Counter)が蓄積し、メモリリークやAutoCAD自体の不安定化を引き起こす。
ループの最後で必ず `Set ent = Nothing` および `Set blkRef = Nothing` を実行し、参照を切断することが、プロフェッショナルエンジニアの最低限の作法である。

4. システム間連携への拡張:Excel/Databaseへのシームレスな流し込み

抽出した `blockData` 配列は、VBAのメモリ上にオンメモリで保持されている。これをExcelのワークシートへ一括出力(バルク転記)することで、ファイルI/Oのオーバーヘッドをゼロに近づけることができる。

‘ Excelへの爆速バルク転記の例(AutoCAD VBAからExcelを操作する場合)
Dim xlApp As Object
Dim xlBook As Object
Dim xlSheet As Object

Set xlApp = CreateObject(“Excel.Application”)
Set xlBook = xlApp.Workbooks.Add
Set xlSheet = xlBook.Sheets(1)

‘ 配列を一括でレンジに流し込む(セルを1つずつループする愚を犯さないこと)
xlSheet.Range(“A1:E” & UBound(blockData, 2)).Value = xlApp.WorksheetFunction.Transpose(blockData)

xlApp.Visible = True

※注: `Transpose` 関数には要素数の制限(通常65536件など、古いExcel仕様に依存)があるため、大規模なデータ群に対しては行と列の持ち方を設計段階で反転させておく等のアーキテクチャ上の配慮が求められる。

総括

AutoCAD VBAはレガシーな技術と見なされがちだが、その背後にあるCOMアーキテクチャの本質を理解していれば、現代のC#/.NETアドイン開発に匹敵する軽量かつ強力な自動化ツールとして君臨し続ける。

オブジェクトのライフサイクルを完全に掌握し、メモリの脈動まで感じ取りながらコードを書くこと。それこそが、現場を救う真のエンジニアの姿である。

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