SolidWorks VBAを掌握する極限の知見:アセンブリ構造の完全JSONシリアライゼーション
序章:アセンブリデータ管理の深淵と我々の到達点
長年にわたりSolidWorksの自動化とデータ連携の最前線に立ち、VBAという枯れた技術スタックから、現代的なシステムアーキテクチャまでを股にかけてきた者として、私は常に問い続けてきました。SolidWorksモデルの真の価値は、そのバイナリファイルの中だけでなく、そこから抽出され、再構築可能な「構造と関係性」にこそ宿るのではないか、と。
今日のテーマは、単なるSolidWorks APIの利用法ではありません。それは、SolidWorksの複雑なアセンブリ構造を、完全にテキストデータ(JSON)としてシリアライズし、そしてコードベースから全く同一のアセンブリを再構築するという、究極のデータ管理手法への挑戦です。これは、バージョン管理、外部システム連携、自動生成といった、現代の設計プロセスが抱える多くの課題に対する、根本的なソリューションとなり得ます。
なぜJSONなのか。SolidWorksのネイティブファイル(SLDASM, SLDPRT)は、バージョン依存性が高く、テキストエディタで内容を覗き見ることもできず、外部システムとの連携には常にAPIを介したSolidWorks本体の起動が必須でした。これは、データドリブンな設計ワークフローを阻害する最大の要因です。JSONという普遍的なテキストフォーマットにアセンブリの「設計意図」を落とし込むことで、我々はCADシステムの制約からデータを解放し、真のデータ独立性を手に入れることができます。
本記事では、この壮大な目標を達成するための、SolidWorks VBAとAPIを駆使した極限の知見を解説します。Windows APIの呼び出し、COMオブジェクトのライフサイクル管理、レガシー環境での堅牢なシステム構築、そして現代的なシステム間連携を見据えたアーキテクチャ設計まで、私の経験の全てをここに記します。
Core Concept: アセンブリ構造の「設計意図」をJSONに封じ込める
アセンブリをテキストデータとして「完全復元」するには、以下の三つの核心要素を正確に抽出・表現し、そして再構築する必要があります。
1. ツリー階層構造: どのコンポーネントがどのアセンブリの直下に配置されているか。
2. コンポーネントの配置情報: 各コンポーネントが親アセンブリに対してどのような位置と姿勢(回転)で配置されているか。これは「変換マトリクス(Transform Matrix)」によって表現されます。
3. 合致(Mate)情報: 各コンポーネント間の幾何学的拘束条件。合致タイプ、合致対象エンティティ、合致条件(距離、角度、反転など)。
これら三要素をJSONスキーマに落とし込むことが、このエンジンの核となります。
JSONスキーマ設計の要諦
以下に、我々が提唱するJSONスキーマの設計思想と、その構造の具体例を示します。
{
“AssemblyRoot”: {
“DocumentPath”: “C:\\SolidWorksData\\MainAssembly.SLDASM”,
“Components”: [
{
“InstanceName”: “PartA-1”,
“ReferencedPath”: “C:\\SolidWorksData\\Parts\\PartA.SLDPRT”,
“TransformMatrix”: [
1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, // 回転行列 (3×3)
0.0, 0.0, 0.0 // 並進ベクトル (3×1)
],
“IsFixed”: true,
“SubComponents”: []
},
{
“InstanceName”: “SubAssemblyB-1”,
“ReferencedPath”: “C:\\SolidWorksData\\SubAssemblies\\SubAssemblyB.SLDASM”,
“TransformMatrix”: [
0.866, -0.5, 0.0, 0.5, 0.866, 0.0, 0.0, 0.0, 1.0,
10.0, 20.0, 30.0
],
“IsFixed”: false,
“SubComponents”: [ // 再帰的な構造
{
“InstanceName”: “PartC-1”,
“ReferencedPath”: “C:\\SolidWorksData\\Parts\\PartC.SLDPRT”,
“TransformMatrix”: [ / … / ],
“IsFixed”: false,
“SubComponents”: []
}
]
}
],
“Mates”: [
{
“MateType”: “swMateCOINCIDENT”,
“MateName”: “Mate1”,
“Component1”: “PartA-1”,
“Entity1Ref”: “PartA-1@Face<1>“, // エンティティの参照方法が鍵
“Component2”: “SubAssemblyB-1”,
“Entity2Ref”: “SubAssemblyB-1@PartC-1@Edge<2>“,
“Flip”: false,
“Alignment”: “Aligned”
},
{
“MateType”: “swMateDISTANCE”,
“MateName”: “Mate2”,
“Component1”: “PartA-1”,
“Entity1Ref”: “PartA-1@Face<2>“,
“Component2”: “SubAssemblyB-1”,
“Entity2Ref”: “SubAssemblyB-1@Face<3>“,
“Distance”: 10.0,
“Flip”: true
}
]
}
}
このスキーマにおいて、特に重要なのは`TransformMatrix`と`EntityRef`です。
- `TransformMatrix`: SolidWorks APIの`IMathTransform`オブジェクトから取得できる9要素(3×3回転行列)と3要素(3×1並進ベクトル)を合わせた12要素の配列で表現します。これは、アセンブリ内のコンポーネントの位置と姿勢を完全に記述するものです。
- `EntityRef`: 合致のターゲットとなるエンティティ(面、エッジ、頂点、フィーチャーなど)を、一意に識別可能な文字列で表現します。これはSolidWorks APIの最も奥深い課題の一つであり、後述する「合致の再構築」セクションで詳しく解説します。
アセンブリ構造の解析とJSONシリアライゼーション(データ出力)
シリアライズのプロセスは、開いているSolidWorksアセンブリドキュメントを再帰的に走査し、上記スキーマに従ってデータを収集することに尽きます。
1. ツリー構造とコンポーネント情報の抽出
`IAssemblyDoc`の`GetComponents`または`GetComponentCount2`/`GetComponentAtIndex2`メソッドを用いて、ルートから順にコンポーネントを再帰的に走査します。
‘ VBAでJSONを扱うための外部ライブラリを想定。
‘ 例: VBA-JSON (https://github.com/VBA-tools/VBA-JSON)
‘ ここでは Dict (Scripting.Dictionary) と Collection でJSON構造を構築する例を示す。
‘ 実際には JsonConverter.bas などのモジュールをインポートして利用する方が効率的。
Private Function SerializeAssembly(swApp As SldWorks.SldWorks, swAssy As SldWorks.IAssemblyDoc) As Scripting.Dictionary
Dim dictAssembly As New Scripting.Dictionary
Dim colComponents As New Collection
Dim colMates As New Collection
‘ アセンブリパス
dictAssembly.Add “DocumentPath”, swAssy.GetPathName
‘ コンポーネントの再帰的な走査
Dim vComponents As Variant
vComponents = swAssy.GetComponents(True) ‘ Trueで非表示コンポーネントも取得
Dim i As Long
If Not IsEmpty(vComponents) Then
For i = LBound(vComponents) To UBound(vComponents)
Dim swComp As SldWorks.IComponent2
Set swComp = vComponents(i)
Call ProcessComponent(swComp, colComponents)
Next i
End If
dictAssembly.Add “Components”, colComponents
‘ 合致情報の抽出
Call ExtractMates(swAssy, colMates)
dictAssembly.Add “Mates”, colMates
Set SerializeAssembly = dictAssembly
End Function
Private Sub ProcessComponent(swComp As SldWorks.IComponent2, ByRef colParentComponents As Collection)
Dim dictComp As New Scripting.Dictionary
Dim colSubComponents As New Collection
dictComp.Add “InstanceName”, swComp.Name2
dictComp.Add “ReferencedPath”, swComp.GetPathName
dictComp.Add “IsFixed”, swComp.IsFixed
‘ 変換マトリクスの抽出
Dim swMathUtil As SldWorks.IMathUtility
Set swMathUtil = swComp.GetModelDoc2.Extension.CreateMathUtility
Dim swTransform As SldWorks.IMathTransform
Set swTransform = swComp.Transform2
Dim vTransformData As Variant
vTransformData = swTransform.ArrayData ‘ 9要素の回転行列と3要素の並進ベクトル (計12要素)
‘ TransformMatrixはJSONの配列として格納
Dim arrTransform(0 To 11) As Double
Dim j As Long
For j = 0 To 11
arrTransform(j) = vTransformData(j)
Next j
dictComp.Add “TransformMatrix”, arrTransform
‘ 子コンポーネントの再帰処理
Dim vSubComponents As Variant
vSubComponents = swComp.GetChildren
If Not IsEmpty(vSubComponents) Then
For j = LBound(vSubComponents) To UBound(vSubComponents)
Dim swChildComp As SldWorks.IComponent2
Set swChildComp = vSubComponents(j)
Call ProcessComponent(swChildComp, colSubComponents)
Next j
End If
dictComp.Add “SubComponents”, colSubComponents
colParentComponents.Add dictComp
‘ オブジェクトの明示的解放は、VBAではSet obj = Nothing で十分だが、
‘ ループ内で大量に生成される場合はガベージコレクションのタイミングに注意が必要。
‘ 特に SolidWorks API オブジェクトは COM オブジェクトのため、参照カウント管理が重要。
Set swTransform = Nothing
Set swMathUtil = Nothing
Set swComp = Nothing
End Sub
【極限の知見:COMオブジェクトのライフサイクルとメモリ】
VBAにおける`Set obj = Nothing`は、COMオブジェクトの参照カウントをデクリメントする最も基本的な操作です。しかし、これが即座にメモリ解放を保証するわけではありません。特にSolidWorks APIのような複雑なCOMオブジェクトは、内部で複数の参照を保持していることがあり、明示的な`Set Nothing`を怠ると、メモリリークや未定義動作の原因となります。大量のコンポーネントを処理する際には、ループ内でオブジェクトを生成したら、必ずそのスコープの終わりに`Set Nothing`を行う習慣を徹底してください。VB.NETやC#の`Marshal.ReleaseComObject`のような強制的な解放手段はVBAには存在しないため、参照管理はより慎重に行う必要があります。
2. 合致情報の抽出
合致情報の抽出は、`IAssemblyDoc.GetMates`メソッドから`IMate`オブジェクトを取得し、そのプロパティを読み取ります。しかし、合致の最も難しい部分は「合致対象のエンティティをどう識別するか」です。SolidWorks APIは`IMateEntity`オブジェクトを提供しますが、これをそのままJSONにシリアライズしても、復元時にそのエンティティを特定することはできません。
我々の戦略は、コンポーネント内のフィーチャーやエンティティを、その「名前」または「パス」で参照するというものです。
Private Sub ExtractMates(swAssy As SldWorks.IAssemblyDoc, ByRef colMates As Collection)
Dim vMates As Variant
vMates = swAssy.GetMates
If Not IsEmpty(vMates) Then
Dim i As Long
For i = LBound(vMates) To UBound(vMates)
Dim swMate As SldWorks.IMate2
Set swMate = vMates(i)
Dim dictMate As New Scripting.Dictionary
dictMate.Add “MateName”, swMate.Name
dictMate.Add “MateType”, swMate.Type ‘ swMateType_e 列挙値
‘ 合致エンティティの抽出
Dim swMateEntity1 As SldWorks.IMateEntity
Dim swMateEntity2 As SldWorks.IMateEntity
Set swMateEntity1 = swMate.MateEntity1
Set swMateEntity2 = swMate.MateEntity2
‘ MateEntity1 のコンポーネントとエンティティ
If Not swMateEntity1 Is Nothing Then
Dim swComp1 As SldWorks.IComponent2
Set swComp1 = swMateEntity1.ReferenceComponent
dictMate.Add “Component1”, swComp1.Name2
dictMate.Add “Entity1Ref”, GetEntityReferenceString(swMateEntity1, swComp1) ‘ エンティティ参照文字列を生成
Set swComp1 = Nothing
End If
‘ MateEntity2 のコンポーネントとエンティティ
If Not swMateEntity2 Is Nothing Then
Dim swComp2 As SldWorks.IComponent2
Set swComp2 = swMateEntity2.ReferenceComponent
dictMate.Add “Component2”, swComp2.Name2
dictMate.Add “Entity2Ref”, GetEntityReferenceString(swMateEntity2, swComp2)
Set swComp2 = Nothing
End If
‘ その他の合致条件(距離、角度、反転など)
If swMate.Type = swMateDISTANCE Then
dictMate.Add “Distance”, swMate.Distance
dictMate.Add “Flip”, swMate.Flip
‘ 他の合致タイプに応じたプロパティを追加
End If
‘ 例: 同心円
If swMate.Type = swMateCONCENTRIC Then
dictMate.Add “Alignment”, swMate.Alignment ‘ swMateAlign_e
End If
colMates.Add dictMate
Set swMateEntity1 = Nothing
Set swMateEntity2 = Nothing
Set swMate = Nothing
Next i
End If
End Sub
Private Function GetEntityReferenceString(swMateEnt As SldWorks.IMateEntity, swComp As SldWorks.IComponent2) As String
‘ ここが最も挑戦的な部分。エンティティを一意に識別するロジック。
‘ 一般的には、コンポーネント名@フィーチャー名@エンティティタイプ
‘ または、ISelectionMgr.GetSelectedObject6 の Name プロパティが返す文字列を利用する。
‘ このNameプロパティは、通常 “Face@PartName-1<1>” のような形式で、復元時の SelectByID2 に非常に有効。
Dim swSelObj As SldWorks.IModelDocExtension
Set swSelObj = swComp.GetModelDoc2.Extension
‘ MateEntity から IEntity を取得し、それを選択状態にしてから情報を引き抜く
‘ 厳密には、IMateEntity.Reference が返すオブジェクトが IEntity であることを確認し、
‘ IModelDoc2.Extension.SelectByID2 で選択できる文字列を構築する。
‘ ここでは簡略化のため、Nameプロパティを直接参照するようなイメージで記述。
‘ 実際には、swMateEnt.Reference を IEntity としてキャストし、
‘ その IEntity が属するフィーチャーやボディの情報を取得して文字列を構築する必要がある。
‘ 例: GetSelectByIDString(swMateEnt.Reference)
‘ 最も堅牢な方法は、IMateEntityが参照するIEntityオブジェクトを取得し、
‘ そのIEntityのGetSpecificEntityReferenceNameメソッドやGetDisplayNameメソッドを使うこと。
‘ しかし、これらのメソッドは常に復元可能な参照名を返すとは限らない。
‘ そこで、IModelDoc2.Extension.SelectByID2 で選択可能な形式を自前で構築する必要がある。
‘ SolidWorksの内部的なエンティティIDは不安定なため、ユーザー定義フィーチャー名や面の色/面積など、
‘ より「意味のある」情報を使うことも検討する。
‘ 今回は、シンプルに IEntity の Name プロパティ(もし存在すれば)や、
‘ SelectByID2 で使われるような形式の文字列を生成する方針で進める。
Dim sEntRef As String
Dim vRef As Variant
Set vRef = swMateEnt.Reference
If Not vRef Is Nothing Then
On Error Resume Next ‘ エラーハンドリングを有効にする
‘ IEntity の GetName2 を試みる
Dim swEntity As SldWorks.IEntity
Set swEntity = vRef
If Not swEntity Is Nothing Then
sEntRef = swComp.Name2 & “@” & swEntity.GetName2 ‘ 基本的にはこれが最も望ましい
Else
‘ IFeature の場合
Dim swFeature As SldWorks.IFeature
Set swFeature = vRef
If Not swFeature Is Nothing Then
sEntRef = swComp.Name2 & “@” & swFeature.Name
Else
‘ それ以外のケース(ボディ、サーフェス、エッジなど)
‘ これは IModelDocExtension.SelectByID2 が認識する文字列を生成する複雑なロジックが必要
‘ 例: “Face<1>@PartName-1″
‘ このロジックは SolidWorks API の内部構造に深く依存するため、非常にデリケート
‘ ここでは仮に TypeName と Object ID を利用するが、これは実用には不十分
sEntRef = swComp.Name2 & “@” & TypeName(vRef) & “<" & swMateEnt.ReferenceID & ">”
End If
End If
On Error GoTo 0 ‘ エラーハンドリングを無効にする
End If
If sEntRef = “” Then
‘ 適切な参照名が得られない場合のフォールバック
sEntRef = swComp.Name2 & “@UnknownEntity<" & swMateEnt.ReferenceID & ">”
End If
GetEntityReferenceString = sEntRef
Set vRef = Nothing
Set swEntity = Nothing
Set swFeature = Nothing
End Function
【極限の知見:エンティティ参照の堅牢性】
SolidWorksのエンティティ(面、エッジなど)は、その内部IDが設計変更によって容易に変化します。そのため、IDを基準とした参照は極めて脆弱です。より堅牢なアプローチは以下の組み合わせです。
1. フィーチャー名: スケッチ名、穴ウィザードフィーチャー名など、ユーザーが明示的に命名するフィーチャーは比較的安定しています。
2. エンティティタイプとインデックス: 例えば「ボディの最初の面」「フィーチャーのN番目のエッジ」といった、相対的な位置関係。ただし、これも設計変更で破綻しやすい。
3. カスタムプロパティ: エンティティ自体にユニークな識別子をカスタムプロパティとして付与する。これは事前準備が必要ですが、最も信頼性が高い方法の一つです。
4. `IModelDocExtension.SelectByID2` が認識する文字列: SolidWorksが内部的に使用するエンティティ識別文字列(例: `”Face@Part1<1>“`)。これを`IMateEntity.Reference`から逆算して生成するロジックは、SolidWorks APIの深い理解を要求されます。`IEntity.GetName2`が最も安定していることが多いですが、すべてのエンティティに有効ではありません。
本番環境では、これらのアプローチを組み合わせ、かつエラー発生時の代替ロジック(例: 合致に失敗した場合は手動介入を促すログを出す)を組み込むべきです。
アセンブリの復元(デシリアライゼーション)
JSONからアセンブリを再構築するプロセスは、データの読み込み、新規アセンブリの作成、コンポーネントの挿入、そして合致の再定義というステップを踏みます。
1. JSONデータのパース
VBAでJSONをパースするには、通常、`JsonConverter.bas`のような外部ライブラリを使用するのが現実的です。`Scripting.Dictionary`と`Collection`オブジェクトの組み合わせにより、JSON構造をメモリ上に再現します。
‘ JSON文字列をパースして Dictionary オブジェクトとして取得する(JsonConverter.bas を利用する想定)
‘ Dim dictParsedJson As Scripting.Dictionary
‘ Set dictParsedJson = JsonConverter.ParseJson(sJsonString)
2. アセンブリとコンポーネントの作成
JSONデータから`DocumentPath`を読み取り、新しいアセンブリドキュメントを作成します。その後、ルートコンポーネントから順に子コンポーネントを再帰的に挿入していきます。
Private Sub RebuildAssembly(swApp As SldWorks.SldWorks, dictAssembly As Scripting.Dictionary)
‘ 新規アセンブリドキュメントを作成
Dim sAssyTemplate As String
sAssyTemplate = swApp.GetUserPreferenceStringValue(swDefaultTemplateAssembly)
If sAssyTemplate = “” Then
Err.Raise vbObjectError + 1001, “RebuildAssembly”, “アセンブリテンプレートが見つかりません。”
End If
Dim swModel As SldWorks.IModelDoc2
Set swModel = swApp.NewDocument(sAssyTemplate, 0, 0, 0)
If swModel Is Nothing Then
Err.Raise vbObjectError + 1002, “RebuildAssembly”, “新規アセンブリドキュメントの作成に失敗しました。”
End If
Dim swAssy As SldWorks.IAssemblyDoc
Set swAssy = swModel
‘ 再描画を一時停止し、パフォーマンスを最大化
swModel.Extension.SelectByID2 “”, “”, 0, 0, 0, False, 0, Nothing, 0
swModel.EditRebuild3
swModel.GraphicsRedrawOff
swModel.SetDisplayValidation False
swApp.SetUserPreferenceToggle swUserPreferenceToggle_e.swViewDynamicHighlight, False
‘ ルートコンポーネントの挿入(JSONのComponentsリストの最初の要素がルートコンポーネントと仮定)
Dim colComponents As Collection
Set colComponents = dictAssembly(“Components”)
If colComponents.Count > 0 Then
Call InsertComponentRecursive(swApp, swAssy, colComponents, Nothing)
End If
‘ 合致の再定義
Dim colMates As Collection
Set colMates = dictAssembly(“Mates”)
If colMates.Count > 0 Then
Call RebuildMates(swApp, swAssy, colMates)
End If
‘ 元の設定に戻し、再描画
swApp.SetUserPreferenceToggle swUserPreferenceToggle_e.swViewDynamicHighlight, True
swModel.SetDisplayValidation True
swModel.GraphicsRedrawOn
swModel.ForceRebuild
swModel.ViewZoomtofit2
Set swAssy = Nothing
Set swModel = Nothing
End Sub
Private Sub InsertComponentRecursive(swApp As SldWorks.SldWorks, swParentAssy As SldWorks.IAssemblyDoc, _
colComponents As Collection, swParentComp As SldWorks.IComponent2)
Dim dictComp As Scripting.Dictionary
Dim sComponentPath As String
Dim sInstanceName As String
Dim vTransformMatrix As Variant
Dim bIsFixed As Boolean
Dim swMathUtil As SldWorks.IMathUtility
Set swMathUtil = swApp.GetMathUtility
For Each dictComp In colComponents
sComponentPath = dictComp(“ReferencedPath”)
sInstanceName = dictComp(“InstanceName”)
vTransformMatrix = dictComp(“TransformMatrix”)
bIsFixed = dictComp(“IsFixed”)
‘ 変換マトリクスを再構築
Dim swTransform As SldWorks.IMathTransform
Set swTransform = swMathUtil.CreateTransform(vTransformMatrix) ‘ JSON配列からIMathTransformを生成
Dim swNewComp As SldWorks.IComponent2
If swParentComp Is Nothing Then ‘ ルートコンポーネントの場合
‘ InsertComponent2 はルートアセンブリに直接挿入
Set swNewComp = swParentAssy.InsertComponent2(sComponentPath, swTransform, sInstanceName)
Else ‘ サブコンポーネントの場合
‘ AddComponents5 は親コンポーネント(サブアセンブリ)の子として挿入
Dim vNames(0) As String
Dim vPaths(0) As String
Dim vTransforms(0) As SldWorks.IMathTransform
vNames(0) = sInstanceName
vPaths(0) = sComponentPath
Set vTransforms(0) = swTransform
Dim vAddedComps As Variant
vAddedComps = swParentAssy.AddComponents5(vPaths, vTransforms, vNames)
If Not IsEmpty(vAddedComps) Then
Set swNewComp = vAddedComps(0)
End If
End If
If Not swNewComp Is Nothing Then
If bIsFixed Then
swNewComp.SetFixed True
End If
‘ 子コンポーネントを再帰的に挿入
Dim colSubComponents As Collection
Set colSubComponents = dictComp(“SubComponents”)
If colSubComponents.Count > 0 Then
Call InsertComponentRecursive(swApp, swParentAssy, colSubComponents, swNewComp)
End If
End If
Set swNewComp = Nothing
Set swTransform = Nothing
Next dictComp
Set swMathUtil = Nothing
End Sub
【極限の知見:パフォーマンス最適化】
アセンブリの再構築は、特にコンポーネント数が多い場合に非常に時間がかかります。上記コードでは以下の最適化を施しています。
- 描画の停止: `GraphicsRedrawOff` および `SetDisplayValidation False` は、SolidWorksの画面描画と内部バリデーションを一時停止し、API操作のオーバーヘッドを大幅に削減します。
- 動的ハイライトの無効化: `swViewDynamicHighlight` を `False` に設定することで、マウスオーバー時のグラフィック更新を抑制します。
- 一括挿入: `AddComponents5` は複数のコンポーネントを一括で挿入できるため、`InsertComponent2` を個別に呼び出すよりも効率的です。
これらの設定は、VBAの実行速度がC#やVB.NETに劣る環境では特に重要です。最終的に`GraphicsRedrawOn`などで元に戻し、`ForceRebuild`でアセンブリを再構築することを忘れないでください。
3. 合致の再定義
これが最も難易度の高い部分です。JSONに格納された`EntityRef`文字列を使って、SolidWorksモデル内の特定のエンティティを選択し、それらを対象に合致を作成します。`IModelDocExtension.SelectByID2`がこの鍵となります。
Private Sub RebuildMates(swApp As SldWorks.SldWorks, swAssy As SldWorks.IAssemblyDoc, colMates As Collection)
Dim dictMate As Scripting.Dictionary
Dim swModel As SldWorks.IModelDoc2
Set swModel = swAssy
‘ 選択マネージャ
Dim swSelMgr As SldWorks.ISelectionMgr
Set swSelMgr = swModel.SelectionManager
For Each dictMate In colMates
Dim sComp1Name As String
Dim sEntity1Ref As String
Dim sComp2Name As String
Dim sEntity2Ref As String
Dim lMateType As Long
Dim sMateName As String
Dim bFlip As Boolean
sComp1Name = dictMate(“Component1”)
sEntity1Ref = dictMate(“Entity1Ref”)
sComp2Name = dictMate(“Component2”)
sEntity2Ref = dictMate(“Entity2Ref”)
lMateType = dictMate(“MateType”) ‘ swMateType_e
sMateName = dictMate(“MateName”)
‘ エラーハンドリングは必須。エンティティが見つからない場合があるため。
On Error GoTo MateError
‘ 既存の選択をクリア
swModel.Extension.SelectByID2 “”, “”, 0, 0, 0, False, 0, Nothing, 0
‘ エンティティ1を選択
‘ SelectByID2 の文字列は “TypeName@ComponentName
‘ 例: “Face@PartA-1<1>”
‘ GetEntityReferenceString で生成した文字列をそのまま使用する
Dim bRet1 As Boolean
bRet1 = swModel.Extension.SelectByID2(sEntity1Ref, “”, 0, 0, 0, True, 0, Nothing, 0)
If Not bRet1 Then
Debug.Print “Warning: Entity1 not found for mate ” & sMateName & “: ” & sEntity1Ref
GoTo NextMate ‘ この合致はスキップ
End If
‘ エンティティ2を選択
Dim bRet2 As Boolean
bRet2 = swModel.Extension.SelectByID2(sEntity2Ref, “”, 0, 0, 0, True, 0, Nothing, 0)
If Not bRet2 Then
Debug.Print “Warning: Entity2 not found for mate ” & sMateName & “: ” & sEntity2Ref
GoTo NextMate ‘ この合致はスキップ
End If
‘ 合致を作成
Dim swFeature As SldWorks.IFeature
Dim swMateFeature As SldWorks.IMateFeature
Set swFeature = swAssy.AddMate(lMateType, swMateAlign_e.swMateAlignALIGNED, False, 0, 0, 0) ‘ 基本はALIGNEDで
If Not swFeature Is Nothing Then
Set swMateFeature = swFeature.Get ‘ IFeatureからIMateFeatureを取得
swMateFeature.Name = sMateName ‘ 合致名をセット
‘ 特定の合致タイプに応じた追加設定
Select Case lMateType
Case swMateDISTANCE
If dictMate.Exists(“Distance”) Then
swMateFeature.Distance = dictMate(“Distance”)
End If
If dictMate.Exists(“Flip”) Then
swMateFeature.Flip = dictMate(“Flip”)
End If
Case swMateCONCENTRIC
If dictMate.Exists(“Alignment”) Then
swMateFeature.Alignment = dictMate(“Alignment”)
End If
End Select
swModel.EditRebuild3 ‘ 合致の作成後、リビルド
Else
Debug.Print “Error: Failed to create mate ” & sMateName
End If
NextMate:
Set swFeature = Nothing
Set swMateFeature = Nothing
On Error GoTo 0 ‘ エラーハンドリングを無効に戻す
Next dictMate
Set swSelMgr = Nothing
Set swModel = Nothing
Exit Sub
MateError:
Debug.Print “Runtime error during mate creation: ” & Err.Description & ” at mate ” & sMateName
Resume NextMate
End Sub
【極限の知見:`SelectByID2`の挙動と堅牢性】
`SelectByID2`はSolidWorks APIの根幹をなすメソッドの一つですが、その文字列引数は非常にデリケートです。
- 文字列フォーマット: `”Type@ComponentName
“`が基本的な形式です。`Type`は`”Face”`, `”Edge”`, `”Vertex”`, `”Feature”`など。`ID`はSolidWorks内部で振られるインデックスで、これは非常に不安定です。 - フィーチャー名: 面やエッジがフィーチャーに属している場合、`”Face@Extrude1@PartA-1″`のような形式でフィーチャー名を指定できることがあります。これはIDよりも安定しやすい場合があります。
- カスタムプロパティ: 究極的には、全ての参照対象エンティティにユニークなカスタムプロパティを付与し、それを基に`SelectByID2`ではなく、`IModelDoc2.Extension.GetEntityByName`のようなメソッド(もしあれば)や、エンティティを走査してプロパティを比較する独自ロジックを構築することが、最も堅牢な方法です。
本記事の例では、`GetEntityReferenceString`が`SelectByID2`に適合する文字列を生成することを前提としていますが、実際の運用ではこの部分に最も多くの知見と試行錯誤が必要となります。
極限の知見:Windows API、メモリ、レガシー、システム連携
ここからは、伝説的なチーフアーキテクトとしての、より深い知見と実践的なアドバイスを提供します。
1. Windows APIの活用:VBAの限界を超越する
VBAはCOMベースの環境であり、直接的なシステムリソースへのアクセスには限界があります。しかし、Windows APIを`Declare`ステートメントで呼び出すことで、その壁を打ち破ることができます。
- 高精度タイマー: `QueryPerformanceCounter`と`QueryPerformanceFrequency`は、API処理の正確なボトルネックを特定するのに不可欠です。SolidWorks APIの各ステップがどれだけの時間を消費しているかをナノ秒単位で計測し、最適化の優先順位を決定します。
‘ Declare function in a standard module
Private Declare PtrSafe Function QueryPerformanceCounter Lib “kernel32” (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib “kernel32” (lpFrequency As Currency) As Long
Public Function GetHighResTimer() As Double
Static freq As Currency
Static initialized As Boolean
If Not initialized Then
QueryPerformanceFrequency freq
initialized = True
End If
Dim count As Currency
QueryPerformanceCounter count
GetHighResTimer = CDbl(count / freq)
End Function
‘ Usage
Dim startTime As Double
startTime = GetHighResTimer()
‘ Your SolidWorks API calls here
Debug.Print “Elapsed time: ” & (GetHighResTimer() – startTime) & ” seconds”
- メモリ監視: `GetProcessMemoryInfo` (Psapi.dll) を使用すれば、VBAプロセスが消費する物理メモリや仮想メモリの量をリアルタイムで監視できます。大量のSolidWorksオブジェクトを扱う際に、メモリリークの兆候を早期に捉えることが可能です。
- これはVBAから直接実装するのはやや複雑ですが、外部DLLを作成してVBAから呼び出す、あるいはVB.NET/C#でホストアプリケーションを作成し、COMを介してSolidWorks VBAと連携させるというシステム間連携の布石となります。
2. COMオブジェクトのライフサイクルとメモリ最適化:見えざる参照カウント
VBAでは`Set obj = Nothing`が唯一の明示的な解放手段ですが、内部的な参照カウントがゼロにならない限り、オブジェクトはメモリに残り続けます。特に、SolidWorks APIオブジェクトは相互に参照し合う構造を持つことが多く、循環参照によるリークが発生しやすい傾向があります。
- 参照を短命に: オブジェクトは必要最小限の期間だけ保持し、スコープを抜ける前に必ず`Set Nothing`します。特にループ内で大量のコンポーネントやフィーチャーを処理する場合、ループの各イテレーションの終わりでオブジェクトを解放することを徹底します。
- グローバル変数・パブリック変数への配置の最小化: グローバルスコープのオブジェクトはライフサイクルが長くなりがちで、意図しない参照保持の原因となります。可能な限りプロシージャスコープのローカル変数を使用します。
- SolidWorksアプリケーションの再起動: 最終手段として、メモリリークが深刻な場合は、SolidWorksアプリケーション自体を定期的に再起動する運用を組み込むことも検討します。これは根本解決ではありませんが、長時間の自動処理には有効なワークアラウンドです。
3. レガシー環境の保守と進化:技術的負債との対峙
SolidWorks VBAは、今や「レガシー」と称されることもありますが、多くの現場で依然として不可欠なツールです。
- バージョン互換性: SolidWorks APIは比較的後方互換性が高いものの、メジャーバージョンアップで一部のメソッドが非推奨になったり、挙動が変わることがあります。常に最新のAPIヘルプを参照し、将来的な変更に備えたコード設計(例: `swApp.GetAPIVersion`でバージョンをチェックする)を心がけます。
- エラーハンドリングの徹底: `On Error GoTo`はVBAの古典的なエラーハンドリングですが、堅牢なシステムには不可欠です。特に、ファイルが見つからない、SolidWorksが予期せぬ状態になったなどの外部要因によるエラーに対しては、ログ出力、リトライ機構、ユーザーへの通知などを実装します。
- 外部ライブラリの管理: `JsonConverter.bas`のようなVBAモジュールは、プロジェクトに直接組み込むことで配布を簡素化できます。ただし、DLLなどの外部参照は、クライアント環境への登録(RegSvr32)が必要となり、展開が複雑になります。レガシー環境では、配布の容易性も重要な設計要件です。
4. システム間連携の極限:JSONを介したデータブリッジ
本記事の核心であるJSONシリアライゼーションは、SolidWorksを孤立したツールから、データ中心のシステムアーキテクチャの一部へと昇華させます。
- バージョン管理システム(VCS)との統合: 生成されたJSONはテキストファイルであるため、GitなどのVCSで差分管理が可能です。これにより、アセンブリ構造の変更履歴を追跡し、特定の時点の状態に復元する「設計のロールバック」をCADファイルに依存せずに行えるようになります。
- PDM/ERP/CAMシステムとの連携: JSONデータをWeb API(RESTful API)経由で外部システムに送信することで、設計情報がリアルタイムで生産計画、部品表、CAMプログラムなどに連携されます。VBAから`MSXML2.XMLHTTP`オブジェクトを使用すれば、HTTPリクエストを送信し、JSONデータを交換することが可能です。
- 自動設計エンジン: JSONスキーマで定義されたアセンブリ構造は、パラメータ駆動型設計の入力として機能します。外部の設計ルールエンジンや最適化アルゴリズムによってJSONが生成・修正され、それをSolidWorks VBAが読み込み、アセンブリを自動生成するといった高度なワークフローが実現します。
結論:データ中心設計の未来へ
SolidWorks VBAを用いたアセンブリ構造のJSONシリアライゼーションと完全復元は、単なるマクロを超えた、データ中心設計(Data-Driven Design)への強力な一歩です。この技術は、SolidWorksのネイティブファイルが持つバージョン依存性やアクセシビリティの課題を克服し、設計データをより柔軟かつ効率的に管理・活用するための基盤を提供します。
本記事で解説した知見は、オブジェクトのライフサイクル管理からWindows APIの活用、そしてJSONによるシステム間連携まで、多岐にわたります。これらは、単に「SolidWorksを動かす」だけでなく、「SolidWorksをシステムの一部として機能させる」という、より高次の目標を達成するために不可欠な要素です。
伝説のチーフアーキテクトとして、私が伝えたいのは、技術は常に進化し、既存のツールを最大限に活用しつつ、未来のニーズを見据えることの重要性です。VBAという限られた環境であっても、深い洞察と徹底したエンジニアリングアプローチがあれば、不可能と思えるような課題も解決できます。この知見が、貴殿のシステム設計と自動化の取り組みに新たな視点と力を与えることを願ってやみません。
