「FullName」の罠を回避せよ:AutoCAD自動化における「保存」の堅牢な設計思想
開発現場において、「動けばいい」というコードは、後に巨大な負債となる。
特にAutoCADのような、複雑なオブジェクトモデルとファイルシステムが密接に絡み合うアプリケーションの自動化において、その傾向は顕著だ。初心者が最も陥りやすく、かつ、業務ツールとしての信頼性を根底から覆すバグ――それが「未保存図面に対する保存処理の設計ミス」である。
本稿では、`FullName` プロパティの挙動を正確に理解し、新規図面(Drawing1.dwg等)を扱う際に発生するランタイムエラーを未然に防ぐ、プロフェッショナルな実装ロジックを伝授する。
1. なぜ `.Save` は裏切るのか:オブジェクトのライフサイクルを理解する
多くの開発者が、以下のような単純なコードを書く。
' 素人の書く、脆いコード
Sub SimpleSave()
ThisDrawing.Save
End Sub
一見、完璧に見える。しかし、ユーザーがAutoCADを立ち上げ、一度も保存していない状態でこのマクロを実行した瞬間、プログラムは悲鳴を上げ、実行時エラーを吐いて停止する。
核心的な問題:メモリ上の存在と物理ファイルの乖離
AutoCADの `Drawing` オブジェクトには、「メモリ上にのみ存在する状態」と「ストレージ(HDD/SSD)に実体として存在する状態」の2つのフェーズがある。
- 新規図面(Drawing1.dwg): 名前は付いているが、OSのファイルシステム上にはパスが存在しない。この状態では、`FullName` プロパティは「仮の名前」を返すが、実体がないため `.Save` メソッドは「どこに書き込めばいいのか分からない」という矛盾に直面し、エラーを発生させる。
- 保存済み図面: すでにパスが確定している。ここでは `.Save` が正しく機能する。
設計者として知っておくべきは、「プロパティの値が返ってくること」と「その操作が物理的に可能であること」は全く別問題であるという事実だ。
2. 実務的な解決策:条件分岐による「Save」と「SaveAs」の使い分け
堅牢なツールを作るなら、図面の「状態」を判定するガードロジックを必ず組み込まなければならない。判定の鍵は、`FullName` が指し示すパスが、実際にファイルシステムに存在するかどうか、あるいは図面が一度でも保存された実績があるかを見極めることにある。
プロダクションレベルの設計指針
1. 状態判定: `ThisDrawing.FullName` を取得し、それが物理的なパスとして有効かをチェックする。
2. 分岐処理:
- 既知のパスであれば、通常の `.Save` を実行。
- 未知のパス(新規図面)であれば、ユーザーに保存先を促すか、あらかじめ定義したルールに基づき `.SaveAs` を実行する。
3. 例外処理: ネットワークドライブの切断や、書き込み権限エラーを想定した `On Error` 処理を完備する。
3. 【実装例】堅牢な保存マネージャー・モジュール
以下に、そのまま実務のライブラリとして組み込めるレベルのコードを示す。単なる条件分岐ではなく、エラーハンドリングと、将来的なDB連携を見据えた設計思想を取り入れている。
Option Explicit
''' <summary>
''' 図面の保存状態を判断し、安全に保存を実行する。
''' 未保存の図面に対しては、指定されたパスへ SaveAs を試みる。
''' </summary>
''' <param name="defaultPath">新規保存時のデフォルトパス(任意)</param>
Public Sub SmartSave(Optional ByVal defaultPath As String = "")
On Error GoTo ErrorHandler
Dim targetDoc As AcadDocument
Set targetDoc = ThisDrawing
' 1. 図面が既に物理ファイルとして存在するか判定
' FullNameが空、もしくはパスに実体がない場合は新規図面とみなす
If IsNewDrawing(targetDoc) Then
Debug.Print "Status: New Drawing detected. Switching to SaveAs mode."
' 保存先パスの決定ロジック
Dim savePath As String
savePath = DetermineSavePath(defaultPath)
If savePath = "" Then
MsgBox "保存先が決定できませんでした。処理を中断します。", vbCritical
Exit Sub
End If
' 2. SaveAs による初回保存の実行
' 第2引数の FileFormat は、必要に応じて ac2018形式等に指定可能
targetDoc.SaveAs savePath
MsgBox "新規図面を保存しました: " & savePath, vbInformation
Else
' 3. 既存図面の場合は通常の Save を実行
Debug.Print "Status: Existing Drawing. Performing standard Save."
targetDoc.Save
End If
Exit Sub
ErrorHandler:
' 権限エラー、パス不正、ファイル使用中などのエラーを捕捉
MsgBox "保存中に致命的なエラーが発生しました。" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Description: " & Err.Description, vbCritical, "Save Error"
End Sub
''' <summary>
''' 図面が新規(未保存)かどうかを判定する内部関数
''' </summary>
Private Function IsNewDrawing(doc As AcadDocument) As Boolean
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
' FullNameが空、またはファイルシステム上に存在しない場合はTrue
If doc.FullName = "" Or Not fso.FileExists(doc.FullName) Then
IsNewDrawing = True
Else
IsNewDrawing = False
End If
Set fso = Nothing
End Function
''' <summary>
''' 保存パスを決定するロジック。
''' ここにデータベース連携や、命名規則に基づいたパス生成ロジックを実装する。
''' </summary>
Private Function DetermineSavePath(defaultPath As String) As String
' 本来はここで、DBから取得したプロジェクト番号や、
' ユーザーの環境変数に基づいたディレクトリ構築を行う。
Dim candidatePath As String
candidatePath = defaultPath
' デフォルトパスが空の場合、デスクトップをフォールバック先にする例
If candidatePath = "" Then
candidatePath = CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\AutoGenerated_" & Format(Now, "yyyymmdd_hhnnss") & ".dwg"
End If
DetermineSavePath = candidatePath
End Function
4. さらなる高みへ:データベース・ファイル連携における注意点
業務自動化ツールとして、このロジックをデータベース(SQL ServerやAccess等)と連携させる場合、設計者は以下の「重み」を意識しなければならない。
命名規則の強制(Determinism)
DBから図面情報を取得して自動生成する場合、`SaveAs` に渡すパスは決定論的(Deterministic)であるべきだ。つまり、「同じ入力に対して、常に同じパスが生成される」こと。
`DetermineSavePath` の中で、DBから取得した `ProjectID` や `DrawingNumber` を結合してパスを生成する設計にすれば、後続の工程(PDF出力やサーバーへのアップロード)との整合性が完璧に保たれる。
バージョン互換性の制御
`SaveAs` メソッドを使用する場合、単にパスを渡すだけでなく、`FileFormat` 引数を意識せよ。
クライアント環境が AutoCAD 2018(DWG 2018形式)である場合、古い形式で保存してしまうと、後続の処理でファイルが読み込めないリスクがある。
' 例: 明示的に2018形式を指定する場合(定数はAutoCADのバージョンに依存)
targetDoc.SaveAs savePath, ac2018format
結論
「`FullName` が取れるから `Save` でいいだろう」という安易な思考は、プロフェッショナルな開発現場では通用しない。
オブジェクトが「メモリ上の幻」なのか「ディスク上の実体」なのか。その境界線を常に意識し、`FileSystemObject` を用いた物理的な存在確認を組み込むこと。これこそが、バグを排除し、ユーザーから「このツールは信頼できる」と評される堅牢なシステムを構築するための、エンジニアとしての最低限の規律である。
