【実務・中級編】【実務中級者向け】図面保存と連動した「自動e-トランスミット」:外部参照とフォントをパッケージ化して保存する – AutoCAD VBA解析バイブル

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

図面保存を「納品」に変える:堅牢な自動e-Transmit・パッケージング・アーキテクチャ

「図面を保存して、関連する外部参照やフォントも一緒に送っておいて」

クライアントや他部署から、何度この依頼を受けたことがあるだろうか。多くの開発者は、ここで安易な解決策に手を出す。`FileCopy` ステートメントを使い、図面と同じフォルダにあるファイルを力技でコピーする……。

断言しよう。それは「ツール」ではない。単なる「バグの温床」だ。

外部参照(Xref)のパスが相対パスか絶対パスか、ネスト(入れ子)構造になっているか、あるいはフォント(SHX)や印刷スタイル(CTB)がシステムフォルダに依存していないか。これらを考慮せずに書かれたコードは、受け取った相手の環境で「リンク切れ」を連発し、君のエンジニアとしての信頼を失墜させる。

本記事では、単なるファイルコピーの枠を超えた、「依存関係を解析し、完全にパッケージ化する」ためのプロフェッショナルな設計思想と、実装コードを伝授する。

1. なぜ「単純なコピー」では失敗するのか:依存関係のグラフ理論

図面ファイル(DWG)は、単一のデータではない。それは、外部の資産(Assets)へのポインタが張り巡らされた「依存関係のハブ」である。

設計者が陥る典型的なミスは、以下の3点だ。

1. ネストされた依存関係の無視: AがBを参照し、BがCを参照している場合、AだけをコピーしてもCが欠落する。
2. パスの正規化不足: `..\..\Ref\base.dwg` のような相対パスを、パッケージ化する際の新しいディレクトリ構造でどう解決するかを計算していない。
3. リソースの重複と衝突: 異なるフォルダに同名の `detail.dwg` が存在する場合、単純なコピーでは上書きが発生し、図面が壊れる。

プロフェッショナルな実装には、図面を起点とした「依存関係のグラフ(Dependency Graph)」をメモリ上に構築し、全ノード(ファイル)を走査するプロセスが不可欠だ。

2. アーキテクチャ設計:3層レイヤー・アプローチ

堅牢なツールを作るために、ロジックを以下の3つのレイヤーに分離して設計する。

Layer 1: Discovery (探索層)

`ThisDrawing.Blocks` コレクションを走査し、`IsXRef` プロパティが `True` であるものを抽出する。同時に、画像(Raster Image)や、レイアウト設定から紐付く印刷スタイル(CTB)を特定する。

Layer 2: Resolution (解決層)

取得したパスを「絶対パス」に変換し、重複を除去する(`Scripting.Dictionary` を使用)。ここで、コピー先フォルダでの「新しい相対パス」を計算するロジックを組み込む。

Layer 3: Execution (実行層)

`FileSystemObject` を用い、ディレクトリ構造を再構築しながらファイルを物理的にコピーする。

3. プロダクション・グレードの実装例

以下に、実務でそのまま利用可能なレベルのコードを示す。単なるコピペではなく、各ブロックが持つ「意味」を理解して読み解いてほしい。

※ 事前に VBA Editor -> Tools -> References で “Microsoft Scripting Runtime” を参照設定しておくこと。これは、高速な重複チェックとファイル操作に不可欠だ。

Option Explicit

''' <summary>
''' 図面保存と連動した自動パッケージング・ルーチン
''' </summary>
Public Sub ExecuteSmartETransmit()
    Dim fso As New FileSystemObject
    Dim dictFiles As New Scripting.Dictionary
    Dim targetFolder As String
    Dim dwgPath As String
    Dim currentDrawing As AcadDocument
    
    Set currentDrawing = ThisDrawing
    dwgPath = currentDrawing.FullName
    
    ' 1. 保存先ディレクトリの決定 (図面名_Package)
    targetFolder = fso.GetParentFolderName(dwgPath) & "\" & _
                   fso.GetBaseName(dwgPath) & "_Package"
    
    On Error GoTo ErrorHandler
    
    ' 2. 依存関係の探索 (Xref & Images)
    Debug.Print "--- Dependency Discovery Started ---"
    Call CollectDependencies(currentDrawing, dictFiles)
    
    ' 3. パッケージディレクトリの作成
    If Not fso.FolderExists(targetFolder) Then
        fso.CreateFolder targetFolder
    End If
    
    ' 4. ファイルの物理コピー実行
    Debug.Print "--- Copying Files to: " & targetFolder & " ---"
    Dim key As Variant
    For Each key In dictFiles.Keys
        ' key = 取得した絶対パス, dictFiles(key) = 保持するファイル名
        ' ※ 本来はここで相対パス構造を維持するロジックを挟むべきだが、
        ' 今回は簡略化のためフラットな構造で実装
        Call SafeFileCopy(CStr(key), targetFolder & "\" & dictFiles(key), fso)
    Next key
    
    ' 5. 本体の図面をパッケージフォルダへ保存
    currentDrawing.SaveAs targetFolder & "\" & fso.GetFileName(dwgPath)
    
    MsgBox "パッケージ化が完了しました。" & vbCrLf & "保存先: " & targetFolder, vbInformation
    Exit Sub

ErrorHandler:
    MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub

''' <summary>
''' 図面内のブロック(Xref)を走査し、依存ファイルをDictionaryに格納する
''' </summary>
Private Sub CollectDependencies(doc As AcadDocument, ByRef dict As Scripting.Dictionary)
    Dim objBlock As AcadBlock
    Dim xRefPath As String
    Dim fso As New FileSystemObject
    
    ' 全てのブロック(Xref含む)を走査
    For Each objBlock In doc.Blocks
        ' Xrefであるか、あるいは外部参照として定義されているかを確認
        If objBlock.IsXRef Then
            xRefPath = objBlock.Path
            
            ' パスが有効か、かつファイルが存在するかを検証
            If xRefPath <> "" And fso.FileExists(xRefPath) Then
                ' 重複を避けるため、絶対パスをキーにする
                ' 値には、ファイル名のみを格納(フラット構造用)
                If Not dict.Exists(xRefPath) Then
                    dict.Add xRefPath, fso.GetFileName(xRefPath)
                    Debug.Print "Found Xref: " & xRefPath
                End If
            End If
        End If
    Next objBlock
    
    ' 【拡張ポイント】ここに画像(RasterImage)やCTBの収集ロジックを追加可能
End Sub

''' <summary>
''' ファイルコピー時のエラーをハンドリングし、堅牢性を確保する
''' </summary>
Private Sub SafeFileCopy(source As String, dest As String, fso As FileSystemObject)
    On Error Resume Next
    ' 同名ファイルが既に存在する場合は削除してからコピー(上書き防止)
    If fso.FileExists(dest) Then fso.DeleteFile dest, True
    
    fso.CopyFile source, dest, True
    
    If Err.Number <> 0 Then
        Debug.Print "Failed to copy: " & source & " Error: " & Err.Description
    Else
        Debug.Print "Successfully copied: " & fso.GetFileName(source)
    End If
    On Error GoTo 0
End Sub

4. プロフェッショナルとしての「こだわり」:設計の深淵

このコードをベースに、さらに「製品レベル」へ引き上げるためのアドバイスを送る。

A. 衝突回避(Collision Avoidance)の重要性

上記のコードは簡略化のため、すべてのファイルをフラットなフォルダに集約している。しかし、実務では `ProjectA/Detail.dwg` と `ProjectB/Detail.dwg` が混在することがある。
真のアーキテクトなら、`dictFiles` に格納する値をファイル名ではなく、「ソースパスに基づいたサブディレクトリ構造」にするべきだ。
例:`targetFolder \ ProjectA \ Detail.dwg`

B. エラーハンドリングの哲学

`On Error Resume Next` は諸刃の剣だ。`SafeFileCopy` のように、「失敗してもプロセス全体を止めずに、ログ(Debug.Print)を残して次へ進む」という使い方は正しい。しかし、ディレクトリの作成や、メインの図面保存に失敗した場合は、即座に例外を投げてユーザーに通知しなければならない。

C. パフォーマンスの最適化

大量の外部参照があるプロジェクトでは、`FileSystemObject` の呼び出し回数がパフォーマンスに直結する。`Dictionary` を使って「コピーすべきリスト」を先に完全に確定させてから、一気にI/O処理を行う現在の設計は、計算量 $O(n)$ を維持するための正しい戦略だ。

結びに代えて

自動化ツールの価値は、「動くこと」ではなく「例外に対してどれだけ強靭か」で決まる。

「たぶん大丈夫だろう」という設計は、現場では「動かない」と同義だ。今回紹介した依存関係の解析、パスの正規化、そして堅牢なエラーハンドリングの概念を、君のコードに組み込んでほしい。

それが、単なるプログラマーと、伝説的なアーキテクトを分かつ境界線なのだから。

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