保存前「名前削除(Purge)」自動化:AutoCAD DWGファイル最適化の最終奥義
長年、CADシステムの最前線で数多の自動化プロジェクトを指揮してきた者として、今回はAutoCAD VBAの深淵に触れる、実務中級者以上、そして社内システム管理者を唸らせるほどの「極限の知見」を皆さんと共有したい。
図面ファイル(DWG/DXF)のオープン、セーブ、そしてバージョン互換性の制御は、CAD運用の中核をなす。だが、多くの現場で見過ごされている、いや、見過ごさざるを得ない「ファイルサイズ肥大化」とそれに伴う「セーブ処理の遅延」という慢性的な病。これに対する特効薬こそ、保存直前の「名前削除(Purge)」自動実行である。
なぜ「保存直前」なのか? ライフサイクルの視点
まず、なぜ「保存直前」にPurgeを実行することが、他のタイミングよりも優れているのか、その理由をオブジェクトのライフサイクルの観点から掘り下げよう。
- 不要オブジェクトの発生源: 編集作業の過程で、未使用のレイヤー、ブロック定義、線種、文字スタイルなどが図面に蓄積されていく。これらは、描画処理に直接影響しないにも関わらず、ファイルサイズを増大させる。
- セーブ時のオーバーヘッド: AutoCADは、セーブ時に図面全体の整合性をチェックし、変更されたオブジェクトをディスクに書き込む。不要なオブジェクトが存在すると、そのチェックや書き込みに余計な時間がかかる。
- 「保存前」の利点:
- 最新の不要オブジェクトを削除: 編集作業の最終段階で生成された不要オブジェクトを、そのままファイルに残すことなく削除できる。
- セーブ処理の直接的な高速化: Purgeによってファイルサイズが削減され、ディスクI/Oが軽減されるため、セーブ処理そのものが高速化する。
- データ整合性の維持: 削除されるのはあくまで「未使用」のオブジェクトであり、図面データそのものには影響を与えない。
単に定期的にPurgeを実行するだけでは、編集途中で再び不要オブジェクトが発生する可能性がある。「保存」という、ファイルが確定する直前のタイミングで実行することが、ファイルサイズ最適化とセーブ高速化の、最も直接的かつ効果的なアプローチなのである。
VBAにおけるPurgeの基本と「落とし穴」
Purgeの基本的なVBAコマンドは、`ThisDrawing.SendCommand “_PURGE”` である。しかし、これをそのまま使うと、対話的な操作を待つことになり、自動化の目的を果たせない。
‘ 基本的なPurgeコマンド(非推奨:対話的になる)
‘ ThisDrawing.SendCommand “_PURGE” & vbCrLf
真の自動化には、`_PURGE` コマンドのサブコマンドを駆使し、全ての未使用オブジェクトを再帰的に削除させる必要がある。
‘ 全ての未使用オブジェクトを削除するコマンドシーケンス
Dim cmd As String
cmd = “_PURGE” & vbCrLf & _
“A” & vbCrLf & _ ‘ All (全て)
“y” & vbCrLf ‘ Yes (確認メッセージを自動でYes)
ThisDrawing.SendCommand cmd
しかし、これだけでは不十分な場合がある。特に、ブロック定義内にネストされた不要オブジェクトなどは、このコマンドだけでは削除しきれないことがある。そこで、より強力なアプローチが必要となる。
【実務中級者向け】Purgeを制御するVBAオブジェクトモデル
VBAでは、`AcadApplication.ActiveDocument.Purge` メソッドは提供されていない。しかし、AutoCADのCOMオブジェクトモデルを深く理解することで、より詳細な制御が可能になる。
最も強力なのは、`AcadDocument.SendCommand` を利用しつつ、各種オブジェクトコレクションを明示的に操作することだ。
1. レイヤー(Layers)のPurge
未使用レイヤーを削除する。
Sub PurgeUnusedLayers()
‘ 使用されていないレイヤーを削除
‘ “N” (Non-graphic objects) を指定することで、ブロック定義などにも含まれる未使用レイヤーを対象とする
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “LA” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
‘ 念のため、再度実行して残留オブジェクトがないか確認
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “LA” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
End Sub
2. ブロック定義(Block Definitions)のPurge
未使用ブロック定義を削除する。
Sub PurgeUnusedBlocks()
‘ 使用されていないブロック定義を削除
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “B” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
‘ 念のため、再度実行
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “B” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
End Sub
3. 線種(Linetypes)のPurge
未使用線種を削除する。
Sub PurgeUnusedLinetypes()
‘ 使用されていない線種を削除
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “L” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
‘ 念のため、再度実行
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “L” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
End Sub
4. 文字スタイル(Text Styles)のPurge
未使用文字スタイルを削除する。
Sub PurgeUnusedTextStyles()
‘ 使用されていない文字スタイルを削除
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “ST” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
‘ 念のため、再度実行
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “ST” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
End Sub
5. Dimension Styles(寸法スタイル)のPurge
未使用寸法スタイルを削除する。
Sub PurgeUnusedDimStyles()
‘ 使用されていない寸法スタイルを削除
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “D” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
‘ 念のため、再度実行
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “D” & vbCrLf & “N” & vbCrLf & “y” & vbCrLf
End Sub
6. 全ての未使用オブジェクトを再帰的にPurgeする
上記を組み合わせ、最も強力なPurgeを実行する。
Sub FullPurge()
‘ 全ての未使用オブジェクト(レイヤー、ブロック、線種、スタイルなど)を再帰的に削除
‘ “A” (All) を指定し、”y” (Yes) で確認をスキップ
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “A” & vbCrLf & “y” & vbCrLf
‘ AutoCADによっては、一度の実行で全て削除されない場合があるため、複数回実行することを推奨
‘ より確実にするために、数回繰り返し実行する
Dim i As Integer
For i = 1 To 3 ‘ 3回繰り返す
ThisDrawing.SendCommand “_PURGE” & vbCrLf & “A” & vbCrLf & “y” & vbCrLf
Next i
‘ 最後に、コマンドラインに「PURGE」と入力するだけで、残っているものがあれば対話的に削除できるようになる
‘ これは、自動化では拾いきれない例外的なケースに対応するため
‘必要であれば、この行をコメントアウトしてください
‘ThisDrawing.SendCommand “_PURGE” & vbCrLf
End Sub
注意点: `SendCommand` は、AutoCADのコマンドラインに直接入力するのと同等です。そのため、コマンドのスペルミスや、意図しないタイミングでの実行は、予期せぬ動作を引き起こす可能性があります。実行前には必ず図面のバックアップを取ることを強く推奨します。
メモリ最適化とオブジェクトの明示的解放
VBAにおけるメモリ管理は、VB6時代からの「自動解放」に慣れていると、見落としがちだ。しかし、AutoCAD VBA、特に大量のオブジェクトを扱う場合、オブジェクト変数の明示的な解放は、パフォーマンスと安定性に絶大な影響を与える。
Sub ExampleWithExplicitRelease()
Dim acadApp As AutoCAD.Application
Dim acadDoc As AutoCAD.Document
Dim acadBlock As AcadBlock
Dim acadBlockRef As AcadBlockReference
‘ AutoCADアプリケーションオブジェクトの取得
On Error Resume Next ‘ エラー発生時も続行
Set acadApp = GetObject(, “AutoCAD.Application”)
If acadApp Is Nothing Then
MsgBox “AutoCADが起動していません。”, vbCritical
Exit Sub
End If
On Error GoTo 0 ‘ エラーハンドリングを元に戻す
‘ アクティブなドキュメントを取得
Set acadDoc = acadApp.ActiveDocument
‘ 特定のブロック定義を取得 (例: “MY_BLOCK”)
On Error Resume Next
Set acadBlock = acadDoc.Blocks.Item(“MY_BLOCK”)
On Error GoTo 0
If Not acadBlock Is Nothing Then
‘ ブロック定義を参照しているオブジェクトを列挙
For Each acadBlockRef In acadDoc.ModelSpace
If acadBlockRef.ObjectName = “AcDbBlockReference” Then
If acadBlockRef.Name = acadBlock.Name Then
‘ ここでacadBlockRefに対する処理を行う
‘ 例: acadBlockRef.Delete
End If
End If
Next acadBlockRef
‘ オブジェクト変数を解放
Set acadBlockRef = Nothing
Set acadBlock = Nothing
End If
‘ ドキュメントオブジェクトを解放
Set acadDoc = Nothing
‘ アプリケーションオブジェクトを解放
Set acadApp = Nothing
‘ 念のため、明示的にガベージコレクションを呼び出す(必須ではないが、デバッグ時に有効)
‘ Call Shell(“cmd /c taskkill /f /im acad.exe”, vbHide) ‘ AutoCADを強制終了させるのは非推奨
‘ VBAのメモリ解放は自動で行われるが、明示的なSet = Nothing は重要
End Sub
`Set obj = Nothing` を適切に実行することで、COMオブジェクトが占有するメモリが解放され、AutoCAD全体の応答性が向上する。特に、ループ処理で多数のオブジェクトを生成・操作する際には、この明示的解放を徹底することが、システム安定化の鍵となる。
Windows APIの活用:レガシー環境とシステム間連携の極限
VBA単体では到達できない領域、特にレガシー環境の保守や、他システムとの連携において、Windows APIの呼び出しは避けて通れない。
例えば、特定のプロセスのメモリ使用量を監視したり、AutoCADのプロセスを制御したりする際にAPIは役立つ。
例:AutoCADプロセスのファイルハンドル数を取得する(参考:難易度高)
これは高度な例だが、APIを駆使することで、普段見えない部分にアクセスできることを示す。
‘— 標準モジュールに記述 —
‘ Declare statements for Kernel32.dll and Psapi.dll
Public Declare Function OpenProcess Lib “kernel32” ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Public Declare Function CloseHandle Lib “kernel32” ( _
ByVal hObject As Long) As Long
Public Declare Function EnumProcessHandles Lib “psapi.dll” ( _
ByVal hProcess As Long, _
ByVal phHandleArray As Long, _
ByVal cb As Long, _
ByRef pdwSize As Long) As Long
Public Declare Function GetProcessId Lib “kernel32” ( _
ByVal hProcess As Long) As Long
‘ Constants
Const PROCESS_QUERY_INFORMATION As Long = &H1000
Const PROCESS_VM_READ As Long = &H10
‘— 関数 —
Function GetFileHandleCount(ByVal ProcessName As String) As Long
Dim hProcess As Long
Dim PID As Long
Dim buffer(0 To 1023) As Long ‘ Handle array buffer
Dim bytesCopied As Long
Dim handleCount As Long
On Error GoTo ErrorHandler
‘ Find the process by name
PID = FindProcessId(ProcessName)
If PID = 0 Then
GetFileHandleCount = -1 ‘ Process not found
Exit Function
End If
‘ Open the process
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, 0, PID)
If hProcess = 0 Then
‘ Error opening process, likely insufficient privileges
GetFileHandleCount = -2 ‘ Cannot open process
Exit Function
End If
‘ Enumerate handles
‘ The buffer size needs to be dynamic, this is a simplified example
If EnumProcessHandles(hProcess, VarPtr(buffer(0)), UBound(buffer) 4 + 4, pdwSize) Then
‘ The buffer size might need adjustment if more handles exist
‘ For a more robust solution, you’d resize the buffer dynamically
handleCount = pdwSize \ 4 ‘ Assuming each handle is 4 bytes (Long)
GetFileHandleCount = handleCount
Else
‘ Error enumerating handles
GetFileHandleCount = -3 ‘ Error enumerating
End If
‘ Close the process handle
CloseHandle hProcess
Exit Function
ErrorHandler:
GetFileHandleCount = -99 ‘ General error
If hProcess <> 0 Then CloseHandle hProcess
End Function
‘ Helper function to find process ID by name (requires Toolhelp32.dll functions – not shown here for brevity)
‘ For simplicity, you might use WMI or other methods to get PID.
‘ Example using WMI (requires reference to Microsoft WMI Scripting Library)
‘ Public Function FindProcessId(ByVal ProcessName As String) As Long
‘ Dim objWMIService As Object
‘ Dim colProcessList As Object
‘ Dim objProcess As Object
‘
‘ Set objWMIService = GetObject(“winmgmts:\\.\root\cimv2”)
‘ Set colProcessList = objWMIService.ExecQuery(“SELECT FROM Win32_Process WHERE Name = ‘” & ProcessName & “‘”)
‘
‘ For Each objProcess In colProcessList
‘ FindProcessId = objProcess.ProcessId
‘ Exit Function
‘ Next
‘ FindProcessId = 0 ‘ Not found
‘ End Function
‘— 使用例 —
Sub CheckAutoCADHandles()
Dim acadPID As Long
Dim handleCount As Long
‘ AutoCADのプロセス名 (実行ファイル名)
Const AUTOCAD_PROCESS_NAME As String = “acad.exe”
‘ WMIを使ってPIDを取得 (上記Commented Out の FindProcessId 関数を有効にするか、別途実装)
‘ acadPID = FindProcessId(AUTOCAD_PROCESS_NAME)
‘ MsgBox “AutoCAD PID: ” & acadPID
‘ ここでは、直接APIでプロセス名からPIDを取得する処理は省略し、
‘ 既にPIDが分かっている、または他の方法で取得できたという前提で進める。
‘ 実際には、FindProcessId関数を実装するか、Task Managerなどで確認したPIDを直接指定する必要がある。
‘ 例として、仮にPIDが 12345 だとする
‘ acadPID = 12345 ‘ <-- 実際のPIDに置き換える
' より現実的なアプローチ:WMIでPIDを取得する (Microsoft Scripting Runtime または WMI Scripting Library の参照設定が必要)
Dim objWMIService As Object
Dim colProcessList As Object
Dim objProcess As Object
Dim foundPID As Long
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set colProcessList = objWMIService.ExecQuery("SELECT FROM Win32_Process WHERE Name = '" & AUTOCAD_PROCESS_NAME & "'")
If colProcessList.Count > 0 Then
For Each objProcess In colProcessList
foundPID = objProcess.ProcessId
‘ 複数のAutoCADインスタンスがある場合、最初のものを使用する
Exit For
Next
Else
MsgBox “AutoCADプロセスが見つかりませんでした。”, vbExclamation
Exit Sub
End If
If foundPID > 0 Then
handleCount = GetFileHandleCount(AUTOCAD_PROCESS_NAME) ‘ ProcessNameは実際にはPIDで渡す方が一般的
Select Case handleCount
Case Is > 0
MsgBox “AutoCAD (” & AUTOCAD_PROCESS_NAME & “) のファイルハンドル数: ” & handleCount
Case -1
MsgBox “プロセス ‘” & AUTOCAD_PROCESS_NAME & “‘ が見つかりませんでした。”, vbExclamation
Case -2
MsgBox “AutoCADプロセスを開けませんでした。権限を確認してください。”, vbExclamation
Case -3
MsgBox “AutoCADのハンドル列挙中にエラーが発生しました。”, vbExclamation
Case Else
MsgBox “不明なエラーが発生しました (コード: ” & handleCount & “)”, vbExclamation
End Select
Else
MsgBox “AutoCADのプロセスIDを取得できませんでした。”, vbExclamation
End If
Set objProcess = Nothing
Set colProcessList = Nothing
Set objWMIService = Nothing
End Sub
注意: 上記のAPIコードは、あくまで概念を示すためのものです。実際の運用には、エラーハンドリングの強化、リソースの確実な解放、そして対象OSやAutoCADのバージョンに応じたテストが不可欠です。特に `EnumProcessHandles` は、バッファサイズ管理が複雑なため、実運用ではより洗練された実装が求められます。
システム連携と自動化の「落とし穴」
- AutoCADのバージョン互換性: `SendCommand` は、コマンドの引数や挙動がバージョンによって微妙に異なる場合があります。レガシー環境の保守では、この差異を吸収するためのロジックが必要です。
- ユーザーインターフェースの干渉: 自動化スクリプトが実行中に、ユーザーが手動でコマンドを入力したり、ダイアログボックスを操作したりすると、予期せぬエラーが発生します。AutoCADの `Document_CommandWillStart` イベントなどを利用して、スクリプト実行中のユーザー操作を一時的に無効化する、といった対策も考えられます。
- エラーハンドリングの徹底: `On Error Resume Next` は、デバッグ時には便利ですが、本番運用では「何が原因でエラーが発生したのか」を特定することが極めて重要です。より詳細なエラーログ出力や、例外処理の段階的実装が不可欠です。
- 外部プロセスからの制御: VB.NETなどのより強力な言語からAutoCADをCOM経由で制御する場合、VBAよりも複雑なオブジェクトライフサイクル管理や、スレッドセーフティの考慮が必要になります。
まとめ:最適化は継続的なプロセス
保存直前のPurge自動実行は、ファイルサイズ最適化とセーブ高速化のための強力な一手です。しかし、これはあくまで「最適化」という継続的なプロセスの一部に過ぎません。
- 定期的な見直し: 図面の使用状況や、追加されるオブジェクトの種類に応じて、Purge対象を最適化する。
- コードの可読性と保守性: 誰が読んでも理解できる、コメントの充実したコードを心がける。
- テストと検証: 変更を加えるたびに、様々な環境で十分なテストを行う。
AutoCAD VBAの世界は奥深く、その真髄に触れるためには、表面的な知識に留まらず、オブジェクトのライフサイクル、メモリ管理、そしてOSレベルの連携まで理解する必要があります。今回解説した知見が、皆さんのAutoCAD運用をより効率的、そして堅牢なものにする一助となれば幸いです。
