AutoCAD VBAを掌握する極限の知見
【第1回】図面の見えない脊髄を暴く:Linetypes・TextStylesコレクションの完全制圧とメモリ最適化
シニアアーキテクト諸君。日々の図面自動化において、`AcadLine`や`AcadCircle`といった「画面で見える図形(グラフィカルデータ)」の操作にばかり気を取られてはいないか。
実務で真にシステムを崩壊させるのは、画面の裏側に巣食う「非グラフィカルデータ」の腐敗である。
肥大化したLinetype(線種)、無秩序に乱立したTextStyle(文字スタイル)、そして名前空間を汚染するLayer(画層)。これらをプログラムから正確に列挙し、ゾンビデータ(未使用定義)を刈り取る技術こそ、真のAutoCAD VBAエンジニアに求められる素養だ。
今回は、`AcadDocument`の背後にあるディクショナリ構造を剥ぎ取り、COMのライフサイクルを完全に手懐けるための極限の知見を授ける。
—
1. 非グラフィカルデータへのアプローチとCOMの闇
AutoCADのオブジェクトモデルにおいて、`Linetypes` や `TextStyles` は、`AcadDocument` 直下にあるコレクションオブジェクトである。これらは通常の図形エンティティ(`ModelSpace` や `PaperSpace`)とは異なり、図面の「定義テーブル」そのものを指す。
ここで初心者が必ず陥る致命的な罠がある。
「取得したCOMオブジェクトを野放しにすること」だ。
VBAの背後では、AutoCAD(ObjectARXのC++コア)とVBAのランタイム(OLE/COM)がプロセス境界を越えて通信している。コレクションのループ処理やオブジェクトの参照をいい加減に書くと、AutoCADのプロセス(`acad.exe`)内にメモリリークが発生し、最悪の場合は図面の破損や致命的なクラッシュ(FATAL ERROR)を引き起こす。
シニアエンジニアたる者、オブジェクトの取得と解放のライフサイクルは常に意識しなければならない。
—
2. 未使用スタイル・線種を特定する実用コード
以下のコードは、図面内に存在するすべての `TextStyles` と `Linetypes` を列挙し、「現在エンティティから参照されていない(=削除可能な)未使用定義」を特定・ログ出力する実戦的なプロシージャだ。
無駄なエラーハンドリングを削ぎ落とし、COMの参照カウンタを意識した厳格な記述を行っている。
Option Explicit
” ==============================================================================
” 概要: 図面内の非グラフィカルデータ(線種・文字スタイル)を解析し、
未使用の定義を特定してイミディエイトウィンドウに出力する。
” ターゲット: AutoCAD 2004 〜 最新バージョン (VBA 7.1対応)
” ==============================================================================
Public Sub AnalyzeUnusedDefinitions()
Dim acadApp As AcadApplication
Dim acadDoc As AcadDocument
‘ 1. アプリケーションおよびアクティブドキュメントの安全な取得
On Error Resume Next
Set acadApp = GetObject(, “AutoCAD.Application”)
If Err.Number <> 0 Then
MsgBox “AutoCADが起動していません。”, vbCritical, “致命的エラー”
Exit Sub
End If
Set acadDoc = acadApp.ActiveDocument
On Error GoTo 0
If acadDoc Is Nothing Then
MsgBox “有効な図面が開かれていません。”, vbCritical, “エラー”
Exit Sub
End If
‘ イミディエイトウィンドウのヘッダー出力
Debug.Print “=== 非グラフィカルデータ解析開始: ” & acadDoc.Name & ” ===”
‘ 2. TextStyles の解析
Call AuditTextStyles(acadDoc)
‘ 3. Linetypes の解析
Call AuditLinetypes(acadDoc)
Debug.Print “=== 解析完了 ===”
‘ 4. オブジェクト参照の完全解放 (Garbage Collectionの促進)
Set acadDoc = Nothing
Set acadApp = Nothing
End Sub
Private Sub AuditTextStyles(ByVal doc As AcadDocument)
Dim tStyles As AcadTextStyles
Dim tStyle As AcadTextStyle
Dim styleName As String
Dim i As Long
Dim isUsed As Boolean
Set tStyles = doc.TextStyles
Debug.Print vbCrLf & “— [TextStyles 解析] (総数: ” & tStyles.Count & “) —”
For i = 0 To tStyles.Count – 1
‘ 厳密なインデックスアクセス
Set tStyle = tStyles.Item(i)
styleName = tStyle.Name
‘ “Standard” や “Annotative” などのシステム予約スタイルは除外判定の対象とする
If StrComp(styleName, “Standard”, vbTextCompare) = 0 Then
Debug.Print ” [保持] ” & styleName & ” (システム予約スタイル)”
Else
‘ 図形からの参照チェック(簡略化のため、ここではフラグ判定のロジック骨子を示す)
isUsed = IsTextStyleReferenced(doc, styleName)
If Not isUsed Then
Debug.Print ” [未使用・削除候補] ” & styleName
‘ 実務ではここで doc.TextStyles.Item(styleName).Delete を検討する
Else
Debug.Print ” [使用中] ” & styleName
End If
End If
‘ ループ内でのCOMオブジェクト参照の即時解放
Set tStyle = Nothing
Next i
Set tStyles = Nothing
End Sub
Private Sub AuditLinetypes(ByVal doc As AcadDocument)
klines As AcadLinetypes
Dim lLine As AcadLinetype
Dim lineName As String
Dim i As Long
Set llines = doc.Linetypes
Debug.Print vbCrLf & “— [Linetypes 解析] (総数: ” & llines.Count & “) —”
For i = 0 To llines.Count – 1
Set lLine = llines.Item(i)
lineName = lLine.Name
‘ “ByLayer”, “ByBlock”, “Continuous” などの特殊線種は削除不可
Select Case UCase(lineName)
Case “BYLAYER”, “BYBLOCK”, “CONTINUOUS”
Debug.Print ” [保持] ” & lineName & ” (システム予約線種)”
Case Else
If Not IsLinetypeReferenced(doc, lineName) Then
Debug.Print ” [未使用・削除候補] ” & lineName
Else
Debug.Print ” [使用中] ” & lineName
End If
End Select
Set lLine = Nothing
Next i
Set llines = Nothing
End Sub
‘ 補助関数: 文字スタイルがエンティティから参照されているか判定
Private Function IsTextStyleReferenced(ByVal doc As AcadDocument, ByVal targetName As String) As Boolean
Dim ent As AcadEntity
Dim found As Boolean
found = False
‘ ModelSpaceの全エンティティを走査
For Each ent in doc.ModelSpace
If TypeOf ent Is AcadText Then
If StrComp(DirectCast_Text(ent).StyleName, targetName, vbTextCompare) = 0 Then
found = True
Exit For
End If
ElseIf TypeOf ent Is AcadMText Then
If StrComp(DirectCast_MText(ent).StyleName, targetName, vbTextCompare) = 0 Then
found = True
Exit For
End If
End If
Set ent = Nothing
Next
IsTextStyleReferenced = found
End Function
‘ 補助関数: 線種がエンティティから参照されているか判定
Private Function IsLinetypeReferenced(ByVal doc As AcadDocument, ByVal targetName As String) As Boolean
Dim ent As AcadEntity
Dim found As Boolean
found = False
For Each ent In doc.ModelSpace
If StrComp(ent.Linetype, targetName, vbTextCompare) = 0 Then
found = True
Exit For
End If
Set ent = Nothing
Next
IsLinetypeReferenced = found
End Function
—
3. チーフアーキテクトが指摘する「VBAの限界とメモリ最適化の極意」
上記のコードをそのまま巨大な図面(数万〜数十万エンティティを持つインフラ図面やプラント図面)で実行するとどうなるか。
`For Each` による図形走査で、VBAのガベージコレクションが追いつかず、メモリ消費量がうなぎ登りに増大する。これがVBAの限界であり、多くの開発者が挫折するポイントだ。
この限界を突破するための極限の知見を授けよう。
1. `For Each` による暗黙のCOMラッパー生成を避ける
`ModelSpace` に対する `For Each` は、内部でIEnumVARIANTインターフェースを呼び出し、ループのたびにCOMラッパーオブジェクトをヒープ上に生成する。数万回これが走ると、VBAランタイムのメモリ管理が破綻する。
極限を求めるならば、`For i = 0 To ModelSpace.Count – 1` とし、`Item(i)` で直接アクセスする構造にリファクタリングすべきだ。さらに言えば、本格的な大規模図面の解析には、VBAではなく `.NET (C# / VB.NET) + ObjectARX / DatabaseObject` を用いるべきである。VBAはあくまで「迅速なプロトタイピングと小規模自動化」の特権武器として使い分けること。
2. オブジェクト変数の即時破棄(`Set xxx = Nothing`)
ループ内で生成したオブジェクト変数(`tStyle`, `lLine`, `ent` 等)は、スコープを抜ける前、あるいは次のループに移行する直前に必ず `Set xxx = Nothing` を明示的に叩け。VBAの参照カウンタ(AddRef / Release)をプログラマの意図通りに制御することが、AutoCADを何時間連続稼働させても落ちないシステムを作る唯一の防衛策である。
3. Purge(パージ)コマンドとの使い分け
AutoCADにはネイティブの `PURGE` コマンドが存在する。しかし、ネイティブのパージは「一括して全削除」するため、どの図面要素がどの定義を占有しているのかという「監査ログ(Audit Log)」を社内システムやDBに連携することができない。
今回解説したようなプログラムによる列挙・解析ロジックこそが、社内のCAD標準化や品質管理オートメーションの基盤となるのだ。
—
総括
非グラフィカルデータの制御は、AutoCADの内部構造(DXFグループコードやシンボルテーブル)の理解に直結する。単に「動くコード」を書くフェーズは卒業し、メモリの重量、COMのライフサイクル、そしてプロセス間の通信コストまでを支配するエンジニアたれ。
次回の極限の知見では、さらに踏み込み、レイヤー(AcadLayers)と画層状態管理の深い闇を暴く。期待して待て。
