【実務・中級編】図形同士の接触・重なりを正確に検知:Shape.HitTestメソッドによるレイアウト干渉チェック – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:Shape.HitTestで実現する「接触・干渉ゼロ」の自動レイアウト・エンジン

図面自動化ツールを開発する際、最もエンジニアの頭を悩ませる問題は何か?
それは「シェイプの座標計算と配置」だ。

「データを読み込んでシェイプを並べたはいいが、テキストが重なっている」「コネクタの引き回しで図形と線が接触し、図面として判読できない」。こうしたレイアウト崩れを人力で修正しているようでは、業務自動化エンジニアの名が廃る。

Visio VBAにおける座標の計算は、単純な四角形の包含判定(Bounding Box)だけでは不十分だ。特に回転したシェイプや、複雑なジオメトリを持つシェイプにおいて、単なる`PinX`/`PinY`や幅・高さの比較は全く使い物にならない。

今回は、Visioのオブジェクトモデルが持つ隠れた隠し球、`Shape.HitTest`メソッドを完全に掌握し、図形同士のミリ単位の干渉を高精度に検知、そして「安全な空き領域」へと自動エスケープさせるプロダクションコードを伝授する。

1. なぜ「Bounding Box(外接矩形)」の比較では破綻するのか?

多くの初学者は、2つのシェイプが重なっているかを判定するために、それぞれの`PinX`, `PinY`, `Width`, `Height`から算出した矩形の交差判定(AABB: Axis-Aligned Bounding Box)を実装しようとする。

しかし、以下の現実を直視してほしい。
1. 回転(Angle)の存在:Visioのシェイプは回転する。回転したシェイプのAABBは実体のない「無駄な余白」を含むため、実際には接触していないのに「重なっている」と誤判定される。
2. カスタムジオメトリ:L字型や複雑なパスを持つシェイプの場合、外接矩形の中身はスカスカである。
3. ストローク(線の太さ)と影:見た目の当たり判定と、論理的な座標が乖離する。

これらを一網打尽に解決するのが、Visioネイティブの幾何学エンジンを直接叩く `HitTest` メソッド である。

2. HitTest メソッドのメカニズムと正しい仕様把握

`Shape.HitTest` は、対象シェイプのジオメトリに対して、特定の「点」あるいは「領域」が接触(ヒット)しているかを判定する非常に強力なメソッドだ。

retVal = targetShape.HitTest(objectToTest, tolerance)

  • `objectToTest`: 接触を検証したい「点(Point)」を表す座標、または別の「シェイプ(Shape)」を指定できる。
  • `tolerance`: 許容誤差(インチ単位)。通常は `0` または微小値(`0.01` など)を指定。
  • 戻り値(`retVal`):
  • `visHitTestInside` (0):完全に内部に含まれている
  • `visHitTestIntersection` (1):境界線が交差している
  • `visHitTestMiss` (2):ヒットしていない(接触なし)

今回は、「あるシェイプが、既存のどのシェイプ群と干渉しているか」を高精度に検知するエンジンを構築する。

3. 【プロダクションコード】干渉検知 & 自動退避レイアウト・エンジン

以下のコードは、新規に配置しようとするシェイプが既存のシェイプ群とヒットしているかを`HitTest`で判定し、もし干渉していれば「安全なグリッド位置」へと自動的にスライド(エスケープ)させる実用モジュールだ。

実務でのパフォーマンス低下を防ぐため、無駄なループを排除し、エラーハンドリングを徹底した堅牢な設計にしている。

Option Explicit

‘ ==============================================================================
‘ 担当者・モジュール名: AdvancedLayoutEngine.bas
‘ 概要: Shape.HitTestを用いた高精度な干渉検知と自動空き領域配置アルゴリズム
‘ ==============================================================================

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

‘ 処理の高速化(画面描画・イベントを一時停止)
Application.ScreenUpdating = False
Application.EventEnabled = False

On Error GoTo ErrorHandler

‘ 1. テスト対象となるシェイプ(例:最後に選択された、または新規作成されたシェイプ)
Dim targetShape As Visio.Shape
If vsoPage.Selection.Count = 0 Then
MsgBox “レイアウト対象となるシェイプを選択してください。”, vbExclamation, “レイアウトエラー”
GoTo CleanUp
End If
Set targetShape = vsoPage.Selection(1)

‘ 2. 干渉チェックと安全な位置への自動配置を実行
ResolveCollisionAndPlace vsoPage, targetShape

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

CleanUp:
Application.ScreenUpdating = True
Application.EventEnabled = True
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub

Private Sub ResolveCollisionAndPlace(vsoPage As Visio.Page, targetShape As Visio.Shape)
Dim shp As Visio.Shape
Dim isColliding As Boolean
Dim maxRetries As Integer
Dim retryCount As Integer

maxRetries = 50 ‘ 無限ループ防止用の最大試行回数
retryCount = 0

Do
isColliding = False

‘ ページ内の全シェイプとヒットテストを実施
For Each shp In vsoPage.Shapes
‘ 自分自身とは比較しない
If shp.ID <> targetShape.ID Then

‘ HitTestメソッドによる干渉判定
‘ visHitTestInside(0) または visHitTestIntersection(1) の場合、接触とみなす
Dim hitResult As Integer
hitResult = targetShape.HitTest(shp, 0.01)

If hitResult = 0 Or hitResult = 1 Then
isColliding = True
Exit For ‘ 1つでもヒットしたら即座にループを抜けて位置をずらす
End If

End If
Next shp

‘ 干渉している場合、右方向および下方向へシフトさせる(アルゴリズムのキモ)
If isColliding Then
retryCount = retryCount + 1
If retryCount > maxRetries Then
Err.Raise 9999, , “配置可能な空き領域が見つかりませんでした(領域が飽和状態です)。”
End If

‘ 現在のPinX, PinYを取得し、右へ 0.5インチ、下へ 0.5インチ移動
Dim currentX As Double, currentY As Double
currentX = targetShape.CellsU(“PinX”).ResultIU
currentY = targetShape.CellsU(“PinY”).ResultIU

‘ 新しい座標を設定(例:右へグリッドシフト、必要に応じて螺旋状探索に変更可能)
targetShape.CellsU(“PinX”).ResultIU = currentX + 0.75
targetShape.CellsU(“PinY”).ResultIU = currentY – 0.75
End If

Loop While isColliding And (retryCount <= maxRetries) End Sub ---

4. チーフアーキテクトが教える、現場で絶対につまづかないための設計思想

このコードを実際のエンタープライズ環境(DB連携ツールや自動図面生成バッチ)に組み込む際、以下の3点を必ず遵守してほしい。

① 画面描画の抑制(ScreenUpdating)の徹底

`HitTest` は内部で幾何学的計算(ベクトル演算)を大量に実行する。ループ内でシェイプを動かすたびにVisioが画面を描画(Render)していると、処理速度が何十倍も低下する。
必ず処理の冒頭で `Application.ScreenUpdating = False` をかけ、処理終了時に戻すこと。これだけで数秒の処理が数ミリ秒に短縮される。

② 座標系は必ず「内部単位(IU)」で扱う

Visio VBAでは `Cells` プロパティを使う際、`Cells`(表示単位依存:mmやインチが混ざる)と `CellsU`(常に内部単位=インチ)が存在する。
干渉判定や座標移動のロジックを書くときは、必ず `CellsU` を使用すること。 ユーザーの画面表示単位(センチメートル、フィートなど)に依存しない、堅牢なロジックが担保される。

③ データベースや外部ファイル連携時の注意点

もしこのスクリプトを「外部DBからデータを取得し、動的にシェイプを生成・配置するバッチ処理」の一部として組み込む場合、生成順序がランダムだと干渉が頻発する。
必ず「重要度が高いマスターシェイプから順に配置し、最後にディテールシェイプを置く」というソートロジックをSQL側またはVBAの事前処理(Collection等での並び替え)で担保した上で、この `HitTest` エスケープエンジンを走らせるべきだ。

5. おわりに

Visio VBAは、単なる「お絵かきマクロ」の道具ではない。適切なAPIをチョイスし、幾何学的な判定をコードに落とし込むことで、プロフェッショナルな「CADに匹敵する自動レイアウトエンジン」へと昇華させることができる。

今回紹介した `Shape.HitTest` をマスターすれば、図形の重なりに悩まされる無駄な残業とは永遠に決別できるはずだ。
あなたの開発する自動化ソリューションの武器として、ぜひこの知見を現場で役立ててほしい。

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