【上級】AcadDocument.Database.LayoutsのTabOrder再整列:図面名称の連番に基づくレイアウトタブ自動ソートの極意
図面管理の現場において、レイアウトタブの順序は単なる「見た目」の問題ではない。それは設計思想の順序であり、納品物としての品質を担保する不可欠な要素だ。
しかし、実務ではどうか。設計変更や急ぎの修正、あるいは外部から受領した図面を寄せ集めた結果、レイアウトタブの順序はバラバラになり、「Model」の直後に「S-105」が来たり、枝番が乱雑に錯綜したりする。これを手動でドラッグ&ドロップして整列させるなどというのは、エンジニアの労力の無駄遣いであり、ヒューマンエラーの温床でしかない。
今回は、AutoCAD VBAのオブジェクトモデルの深層に踏込み、`AcadDocument.Database.Layouts` コレクションと `TabOrder` プロパティを完全に手なずけて、図面名称の連番に基づいてレイアウトタブを完璧に昇順・降順ソートする極限のアルゴリズムを伝授する。
レガシーなVBAの限界を突破し、メモリリークを根絶するためのアーキテクチャ設計を共に見据えよう。
—
1. AutoCADオブジェクトモデルの裏側:LayoutsとTabOrderの罠
AutoCAD VBAにおいて、レイアウトを操作する経路は主に2つ存在する。
1. `ThisDrawing.Layouts` (または `AcadDocument.Layouts`)
2. `ThisDrawing.Database.Layouts`
一見して前者が手軽に見えるが、システム開発の観点からはDatabase直下のLayoutsコレクションを直接叩くべきだ。図面データベース(Database)のトランザクション管理とオブジェクトライフサイクルに直結しており、予期せぬCOMの参照ズレを防ぐことができる。
TabOrderの仕様の歪み
各 `AcadLayout` オブジェクトには `TabOrder` という整数プロパティが存在する。これがタブの物理的な並び順を決定するが、ここには致命的な特性がある:
- 「Model」タブのTabOrderは常に `0` 固定である。
- ペーパー空間のレイアウトの `TabOrder` は `1` から始まる連続した整数値を持つべきだが、要素の削除や追加を繰り返すと、このインデックスが歯抜けになったり、重複したりする。
したがって、単に文字列ソートした結果をそのまま代入するだけでは、AutoCAD内部のインデックス整合性が崩れ、最悪の場合クラッシュを引き起こす。「全レイアウトのTabOrderを一度一意にクリアし、ソート順に従って `1` から連番で再割り当てする」というアトミックな処理が必須となる。
—
2. 実装コード:堅牢性とパフォーマンスを極めたソートエンジン
以下のコードは、実務の現場で即座に稼働しうる、エラーハンドリングとメモリ管理を徹底したプロダクションクオリティのVBAマクロである。図面名称(レイアウト名)に含まれる数字を抽出し、自然順序(Natural Sort)に近い形で正確に並び替えるロジックを組み込んでいる。
Option Explicit
‘ =================================================================================
‘ módulo: ModLayoutSorter
‘ 概要: アクティブ図面のレイアウトタブを名称の連番に基づいて自動ソートする
‘ アーキテクト: チーフアーキテクチャ・シニアエンジニアリング
‘ =================================================================================
Public Sub SortLayoutTabsByNumber()
Dim acadDoc As AcadDocument
Set acadDoc = ThisDrawing
‘ トランザクション的スコープの確保とエラー監視
On Error GoTo ErrorHandler
‘ 1. ソート対象のレイアウト情報を格納する動的配列の準備
Dim layoutsList() As Variant
Dim count As Long
count = 0
Dim oLayout As AcadLayout
Dim db As AcadDatabase
Set db = acadDoc.Database
‘ 2. Modelタブを除外してペーパー空間レイアウトのみを収集
For Each oLayout In db.Layouts
If Not (UCase(oLayout.Name) = “MODEL”) Then
ReDim Preserve layoutsList(1, count)
layoutsList(0, count) = oLayout.Name
layoutsList(1, count) = oLayout ‘ COMオブジェクトの参照保持
count = count + 1
End If
Next oLayout
If count <= 1 Then
MsgBox "ソート対象のレイアウトが不足しています。", vbInformation, "レイアウト整列"
GoTo CleanUp
End If
' 3. バブルソートアルゴリズムによるレイアウト名昇順ソート(数値抽出考慮)
' ※大規模図面の場合はQuickSort等の実装に差し替えてください
Dim i As Long, j As Long
Dim tempName As Variant, tempObj As Variant
For i = 0 To count - 2
For j = i + 1 To count - 1
If CompareLayoutNames(layoutsList(0, i), layoutsList(0, j)) > 0 Then
‘ 名前の入れ替え
tempName = layoutsList(0, i)
layoutsList(0, i) = layoutsList(0, j)
layoutsList(0, j) = tempName
‘ オブジェクト参照の入れ替え
Set tempObj = layoutsList(1, i)
Set layoutsList(1, i) = layoutsList(1, j)
Set layoutsList(1, j) = tempObj
End If
Next j
Next i
‘ 4. TabOrderの再整列(アトミックなインデックス再割り当て)
‘ Modelタブが常に0であることを前提に、1から連番を振る
For i = 0 To count – 1
Set oLayout = layoutsList(1, i)
‘ TabOrderの割り当て(Cintによる型安全性の確保)
oLayout.TabOrder = CInt(i + 1)
Next i
‘ 5. 画面の強制再描画とビューポートの整合性維持
acadDoc.Regen acAllViewports
MsgBox “レイアウトタブの自動ソートが正常に完了しました。” & vbCrLf & _
“処理件数: ” & count & ” レイアウト”, vbInformation, “完了”
CleanUp:
‘ 6. メモリリーク防止:COMオブジェクトの明示的解放
On Error Resume Next
Erase layoutsList
Set oLayout = Nothing
Set db = Nothing
Set acadDoc = Nothing
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
Resume CleanUp
End Sub
‘ =================================================================================
‘ 補助関数: レイアウト名の比較ロジック(数値の大小を考慮したカスタム比較)
‘ =================================================================================
Private Function CompareLayoutNames(ByVal name1 As String, ByVal name2 As String) As Integer
‘ 単純な文字列比較ではなく、文字列内の数値を抽出して数値として比較する(例: S-2 < S-10)
Dim num1 As Long, num2 As Long
num1 = ExtractLeadingNumber(name1)
num2 = ExtractLeadingNumber(name2)
If num1 <> num2 And num1 >= 0 And num2 >= 0 Then
CompareLayoutNames = Sgn(num1 – num2)
Else
‘ 数値が取得できない場合は通常の文字列比較にフォールバック
CompareLayoutNames = StrComp(name1, name2, vbTextCompare)
End If
End Function
‘ =================================================================================
‘ 補助関数: 文字列から最初に現れる数値を抽出する
‘ =================================================================================
Private Function ExtractLeadingNumber(ByVal targetStr As String) As Long
Dim i As Long
Dim digits As String
digits = “”
For i = 1 To Len(targetStr)
Dim c As String
c = Mid(targetStr, i, 1)
If c Like “[0-9]” Then
digits = digits & c
ElseIf Len(digits) > 0 Then
‘ 数字の連続が途切れたらそこで抽出を打ち切る(必要に応じて調整)
Exit For
End If
Next i
If Len(digits) > 0 Then
ExtractLeadingNumber = CLng(digits)
Else
ExtractLeadingNumber = -1
End If
End Function
—
3. チーフアーキテクトが解説するコードの急所と技術的ポイント
オブジェクトの明示的解放(メモリ管理の鉄則)
VBAのガベージコレクションは頼りにならない。特にAutoCADのCOMラッパーオブジェクト (`AcadLayout` や `AcadDatabase`) をループ内で大量に生成・破棄する場合、参照カウントが残存し、AutoCADプロセスのメモリフットプリントが肥大化する。
コード内の `CleanUp` ラベルにあるように、使用後のオブジェクト変数には必ず `Set oLayout = Nothing` を明示し、配列自体も `Erase` によってヒープメモリを即時解放する設計を徹底している。
自然順序(Natural Sort)の重要性
素朴なVBAの `>` 演算子や `StrComp` によるソートでは、`”S-10″` と `”S-2″` を比較した際、文字コード順(`”S-1″` が `”S-2″` より先)になってしまうため、`”S-1″` の次に `”S-10″`、そのあとに `”S-2″` が来てしまうという悲劇が起きる。
これを防ぐため、`ExtractLeadingNumber` 関数によって文字列内の数値をパースし、数値としての大小比較(`Sgn(num1 – num2)`)を優先させる堅牢なアルゴリズムを採用している。
Transaction と Regen の整合性
`TabOrder` の書き換えは図面データベースに対する直接的なメタデータ変更である。変更直後にAutoCADのビューポート描画エンジンが追従しきれず、タブの表示がバグる現象を防ぐため、処理の最後に `acadDoc.Regen acAllViewports` を明示的に呼び出し、グラフィックスキャッシュとUIの同期を強制している。
—
4. エンタープライズ環境への展開:システム間連携とバッチ処理
もしこれが単体図面ではなく、数千枚におよぶ社内ライブラリ図面の一括統制であれば、VBAのマクロを1枚ずつ手動で叩くなどナンセンスだ。
このVBAロジックの核心部は、そのまま C# (.NET API) または COM Automation経由の外部VBScript / PowerShell に移植可能である。外部から `AcadApplication.Documents.Open` を叩き、サイレントモード(`SDI=0` またはドキュメント非表示)で上記のソートロジックを走らせて `Save` するバッチエンジンを構築すれば、夜間のうちに全社の図面資産のレイアウト順序を完全に規程化・統一することが可能となる。
レガシーなVBAであっても、その背後にあるアーキテクチャの本質(状態管理、メモリ最適化、アルゴリズムの選択)を理解していれば、最新のBIM/CIMワークフローに匹敵する堅牢な自動化基盤を構築できる。
あなたの図面管理の現場から、タブの並び替えという無駄な手作業を今すぐ根絶せよ。
