【実務・中級編】【実務中級者向け】図面内の「特定ブロックの属性値」をファイル名に採用して自動リネーム保存するツール開発 – AutoCAD VBA解析バイブル

スポンサーリンク
スポンサーリンク

属性値による図面自動リネームの極意:堅牢なプロダクションコードを設計する

「ブロック内の属性値を読み取って、その値でファイル名を付けて保存する」

一見すると、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による自動化の本質は、コードの行数ではない。「想定外の事態が起きても、システムを壊さず、ユーザーに正しい情報を返す」という堅牢性にある。

今回紹介した、責務の分離、サニタイズ、そして詳細なエラーハンドリングを実装することで、君の書くコードは「単なるスクリプト」から「信頼される業務システム」へと進化する。

さあ、この設計思想を胸に、次のプロジェクトに挑んでほしい。

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