AcadApplication.GetInterfaceObjectによる「Sheet Set Manager」の極限操作:複数図面を跨ぐシート番号の一括更新と自動採番
大規模プロジェクトにおける図面管理の要でありながら、AutoCAD VBAの標準的なオブジェクトモデルからは容易にアクセスできないブラックボックス――それが「シートセットマネージャ(Sheet Set Manager: SSM)」である。
数百枚におよぶ図面(.dwg)のシート番号やタイトルを手作業で更新する作業は、ヒューマンエラーの温床であり、エンジニアリングにおける最大のボトルネックの一つだ。これを自動化するには、AutoCADの内部COMコンポーネントである `AcSmComponents`(Sheet Set Manager API)へ直接アクセスし、メモリ管理と排他制御を完全に掌握する必要がある。
本稿では、`AcadApplication.GetInterfaceObject` を駆使し、VBAからシートセットデータベース(.dst)を直接マニピュレートして、複数図面を跨ぐシート番号の一括更新と自動採番を極めて堅牢に実行するプロトタイプコードとそのアーキテクチャを解説する。
—
1. アーキテクチャの真髄:なぜ `GetInterfaceObject` なのか?
AutoCADのActiveX/COM API(`AcadDocument` や `AcadDatabase` など)は、図面内のエンティティ操作には優れているが、シートセットの管理機能を持たない。シートセットは図面とは独立した外部データベース(.dstファイル)として管理されているためだ。
このDSTファイルを操作するためには、AutoCADが内部的に保持しているCOM DLL(`AcSmComponentsXX.dll`)のインターフェースを動的に取得しなければならない。
[ AutoCAD VBA (Host) ]
│
├─ (GetInterfaceObject) ──> [ AcSmComponents (COM Object) ]
│ │
│ (DST Database Open)
│ │
▼ ▼
[ Active Document ] [ .dst File (Sheet Set) ]
バージョン依存性(ProgID)の壁
`GetInterfaceObject` に渡す ProgID(Program ID)は、AutoCADのバージョン(内部バージョン)によって異なる。このバージョン依存性をコード内で動的に吸収しなければ、レガシー環境やマルチバージョンが混在するオフィスでの保守は不可能となる。
- AutoCAD 2021: `AcSmComponents24.AcSmSheetSetMgr`
- AutoCAD 2022: `AcSmComponents24.AcSmSheetSetMgr`
- AutoCAD 2023: `AcSmComponents24.AcSmSheetSetMgr`
- AutoCAD 2024: `AcSmComponents24.AcSmSheetSetMgr`
- AutoCAD 2025: `AcSmComponents25.AcSmSheetSetMgr` (※バージョンにより追従)
本設計では、ホストアプリケーションの `Version` プロパティからメジャーバージョンを解析し、適切な ProgID を動的に生成する。
—
2. メモリリークとの闘い:COM参照カウントの制御
VBAから外部COMオブジェクト(特に `AcSmComponents`)を操作する際、最も致命的なバグを引き起こすのが「メモリリークによるDSTファイルのロック残留」である。
DSTファイルはマルチユーザー環境で共有されることが多く、コードの異常終了やオブジェクトの解放漏れが発生すると、DSTファイルが不完全にロックされ、他のユーザーがシートセットを開けなくなる。これを防ぐために、以下の3原則を厳守しなければならない。
1. 決定論的デストラクション: `Set Object = Nothing` による明示的解放を、呼び出しスタックの逆順で確実に行う。
2. 構造化例外処理(Structured Exception Handling): 途中でエラーが発生した場合でも、必ずロックを解除(`UnlockDb`)し、ファイルをクローズする専用のクリーンアップブロック(`Catch` / `Finally` 相当)を通す。
3. グローバル・ステートの回避: 可能な限りローカル変数にオブジェクトを閉じ込め、スコープアウト時の自動解放に頼るのではなく、明示的に解放する。
—
3. 実装:シートセット一括自動採番システム
以下に、指定したDSTファイルを開き、シートセット内の全シートを再帰的に走査し、指定したプレフィックスと桁数でシート番号(Sheet Number)を自動的にリナンバリングする、実戦仕様のVBAコードを示す。
参照設定について
本コードはレイトバインディング(動的結合)で記述されているため、参照設定(References)への追加は不要である。環境の差異に左右されず、コピペのみで即座に動作する。
Option Explicit
‘ ==============================================================================
‘ Windows API宣言 (パフォーマンス計測および待機処理用)
‘ ==============================================================================
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
‘ ==============================================================================
‘ システム定数
‘ ==============================================================================
Private Const COMPONENT_CLASS_BASE As String = “AcSmComponents”
Private Const MANAGER_CLASS_NAME As String = “AcSmSheetSetMgr”
”’
”’
Public Sub ForceRenumberSheetSet()
Dim acadApp As AcadApplication
Set acadApp = ThisDrawing.Application
‘ ————————————————————————–
‘ 1. 設定パラメータ(実務に応じて動的に取得・変更可能)
‘ ————————————————————————–
Dim dstFilePath As String
‘ ※環境に合わせた DST ファイルのフルパスを指定してください
dstFilePath = “C:\Projects\SampleProject\ProjectLayout.dst”
Dim sheetNumberPrefix As String: sheetNumberPrefix = “A-”
Dim startNumber As Long: startNumber = 100
Dim paddingWidth As Integer: paddingWidth = 3 ‘ 例: “A-100”, “A-101″…
‘ ————————————————————————–
‘ 2. バージョン適合型 ProgID の動的解決
‘ ————————————————————————–
Dim progID As String
progID = GetSheetSetMgrProgID(acadApp)
If progID = “” Then
MsgBox “適切な AcSmComponents がシステム内に検出できませんでした。”, vbCritical, “初期化エラー”
Exit Sub
End If
‘ ————————————————————————–
‘ 3. COMオブジェクトの初期化
‘ ————————————————————————–
Dim sheetSetMgr As Object
On Error Resume Next
Set sheetSetMgr = acadApp.GetInterfaceObject(progID)
On Error GoTo 0
If sheetSetMgr Is Nothing Then
MsgBox “Sheet Set Manager COMオブジェクトのインスタンス化に失敗しました。” & vbCrLf & _
“ProgID: ” & progID, vbCritical, “COMエラー”
Exit Sub
End If
‘ ————————————————————————–
‘ 4. DST データベースのオープンとロック
‘ ————————————————————————–
Dim database As Object
Dim lockStatus As Long
On Error GoTo ErrorHandler
‘ OpenDatabase の第二引数は「プロンプト表示フラグ(False = 非表示)」
Set database = sheetSetMgr.OpenDatabase(dstFilePath, False)
If database Is Nothing Then
Err.Raise vbObjectError + 1001, “SheetSetEngine”, “DST ファイルを開けませんでした。パスを確認してください。”
End If
‘ データベースの排他ロックを確保(書き込みを行うための必須処理)
lockStatus = database.LockDb(database)
If lockStatus = 0 Then ‘ 0 = AcSmLoadStatus_Success
‘ ロック成功
Else
Err.Raise vbObjectError + 1002, “SheetSetEngine”, “データベースのロックに失敗しました。他ユーザーが編集中です。”
End If
‘ ————————————————————————–
‘ 5. シートセット構造の走査と一括更新
‘ ————————————————————————–
Dim sheetSet As Object
Set sheetSet = database.GetSheetSet()
Dim currentNumber As Long
currentNumber = startNumber
‘ 再帰的走査プロシージャの呼び出し
Call TraverseAndRenumber(sheetSet, sheetNumberPrefix, currentNumber, paddingWidth)
‘ ————————————————————————–
‘ クリーンアップ & 正常終了処理
‘ ————————————————————————–
‘ 変更をコミットしてロック解除
database.UnlockDb database, True
sheetSetMgr.Close database
‘ 参照の明示的解放
Set sheetSet = Nothing
Set database = Nothing
Set sheetSetMgr = Nothing
MsgBox “シート番号の一括自動更新が正常に完了しました。”, vbInformation, “処理完了”
Exit Sub
ErrorHandler:
Dim errDesc As String
errDesc = Err.Description
‘ 異常終了時のフォールバック処理(ロックの確実な解除)
On Error Resume Next
If Not database Is Nothing Then
database.UnlockDb database, False ‘ 変更をロールバックして解除
sheetSetMgr.Close database
End If
Set sheetSet = Nothing
Set database = Nothing
Set sheetSetMgr = Nothing
MsgBox “エラーが発生したため、処理を中断しました。” & vbCrLf & “詳細: ” & errDesc, vbCritical, “システムエラー”
End Sub
”’
”’
Private Function GetSheetSetMgrProgID(ByVal acadApp As AcadApplication) As String
Dim versionString As String
versionString = acadApp.Version
‘ AutoCADのバージョン文字列(例: “24.1s (LMS Tech)”)からメジャーバージョンを抽出
Dim majorVersion As Double
majorVersion = Val(versionString)
Dim suffix As String
‘ AutoCAD内部バージョンとCOMコンポーネントクラスの紐付け
‘ 24.0 (AutoCAD 2021) ~ 24.3 (AutoCAD 2024) -> AcSmComponents24
‘ 25.0 (AutoCAD 2025) -> AcSmComponents25
If majorVersion >= 25# Then
suffix = “25”
ElseIf majorVersion >= 24# Then
suffix = “24”
ElseIf majorVersion >= 23# Then
suffix = “23”
ElseIf majorVersion >= 22# Then
suffix = “22”
ElseIf majorVersion >= 21# Then
suffix = “21”
Else
suffix = “20” ‘ レガシーサポートフォールバック
End If
GetSheetSetMgrProgID = COMPONENT_CLASS_BASE & suffix & “.” & MANAGER_CLASS_NAME
End Function
”’
”’
Private Sub TraverseAndRenumber( _
ByVal parentComponent As Object, _
ByVal prefix As String, _
ByRef currentNumber As Long, _
ByVal paddingWidth As Integer)
Dim enumerator As Object
Set enumerator = parentComponent.GetEnumerator()
Dim component As Object
Set component = enumerator.Next()
Do While Not component Is Nothing
‘ オブジェクトのクラス名(型情報)を取得
Dim typeName As String
typeName = component.GetTypeName()
Select Case typeName
Case “AcSmSheet”
‘ シートオブジェクトである場合、採番を実行
Dim sheet As Object
Set sheet = component
‘ 新しいシート番号の生成 (例: “A-” & Format(100, “000”) -> “A-100”)
Dim newNumber As String
newNumber = prefix & Format(currentNumber, String(paddingWidth, “0”))
‘ シートプロパティの書込み
sheet.SetNumber newNumber
‘ カウンタのインクリメント
currentNumber = currentNumber + 1
Set sheet = Nothing
Case “AcSmSubset”
‘ サブセット(フォルダ構造)である場合、さらに深く再帰走査
Call TraverseAndRenumber(component, prefix, currentNumber, paddingWidth)
End Select
‘ 次のコンポーネントへ遷移
Set component = enumerator.Next()
Loop
‘ メモリ解放
Set enumerator = Nothing
Set component = Nothing
End Sub
—
4. プロフェッショナルが知るべき運用の罠とTips
実務でこのコードを運用するにあたり、設計書に書き加えるべき「極限の知見」を共有する。
1. DSTファイルの遅延書き込み(非同期処理の壁)
シートセットマネージャのAPIを叩いて `UnlockDb` をコールした瞬間、DSTファイルへの書き込みがOSレベルで非同期に行われることがある。
大量のシート(500枚以上など)を処理した直後に、VBA側で即座に次の図面オープン処理に移行すると、DSTファイルが一時的に「使用中」となり、競合エラーを引き起こす。
これを防ぐため、一連の処理終了後に `Sleep 500`(500ミリ秒待機)を挟み、OSのI/Oキューが完全にフラッシュされるのを待つのが、現場で培われた堅牢なプラクティスである。
2. レイアウトとの同期(フィールドの自動更新)
シートセットマネージャ側でシート番号を書き換えても、各図面のペーパー空間(レイアウト)に配置された「シート番号フィールド」は自動的には更新されない。
図面を開いたタイミング、または印刷(パブリッシュ)時に自動更新されるが、強制的に同期させたい場合は、図面を開き、以下のコマンドをVBAから `SendStringToExecute` で流し込む必要がある。
ThisDrawing.SendCommand “UPDATEFIELD” & vbCrLf & “ALL” & vbCrLf & vbCrLf
3. DSTデータベースの破損対策
万が一、コードの実行中にAutoCADが強制終了した場合、DSTデータベースが破損し、読み込み不可能になるリスクがある。
プロフェッショナルなシステム管理者が構築するツールでは、処理の最重要フェーズに入る直前に、DSTファイルの「シャドウコピー(バックアップ)」を一時フォルダに作成するロジックを必ず組み込む。これにより、`On Error` ブロック内でバックアップからリストアする仕組みを構築でき、人為的なデータ消失リスクをゼロに抑え込むことができる。
