マスターシェイプの「魂」を継承せよ:Visioにおける大規模図面更新のアーキテクチャ
図面管理の現場において、ステンシル(Stencil)の進化は避けられない。数年前に定義された「サーバー」のマスターシェイプと、最新のコンプライアンスに基づいた「サーバー」では、描画レイヤーも、保持すべきシェイプデータ(Shape Data)の定義も異なる。
多くのエンジニアは、古いシェイプを見つけると「手動で削除して、新しいマスターから引き直す」という、あまりに低レイヤーで、かつリスクの高い作業を選択する。しかし、数千個のシェイプが散りばめられた大規模なネットワーク構成図やシステムアーキテクチャ図において、そのような作業は非効率の極みであり、さらに致命的な欠陥を招く。「接続関係(Connectors)の断絶」と「ユーザー定義データの消失」だ。
本稿では、単なる「置換」ではない。既存シェイプが保持する「状態(State)」を、新しいマスターへと「移行(Migration)」させるための、極限の更新ロジックについて解説する。
1. 識別戦略:バージョン情報の「真実の源泉(Source of Truth)」
まず、どのシェイプが「古い」のかを判定するロジックが必要だ。マスターの名称(Name)に頼るのは素人の仕事だ。マスターの名前は、組織の命名規則変更によって容易に変遷する。
プロフェッショナルな設計においては、マスターシェイプ自体に「スキーマバージョン」を管理するShape Data(ユーザー定義セル)を埋め込んでおく。
バージョン判定の設計指針
- メタデータの埋め込み: マスター作成時に `Version` という名前の `ShapeData` セルを定義し、`1.0.0` のようなセマンティックバージョニングを付与する。
- 判定ロジック: 既存シェイプが持つ `Version` セルの値と、現在のステンシルが規定する `MinimumRequiredVersion` を比較する。
2. 置換のアルゴリズム:破壊的置換から「状態の継承」へ
単純に `Shape.Delete` して新しいマスターを `Drop` する手法は、そのシェイプに接続されていたコネクタを「迷子」にし、図面の整合性を破壊する。
我々が実装すべきは、「新しいシェイプを生成し、古いシェイプの属性を転写した後、古いシェイプを破棄する」というトランザクション的なプロセスである。
継承すべきプロパティの優先順位
1. ShapeData (User-defined cells): IPアドレス、ホスト名、ラック番号などの重要データ。
2. Text: ユーザーが書き加えた注釈。
3. Geometry (Transform): 位置(X, Y)、回転、スケール。
4. Connections: これが最難関だ。既存の `Connect` インデックスを追跡し、新しいシェイプへ再接続する必要がある。
3. 実装:極限のマスター更新エンジン
以下に、メモリ管理とオブジェクトのライフサイクルを意識した、実戦的なVBAコードの雛形を示す。ここでは、最も重要かつ難易度の高い「ShapeDataの移行」と「位置の継承」に焦点を当てる。
Option Explicit
' ==============================================================================
' Module: MasterMigrationEngine
' Description: 既存シェイプの属性を新しいマスターへ継承・置換する
' Author: Legendary Chief Architect
' ==============================================================================
Public Sub UpdateAllShapesInDocument(ByVal targetStencilName As String, ByVal minRequiredVersion As String)
Dim vsoPage As Visio.Page
Dim vsoShape As Visio.Shape
Dim vsoNewMaster As Visio.Master
Dim vsoStencil As Visio.Document
Dim i As Long
' 高速化のための環境設定
On Error GoTo ErrorHandler
Application.ScreenUpdating = False
Application.DebugPrint "Migration Started..."
' 1. ステンシルのロード(メモリ効率を考慮し、参照を最小限にする)
Set vsoStencil = Application.Documents.OpenEx(targetStencilName, visOpenDocked)
If vsoStencil Is Nothing Then
Err.Raise 513, , "Target stencil not found: " & targetStencilName
End If
' 2. 全ページを走査
For Each vsoPage In Application.ActiveDocument.Pages
DebugPrint "Processing Page: " & vsoPage.Name
' シェイプの削除によるインデックスの狂いを防ぐため、後ろからループ
For i = vsoPage.Shapes.Count To 1 Step -1
Set vsoShape = vsoPage.Shapes.Item(i)
' 3. バージョンチェック
If IsOutdated(vsoShape, minRequiredVersion) Then
DebugPrint "Updating Shape: " & vsoShape.Name & " (ID: " & vsoShape.ID & ")"
' マスターの特定と置換実行
ReplaceShapeWithNewMaster vsoPage, vsoShape, vsoStencil
End If
Next i
Next vsoPage
CleanUp:
Application.ScreenUpdating = True
' COMオブジェクトの明示的な解放(メモリリーク防止)
Set vsoStencil = Nothing
DebugPrint "Migration Completed Successfully."
Exit Sub
ErrorHandler:
MsgBox "Critical Error during migration: " & Err.Description, vbCritical
Resume CleanUp
End Sub
' ------------------------------------------------------------------------------
' 関数: IsOutdated
' 目的: ShapeDataのVersionセルを確認し、更新が必要か判定する
' ------------------------------------------------------------------------------
Private Function IsOutdated(ByVal vsoShape As Visio.Shape, ByVal minVersion As String) As Boolean
Dim vsoCell As Visio.Cell
On Error Resume Next
' ShapeData内の"Version"セルを参照
Set vsoCell = vsoShape.CellsU("Prop.Version")
On Error GoTo 0
If vsoCell Is Nothing Then
' バージョン情報を持っていないシェイプは「古い」とみなす(保守的アプローチ)
IsOutdated = True
Else
' 簡易的な文字列比較(本来はセマンティックバージョニングの数値比較を推奨)
If vsoCell.ResultIU < VersionToInteger(minVersion) Then
IsOutdated = True
Else
IsOutdated = False
End If
End If
End Function
' ------------------------------------------------------------------------------
' 関数: ReplaceShapeWithNewMaster
' 目的: 新しいマスターをドロップし、属性を転写して旧シェイプを消去する
' ------------------------------------------------------------------------------
Private Sub ReplaceShapeWithNewMaster(ByVal vsoPage As Visio.Page, _
ByVal oldShape As Visio.Shape, _
ByVal vsoStencil As Visio.Document)
Dim vsoNewMaster As Visio.Master
Dim newShape As Visio.Shape
Dim vsoCell As Visio.Cell
Dim i As Integer
' 新しいマスターの取得(名称は要件に合わせて調整)
' ここでは、ステンシル内の特定のマスターを探すロジックとする
Set vsoNewMaster = vsoStencil.Masters.Item("New_Standard_Server")
' 1. 新しいシェイプを既存の位置にドロップ
' oldShape.X, oldShape.Y を使用して座標を完全に一致させる
Set newShape = vsoPage.Drop(vsoNewMaster, oldShape.X, oldShape.Y)
' 2. 座標・回転の精密な同期
newShape.CellsU("PinX").FormulaU = oldShape.CellsU("PinX").FormulaU
newShape.CellsU("PinY").FormulaU = oldShape.CellsU("PinY").FormulaU
newShape.CellsU("Angle").FormulaU = oldShape.CellsU("Angle").FormulaU
' 3. テキストの継承
newShape.Text = oldShape.Text
' 4. ShapeData (User-defined cells) の転送
' 全てのPropセルを走査し、新しいマスターに存在する項目のみ転送する
For i = 0 To oldShape.CellCount - 1
Set vsoCell = oldShape.CellsU(i)
' Propで始まるセル(ShapeData)のみを対象とする
If Left(vsoCell.NameU, 5) = "Prop." Then
On Error Resume Next
' 新しいシェイプに同じ名前のセルが存在する場合のみ値をコピー
newShape.CellsU(vsoCell.NameU).FormulaU = vsoCell.FormulaU
On Error GoTo 0
End If
Next i
' 5. 旧シェイプの破棄
' ※ 本来はここでコネクタの再接続ロジック(newShape.GlueTo)を挟む必要がある
oldShape.Delete
' メモリ管理
Set newShape = Nothing
Set oldShape = Nothing
End Sub
' 補助関数: バージョン文字列を比較可能な整数に変換(簡易実装)
Private Function VersionToInteger(ByVal v As String) As Long
' "1.2.0" -> 10200 等に変換するロジックを実装
' 本来は正規表現やSplitを使用
VersionToInteger = CLng(Replace(v, ".", ""))
End Function
4. アーキテクトの視点:メモリ管理とパフォーマンスの極意
このコードを商用環境、特に大規模な図面(数千ページ、数万シェイプ)に適用する場合、以下の3点に留意せよ。
Windows APIとCOMのライフサイクル
VBAはCOM(Component Object Model)の上で動いている。`Set vsoShape = Nothing` を明示的に行わない場合、Visioのプロセスメモリ内に参照カウントが残り続け、長時間稼働する自動化システムでは、メモリ不足による `Out of Memory` エラーを引き起こす。特にループ内で大量のオブジェクトを生成・破棄する場合は、極めて慎重に管理せよ。
トランザクションの整合性
図面の更新中にエラーが発生した場合、図面は「半分だけ新しいマスター、半分だけ古いマスター」という、カオスな状態に陥る。
実運用では、更新処理を開始する前に `ActiveDocument.SaveCopyAs` を用いてスナップショットを作成するか、エラーハンドリング内で「ロールバック(Undo)」を実装する、あるいは「更新ログ」を外部ファイルに出力して、失敗したシェイプを特定できるように設計するのがプロの仕事だ。
スケーラビリティへの配慮
`Application.ScreenUpdating = False` は必須だが、それだけでは不十分だ。数万個のシェイプを処理する場合、OSのメッセージキューが詰まることがある。適宜 `DoEvents` を挟む検討をせよ。ただし、`DoEvents` はコンテキストスイッチのコストを伴うため、頻度には細心の注意を払え。
結言
マスターシェイプの更新は、単なる「見た目の変更」ではない。それは、図面という名の「構造化データ」のスキーマ・マイグレーションである。
エンジニアが提供すべきは、使い勝手の良いツールではない。「データの整合性を担保し、物理的な接続を維持し、不変の属性を次世代へ受け渡す」という、極めて強固なアーキテクチャなのだ。
