図面の「美しさ」をコードで制御せよ:寸法線の衝突検知と自動オフセット再配置アルゴリズム
自動化エンジニアとして、多くのプロジェクトを見てきた。
「ビューを自動生成するマクロ」を作る人間は腐るほどいる。しかし、「生成された図面を、人間が書いたかのように美しく整列させるマクロ」を作れる人間は、極めて稀だ。
単にAPIを叩いて寸法を出すだけなら、初心者でもできる。だが、実務における真の課題は、自動生成によって発生する「寸法の重なり」「引き出し線の交差」「視認性の欠如」という、いわば「図面のノイズ」をどう制御するかにある。
本記事では、中級者への階段を登ろうとするエンジニアに向けて、寸法線の衝突を検知し、幾何学的な計算に基づきオフセット位置を再計算する、プロダクションレベルの設計思想と実装コードを伝授する。
1. 凡庸な設計と、プロフェッショナルな設計の決定的な違い
多くの開発者が陥る罠がある。それは、「ループの中で直接オブジェクトを操作する」という設計だ。
❌ 非効率な設計(アンチパターン)
1. 図面内の全ての寸法をループで回す。
2. 現在の寸法と、他の寸法との距離を計算する。
3. 重なっていたら、その場で `SetPosition` で位置を動かす。
この設計は、一見シンプルだが、実務では致命的なバグとパフォーマンス低下を招く。SolidWorksのジオメトリエンジンは、オブジェクトが移動するたびに再計算を行う。ループ内で移動を繰り返すと、計算コストが指数関数的に増大し、最悪の場合、SolidWorksがフリーズするか、移動させた結果、別の寸法と衝突するという「玉突き事故」が発生する。
✅ 堅牢な設計(アーキテクトの視点)
プロフェッショナルは、「状態の分離(Decoupling)」を行う。
1. Collect(収集): 全ての寸法オブジェクトとその現在の座標(Bounding Box)をメモリ上のデータ構造(CollectionやDictionary)に格納する。
2. Analyze(分析): 図面(API)には一切触れず、メモリ上のデータのみを用いて、衝突している寸法のペアを特定する。
3. Plan(計画): 衝突を解消するための「移動量」をあらかじめ計算し、移動計画リストを作成する。
4. Execute(実行): 最後に一括して、計算済みの座標をAPI経由で適用する。
この「収集 → 分析 → 実行」のパイプラインこそが、バグのない、高速な自動化ツールの背骨となる。
2. アルゴリズムの核心:衝突検知とオフセット計算
寸法線の衝突を判定する際、厳密な線分交差判定(Line-Line Intersection)を実装するのは賢明ではない。計算負荷が高すぎる上に、寸法文字の「高さ」や「余白」を考慮しきれないからだ。
実務的なアプローチは、「Bounding Box(境界ボックス)による矩形衝突判定」である。
1. 各寸法の `GetBoundingBox` を取得する。
2. 2つの矩形が重なっているか、あるいは距離が一定のしきい値(`Threshold`)以下かを判定する。
3. 重なっている場合、寸法の「配置方向(Horizontal/Vertical)」に基づき、軸方向のオフセット量を算出する。
3. プロダクション・コード例
以下に、この設計思想を具現化したVBAコードのテンプレートを示す。これは単なるサンプルではない。エラーハンドリングと、保守性を考慮した構造的なコードだ。
Option Explicit
' ==============================================================================
' Module: DimensionLayoutManager
' Description: 図面上の寸法衝突を検知し、自動的にオフセットを適用する
' Author: Chief Architect
' ==============================================================================
' 寸法情報を格納するためのカスタムクラスの代わりとなる構造体
Private Type DimInfo
Obj As DisplayDimension
X As Double
Y As Double
Width As Double
Height As Double
IsHorizontal As Boolean
End Type
' 設定定数
Private Const OFFSET_BUFFER As Double = 0.005 ' 寸法間の最小間隔 (m)
Private Const TOLERANCE As Double = 0.001 ' 判定許容誤差 (m)
Sub Main()
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2
Dim swDraw As SldWorks.DrawingDoc
Dim swSheet As SldWorks.Sheet
On Error GoTo ErrorHandler
Set swApp = Application.SldWorks
Set swModel = swApp.ActiveDoc
' 1. 整合性チェック
If swModel Is Nothing Then Exit Sub
If swModel.GetType <> swDocDRAWING Then
MsgBox "図面ファイルを開いてから実行してください。", vbCritical
Exit Sub
End If
Set swDraw = swModel
Set swSheet = swDraw.GetCurrentSheet
Debug.Print "--- Layout Optimization Start ---"
' 2. データの収集 (Collect Phase)
Dim dimList As Collection
Set dimList = CollectDimensions(swDraw)
If dimList.Count < 2 Then
Debug.Print "処理対象の寸法が不足しています。"
Exit Sub
End If
' 3. 衝突分析と移動計画の策定 (Analyze & Plan Phase)
' ここでは簡略化のため、直接移動を行うロジックを実装するが、
' 本来はここで「移動計画リスト」を作成すべきである。
OptimizeLayout swDraw, dimList
' 4. 画面更新の制御 (パフォーマンス最適化)
swModel.GraphicsRedraw2
Debug.Print "--- Layout Optimization Completed ---"
MsgBox "寸法の再配置が完了しました。", vbInformation
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub
' ------------------------------------------------------------------------------
' 関数: CollectDimensions
' 目的: シート上の全寸法を走査し、座標情報をメモリに格納する
' ------------------------------------------------------------------------------
Private Function CollectDimensions(swDraw As SldWorks.DrawingDoc) As Collection
Dim col As New Collection
Dim swView As SldWorks.View
Dim swDispDim As SldWorks.DisplayDimension
Dim vDims As Variant
Dim i As Integer
Dim dimData As DimInfo
Dim vBox As Variant
' 全てのビューを走査
Set swView = swDraw.GetFirstView ' Sheet自体が最初のView
Do While Not swView Is Nothing
vDims = swView.GetDisplayDimensions
If Not IsEmpty(vDims) Then
For i = 0 To UBound(vDims)
Set swDispDim = vDims(i)
' 境界ボックスを取得 (x, y, z, x2, y2, z2)
vBox = swDispDim.GetBoundingBox
' データ格納
With dimData
Set .Obj = swDispDim
.X = vBox(0)
.Y = vBox(1)
.Width = vBox(3) - vBox(0)
.Height = vBox(4) - vBox(1)
' 水平・垂直の判定 (簡易的な実装)
.IsHorizontal = (Abs(.Width) > Abs(.Height))
End With
col.Add dimData
Next i
End If
Set swView = swView.GetNextView
Loop
Set CollectDimensions = col
End Function
' ------------------------------------------------------------------------------
' サブルーチン: OptimizeLayout
' 目的: 衝突を検知し、オフセットを適用する
' ------------------------------------------------------------------------------
Private Sub OptimizeLayout(swDraw As SldWorks.DrawingDoc, dimList As Collection)
Dim i As Long, j As Long
Dim d1 As DimInfo, d2 As DimInfo
Dim swModel As SldWorks.ModelDoc2
Set swModel = swDraw.GetModelDoc
' 2重ループによる衝突判定 (O(N^2) - 小規模な図面なら実用的)
For i = 1 To dimList.Count
d1 = dimList(i)
For j = i + 1 To dimList.Count
d2 = dimList(j)
' 矩形衝突判定 (AABB Collision Detection)
If Not (d1.X + d1.Width + OFFSET_BUFFER < d2.X Or _
d1.X > d2.X + d2.Width + OFFSET_BUFFER Or _
d1.Y + d1.Height + OFFSET_BUFFER < d2.Y Or _
d1.Y > d2.Y + d2.Height + OFFSET_BUFFER) Then
' 衝突検知:オフセット計算
' ここでは、水平方向の衝突の場合、y軸方向にずらすロジックとする
Debug.Print "Collision detected between Dim " & i & " and " & j
Dim newY As Double
If d1.IsHorizontal And d2.IsHorizontal Then
' 両方水平なら、重ならないようにY座標を調整
newY = d1.Y + d1.Height + OFFSET_BUFFER
d1.Obj.SetPosition d1.X, newY
End If
' ※注意: 実際の実装では、ここで直接SetPositionせず、
' 事前に計算した「移動量」を適用する設計にすることを強く推奨する。
End If
Next j
Next i
End Sub
4. 実務における運用上の注意点
このツールを「プロダクション・ツール」として昇華させるには、以下の3点に留意せよ。
① データベースとの連携(ログ管理)
大規模な自動化プロジェクトでは、マクロが「何を動かしたか」の履歴が不可欠だ。
`Debug.Print` で済ませるのではなく、CSVファイルやSQLデータベースに、`[Timestamp, SheetName, DimID, OldPos, NewPos]` を出力する機能を組み込め。これにより、自動化による意図しない変更の追跡が可能になる。
② 幾何学的特異点への配慮
寸法線が完全に垂直、あるいは完全に水平ではない「斜めの寸法」の場合、上記の矩形判定だけでは不十分だ。斜め寸法を扱う場合は、回転行列を用いた座標変換、あるいは `swDimension.GetDimensionText` 等を用いて、寸法線自体の角度を取得するロジックを組み込む必要がある。
③ ユーザーの「意図」の尊重
自動化エンジニアが最も陥りやすい過ちは、「ユーザーが意図的に配置したレイアウトまで書き換えてしまうこと」だ。
「この寸法は自動配置の対象外とする」というフラグを、カスタムプロパティや特定のレイヤー(図層)によって持たせる設計を忘れてはならない。
結びに代えて
図面の自動整列は、単なる「見た目の修正」ではない。それは、設計者が図面を読み取る際の「認知負荷」を軽減するための、高度な情報設計である。
APIを使いこなす技術力も重要だが、それ以上に「どのようにデータを扱い、いかに安全に、いかに美しく結果を出すか」というアーキテクトとしての視点を磨け。それが、凡庸なスクリプターと、伝説的なエンジニアを分かつ境界線である。
