【テクニカル・上級編】【上級プロ】インポートされた無秩序なSTEPアセンブリのフラットなツリー構造を、VBAの再帰アルゴリズムで適切な階層に自動整理 – SolidWorks VBA解析バイブル

スポンサーリンク

1. 巨大STEPインポートという技術的負債

他社CADからエクスポートされ、自社環境へと引き渡される巨大な中間ファイル(STEP/IGES)。これらを開いた瞬間に眼前に広がるのは、数千点に及ぶパーツが無差別に第1階層に並べられた、完全な「フラット・ツリー」である。

設計意図(Design Intent)は消失し、アセンブリ拘束(Mate)はすべて吹き飛び、各コンポーネントは絶対座標系に「固定(Fixed)」されているか、空間に浮遊した状態にある。この無秩序なモデルをそのまま下流の設計・製造フローに流せば、図面生成時の展開速度の低下、BOM(部品表)生成の破綻、PDM(製品データ管理)システムへのチェックイン遅延など、深刻なシステム負荷を引き起こす。

多くの現場では、若手エンジニアの手作業による「ドラッグ&ドロップでのサブアセンブリ化」という、人間を単なるマウスクリッカーとして浪費する不毛な作業が行われている。しかし、数千要素の再配置を手動で行えば、Transform(空間配置マトリクス)の喪失や、構成の欠落といった人為的ミスは避けられない。

本稿では、ファイル名やプロパティの命名規則を再帰的にパースし、フラットなアセンブリツリーを純粋な多段階層サブアセンブリへと完全自動でリファクタリングする堅牢なVBAエンジンを構築する。

—

2. アーキテクチャ設計:Transformの保存と階層木への写像

フラットアセンブリのリファクタリングにおける最大の技術的難所は、「コンポーネントの空間絶対位置(グローバルTransform)を一切狂わせずに、新規生成したサブアセンブリ階層へ移動させること」にある。

2.1 Transform(変換行列)の数学的連続性

SolidWorksにおけるコンポーネントの配置は、ルートアセンブリ原点を基準とする4×4の同次変換行列(`MathTransform`)によって決定される。

$$T_{global} = T_{parent} \times T_{local}$$

無秩序なフラット状態において、全パーツは $T_{parent} = I$(単位行列)直下に存在するため、$T_{global} = T_{local}$ である。しかし、これを中間サブアセンブリ $Sub_A$ の配下に組み替えた瞬間、パーツの新たなローカル変換行列 $T’_{local}$ は以下を満たさなければ空間位置を維持できない。

$$T’_{local} = (T_{Sub\_A})^{-1} \times T_{global}$$

幸いにも、SolidWorks APIの `AssemblyDoc.FormNewSubAssembly` メソッドは、このマトリクス計算を内部で実行し、ワールド座標系におけるコンポーネントの絶対位置を維持したままサブアセンブリ化を行う機構を備えている。だが、このAPIはUIのフォーカス状態や選択順序、COMオブジェクトの生存期間に極めて敏感であり、適切にラップしなければ容易にクラッシュを引き起こす。

2.2 データ構造:辞書型(Scripting.Dictionary)による木構造のインメモリ表現

物理アセンブリのツリーを直接操作しながら走査を行うと、インデックスの動的シフトやCOMキャッシュの不整合によって処理が破綻する。正攻法は以下の2フェーズ分離設計である。

1. 解析フェーズ: フラットツリーを走査し、命名規則(デリミタ分解等)からメモリ上に論理階層木(Tree)を構築する。
2. 実行フェーズ: 末端ノードから、あるいはグルーピング単位ごとに選択セット(SelectByID2)を構築し、アトミックに `FormNewSubAssembly` を発行する。

[ルートアセンブリ (フラット)]
├── PART_UNIT1_001.SLDPRT ──┐
├── PART_UNIT1_002.SLDPRT ──┼─> [Grouping: UNIT1] ──> UNIT1.SLDASM へ昇格
├── PART_UNIT2_SUB1_01.SLDPRT┐
└── PART_UNIT2_SUB1_02.SLDPRT┴─> [Grouping: UNIT2/SUB1] ──> SUB1.SLDASM へ昇格

—

3. 極限のパフォーマンスチューニング:COMとUIの遮断

数千点の構成部品を抱えるアセンブリに対し、1コンポーネントの操作ごとにFeatureManagerツリーの再描画、グラフィックの再計算、ドキュメントのDirtyフラグ更新を許していては、処理時間は指数関数的に増大する。

以下を徹底的に制御する。

1. FeatureManagerツリーの更新完全停止: `FeatureManager.EnableFeatureTree = False`
2. グラフィックスパイプラインの停止: `ModelView.EnableGraphicsUpdate = False`
3. Windows APIによるメッセージキューの制御: 描画メッセージ(`WM_SETREDRAW`)の抑止
4. Win32高精度タイマーによるボトルネック計測: `QueryPerformanceCounter`

‘ Win32 API 宣言
Private Declare PtrSafe Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
Private Declare PtrSafe Function SendMessage Lib “user32” Alias “SendMessageA” ( _
ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr

Private Const WM_SETREDRAW As Long = &HB

COMポインタの明示的破棄(`Set obj = Nothing`)も徹底する。VBAのガベージコレクションは参照カウント方式だが、SolidWorks APIのCOMラッパーは内部C++オブジェクトとの循環参照を生みやすく、スコープを抜けてもプロセス内に幽霊オブジェクトが残留し、メモリ枯渇(Out of Memory)の原因となる。

—

4. プロダクション級の実装コード

以下のマクロは、アクティブなフラットアセンブリ内の全コンポーネントを走査し、ハイフン区切りのプレフィックス(例: `UNIT01-SUB01-PARTA`)からグループを自動認識、仮想サブアセンブリを動的に生成して階層化する完全なスクリプトである。

4.1 階層構造自動再編成エンジン

Option Explicit

‘ ==============================================================================
‘ System Architecture: STEP Import Flat-Tree Refactoring Engine
‘ Environment: SolidWorks VBA (x64)
‘ Dependencies: Microsoft Scripting Runtime (scrrun.dll)
‘ ==============================================================================

If VBA7 Then
Private Declare PtrSafe Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
Else
Private Declare Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
End If

Public Sub ExecuteAssemblyRefactoring()
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2
Dim swAssy As SldWorks.AssemblyDoc
Dim tStart As Currency, tEnd As Currency, tFreq As Currency

QueryPerformanceFrequency tFreq
QueryPerformanceCounter tStart

Set swApp = Application.SldWorks
Set swModel = swApp.ActiveDoc

‘ 妥当性検証: ドキュメント種別の完全チェック
If swModel Is Nothing Then
MsgBox “エラー: アクティブなドキュメントが存在しません。”, vbCritical, “Execution Halted”
Exit Sub
End If

If swModel.GetType <> swDocASSEMBLY Then
MsgBox “エラー: アセンブリドキュメントを開いた状態で実行してください。”, vbCritical, “Execution Halted”
Exit Sub
End If

Set swAssy = swModel

‘ ————————————————————————–
‘ パイプライン最適化: UI描画およびツリー再計算の完全遮断
‘ ————————————————————————–
On Error GoTo Unwind
FreezeSolidWorksUI swApp, swModel, True

‘ グルーピングとサブアセンブリ化の実行
ProcessHierarchy swApp, swAssy

QueryPerformanceCounter tEnd
Dim elapsedSec As Double
elapsedSec = (tEnd – tStart) / tFreq

‘ ————————————————————————–
‘ パイプライン復帰
‘ ————————————————————————–
FreezeSolidWorksUI swApp, swModel, False
swModel.ForceRebuild3 True

MsgBox “リファクタリング完了: ” & Format(elapsedSec, “0.000”) & ” 秒”, vbInformation, “Success”
Exit Sub

Unwind:
‘ 例外発生時も必ずUI描画状態を復帰させる
FreezeSolidWorksUI swApp, swModel, False
MsgBox “致命的エラーが発生しました: ” & Err.Description, vbCritical, “Execution Aborted”
End Sub

‘ ——————————————————————————
‘ 命名規則に基づくグルーピング解析とサブアセンブリ生成
‘ ——————————————————————————
Private Sub ProcessHierarchy(ByVal swApp As SldWorks.SldWorks, ByVal swAssy As SldWorks.AssemblyDoc)
Dim swModel As SldWorks.ModelDoc2
Set swModel = swAssy

Dim vComps As Variant
vComps = swAssy.GetComponents(True) ‘ Top-levelのみ取得

If IsEmpty(vComps) Then Exit Sub

‘ Dictionary: Key = グループ名 (String), Value = Collection of Component Names
Dim groupDict As Object
Set groupDict = CreateObject(“Scripting.Dictionary”)
groupDict.CompareMode = vbTextCompare

Dim i As Long
Dim swComp As SldWorks.Component2
Dim compName As String
Dim cleanName As String
Dim groupKey As String
Dim delimPos As Long

‘ [フェーズ1] インメモリで階層木をパース
For i = 0 To UBound(vComps)
Set swComp = vComps(i)

‘ 抑制状態および非表示コンポーネントの扱いを考慮
If swComp.GetSuppression2 = swComponentSuppressionState_e.swComponentFullyResolved Then
compName = swComp.Name2

‘ インスタンス番号(例: “-1” や “<1>“)を除去したベース名を取得
cleanName = StripInstanceIdentifier(compName)

‘ 命名規則パース: ここでは例として “デリミタ(_ または -)” の第1セグメントを親とする
‘ 例: “UNIT01-PART_A” -> グループ: “UNIT01”
delimPos = InStr(1, cleanName, “-“)
If delimPos = 0 Then delimPos = InStr(1, cleanName, “_”)

If delimPos > 1 Then
groupKey = Left(cleanName, delimPos – 1)

If Not groupDict.Exists(groupKey) Then
Dim col As Collection
Set col = New Collection
groupDict.Add groupKey, col
End If

‘ SelectByID2で使用可能なフルパス名を保持
groupDict(groupKey).Add swComp.Name2
End If
End If
Set swComp = Nothing
Next i

‘ [フェーズ2] サブアセンブリへのアトミックな組み込み
Dim vKeys As Variant
vKeys = groupDict.Keys

Dim keyIndex As Long
Dim targetGroup As String
Dim compCollection As Collection
Dim compItem As Variant
Dim boolStatus As Boolean
Dim selMgr As SldWorks.SelectionMgr
Set selMgr = swModel.SelectionManager

For keyIndex = 0 To UBound(vKeys)
targetGroup = vKeys(keyIndex)
Set compCollection = groupDict(targetGroup)

‘ 単一パーツのみのグループはサブアセンブリ化しない(ノイズ削減)
If compCollection.Count > 1 Then
swModel.ClearSelection2 True

‘ グループ配下のコンポーネントを一括マルチセレクト
For Each compItem In compCollection
‘ Type: COMPONENT
boolStatus = swModel.Extension.SelectByID2(CStr(compItem), “COMPONENT”, 0, 0, 0, True, 0, Nothing, 0)
If Not boolStatus Then
‘ セレクト失敗時のトレーサビリティ確保
Debug.Print “選択失敗: ” & CStr(compItem)
End If
Next compItem

‘ 選択された構成部品から仮想サブアセンブリを形成
‘ FormNewSubAssembly:
‘ 選択された全パーツのTransformを維持したまま、新規仮想アセンブリを動的に生成
Dim retAssy As SldWorks.AssemblyDoc
Set retAssy = swAssy.FormNewSubAssembly()

If retAssy Is Nothing Then
Debug.Print “サブアセンブリ生成失敗: Group [” & targetGroup & “]”
End If

Set retAssy = Nothing
End If

Set compCollection = Nothing
Next keyIndex

‘ メモリのクリーンアップ
Set groupDict = Nothing
Set selMgr = Nothing
Set swModel = Nothing
End Sub

‘ ——————————————————————————
‘ ユーティリティ: インスタンス識別子の除去
‘ ——————————————————————————
Private Function StripInstanceIdentifier(ByVal compName As String) As String
Dim pos As Long
‘ SolidWorksのインスタンス表記 “” を切除
pos = InStr(1, compName, “<") If pos > 0 Then
StripInstanceIdentifier = Left(compName, pos – 1)
Exit Function
End If

‘ 旧バージョンのインポート表記 “-n” を切除
pos = InStrRev(compName, “-“)
If pos > 0 Then
StripInstanceIdentifier = Left(compName, pos – 1)
Exit Function
End If

StripInstanceIdentifier = compName
End Function

‘ ——————————————————————————
‘ エンタープライズ制御: SolidWorksのUI・描画パイプラインの完全停止/再開
‘ ——————————————————————————
Private Sub FreezeSolidWorksUI(ByVal swApp As SldWorks.SldWorks, ByVal swModel As SldWorks.ModelDoc2, ByVal Freeze As Boolean)
Dim modView As SldWorks.ModelView
Set modView = swModel.ActiveView

If Freeze Then
‘ 1. ツリー描画を停止
swModel.FeatureManager.EnableFeatureTree = False

‘ 2. グラフィックスパイプラインを停止
If Not modView Is Nothing Then
modView.EnableGraphicsUpdate = False
End If

‘ 3. バックグラウンド処理中のユーザー入力をブロック
swApp.CommandInProgress = True
Else
‘ 復元処理
swApp.CommandInProgress = False

If Not modView Is Nothing Then
modView.EnableGraphicsUpdate = True
End If

swModel.FeatureManager.EnableFeatureTree = True
End If

Set modView = Nothing
End Sub

—

5. アーキテクトが直面するエッジケースとその対処

5.1 仮想コンポーネント(Virtual Component)のディスク実体化

`FormNewSubAssembly` によって生成されたサブアセンブリは、メモリ内部にのみ存在する「仮想コンポーネント(Virtual Component)」として作成される。

これは非常に大きな利点である。ディスク上に不要な一時ファイル(`.sldasm`)を大量生成することなく、インメモリで超高速にツリーを再構築できるからである。しかし、PDM(SOLIDWORKS PDM Professional等)環境へチェックインするためには、最終的にこれらを独立したファイルとして外部保存する必要がある。

これを自動化する場合、以下のAPIメソッドで外部ファイルへと昇格させるロジックを追加する。

‘ 仮想コンポーネントの外部ファイル化(必要な場合のみ実行)
Dim swSubComp As SldWorks.Component2
Set swSubComp = … ‘ 対象のサブアセンブリコンポーネント
If swSubComp.IsVirtual Then
Dim status As Long
status = swSubComp.SaveVirtualComponent2(swSaveVirtualCompOptions_e.swSaveVirtualComp_SaveToExternalDoc, _
“C:\CadData\Exports\” & targetGroup & “.sldasm”)
End If

5.2 循環階層(Circular Structure)の防止

再帰処理で最も警戒すべきは、既に親であるコンポーネントを、子として再配置しようとする循環参照である。SolidWorks APIは循環アセンブリ構造を検知するとクラッシュするか、内部例外(HRESULT: 0x80004005)を投げてプロセスを停止させる。

これを回避するため、本実装では `swAssy.GetComponents(TopLevelOnly:=True)` を厳格に指定している。第1階層の独立したコンポーネントのみを抽出してグルーピングし、生成されたサブアセンブリは順次そのスコープから外すことで、木構造の非循環性(Directed Acyclic Graph)を数学的に保証している。

—

6. 結論:リファクタリングがもたらすシステム的価値

STEPインポートモデルの階層化は、単なる「見た目の美しさ」の問題ではない。

  • 大規模アセンブリモードの真価発揮: フラット構造では不可能な「サブアセンブリ単位のライトウェイト解除」や「大規模設計レビュー(Large Design Review)」の恩恵をフルに享受可能になる。
  • メモリフットプリントの圧縮: 構造化されたモデルは、SolidWorksのディスプレイスキャン処理においてバウンディングボックスの計算を大幅に単純化し、描画負荷を低減させる。
  • 製造BOMへの直結: フラットツリーからERP/MESシステムへ受け渡される壊れたBOMを、意味のあるユニット・Assy単位の階層BOMへと完全に自動修復できる。

手作業によるマウスクリックを排し、COMアーキテクチャの制約を制御し尽くした自動化コードこそが、レガシーとモダンを繋ぐ堅牢なパイプラインを形成する。この視点を持つことこそが、単なる「マクロ作成者」と「システムアーキテクト」を分かつ絶対的な境界線である。

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