属性値による図面自動リネームの極意:堅牢なプロダクションコードを設計する
「ブロック内の属性値を読み取って、その値でファイル名を付けて保存する」
一見すると、AutoCAD VBAの初級・中級者が最初に取り組む典型的な課題に見えるだろう。しかし、現場で実際に運用され、数千、数万の図面を管理する「プロダクションレベル」のツールとして昇華させるには、単なる`SaveAs`の実行以上の、極めて高度な設計思想が求められる。
多くの開発者が陥る罠がある。それは、「図面データ(AutoCAD内部)」と「ファイルシステム(OS)」の境界における例外処理の欠如だ。属性値に「/」や「*」が混じっていたら? 属性値が空だったら? 保存先のディレクトリに書き込み権限がなかったら? あるいは、同名のファイルが既に開かれていたら?
本記事では、これらのエッジケースを完全に掌握し、バグの入り込む余地を排除した、プロフェッショナル仕様の自動リネームツールの設計と実装を伝授する。
1. 設計思想:なぜ「単純なループ」では失敗するのか
初心者は、`ModelSpace`内のすべてのオブジェクトをループし、`IsBlock`を判定して、属性を探すという書き方をする。これは動くが、「計算コスト」と「堅牢性」の観点から見て極めて不適切だ。
効率的な探索:ターゲットの絞り込み
図面枠(Title Block)は通常、特定のブロック名を持っている。全オブジェクトを走査するのではなく、ブロック定義の特性を利用して、目的の属性へ最短距離でアクセスする設計が必要だ。
データの整合性:ファイルシステムの制約
AutoCADの属性値は「文字列」だが、OSのファイル名として使えない文字(`\ / : * ? ” < > |`)が混入する可能性は常に存在する。これを事前に「サニタイズ(無害化)」する工程を、ロジックの最優先事項として組み込まなければならない。
2. 実装:プロフェッショナル・アーキテクチャ
以下に、保守性と堅牢性を極限まで高めたコード例を示す。このコードは、単一のプロシージャに全てを詰め込むのではなく、「抽出」「洗浄」「保存」の責務を分離している。
Option Explicit
' ==============================================================================
' Module: DrawingAutoRenamer
' Description: 特定ブロックの属性値に基づき、図面をリネーム保存する
' Author: Chief Architect
' ==============================================================================
' 定数定義:マジックナンバーを排除し、保守性を高める
Private Const TARGET_BLOCK_NAME As String = "TITLE_BLOCK_NAME" ' ターゲットとなる図面枠ブロック名
Private Const ATTR_TAG_DRAWING_NO As String = "DWG_NO" ' 図番属性タグ
Private Const ATTR_TAG_TITLE As String = "TITLE" ' 名称属性タグ
Private Const DEFAULT_EXTENSION As String = ".dwg"
''' <summary>
''' メインエントリポイント:図面の自動リネーム・保存を実行する
''' </summary>
Public Sub ExecuteAutoRename()
Dim acadDoc As AcadDocument
Set acadDoc = ThisDrawing
On Error GoTo ErrorHandler
' 1. 属性値の抽出
Dim drawingNo As String
Dim drawingTitle As String
If Not GetDrawingAttributes(acadDoc, drawingNo, drawingTitle) Then
MsgBox "エラー: 指定されたブロックまたは属性値が見つかりません。", vbCritical
Exit Sub
End If
' 2. ファイル名の生成とサニタイズ
Dim newFileName As String
newFileName = SanitizeFileName(drawingNo & "_" & drawingTitle)
If newFileName = "" Then
MsgBox "エラー: 有効なファイル名を作成できませんでした。", vbCritical
Exit Sub
End If
' 3. 保存パスの決定(現在のフォルダをベースにする例)
Dim currentPath As String
currentPath = acadDoc.Path
If currentPath = "" Then
Msg[] "エラー: 図面が一度も保存されていません。先に手動で保存してください。", vbCritical
Exit Sub
End If
Dim fullSavePath As String
fullSavePath = currentPath & "\" & newFileName & DEFAULT_EXTENSION
' 4. 保存の実行(SaveAs)
' 注意:SaveAsは現在のドキュメントを新しい名前で上書き保存し、
' アクティブなドキュメントをその新しいファイルに切り替える。
acadDoc.SaveAs fullSavePath
MsgBox "保存完了: " & vbCrLf & fullSavePath, vbInformation
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 70 ' Permission Denied
MsgBox "エラー: ファイルが他のプロセスによって使用されているか、書き込み権限がありません。", vbCritical
Case 76 ' Path not found
MsgBox "エラー: 指定されたパスが見つかりません。", vbCritical
Case Else
MsgBox "予期せぬエラーが発生しました: " & Err.Description, vbCritical
End Select
End Sub
''' <summary>
''' 図面内の特定ブロックから属性値を取得する
''' </summary>
Private Function GetDrawingAttributes(doc As AcadDocument, ByRef dwgNo As String, ByRef title As String) As Boolean
Dim ent As AcadEntity
Dim blockRef As AcadBlockReference
Dim attrColl As Variant
Dim i As Integer
Dim foundNo As Boolean: foundNo = False
Dim foundTitle As Boolean: foundTitle = False
' ModelSpaceを走査(実務ではBlockSpaceも検討すべき)
For Each ent In doc.ModelSpace
If TypeOf ent Is AcadBlockReference Then
Set blockRef = ent
' ブロック名が一致するか確認(大文字小文字を区別しない)
If UCase(blockRef.Name) = UCase(TARGET_BLOCK_NAME) Then
' 属性コレクションの取得
Dim attrs As AcadAttributeCollection
Set attrs = blockRef.GetAttributes
For i = 0 To attrs.Count - 1
Dim att As AcadAttribute
Set att = attrs.Item(i)
Select Case UCase(att.TagString)
Case UCase(ATTR_TAG_DRAWING_NO)
dwgNo = att.TextString
foundNo = True
Case UCase(ATTR_TAG_TITLE)
title = att.TextString
foundTitle = True
End Select
Next i
' 両方の属性が見つかれば終了
If foundNo And foundTitle Then
GetDrawingAttributes = True
Exit Function
End If
End If
End If
Next ent
GetDrawingAttributes = False
End Function
''' <summary>
''' ファイル名として使用不可能な文字を置換・除去する
''' </summary>
Private Function SanitizeFileName(ByVal fileName As String) As String
Dim invalidChars As Variant
Dim i As Integer
' Windowsのファイル名禁止文字
invalidChars = Array("\", "/", ":", "*", "?", """", "<", ">", "|")
' 禁止文字をアンダースコアに置換
For i = LBound(invalidChars) To UBound(invalidChars)
fileName = Replace(fileName, invalidChars(i), "_")
Next i
' 前後の空白を除去
SanitizeFileName = Trim(fileName)
End Function
3. アーキテクトの視点:実務で差がつく「3つの重要事項」
コードを書いただけで満足してはいけない。このツールを「現場の武器」にするためには、以下の概念を理解しておく必要がある。
① バージョン互換性の制御
`SaveAs`メソッドには、保存するファイルのバージョンを指定する引数がある。
実務では、古いバージョンのAutoCADを使用しているクライアントにファイルを渡す必要がある。その場合、単に`SaveAs`を呼ぶのではなく、`acR2013`(AutoCAD 2013形式)などの定数を意識した設計が必要だ。
② エラーハンドリングの階層化
上記のコードでは`Select Case Err.Number`を用いている。これは、単に「エラーが出ました」と表示するのではなく、「なぜ失敗したのか(権限か、パスか、ファイルロックか)」をユーザーに明確に伝えるためだ。これができていないツールは、ユーザーの作業を止める「ストレスの源」になる。
③ 属性値の「空」に対するガード
属性タグは存在するが、値が入力されていないケースは日常茶飯事だ。`GetDrawingAttributes`関数内で、値が空文字列(`””`)の場合にどのように振る舞うか(デフォルト値を使うのか、エラーとするのか)を、業務要件に基づいて厳格に定義せよ。
結論
AutoCAD VBAによる自動化の本質は、コードの行数ではない。「想定外の事態が起きても、システムを壊さず、ユーザーに正しい情報を返す」という堅牢性にある。
今回紹介した、責務の分離、サニタイズ、そして詳細なエラーハンドリングを実装することで、君の書くコードは「単なるスクリプト」から「信頼される業務システム」へと進化する。
さあ、この設計思想を胸に、次のプロジェクトに挑んでほしい。
