【テクニカル・上級編】【上級プロ】大規模アセンブリの軽量化とメモリ最適化:ライトウェイト(Lightweight)モードと解凍状態をVBAで動的制御するメモリ管理術 – SolidWorks VBA解析バイブル

スポンサーリンク

1. はじめに:大規模アセンブリにおける「メモリの死線」

数千点、あるいは数万点に及ぶ構成部品を抱える大規模アセンブリモデル。この巨大なデータ構造に対して、ナイーブに実装されたSolidWorks VBAマクロを流した瞬間、プロセスのコミットチャージは急上昇し、やがて応答停止、最悪の場合はクラッシュダンプを残してCADごと蒸発する――。現場の自動化システム開発で、誰もが一度は直面する悪夢です。

多くのエンジニアは「VBAの処理速度が遅い」「PCの物理メモリが足りない」と片付けがちですが、本質はそこにありません。問題はSolidWorks APIの背後にあるParasolidモデリングカーネルの幾何メモリ管理と、COM(Component Object Model)ラッパーのライフサイクルに対する無理解にあります。

アセンブリ内のすべての部品を「完全解凍(Resolved)」状態でオンメモリに保持すれば、どんな超弩級ワークステーションであっても破綻します。大規模アセンブリの自動化を極める上で不可欠なのは、「軽量(Lightweight)状態でアセンブリツリーを走査し、幾何演算が必要な瞬間だけ該当ノードをピンポイントで解凍、処理完了と同時に即座に軽量状態へフォールバックさせる」というオンデマンドのメモリ制御戦略です。

本稿では、レガシー環境の最前線で数々のCAD自動化システムを設計してきたアーキテクトの視点から、VBAによるアセンブリ状態の動的制御と、Windows APIを組み合わせたプロセスメモリの極限管理術を解説します。

—

2. Parasolid幾何メモリとCOM参照の二重構造

なぜ、単純なループ処理で大規模アセンブリを走査するとメモリが枯渇するのか。SolidWorksの内部アーキテクチャから紐解きます。

完全解凍(Resolved)と軽量(Lightweight)の幾何データ構造の差異

  • 完全解凍(Resolved / `swComponentFullyResolved`):

フィーチャー履歴ツリー、全B-Rep(トポロジ・ジオメトリ)データ、パラメトリック関係がParasolidカーネル空間にフル展開されます。合致(Mate)の幾何参照面やトポロジ探索が可能になる代償として、構成部品1点あたり数MB〜数十MBのメモリを占有します。

  • 軽量(Lightweight / `swComponentLightweight`):

メモリ上にはキャッシュされたテッセレーション(表示用グラフィックスファセット)データと、アセンブリツリーの参照メタデータのみがロードされます。幾何トポロジは実メモリ上に存在しないため、メモリ消費量は完全解凍時の数分の一から数十分の一に抑制されます。合致条件のトポロジ取得やマスプロパティの精密計算は直接行えませんが、階層構造の走査やプロパティの読み取りは高速に実行可能です。

[大規模アセンブリ走査のメモリ占有イメージ]

Resolved一括展開:
[ルートアセンブリ]
├── [部品A (Fully Resolved)] –> [Parasolid幾何・履歴データ (大)]
├── [部品B (Fully Resolved)] –> [Parasolid幾何・履歴データ (大)]
└── … (数千点) –> 物理メモリ枯渇 (Out of Memory)

オンデマンド制御:
[ルートアセンブリ] (全Lightweight)
├── [部品A (Lightweight)] –> [グラフィックスメタデータのみ (極小)]
├── [部品B (一時的Resolved)] –> [ジオメトリ演算] –> [即座にLightweight復元・解放]
└── [部品C (Lightweight)] –> [グラフィックスメタデータのみ (極小)]

COM参照リークのメカニズム

VBAで`swAssy.GetComponents(False)`などを呼び出し、Variant配列に数千個の`Component2`オブジェクトを受け取った時点で、VBAランタイム内には数千個のCOMインターフェースポインタ(`IUnknown`参照)が生成されます。
VBAの参照カウント管理はスコープ終了時に解放される設計ですが、大規模ループ内で明示的に`Set obj = Nothing`を行わず、かつSolidWorks側の幾何エンティティ(`Face2`、`Edge2`など)を保持し続けると、SolidWorks内部のガベージコレクションが機能せず、C++層のヒープが解放されません。結果として、プロセスのワーキングセットとプライベートコミットバイト数が天井知らずに増大します。

—

3. アセンブリ状態の動的制御API

動的制御の中核となるのは、`Component2`インターフェースの抑制状態制御メソッドです。

`SetSuppression2` メソッドの挙動

Function SetSuppression2(State As Long) As Long

引数 `State` には `swComponentSuppressionState_e` 列挙型を渡します。

  • `swComponentLightweight` (1): 軽量状態へ移行
  • `swComponentFullyResolved` (2): 完全解凍状態へ移行

この呼び出しは、SolidWorksの内部ドキュメントマネージャに対して動的なロード/アンロードを命令します。特定のパーツに対してのみ `swComponentFullyResolved` を発行し、トポロジの抽出や合致参照の作成を完遂した後、再び `swComponentLightweight` を指定することで、Parasolid空間から幾何データを強制退去させることができます。

—

4. 実践:オンデマンド・リゾルブ・エンジンの実装

ここでは、数千規模の部品を持つアセンブリを走査し、特定の条件に合致する部品のみを一時的に完全解凍して幾何演算(例:カスタムプロパティの精密更新や合致ターゲットの検査)を行い、即座に軽量状態へ戻す堅牢なコードを提示します。

また、処理中のプロセスメモリ消費量をリアルタイムで可視化・監視するために、Windows API(`psapi.dll`)の呼び出しを組み込みます。

フルソースコード(VBA)

Option Explicit

‘ ==============================================================================
‘ Windows API 定義: プロセスメモリ監視
‘ ==============================================================================
If VBA7 Then
Private Declare PtrSafe Function GetCurrentProcess Lib “kernel32” () As LongPtr
Private Declare PtrSafe Function GetProcessMemoryInfo Lib “psapi.dll” ( _
ByVal hProcess As LongPtr, _
ByRef ppsmemCounters As PROCESS_MEMORY_COUNTERS, _
ByVal cb As Long) As Long
Else
Private Declare Function GetCurrentProcess Lib “kernel32” () As Long
Private Declare Function GetProcessMemoryInfo Lib “psapi.dll” ( _
ByVal hProcess As Long, _
ByRef ppsmemCounters As PROCESS_MEMORY_COUNTERS, _
ByVal cb As Long) As Long
End If

Private Type PROCESS_MEMORY_COUNTERS
cb As Long
PageFaultCount As Long
PeakWorkingSetSize As LongPtr
WorkingSetSize As LongPtr
QuotaPeakPagedPoolUsage As LongPtr
QuotaPagedPoolUsage As LongPtr
QuotaPeakNonPagedPoolUsage As LongPtr
QuotaNonPagedPoolUsage As LongPtr
PagefileUsage As LongPtr ‘ Private Commit Bytes
PeakPagefileUsage As LongPtr
End Type

‘ ==============================================================================
‘ メインエントリポイント
‘ ==============================================================================
Public Sub ExecuteLargeAssemblyOptimization()
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2
Dim swAssy As SldWorks.AssemblyDoc
Dim vComps As Variant
Dim swComp As SldWorks.Component2
Dim i As Long
Dim initialMemMB As Double
Dim currentMemMB As Double

‘ SolidWorks インスタンスの捕捉
Set swApp = Application.SldWorks
Set swModel = swApp.ActiveDoc

If swModel Is Nothing Then
MsgBox “対象のアセンブリドキュメントが開かれていません。”, vbCritical, “エラー”
Exit Sub
End If

If swModel.GetType <> swDocASSEMBLY Then
MsgBox “アクティブなドキュメントはアセンブリではありません。”, vbCritical, “エラー”
Exit Sub
End If

Set swAssy = swModel
initialMemMB = GetPrivateBytesMB()
Debug.Print “=========================================================”
Debug.Print “バッチ処理開始時コミットメモリ: ” & Format(initialMemMB, “0.00”) & ” MB”
Debug.Print “=========================================================”

‘ 描画更新と自動リビルドの完全抑止(パフォーマンス極限化)
swModel.FeatureManager.EnableFeatureTree = False
swApp.CommandInProgress = True

On Error GoTo ErrorHandler

‘ トップレベルおよび全階層の構成部品を取得(TopLevelOnly = False)
vComps = swAssy.GetComponents(False)

If Not IsEmpty(vComps) Then
For i = 0 To UBound(vComps)
Set swComp = vComps(i)

‘ 抑制(Suppressed)されていない有効な部品のみを対象とする
If swComp.GetSuppression2 <> swComponentSuppressed Then
ProcessComponentOnDemand swComp
End If

‘ COMオブジェクトの参照カウントをループごとに明示的デクリメント
Set swComp = Nothing

‘ 500部品ごとにメモリ使用量をモニタリング&VBAイベント解放
If (i Mod 500) = 0 And i > 0 Then
currentMemMB = GetPrivateBytesMB()
Debug.Print “走査進捗: ” & i & ” 点完了 | 現行コミット: ” & Format(currentMemMB, “0.00”) & ” MB”
DoEvents
End If
Next i
End If

Debug.Print “全走査完了。”

CleanUp:
‘ 描画・イベント抑止の解除
swModel.FeatureManager.EnableFeatureTree = True
swApp.CommandInProgress = False

currentMemMB = GetPrivateBytesMB()
Debug.Print “=========================================================”
Debug.Print “バッチ処理終了時コミットメモリ: ” & Format(currentMemMB, “0.00”) & ” MB”
Debug.Print “メモリ差分: ” & Format(currentMemMB – initialMemMB, “+0.00;-0.00″) & ” MB”
Debug.Print “=========================================================”

‘ ガベージコレクションとドキュメントツリーの整合性リフレッシュ
swModel.GraphicsRedraw2
Set swComp = Nothing
Set swAssy = Nothing
Set swModel = Nothing
Set swApp = Nothing
Exit Sub

ErrorHandler:
Debug.Print “致命的エラー発生 [Code: ” & Err.Number & “]: ” & Err.Description
Resume CleanUp
End Sub

‘ ==============================================================================
‘ 個別コンポーネントのオンデマンド処理ルーチン
‘ ==============================================================================
Private Sub ProcessComponentOnDemand(ByVal swComp As SldWorks.Component2)
Dim originalState As Long
Dim compModel As SldWorks.ModelDoc2
Dim isResolvedByUs As Boolean

isResolvedByUs = False
originalState = swComp.GetSuppression2

‘ 仮想構成部品や抑制中を除外
If originalState = swComponentSuppressed Then Exit Sub

‘ 処理判定:例として「特定プレフィックスを持つ部品」や「特定条件の部品」のみ解凍
‘ ここでは軽量状態の部品のみを一時的にResolvedにするロジックを提示
If originalState = swComponentLightweight Then
‘ 幾何情報が必要なため、一時的に完全解凍(Resolved)へプロモート
swComp.SetSuppression2 swComponentFullyResolved
isResolvedByUs = True
End If

‘ 実体化されたModelDoc2の取得(Resolved状態でないと幾何ポインタは取得不可)
Set compModel = swComp.GetModelDoc2

If Not compModel Is Nothing Then
‘ ————————————————————-
‘ 【重要処理領域】幾何演算・トポロジ解析・プロパティ操作を実施
‘ ————————————————————-
ExecuteCoreGeometryTask compModel, swComp

‘ ModelDoc2の参照を即座に切断
Set compModel = Nothing
End If

‘ 自前で解凍した場合のみ、即座にLightweightへフォールバックして幾何データを破棄
If isResolvedByUs Then
swComp.SetSuppression2 swComponentLightweight
End If
End Sub

‘ ==============================================================================
‘ 幾何演算コアタスク(ダミー実装例)
‘ ==============================================================================
Private Sub ExecuteCoreGeometryTask(ByVal targetModel As SldWorks.ModelDoc2, ByVal targetComp As SldWorks.Component2)
‘ ここで合致対象のFace探索、ボディ・バウンディングボックス計算などを実行
‘ ※ 巨大なジオメトリオブジェクト(Body2, Face2等)はプロシージャ内で完結させ、
‘ グローバル変数やコレクションに残さないこと
Dim partTitle As String
partTitle = targetModel.GetTitle

‘ 極小タスク例(タイトル読み取りと特定属性の確認)
‘ Debug.Print ” -> Resolved処理実行: ” & partTitle
End Sub

‘ ==============================================================================
‘ Windows APIを用いたプロセスプライベートバイト(コミットチャージ)の取得
‘ ==============================================================================
Private Function GetPrivateBytesMB() As Double
Dim hProcess As LongPtr
Dim pmc As PROCESS_MEMORY_COUNTERS
Dim ret As Long

hProcess = GetCurrentProcess()
pmc.cb = LenB(pmc)

ret = GetProcessMemoryInfo(hProcess, pmc, pmc.cb)
If ret <> 0 Then
‘ PagefileUsage がプロセスの Private Commit Bytes(幾何データを含む実メモリ割り当て)を示す
GetPrivateBytesMB = CDbl(pmc.PagefileUsage) / (1024# 1024#)
Else
GetPrivateBytesMB = 0#
End If
End Function

—

5. コードの設計意図とアーキテクチャ解説

1. `FeatureManager.EnableFeatureTree = False` と `CommandInProgress`

大規模アセンブリでコンポーネントの状態(軽量 ⇔ 解凍)を切り替えるたびに、SolidWorksはデフォルトでFeatureManagerデザインツリーの再描画とUIイベントの発行を行います。数千回のツリー再描画は、膨大なGDIハンドルの消費とUIスレッドのブロッキングを引き起こします。これを完全にロックすることで、描画オーバーヘッドをゼロに抑え込みます。

2. COM参照のライフサイクル局所化

`ProcessComponentOnDemand` サブルーチンを分離しているのは単なる可読性のためではありません。VBAのスタックフレームを利用して、`compModel`(`ModelDoc2`)などのCOM参照変数のスコープを強制的に閉じるためです。サブルーチンを抜けることでローカル変数はスコープアウトし、COMの`Release`が即座に呼ばれます。メインループ内にすべての変数をベタ書きすることは、参照解放の遅延を招く典型的なアンチパターンです。

3. Windows APIによる「プライベートコミット」の監視

メモリの健康状態を監視する際、タスクマネージャーの標準列である「メモリ(ワーキングセット)」だけを見ていては真実を見誤ります。CADプロセスにおいて監視すべき最重要指標は、プロセスが排他的に割り当てているPrivate Bytes(`PROCESS_MEMORY_COUNTERS.PagefileUsage`)です。Parasolidがヒープを確保すると、まずこのコミットチャージが増大します。これがマシンの物理メモリ+ページファイルの上限に近づいた時点で、SolidWorksはクラッシュします。ログにこの数値を刻むことで、処理のどこでリークが発生しているかを厳密に特定できます。

—

6. エンタープライズ運用の知見:夜間バッチを落とさない鉄則

数千点のアセンブリを数百セット処理するようなエンタープライズレベルの移行・検査バッチでは、単一プロセス内でのオンデマンド制御にも限界が訪れます。SolidWorks自体のC++ヒープ断片化(フラグメンテーション)が蓄積するためです。

本番運用のシステムアーキテクチャでは、以下のプラクティスを設計に組み込みます。

A. ワーキングセットの強制トリミング(`SetProcessWorkingSetSize`)の功罪

Windows APIの `SetProcessWorkingSetSize(hProcess, -1, -1)` を呼ぶと、プロセスの物理メモリが一時的にページファイルに掃き出され、見かけ上のワーキングセットは劇的に下がります。しかし、これは対症療法に過ぎず、推奨されません。退避されたメモリにアクセスする瞬間に大量のハードページフォールトが発生し、I/Oスループットが壊滅的に低下するためです。重要なのは物理メモリの退避ではなく、`Component2.SetSuppression2` による幾何カーネルデータ自体の解放です。

B. プロセスの「計画的リサイクル」アーキテクチャ

真に堅牢なエンタープライズ・バッチを構築する場合、VBAマクロ単体にすべての処理を背負わせてはいけません。

  • 外部コントローラー(C# / PowerShell / Python等)からSolidWorksをCOM経由でプロセス外(Out-of-Process)起動する。
  • 1つの巨大アセンブリの処理が完了するごとに、またはPrivate Bytesが一定閾値(例: 16GB中 10GB)を超えた段階で、ドキュメントを閉じるだけでなくSolidWorksプロセス(`SLDWORKS.exe`)そのものを完全に強制終了・再起動する。

この「クリーンな状態への定期リサイクル」を自動化アーキテクチャの根底に据えることで、メモリリークによるクラッシュを構造的に根絶できます。

—

7. まとめ

大規模アセンブリの自動化において、コードの記述量と品質は比例しません。勝敗を分けるのは、SolidWorks APIの背後で駆動する幾何カーネルの振る舞いに対する解像度です。

1. 基本は常に「Lightweight」。必要なコンポーネントのみを局所的に「Resolved」へ昇格する。
2. 幾何演算が完了したら、即座に「Lightweight」へフォールバックさせる。
3. UIツリー描画の完全抑止と、サブルーチン分割によるCOM参照スコープの極小化を徹底する。
4. Windows APIを用いてプロセスのコミットチャージを監視し、ブラックボックスを排除する。

これらの規律を徹底して初めて、数千・数万点の大規模モデルを前にしても微動だにしない、プロフェッショナルの自動化基盤が完成します。

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