【実務・中級編】Shape.BoundingBoxによる正確な領域検出:図形同士の重なり(バウンディングボックス)計算 – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:Shape.BoundingBoxによる正確な領域検出と衝突回避アルゴリズム

開発プロジェクトの現場において、Visioの自動レイアウトや図形配置ロジックを実装した際、「なぜか図形同士が重なってしまう」「意図した座標に配置されない」という壁にぶつかったことはないだろうか。

多くの開発者が陥る最初の罠。それは、シェイプの `PinX` や `PinY`、あるいは `LocPinX` / `LocPinY` のみを基準に配置計算を行うことだ。

断言しよう。その設計のままでは、実務に耐えうる自動化ツールは絶対に作れない。

今回は、Visioのオブジェクトモデルの深淵を暴き、`Shape.BoundingBox` を用いて「真の占有範囲」を捉え、図形同士の重なりを完璧に検知・回避するプロダクションコードの全貌を伝授する。

なぜ `PinX/PinY` だけではレイアウトが破綻するのか?

Visioの図形(Shape)における `PinX` および `PinY` は、シェイプの「回転中心(Pin)」のページ上の絶対座標を示している。

ここに、次のような致命的なギャップが存在する。
1. 回転(Rotation)の影響: シェイプが回転している場合、ピンを中心とした回転後の外接矩形(Bounding Box)は、元のサイズから大きく拡大・変化する。
2. グループ化の罠: グループシェイプの場合、内部の子シェイプの配置や線の太さ(LineWeight)、影などの効果によって、視覚的な占有範囲はピン座標から大きく乖離する。
3. テキストのいはみ出し: シェイプ本体のサイズを超えてテキストボックスが拡張されている場合、ピン座標だけでは完全に領域を見誤る。

業務自動化において、図形同士の衝突は「見た目の醜悪さ」にとどまらず、配線(Connector)のルーティング破綻や、上流工程・下流システム(DBやExcel)への不正なレイアウトデータ出力直結する致命傷となる。

ここで登場するのが、Visioが隠し持つ最強の幾何学メソッド `Shape.BoundingBox` である。

`Shape.BoundingBox` の仕様と正確なハンドリング

`BoundingBox` メソッドは、指定したシェイプがページ上で占める実際の「外接矩形(軸平行バウンディングボックス:AABB)」の座標を返す。

構文の罠

shp.BoundingBox Flags, IntervalX, IntervalY

特に重要なのが第1引数の `Flags`(`VisBoundingBoxFlags` 列挙体)だ。実務において、単純なジオメトリだけでなく「線の太さ」や「テキスト」も含めた正確な占有範囲を取得するためには、フラグのビット演算による適切な指定が不可欠となる。

  • `visBBoxIncludeData` (包含すべき要素を制御)
  • 通常は `visBBoxUpToVisScale` などを組み合わせるが、実務上は「図形が見た目に占める全範囲(線の太さやテキスト含む)」を取得するため、適切なフラグ設定を行う必要がある。

【実務コード】重なり検知と自動回避エンジン

ここからは、実務の現場でそのままコピペして即戦力として使えるプロダクションコードを公開する。
このコードは、指定した基準シェイプに対し、アクティブページ上の他の全シェイプとのバウンディングボックスベースの衝突判定を行い、重なりが検知された場合に自動的に右方向へシフト(退避)させる堅牢なアルゴリズムである。

Option Explicit

‘ ==============================================================================
‘ módulo名: basCollisionAvoidance
‘ 概要 : BoundingBoxを用いた高精度な図形衝突検知と自動配置回避エンジン
‘ 著者 : チーフアーキテクト
‘ ==============================================================================

Public Sub ExecuteSmartLayoutAvoidance()
Dim vsoPage As Visio.Page
Set vsoPage = ActivePage

If vsoPage Is Nothing Then
MsgBox “アクティブなページが存在しません。”, vbCritical, “エラー”
Exit Sub
End If

‘ エラーハンドリングの要塞化
On Error GoTo ErrorHandler

Dim vsoShape As Visio.Shape
Dim targetShape As Visio.Shape

‘ 例として、選択中の最初のシェイプを「動かす対象(Target)」とする
If ActiveWindow.Selection.Count = 0 Then
MsgBox “対象となるシェイプを選択してください。”, vbExclamation, “操作確認”
Exit Sub
End If

Set targetShape = ActiveWindow.Selection(1)

‘ 衝突判定と回避のメインループ
Dim hasCollision As Boolean
Dim safetyCounter As Integer
safetyCounter = 0

Do
hasCollision = False

For Each vsoShape in vsoPage.Shapes
‘ 自分自身とは比較しない
If vsoShape.ID <> targetShape.ID Then

‘ BoundingBox同士が重なっているか判定
If CheckBoxCollision(targetShape, vsoShape) Then
hasCollision = True

‘ 重なり検知!ターゲットを右側に退避させる(X座標をシフト)
Dim currentPinX As Double
currentPinX = targetShape.CellsU(“PinX”).ResultIU

‘ 相手のBoundingBoxの右端を取得し、そこにマージン(例: 10mm = 0.3937 inch)を足す
Dim otherBBox As Variant
otherBBox = GetBoundingBoxCoordinates(vsoShape)

‘ 新しいPinXを計算(インチ単位で処理するのがVisio VBAの鉄則)
‘ otherBBox(2) は XMax
Dim margin As Double
margin = 0.5 ‘ 0.5インチ(約12.7mm)の余裕

targetShape.CellsU(“PinX”).ResultIU = otherBBox(2) + margin

‘ ループ脱出(座標を変えたので、再度全シェイプとの当たり判定をやり直す)
Exit For
End If

End If
Next vsoShape

safetyCounter = safetyCounter + 1
If safetyCounter > 100 Then
Err.Raise 9999, “LayoutEngine”, “無限ループの危険検知:レイアウトが収束しません。”
End If

Loop While hasCollision

MsgBox “レイアウトの最適配置が完了しました。”, vbInformation, “完了”
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
End Sub

‘ ==============================================================================
‘ 2つのシェイプのBoundingBoxが交差(衝突)しているかを判定する関数
‘ 戻り値: True = 重なっている, False = 離れている
‘ ==============================================================================
Private Function CheckBoxCollision(ByVal shpA As Visio.Shape, ByVal shpB As Visio.Shape) As Boolean
Dim boxA As Variant
Dim boxB As Variant

boxA = GetBoundingBoxCoordinates(shpA)
boxB = GetBoundingBoxCoordinates(shpB)

‘ boxレイアウト: Array(XMin, YMin, XMax, YMax)
‘ 2次元のAABB衝突判定ロジック (Separating Axis Theorem の簡易版)
‘ 軸平行矩形同士の重なり判定条件
‘ (A.XMin <= B.XMax) And (A.XMax >= B.XMin) And (A.YMin <= B.YMax) And (A.YMax >= B.YMin)

If (boxA(0) <= boxB(2)) And (boxA(2) >= boxB(0)) And _
(boxA(1) <= boxB(3)) And (boxA(3) >= boxB(1)) Then
CheckBoxCollision = True
Else
CheckBoxCollision = False
End If
End Function

‘ ==============================================================================
K
‘ シェイプから正確なBoundingBoxの座標配列を取得するラッパー関数
‘ 戻り値: Array(XMin, YMin, XMax, YMax) [単位: 内部単位(インチ)]
‘ ==============================================================================
Private Function GetBoundingBoxCoordinates(ByVal shp As Visio.Shape) As Variant
Dim xMin As Double, yMin As Double, xMax As Double, yMax As Double

‘ VisioのBoundingBoxメソッド呼び出し
‘ 第1引数: visBBoxIncludeText (テキスト領域も含める)
shp.BoundingBox visBBoxIncludeText, xMin, yMin, xMax, yMax

GetBoundingBoxCoordinates = Array(xMin, yMin, xMax, yMax)
End Function

堅牢な設計のためのアーキテクトからの助言

1. 単位系の統一(Internal Unitsの厳守)
Visio VBAでは、`Cells` プロパティを使う際に `mm` や `cm` などの文字列指定を行うとパフォーマンスが著しく低下し、ロケール依存のバグを生む。必ず `.ResultIU`(Internal Units = インチ)を介して計算を行い、座標系を統一すること。上記のコードはすべて内部単位(インチ)で厳密に計算されている。

2. 無限ループの防止(Safety Counter)
自動レイアウトロジックにおいて最も恐ろしいのは、退避させたら別の図形と衝突し、それを繰り返して無限ループに陥ることだ。プロダクションコードでは必ず試行回数の上限(セーフティカウンター)を設け、収束しない場合は強制離脱する設計を義務付けよ。

3. データベースや外部ファイル連携時の前処理
外部のSQLデータベースやExcelから読み込んだマスターデータに基づき動的に図形を生成・配置する場合、生成直後にこの `BoundingBox` による補正プロセスを挟むことで、人間が手動で微調整する必要のない「完全自動ドキュメント生成パイプライン」が完成する。

結び

オブジェクトモデルの表面的なプロパティ(PinX等)に頼る時代は終わった。
`Shape.BoundingBox` を手懐けた者だけが、Visioオートメーションの真の自由を手に入れることができる。

あなたの開発する業務自動化ツールが、この極限の知見によって圧倒的な堅牢性と実用性を備えることを期待する。妥協なきコードで、現場の生産性を限界突破させろ。

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