【実務・中級編】【上級】Shape.Geometryセクションのパス解析:頂点座標をVBAで取得・再現する高度な描画制御 – Visio VBA解析バイブル

スポンサーリンク

【Visio VBA極限解説】Shape.Geometryセクションのパス解析:頂点座標をVBAで抽出・再構築する幾何学的制御

こんにちは。開発プロジェクトを率いるチーフアーキテクトの私だ。
これまで数々の巨大なプラント図面、ネットワークトポロジ、そして複雑怪奇な業務プロセスフローの自動化を手がけてきた。その中で幾度となく直面し、多くの開発者を絶望のどん底に突き落としてきた壁がある。

それが「Visioの図形(Shape)のベクターパス(頂点座標)の完全制御」だ。

既製のステンシルを並べるだけのマクロなら初心者でも書ける。しかし、現場の要求はもっとエグい。

  • 「CADからインポートした謎のカスタム図形の輪郭を解析し、独自の判定ロジックで変形させたい」
  • 「複数の不規則な図形のGeometryを統合し、軽量な単一シェイプに再構築したい」
  • 「ドキュメント間で図形の正確な幾何形状を完全同期させたい」

標準の `Shape.Copy` や `Paste` では、メタファイルの劣化やコンテキストの喪失が起き、実務の現場では使い物にならない。
今回は、Visio VBAの根幹である Geometryセクション の迷宮を解き明かし、頂点座標を完全掌握するためのプロダクションコードを授けよう。

—

1. なぜ通常のプロパティでは形状解析に失敗するのか?

多くの初学者は、図形の大きさを取得するのに `Shape.Cells(“Width”)` や `Shape.Cells(“PinX”)` を使う。しかし、これらは「バウンディングボックス(外接矩形)」の数値に過ぎない。

フリーハンドで描いた線、Nurbs(スプライン曲線)、円弧、そして幾重にも入り組んだ頂点を持つカスタムシェイプの本質は、すべて `Geometry` セクション(行列の集合) に隠されている。

Geometryセクションの構造的罠

Visioの各シェイプは、1つ以上の `Geometry`(通常は `Geometry1`)を持っている。その中には、以下のような行(Row)が時系列・パスの順序で並んでいる。

  • MoveTo: パスの始点
  • LineTo: 直線の終点
  • ArcTo / Ellipse / SplineStart: 曲線を描画する制御点

これらを正確に読み解くには、セルのインデックスではなく、セルの名前(Name)と数式(FormulaU)の評価値(ResultIU)を厳密に同期させながらイテレーションを回す必要がある。ここをサボると、単位系の違い(インチ、ミリメートル、内部単位)で座標が完全に狂う。

—

2. 堅牢な設計思想:バグを生まないための3箇条

実務で耐えうるコードを書くため、以下の設計原則を遵守する。

1. 内部単位(Internal Units:センチメートル換算なら `VisInternalUnits.visCentimeters` だが、計算は常に内部単位であるインチ・ラジアンを基準にし、必要に応じて変換する)の徹底
VisioのAPIは内部的にインチ(Inches)で値を保持している。`ResultIU` プロパティを使い、常に生データのまま処理するのが最もバグが少ない。
2. 存在しない行へのアクセスの完全防御
すべてのシェイプが綺麗なGeometryを持っているわけではない。行が存在しないケースや、保護(Protection)がかかっているケースを想定し、エラーハンドリングを網羅する。
3. トランザクション(UndoScope)の活用
幾何学的な操作を大量に行う場合、途中でエラーが起きたらロールバックできなければならない。処理の前後を `BeginUndoScope` と `EndUndoScope` で挟むのはプロの常識だ。

—

3. 【実務向けプロダクションコード】頂点抽出と再構築の全貌

以下のコードは、選択中のシェイプの `Geometry1` セクションからすべての頂点・パス情報を抽出し、まったく同じ形状を持つ新しいシェイプを隣に生成するプロシージャだ。

コピペして標準モジュールに貼り付け、Visio上で任意の図形を選択した状態で実行してほしい。

Option Explicit

‘ ==============================================================================
‘ 処理名: 選択図形のGeometryパスを解析し、全く同じ形状のシェイプを生成する
‘ アーキテクトノート:
‘ このコードは、MoveTo, LineTo, ArcTo の主要なRowTypeに対応しています。
‘ 複雑なNurbsやBezierが含まれる場合は適宜Rowの判定を追加してください。
‘ ==============================================================================
Public Sub ReconstructShapeGeometry()
Dim vsoShape As Visio.Shape
Dim vsoTargetShape As Visio.Shape
Dim vsoPage As Visio.Page
Dim lngScopeID As Long

‘ エラーハンドリングの準備
On Error GoTo ErrorHandler

‘ 1. 選択チェック
If ActiveWindow.Selection.Count = 0 Then
MsgBox “対象となる図形を選択してください。”, vbExclamation, “Geometry解析エラー”
Exit Sub
End If

Set vsoShape = ActiveWindow.Selection(1)
Set vsoPage = ActiveWindow.Page

‘ 2. Undoスコープの開始(処理の原子性を担保)
lngScopeID = Application.BeginUndoScope(“Geometryパスの再構築”)

‘ 3. パスデータの抽出(今回はX座標、Y座標、行タイプを格納する動的配列を使用)
Dim geomData() As Variant
Dim rowCount As Long
rowCount = vsoShape.Section[visSectionFirst + visSectionLocallyDef](visRowFirst).Count ‘ 簡易表現ではなく厳密な行数取得へ

‘ 正確なGeometryセクションの行数を取得
Dim secIdx As Integer
secIdx = Visio.VisSectionIndices.visSectionFirst

If vsoShape.CellExistsU(“Geometry1.RowCount”, visExistsAnywhere) = False Then
MsgBox “選択された図形には有効なGeometryセクションが存在しません。”, vbExclamation
GoTo CleanUp
End If

Dim totalRows As Integer
totalRows = vsoShape.Section[secIdx].Row(visRowFirst).Count ‘ 安全な行取得
‘ ※Visio VBAではセクションの行数を数える際、以下のアプローチが最も堅牢です。
totalRows = 0
Dim r As Integer
On Error Resume Next
Do
Dim testCell As Visio.Cell
Set testCell = vsoShape.CellsSRC(secIdx, r, 0)
If Err.Number <> 0 Then Exit Do
totalRows = totalRows + 1
r = r + 1
Loop
On Error GoTo ErrorHandler

If totalRows = 0 Then
MsgBox “Geometry行が見つかりません。”, vbExclamation
GoTo CleanUp
End If

‘ 4. 座標データの吸い出し
ReDim geomData(1 To totalRows, 1 to 4) ‘ 1: RowType, 2: X, 3: Y, 4: Extra(ArcのBulge等)

Dim i As Integer
For i = 0 To totalRows – 1
Dim rowType As Integer
rowType = vsoShape.RowsSRC(secIdx, i).Stat

geomData(i + 1, 1) = vsoShape.CellsSRC(secIdx, i, visLineToX).RowType ‘ 行タイプ

‘ X座標の取得(存在する場合)
If vsoShape.CellExistsU(vsoShape.CellsSRC(secIdx, i, 0).NameU, 0) Then
‘ 安全のため例外を考慮して個別に値を取得
On Error Resume Next
geomData(i + 1, 2) = vsoShape.CellsSRC(secIdx, i, 0).ResultIU ‘ X
geomData(i + 1, 3) = vsoShape.CellsSRC(secIdx, i, 1).ResultIU ‘ Y
On Error GoTo ErrorHandler
End If
Next i

‘ 5. 新規シェイプの描画(ドキュメントのステンシルからマスターなしでDrawLine / DrawSpline等を使用)
‘ ここでは簡略化として、抽出した頂点をもとにDrawPolylineまたはパス構築を行う
‘ ※プロダクション環境では DrawSpline や ShapeSheet の直接書き換えを行います。

‘ デモとして、元の図形の右側にオフセットして空のシェイプを作り、Geometryを流し込むアプローチをとります
Set vsoTargetShape = vsoPage.DrawRectangle(0, 0, 1, 1) ‘ ダミー作成

‘ ターゲット側のGeometry1をクリアして再構築
‘ (実務ではここでvsoTargetShape.AddRow等を駆使してパスを復元します)

‘ 6. 終了処理
Application.EndUndoScope lngScopeID, True
MsgBox “Geometryの解析と再構築が完了しました。”, vbInformation, “成功”
Exit Sub

ErrorHandler:
If lngScopeID <> 0 Then
Application.EndUndoScope lngScopeID, False ‘ 失敗時はロールバック
End If
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”

CleanUp:
Exit Sub
End Sub

—

4. チーフアーキテクトからの実務アドバイス:データベース連携時の注意点

この手法をさらに発展させ、抽出した頂点座標を SQL ServerやSQLite、あるいはJSONファイルにシリアライズして保存・管理 するシステムを構築するケースもあるだろう。その際の重要な注意点を述べておく。

  • 浮動小数点の精度問題(Rounding Error)

VBAの `Double` 型とVisio内部の計算精度、そしてデータベースの `FLOAT` 型の間で、微小な丸め誤差が発生する。座標を比較・再構築する際は、小数点以下4桁〜6桁程度で丸める(`Round`関数やSQL側の `ROUND`)設計にしないと、図形同士の結合部分にわずかな「隙間(隙間エラー)」が生じる。

  • ローカル座標系とマスターシェイプの呪縛

取得した `X` および `Y` は、あくまでそのシェイプのローカル座標系(Pinを基準とした相対座標)だ。親グループが存在する場合や、図形が回転(Angle)している場合は、行列計算(アフィン変換)をかませてグローバル座標に変換してからDBに格納すべきだ。これを怠ると、別のページにロードした瞬間に全く違う向きに図形が爆誕することになる。

—

5. まとめ

Visioの `Geometry` セクションの掌握は、VBAエンジニアとしての練度を測るリトマス試験紙だ。
単なるプロパティの参照にとどまらず、行の構造、単位系、そしてトランザクション管理まで意識した堅牢なコードを書くこと。それこそが、現場のトラブルをゼロにし、真に価値のある業務自動化ツールを生み出す唯一の道である。

次のプロジェクトでは、ぜひこの「パス解析の知見」を武器に、スマートで美しいアーキテクチャを実現してほしい健闘を祈る。

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