【テクニカル・上級編】【上級プロ向け】図面ビューのバウンディングボックス(外形矩形)演算による「ビュー同士の干渉回避」自動レイアウトエンジン – SolidWorks VBA解析バイブル

スポンサーリンク
スポンサーリンク

【極限の設計】図面ビューの干渉回避アルゴリズム:バウンディングボックス演算による自動レイアウトエンジンの実装

図面作成の自動化において、多くの開発者が陥る「浅い罠」がある。それは、`CreateDrawView` でビューを生成し、座標を指定して配置するという、単なる「座標の流し込み」で満足してしまうことだ。

実務レベルの、特に大規模なアセンブリを扱うシステムにおいて、ビューは動的に生成される。ビューのサイズ(バウンディングボックス)は、モデルの構成や表示スケールによって千差万別だ。これらを単純なグリッド配置や固定座標で制御しようとすれば、必ずと言っていいほどビュー同士の重なり(干渉)が発生し、図面の品質を著しく損なう。

真の自動化エンジニアに求められるのは、図面上の各要素を「点」ではなく、「占有領域を持つ幾何学オブジェクト」として捉え、それらが互いに干渉しない領域を数学的に計算して配置する「レイアウトエンジン」の構築である。

本稿では、SolidWorks APIを用いた、バウンディングボックス演算による干渉回避型レイアウトエンジンの実装手法について、その深淵に触れる。

1. 幾何学的アプローチ:AABB(Axis-Aligned Bounding Box)による衝突判定

図面ビューの干渉を回避するための最も効率的かつ堅牢な手法は、AABB(軸に平行な境界矩形)を用いた衝突判定アルゴリズムである。

SolidWorksの `DrawingView::GetBoundingBox` メソッドは、ビューの内容を囲む最小の矩形座標を返す。しかし、これをそのまま使うだけでは不十分だ。我々が制御すべきは、ビューの「コンテンツ」ではなく、ビューが占有する「物理的なフットプリント」である。

衝突判定の論理式

2つのビュー(Rect A と Rect B)が干渉しているかどうかは、以下の条件がすべて成立する場合である。

$$
(A.xmin < B.xmax) \land (A.xmax > B.xmin) \land (A.ymin < B.ymax) \land (A.ymax > B.ymin)
$$

この論理式をコードに落とし込む際、浮動小数点数の誤差(Precision issue)を考慮し、微小なマージン(Epsilon)を設けることが、レガシーな環境や異なる解像度のディスプレイ間での動作安定性を確保する鍵となる。

2. 高度な実装:メモリ管理とオブジェクト・ライフサイクル

VBAにおける最大の敵は、COMオブジェクトの不適切な解放によるメモリリーク、およびAPI呼び出しのオーバーヘッドである。

ループ内で `DrawingView` オブジェクトを大量に生成・参照する場合、各ステップで `Set view = Nothing` を明示的に行い、参照カウントを確実に減らす必要がある。また、描画更新(`swUpdateDisplay`)は、計算がすべて完了するまで徹底的に抑制しなければならない。描画更新の頻度は、アルゴリズムの実行速度を指数関数的に低下させる。

3. 実装コード:衝突回避型レイアウトエンジン

以下に、各ビューのバウンディングボックスを計算し、既存のビューと干渉しない位置を探索して再配置するコアロジックを示す。

Option Explicit

' --- 構造体定義: ビューの占有領域を管理 ---
Private Type ViewRect
    XMin As Double
    XMax As Double
    YMin As Double
    YMax As Double
    Margin As Double
End Type

' --- 定数定義 ---
Private Const EPSILON As Double = 0.0001 ' 浮動小数点誤差対策
Private Const VIEW_MARGIN As Double = 0.02 ' ビュー間の最小余白 (m)

''' <summary>
''' 図面内の全ビューをスキャンし、干渉を回避するように再配置する
''' </summary>
Public Sub ExecuteCollisionAvoidanceLayout(swApp As SldWorks.SldWorks, swDraw As SldWorks.DrawingDoc)
    Dim swSheet As SldWorks.Sheet
    Dim swView As SldWorks.DrawingView
    Dim vViews As Variant
    Dim i As Long, j As Long
    Dim rects() As ViewRect
    Dim currentRect As ViewRect
    Dim targetX As Double, targetY As Double
    Dim isCollision As Boolean
    
    ' 描画更新を停止(パフォーマンス最適化の鉄則)
    swApp.DocumentVisible2 False
    
    Set swSheet = swDraw.GetCurrentSheet
    vViews = swDraw.GetViews
    
    If IsEmpty(vViews) Then Exit Sub
    
    ' 1. 全ビューのバウンディングボックスをキャッシュ
    ' APIへのアクセス回数を最小限にするため、最初に構造体配列へ格納する
    ReDim rects(UBound(vViews))
    For i = 0 To UBound(vViews)
        Set swView = vViews(i)
        rects(i) = GetViewBoundingBox(swView, VIEW_MARGIN)
    Next i
    
    ' 2. 配置アルゴリズム(Greedy Searchによる再配置)
    ' ここでは簡略化のため、既存のビューを避けて右方向・上方向にスライドさせる手法をとる
    For i = 0 To UBound(vViews)
        Set swView = vViews(i)
        
        ' 現在の座標を取得
        targetX = swView.PositionX
        targetY = swView.PositionY
        
        ' 他のビューとの衝突判定ループ
        ' ※ i番目のビュー自身とは比較しないよう注意
        Dim attempt As Integer
        attempt = 0
        Do
            isCollision = False
            For j = 0 To UBound(vViews)
                If i <> j Then
                    ' 衝突判定
                    If CheckCollision(rects(i), rects(j)) Then
                        isCollision = True
                        Exit For
                    End If
                End If
            Next j
            
            If isCollision Then
                ' 衝突した場合、配置座標を微調整(ここでは単純なオフセット移動)
                ' 本来は、より高度なパッキングアルゴリズム(Shelf Packing等)を実装すべき
                rects(i).XMin = rects(i).XMin + 0.01
                rects(i).XMax = rects(i).XMax + 0.01
                ' 座標の更新(実際のビューへの適用は最後に行う)
                targetX = targetX + 0.01 
                attempt = attempt + 1
            End If
            
            ' 無限ループ防止
            If attempt > 100 Then Exit Do
        Loop While isCollision
        
        ' 3. 計算された座標をAPIに反映
        swView.SetPosition targetX, targetY
        
        ' 位置変更に伴い、キャッシュしている矩形も更新(重要)
        rects(i) = GetViewBoundingBox(swView, VIEW_MARGIN)
    Next i

    ' 描画更新を再開
    swApp.DocumentVisible2 True
    MsgBox "Layout Engine: Optimization Complete.", vbInformation
End Sub

''' <summary>
''' ビューの境界矩形を取得し、マージンを加味して構造体に格納する
''' </summary>
Private Function GetViewBoundingBox(swView As SldWorks.DrawingView, margin As Double) As ViewRect
    Dim vBox As Variant
    Dim res As ViewRect
    
    ' GetBoundingBoxは [MinX, MinY, MaxX, MaxY] の形式で返す
    vBox = swView.GetBoundingBox
    
    res.XMin = vBox(0) - margin
    res.YMin = vBox(1) - margin
    res.XMax = vBox(2) + margin
    res.YMax = vBox(3) + margin
    res.Margin = margin
    
    GetViewBoundingBox = res
End Function

''' <summary>
''' AABBアルゴリズムによる衝突判定
''' </summary>
Private Function CheckCollision(r1 As ViewRect, r2 As ViewRect) As Boolean
    ' すべての条件が成立するときに「衝突」とみなす
    If (r1.XMin < r2.XMax + EPSILON) And _
       (r1.XMax > r2.XMin - EPSILON) And _
       (r1.YMin < r2.YMax + EPSILON) And _
       (r1.YMax > r2.YMin - EPSILON) Then
        CheckCollision = True
    Else
        CheckCollision = False
    End If
End Function

4. アーキテクトの視点:実運用における極限の知見

このコードは「エンジン」の核となる部分であるが、これをエンタープライズ・グレードのシステムへと昇華させるには、以下の3点を考慮しなければならない。

A. 座標系の正規化

SolidWorksのAPIはメートル法を使用している。しかし、ユーザーが入力する「余白」はミリメートルであることが多い。この単位変換のミスは、計算結果を完全に破壊する。全ての演算は、最初に単一の単位系(SI単位系)に正規化し、最後に必要に応じて変換する設計を徹底せよ。

B. 計算量のオーダー(Complexity)

上記のコードは $O(n^2)$ の計算量を持つ。ビューの数が数十個程度なら問題ないが、数百個の部品が配置される大規模図面では、二重ループは致命的な遅延を招く。その場合、空間分割法(Spatial Partitioning)、例えば「グリッド分割」や「四分木(Quadtree)」を導入し、近傍のビューのみと衝突判定を行うように最適化せよ。

C. Windows APIの活用(パフォーマンスの極限)

VBAの実行速度に限界を感じる場合、高精度なタイミング計測のために `kernel32` の `QueryPerformanceCounter` を呼び出し、アルゴリズムのボトルネックを定量的に特定せよ。また、複雑な幾何計算が必要な場合は、ロジック部分のみをC++で記述したDLLとして切り出し、VBAから呼び出す構成(COM Visible)が、プロフェッショナルの選択である。

結言

図面の自動レイアウトは、単なる「配置」ではない。それは「空間の最適化」という数学的課題である。バウンディングボックスを掌握し、オブジェクトのライフサイクルを制御下に置くこと。それこそが、凡庸なマクロ作成者と、伝説的なエンジニアを分かつ境界線である。

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