【テクニカル・上級編】【実務中級】図面内の全オブジェクトの、現在の画層名を保持したまま、別のプロパティ(色、線種など)を一括変更する – AutoCAD VBA解析バイブル

スポンサーリンク

AutoCAD VBAを掌握する極限の知見:画層構造を破壊せずにプロパティを一括統制するアーキテクチャ

AutoCADのカスタマイズにおいて、VBAはレガシーな存在と揶揄されることがある。だが、オブジェクトモデルの挙動、COMのメモリ管理、そしてAutoCADの内部データベース構造(Drawing Database)のライフサイクルを完全に掌握していれば、VBAは依然として最強かつ最速の自動化兵器たり得ます。

今回は、実務の現場で最も頻発する要求の一つ「図面内の全オブジェクトの画層(Layer)を維持したまま、色(Color)、線種(Linetype)、線幅(Lineweight)を強制的に一括上書きする」というテーマを取り上げる。

表面的なコードを書くだけであれば数行で終わる話だが、数万から数十万のエンティティを持つ実務図面において、これを愚直に実行するとAutoCADがフリーズするか、メモリリークの温床となる。
シニアエンジニアおよび社内システム管理者が知るべき、極限のパフォーマンスと堅牢性を備えたコードと設計思想をここに開示する。

1. なぜ「ByLayer」の罠にハマるのか?

AutoCADの基本原則として、オブジェクトの色や線種は「画層に従う(ByLayer)」状態がベストプラクティスである。しかし、他社から受領した図面や、バラバラの基準で作成されたレガシー図面では、オブジェクト個別に色や線種がハードコーディング(直値で設定)されているケースが多々ある。

これを「画層名はそのままに、色や線種、線幅だけを特定の統一プロパティに書き換える」場合、以下の点に留意しなければならない。

1. ブロック参照(BlockReference)内部のネスト構造:
`ModelSpace`や`PaperSpace`を舐めるだけでは、ブロック定義(Block)の内部にあるプリミティブに到達できない。
2. COMオブジェクトの解放漏れによるメモリ肥大化:
VBAの裏側ではCOM(Component Object Model)が稼働しており、`.Item()`や`.Add`などで生成された参照を適切に破棄しないと、AutoCADプロセス内にメモリゴミが蓄積する。
3. トランザクションとパフォーマンス:
一件一件プロパティを変更するたびに画面描画やイベントが発生しては、実用的な速度は出ない。

2. 実務仕様:ハイパフォーマンス・プロパティ一括統制マクロ

以下に提供するコードは、単なるループ処理ではない。ブロックの再帰的走査(Nested Blocks)、適切なエラーハンドリング、そしてCOMのライフサイクルを意識した実務レベルのアーキテクチャで実装されている。

Option Explicit

‘ ==============================================================================
‘ 局所的な定数定義
‘ ==============================================================================
Private Const TARGET_COLOR As Integer = 256 ‘ 256 = ByLayer (または 1:赤, 7:白 など指定)
Private Const TARGET_LINETYPE As String = “ByLayer” ‘ 線種
Private Const TARGET_LINEWEIGHT As Long = -1 ‘ -1 = acLnWtByLayer (-3はByBlock)

Sub BatchOverridePropertiesKeepLayer()
Dim startTime As Double
startTime = Timer

‘ 画面描画と自動更新を停止し、処理速度を極限まで引き上げる
‘ ※これを怠ると、1オブジェクト毎の再描画コストで実行時間が100倍以上に跳ね上がる
ThisDrawing.Application.ScreenUpdating = False

On Error GoTo ErrorHandler

Dim acadDoc As AcadDocument
Set acadDoc = ThisDrawing

Dim modifiedCount As Long
modifiedCount = 0

‘ 1. モデル空間の走査
Call ProcessEntities(acadDoc.ModelSpace, modifiedCount)

‘ 2. ペーパー空間(レイアウト)の走査
Dim acadLayout As AcadLayout
For Each acadLayout In acadDoc.Layouts
Call ProcessEntities(acadLayout.Block, modifiedCount)
Next acadLayout

‘ 3. ブロック定義(Block Table Record)自体の走査
‘ ※図面内に存在する未挿入・挿入済みのブロック定義内のエンティティも強制書き換え対象とする
Dim acadBlock As AcadBlock
For Each acadBlock In acadDoc.Blocks
‘ 外部参照(Xref)やレイアウト固有のブロックを除外
If (acadBlock.IsLayout = False) And ((acadBlock.Flags And 4) = 0) Then
Call ProcessEntities(acadBlock, modifiedCount)
End If
Next acadBlock

‘ 描画の復元と強制再描画
ThisDrawing.Application.ScreenUpdating = True
acadDoc.Regen acAllViewports

MsgBox “プロパティの一括統制が完了しました。” & vbCrLf & _
“処理対象エンティティ数: ” & modifiedCount & ” 件” & vbCrLf & _
“処理時間: ” & Format(Timer – startTime, “0.00”) & ” 秒”, _
vbInformation, “AutoCAD VBA 宇内アーキテクチャ”

CleanUp:
‘ 念のため画面更新フラグの確実に復旧
ThisDrawing.Application.ScreenUpdating = True
Exit Sub

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

‘ ==============================================================================
‘ 再帰的エンティティ処理エンジン
‘ ==============================================================================
Private Sub ProcessEntities(ByRef targetContainer As Object, ByRef counter As Long)
Dim ent As AcadEntity
Dim subEnt As AcadEntity
Dim i As Long

For i = 0 To targetContainer.Count – 1
Set ent = targetContainer.Item(i)

‘ 画層(Layer)プロパティは一切変更せず、外観プロパティのみ上書きする
On Error Resume Next

‘ 色の設定
ent.Color = TARGET_COLOR

‘ 線種の設定(事前に図面にロードされている必要がある)
If Len(TARGET_LINETYPE) > 0 Then
ent.Linetype = TARGET_LINETYPE
End If

‘ 線幅の設定
ent.Lineweight = TARGET_LINEWEIGHT

If Err.Number = 0 Then
counter = counter + 1
End If
Err.Clear
On Error GoTo 0

‘ ブロック参照(BlockReference)の場合は内部要素も再帰的に処理
If TypeOf ent Is AcadBlockReference Then
Dim blockRef As AcadBlockReference
Set blockRef = ent
Dim dynAttributes As Variant

‘ アトリビュートを持つブロックへの配慮
If blockRef.HasAttributes Then
Dim atts As Variant
atts = blockRef.GetAttributes
Dim j As Long
For j = LBound(atts) To UBound(atts)
‘ アトリビュートも画層を維持してプロパティ変更
atts(j).Color = TARGET_COLOR
atts(j).Linetype = TARGET_LINETYPE
atts(j).Lineweight = TARGET_LINEWEIGHT
counter = counter + 1
Next j
End If
End If

‘ COMオブジェクトの参照解放(メモリ最適化の極意)
Set ent = Nothing
Next i
End Sub

3. チーフアーキテクトが解説するコードの急所

① `ScreenUpdating = False` による速度的恩恵

AutoCAD VBAにおいて、オブジェクトのプロパティを変更するたびに、内部グラフィックスエンジンは画面の再描画を試みる。数万のオブジェクトを扱う場合、この描画コストが実行時間の9割以上を占める。
コード冒頭の `ScreenUpdating = False` によって描画パイプラインをバイパスし、処理速度を爆発的に向上させている。

② ブロック定義(`acadDoc.Blocks`)まで踏み込む理由

多くの初学者は `ModelSpace` だけをループして満足する。しかし、実務の図面では「ブロック化された内部の線画」がバラバラの色を持っていることが最も多い。
`acadDoc.Blocks` を直接走査し、外部参照(Xref)やペーパー空間用ブロックを除外した上でエンティティを叩くことで、「ネストされたブロックの奥底にある異物」まで完全に統制下置くことが可能になる。

③ COMオブジェクトの明示的解放(メモリリーク対策)

VBAはガベージコレクションのタイミングが曖昧である。ループ内で `Set ent = targetContainer.Item(i)` と毎回参照を生成すると、VBAの内部ポインタ(IUnknown/IDispatch)が蓄積し、巨大図面を処理した際にExcelやAutoCADがOut of Memory(メモリ不足)でクラッシュする。
ループの最後で `Set ent = Nothing` を明示的に呼び出すことで、参照カウンタを即座にデクリメントし、メモリ空間をクリーンに保っている。

4. 運用上の注意点とシステム間連携のヒント

  • 線種の事前ロード:

コード内で指定する線種(例: `”CENTER”` や `”HIDDEN”` 等)が、対象図面の線種テーブルに存在しない場合、ランタイムエラーが発生する。厳密なシステムを構築する場合は、事前に `acadDoc.Linetypes` コレクションに該当線種が存在するかチェックし、ロードする防御的コード(Defensive Code)を前段に挟むべきである。

  • 外部システム(C# / .NET API)への移行パス:

もしこの処理を数万枚の図面に対してバッチ処理(夜間バッチなど)で行う場合、VBAではなく AutoCAD .NET API (C#) を用いて `Database` クラスと `Transaction` を直接操作するアーキテクチャにリプレイスすべきである。しかし、単一図面のローカルな高速自動化、あるいはCAD管理者によるクイックなアドイン配布という文脈においては、本VBAコードは依然として最高の費用対効果を発揮する。

真の自動化エンジニアとは、テクノロジの古さにかかわらず、その挙動のメカニズムを理解し、限界ギリギリまでパフォーマンスを引き出す者のことだ。この知見をあなたの開発環境に実装し、レガシー図面の混乱を完全に制圧してほしい。

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