【実務・中級編】【中級者向け】部品表(BOM:Bill of Materials)の図面内自動配置と列幅・並び順の完全制御 – SolidWorks VBA解析バイブル

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

【極限の知見】SolidWorks BOM自動制御:テンプレート依存を脱却し、動的な「設計図」をコードで記述せよ

業務自動化の現場において、多くのエンジニアが「部品表(BOM)の自動挿入」という壁に突き当たる。

マクロ記録機能を使ってBOMを挿入するコードを書くことは容易だ。しかし、実務レベルのツールとして運用しようとした瞬間、そのコードは「砂上の楼閣」へと変貌する。なぜか? それは、「テンプレートの変更」や「アセンブリの構成変更」という外部要因に対して、コードが極めて脆弱だからだ。

「1列目は部品番号、2列目は品名……」といった、列インデックス(Index)に依存したハードコーディングは、プロフェッショナルの仕事ではない。それは、保守という名のコストを次世代に押し付ける「技術的負債」の塊だ。

本稿では、SolidWorks VBAを用いて、BOMの配置、列の並び順、幅、表示・非表示を、「列名(Header Name)」に基づいたロジカルな設計によって完全に掌握する手法を伝授する。

1. 失敗する自動化、成功する自動化:設計思想の転換

まず、君たちが陥りがちな「アンチパターン」を指摘しておこう。

アンチパターン:インデックス依存型制御

' これはゴミだ。テンプレートの列が増えた瞬間に壊れる。
swTable.SetColumnWidth 1, 3000 
swTable.SetColumnWidth 2, 5000

この書き方は、BOMの構造が1ミリでも変われば即座にバグを引き起こす。現場の図面は常に変化する。設計変更によってカスタムプロパティが追加されたり、列の順序が変わったりするのは日常茶飯事だ。

プロフェッショナルの設計:スキーマ・マッピング型制御

我々が目指すべきは、「どの列が何であるか」を名前で特定し、その属性を操作する設計だ。
1. BOMを挿入する。
2. 全列を走査し、ヘッダー名(またはプロパティ名)から「ターゲットとなる列のインデックス」を動的に取得する。
3. 特定されたインデックスに対して、幅や表示状態を適用する。

このアプローチにより、テンプレートの列順が変わっても、コードは「目的の列」を正しく探し出し、意図した通りの制御を継続できる。

2. 実装の核:BOMオブジェクトのライフサイクルを制御する

BOMの操作には、`TableAnnotation` だけでなく、BOM固有のプロパティを司る `BomTableAnnotation` を適切に使い分ける必要がある。

特に重要なのは、「列の表示・非表示」と「列の幅」の操作だ。これらは `swTableAnnotation` インターフェースを通じて行われるが、どの列が「どのカスタムプロパティを指しているか」を判断するには、BOMのコンテキストが必要になる。

3. 【Production-Ready Code】堅牢なBOM制御エンジン

以下に、実務でそのまま組み込めるレベルの、堅牢なBOM制御モジュールを示す。このコードは、列のインデックスを直接指定せず、「ヘッダー名」をキーとして列を制御する設計になっている。

Option Explicit

''' <summary>
''' BOMを自動挿入し、指定されたスキーマ(列構成)に強制的に適合させる
''' </summary>
''' <param name="swDraw">図面ドキュメント</param>
''' <param name="swSheet">対象シート</param>
''' <param name="swView">配置基準となるビュー</param>
Public Sub ProfessionalBOMGenerator(swDraw As SldWorks.DrawingDoc, swSheet As SldWorks.Sheet, swView As SldWorks.View)
    
    Dim swModel As SldWorks.ModelDoc2
    Set swModel = swDraw
    
    ' --- 設定エリア(ここを外部定義やDBから読み込むように設計するのが理想) ---
    ' 制御したい列の定義:[ヘッダー名, 表示フラグ, 列幅(m)]
    Dim columnSchema As Variant
    columnSchema = Array( _
        Array("部品番号", True, 0.03), _
        Array("品名", True, 0.06), _
        Array("材質", True, 0.03), _
        Array("数量", True, 0.015), _
        Array("備考", False, 0.04) _
    )
    ' -----------------------------------------------------------------------

    Dim swBomAnn As SldWorks.BomTableAnnotation
    Dim swTableAnn As SldWorks.TableAnnotation
    Dim swDrawingView As SldWorks.View
    Set swDrawingView = swView
    
    ' 1. BOMの挿入 (アセンブリビューに対して実行)
    ' 第2引数: BOMタイプ, 第3引数: 構成の表示方法...
    ' ここでは標準的なアセンブリBOMを想定
    Set swBomAnn = swDrawingView.InsertBomTable(swBomTableConfigurationType_e.swBomTableConfigurationType_eAutomatic, _
                                               0, 0, 0, 0)
    
    If swBomAnn Is Nothing Then
        Err.Raise vbObjectError + 513, "BOMGenerator", "BOMの挿入に失敗しました。ビューがアセンブリビューであることを確認してください。"
    End If
    
    Set swTableAnn = swBomAnn
    
    ' 2. スキーマに基づいた動的制御
    Dim i As Integer
    Dim colIdx As Integer
    Dim targetHeader As String
    Dim isVisible As Boolean
    Dim targetWidth As Double
    Dim foundCount As Integer: foundCount = 0
    
    ' 全列を走査して、スキーマに合致する列を操作する
    ' ※注意: 列の挿入/削除を行うとインデックスがずれるため、
    ' 「表示/非表示」と「幅」の操作に留めるのが最も安全な設計である。
    
    For i = 0 To columnSchema.UBound
        targetHeader = columnSchema(i)(0)
        isVisible = columnSchema(i)(1)
        targetWidth = columnSchema(i)(2)
        
        colIdx = GetColumnIndexByHeader(swTableAnn, targetHeader)
        
        If colIdx <> -1 Then
            ' 列の表示状態を制御
            swTableAnn.SetColumnVisible colIdx, isVisible
            
            ' 表示されている場合のみ幅を調整
            If isVisible Then
                swTableAnn.SetColumnWidth colIdx, targetWidth
            End If
            foundCount = foundCount + 1
        Else
            ' スキーマにあるが、BOM内に存在しない列の場合
            ' ログ出力などのエラーハンドリングをここに実装
            Debug.Print "Warning: Column not found: " & targetHeader
        End If
    Next i
    
    ' 3. テーブルの整列・仕上げ
    ' 表の折り返し設定(必要に応じて)
    swTableAnn.SetColumnTextWrap colIdx, True
    
    MsgBox "BOMの最適化が完了しました。適用された列数: " & foundCount, vbInformation
    
End Sub

''' <summary>
''' ヘッダー名から列インデックスを逆引きする(堅牢性の要)
''' </summary>
Private Function GetColumnIndexByHeader(swTableAnn As SldWorks.TableAnnotation, headerName As String) As Integer
    Dim colCount As Integer
    Dim c As Integer
    Dim currentHeader As String
    
    colCount = swTableAnn.ColumnCount
    GetColumnIndexByHeader = -1 ' デフォルトは「見つからない」
    
    For c = 0 To colCount - 1
        ' ヘッダーテキストを取得(空白や改行の揺らぎを考慮してTrimするのがプロの作法)
        currentHeader = Trim(swTableAnn.GetText(c, 0)) 
        
        ' 完全一致、または部分一致での判定ロジックをここに組み込む
        If currentHeader = headerName Then
            GetColumnIndexByHeader = c
            Exit Function
        End If
    Next c
End Function

4. アーキテクトの視点:なぜこのコードが「強い」のか

このコードが、単なる「動くマクロ」と一線を画す理由は以下の3点にある。

① 疎結合な設計 (Loose Coupling)

`columnSchema` という配列(本来は外部のExcelやJSONから読み込むべきものだ)によって、「ロジック」と「データ」を分離している。列の名前や幅を変えたいとき、エンジニアはVBAのロジックを書き換える必要はない。データの定義を更新するだけでいい。

② 自己修復的な検索ロジック

`GetColumnIndexByHeader` 関数は、BOMの構造を「ブラックボックス」として扱わない。列の順番が変わろうが、間に新しい列が割り込もうが、名前を頼りに自律的にターゲットを特定する。これが、大規模プロジェクトでの保守性を担保する。

③ 失敗への備え (Error Handling)

`InsertBomTable` が `Nothing` を返した場合の処理や、スキーマに定義されているがBOMに存在しない列に対する警告など、「正常系」だけでなく「異常系」を常に意識した実装になっている。

5. さらなる高みへ:実務での注意点

このツールをプロダクション環境に投入する際は、以下の点に留意せよ。

1. パフォーマンスの重み:
大量のアセンブリ図面をバッチ処理する場合、`swTableAnn.SetColumnWidth` をループ内で多用すると、描画更新のオーバーヘッドで処理が重くなる。必要に応じて `swModel.GraphicsRedraw = False`(※APIの制約により制限あり)等の制御を検討せよ。
2. カスタムプロパティの命名規則:
BOMのヘッダー名は、アセンブリのカスタムプロパティ名に依存することが多い。設計部門に対し、「プロパティ名は一字一句変えないこと」という運用ルールを徹底させる。コードの堅牢性と、設計運用の規律は表裏一体だ。
3. テンプレートの事前準備:
コードで全ての列を制御しようとするのは、コストが高い。あらかじめ「標準的な列構成」を持つテンプレートを用意し、コードは「微調整と強制的な整合性確保」に徹するのが、最も賢明な設計である。

以上だ。
単に「動く」ものを作るな。「変化に耐えうる」ものを作れ。 それが、真のエンジニアリングである。

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