[Mastering AutoCAD VBA] バッチ処理の極意:設計思想が分ける「スクリプト」と「プロダクションツール」の違い
多くの開発者が「AutoCAD VBAを使って、Excelリストに基づいた図面の一括書き換えツールを作りたい」と考えている。しかし、ネット上の断片的なコードを繋ぎ合わせただけの「スクリプト」は、実務の荒波では必ずと言っていいほど瓦解する。
なぜか? それは、「図面オブジェクトのライフサイクル」と「例外に対する防御設計」、そして「ファイルロックの管理」という、プロダクションレベルの設計思想が欠落しているからだ。
本記事では、単なるコピペで終わらない。実務において「絶対に止まらない」「結果が確実にログに残る」堅牢なバッチ処理ツールのアーキテクチャを伝授する。
1. なぜ君のバッチ処理は途中で止まるのか?
プロフェッショナルではないコードは、以下の3つの罠に必ず陥る。
1. メモリリークとファイルロック: `Document` オブジェクトを適切に解放(`Set obj = Nothing`)せず、`Close` も不完全なまま次のループへ進む。結果、AutoCADが図面を掴んだままになり、次のファイルが開けなくなる。
2. 「全か無か」の例外処理: 100枚中1枚の図面が破損していただけで、マクロがエラーで停止し、それまでの処理がすべて台無しになる。
3. バージョンの不整合: `SaveAs` メソッドの引数(`dwgVersion`)を理解せず、意図しないバージョンで保存してしまい、後続の工程(古いCADを使う協力会社への送付など)で開けなくなる。
これらを解決するために、我々は「Isolation(隔離)」と「Auditability(監査可能性)」を設計の柱に据える。
2. プロダクション・アーキテクチャの設計図
今回構築するのは、以下の4層構造を持つ設計である。
- Data Layer (Excel): パス、置換対象属性、保存バージョンを管理。
- Orchestration Layer (Main Loop): Excelを読み込み、個別の処理を呼び出す。エラーが発生しても、そのファイルを「失敗」として記録し、次のファイルへ進む(リトライ・スキップ戦略)。
- Logic Layer (Task Execution): 図面を開き、ブロックを探し、属性を書き換える。ここには「図面を開く」「操作する」「保存する」の責務を分離して実装する。
- Logging Layer: 成功・失敗・エラー内容をExcelまたはテキストに書き出す。
3. 実装:堅牢な一括処理エンジン
以下に、Excel VBAからAutoCADを制御する、実務レベルのコード例を示す。
事前準備
1. ExcelのVBAエディタで `Tools` -> `References` を開き、`AutoCAD [Version] Type Library` にチェックを入れること。
2. Excelシートの構成:
- A列: 図面フルパス
- B列: 置換対象の属性タグ名
- C列: 新しい値
- D列: 保存バージョン (整数: 2018=2013, 2013=2010… ※AutoCADの定数に準拠)
プロダクション・コード
Option Explicit
' ==============================================================================
' AutoCAD Batch Processor (Production Grade)
' 目的: Excelリストに基づいた図面の一括属性更新およびバージョン変換
' ==============================================================================
' AutoCADのバージョン定数(環境に合わせて調整)
' ※実際にはAutoCADの列挙型を使用するのがベスト
Private Const DWG_VER_2018 As Long = 2018 ' 例: AutoCAD 2018形式
Private Const DWG_VER_2013 As Long = 2013 ' 例: AutoCAD 2013形式
Sub ExecuteBatchProcess()
Dim acadApp As Object ' AcadApplication
Dim ws As Worksheet
Dim lastRow As Long
Dim i As Long
Set ws = ThisWorkbook.Sheets("List") ' Excelシート名
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
' 1. AutoCADへの接続(既存プロセスを優先)
On Error Resume Next
Set acadApp = GetObject(, "AutoCAD.Application")
If Err.Number <> 0 Then
Set acadApp = CreateObject("AutoCAD.Application")
acadApp.Visible = True
End If
On Error GoTo 0
If acadApp Is Nothing Then
MsgBox "AutoCADを起動できませんでした。", vbCritical
Exit Sub
End If
' 2. メインループ(Isolation設計)
For i = 2 To lastRow ' 2行目から開始
Dim filePath As String: filePath = ws.Cells(i, 1).Value
Dim tagKey As String: tagKey = ws.Cells(i, 2).Value
Dim newValue As String: newValue = ws.Cells(i, 3).Value
Dim targetVer As Long: targetVer = ws.Cells(i, 4).Value ' 保存バージョン
' 処理結果を記録するための変数
Dim status As String: status = "Success"
Dim errorMsg As String: errorMsg = ""
' ファイルの存在確認(Pre-flight check)
If Dir(filePath) = "" Then
ws.Cells(i, 5).Value = "Error: File Not Found"
GoTo NextIteration
End If
' 個別図面の処理を関数化(例外をこの中で隔離する)
On Error Resume Next
Call ProcessSingleDrawing(acadApp, filePath, tagKey, newValue, targetVer)
If Err.Number <> 0 Then
status = "Error"
errorMsg = Err.Description
Err.Clear
End If
On Error GoTo 0
' 3. 結果の書き出し(Auditability)
ws.Cells(i, 5).Value = status
ws.Cells(i, 6).Value = errorMsg
NextIteration:
Next i
MsgBox "バッチ処理が完了しました。ログを確認してください。", vbInformation
End Sub
' ------------------------------------------------------------------------------
' 個別図面操作ロジック(責務の分離)
' ------------------------------------------------------------------------------
Private Sub ProcessSingleDrawing(ByRef acadApp As Object, _
ByVal dwgPath As String, _
ByVal targetTag As String, _
ByVal newVal As String, _
ByVal saveVer As Long)
Dim acadDoc As Object ' AcadDocument
Dim entity As Object ' AcadEntity
Dim blockRef As Object ' AcadBlockReference
Dim attributRef As Object ' AcadAttributeReference
Dim found As Boolean: found = False
' 図面を開く
Set acadDoc = acadApp.Documents.Open(dwgPath)
' モデル空間およびペーパー空間を走査(実務では両方カバーすべき)
' ここでは簡略化のためModelSpaceのみを対象とする
For Each entity In acadDoc.ModelSpace
' ブロック参照かどうかを判定
If entity.ObjectName = "AcDbBlockReference" Then
Set blockRef = entity
' 属性を持っているブロックか確認
If blockRef.HasAttributes Then
For Each attributRef In blockRef.GetAttributes
' 指定されたタグ名に一致するか
If UCase(attributRef.TagString) = UCase(targetTag) Then
attributRef.TextString = newVal
attributRef.Update
found = True
End If
Next attributRef
End If
End If
Next entity
' 属性が見つからなかった場合のハンドリング(必要に応じて)
If Not found Then
' 必要ならここでエラーをスローさせる
End If
' 4. バージョン指定保存とクローズ(Lifecycle Management)
' SaveAsの引数は AutoCAD の API 仕様に厳密に従う必要がある
' SaveAs(FileName, SaveAsType)
' ※VBAから呼び出す場合、dwgVersion定数は引数として渡す
' 確実に保存して閉じる
acadDoc.SaveAs dwgPath, saveVer ' 本来はSaveAsTypeの定数を使用
acadDoc.Close True ' 変更を保存して閉じる
' オブジェクトの明示的解放(メモリリーク防止の要)
Set attributRef = Nothing
Set blockRef = Nothing
Set entity = Nothing
Set acadDoc = Nothing
End Sub
4. チーフアーキテクトによる解説:なぜこのコードは「強い」のか
① オブジェクトのライフサイクル管理
`ProcessSingleDrawing` 内で、`Set acadDoc = Nothing` を徹底している。AutoCAD VBAにおいて、`Document` オブジェクトがメモリ上に残っている限り、そのファイルはOSレベルで「使用中」となり、削除や移動、あるいは再オープンが不可能になる。これを防ぐことが、バッチ処理の安定性を決定づける。
② 徹底した「例外の隔離」
`ExecuteBatchProcess`(司令塔)と `ProcessSingleDrawing`(実行部)を分けた。
司令塔は `On Error Resume Next` を使い、実行部で発生したエラーを「検知」して「記録」することだけに専念している。これにより、「1つの図面が壊れていても、残りの99枚を完遂する」という、業務ツールとして最低限の要件を満たしている。
③ Pre-flight Check(事前検証)
いきなり `Documents.Open` を叩くのではなく、`Dir(filePath)` でファイルの存在を確認している。ネットワークドライブ上のパスが切れているような初歩的なミスで、AutoCADのCOMインターフェースをエラーで叩き起こさないための、プロの作法だ。
④ 監査ログ(Audit Trail)
処理結果をExcelの隣の列に書き出す。これは単なる親切心ではない。大量の図面を処理した後、「どのファイルが、なぜ失敗したのか」を即座に特定できないツールは、現場では「使い物にならないゴミ」として扱われる。
5. さらなる高みへ:パフォーマンスと拡張性
もし、あなたが数千枚規模の図面を扱うなら、以下のステップを検討せよ。
1. UIの非表示化: `acadApp.Visible = False` にすることで、描画負荷を下げ、処理速度を劇的に向上させることができる。ただし、デバッグ時は `True` にしておくこと。
2. マルチスレッドの検討: VBA自体はシングルスレッドだが、AutoCADのプロセスを複数立ち上げる(極めて困難だが)か、あるいはC# (.NET) で `Object Model` を叩く設計に移行することで、並列処理が可能になる。
3. データベース連携: Excelを捨て、SQL Serverなどのデータベースからリストを取得するようにすれば、多人数での同時作業に耐えうる真のエンタープライズ・ツールへと進化する。
「動くコード」を書くのは初級者だ。「運用に耐えうるシステム」を書くのが、我々プロフェッショナルの仕事である。
