【実務・中級編】【中級者向け】図面内の部品表(BOM)から指定したカスタムプロパティ列を抽出してCSV自動エクスポート – SolidWorks VBA解析バイブル

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

【設計思想】BOMデータ抽出の罠を回避せよ:堅牢なCSVエクスポート・アーキテクチャ

エンジニア諸君、業務自動化の現場で「とりあえず動くコード」を書いて満足していないか?

図面内の部品表(BOM)からデータを抽出する。一見、単純なルーチンに見えるだろう。しかし、現場の図面は、設計者の好みやテンプレートの変更によって、列の順番が入れ替わり、列が追加され、時には意図しないテーブルが混入する。

もし君が「3列目は材質、5列目は重量」といったマジックナンバー(ハードコード)に基づいたコードを書いているとしたら、それは自動化ツールではなく、単なる「時限爆弾」だ。運用が始まれば、必ずどこかでデータがズレ、ERP(基幹システム)にゴミデータを流し込み、会社に甚大な損害を与えることになる。

本稿では、プロフェッショナルとして、「列の構成が変わっても壊れない」「例外的な図面構造でも死なない」、真にプロダクションレベルに耐えうるBOM抽出ロジックの設計思想と実装を伝授する。

1. 避けるべきアンチパターン:インデックス依存の設計

多くの初級者が陥る失敗は、以下のようなアプローチだ。

' --- 典型的な失敗例 (Anti-Pattern) ---
For i = 1 To rowCount
    material = swTable.GetText(i, 2) ' 2列目が材質だと決め打ち
    weight = swTable.GetText(i, 5)   ' 5列目が重量だと決め打ち
    ' ... CSV出力
Next i

この設計の欠陥は明白だ。設計者が「備考」列を左端に挿入した瞬間、このプログラムは「材質」の代わりに「備考」を抽出し、システムに誤った情報を送り込む。「列のインデックス(番号)」ではなく、「ヘッダー名(文字列)」でデータを特定する。これが鉄則だ。

2. アーキテクチャの要:ヘッダー・マッピング戦略

堅牢なツールを作るためには、以下の3フェーズで処理を分離すべきである。

1. Discovery(探索): 図面内のすべてのテーブルを走査し、対象となるBOMテーブルを特定する。
2. Mapping(写像): BOMのヘッダー行をスキャンし、「どの列名が、何番目のインデックスに対応するか」をメモリ上の辞書(Dictionary)に格納する。
3. Extraction(抽出): 辞書を参照して、指定したプロパティ名に基づきデータを正確に抜き出す。

これにより、列の順番がどう変わろうとも、プログラムはヘッダーの名前を探し出し、動的に対応できる。

3. プロダクション・コードの実装例

以下に、実務でそのまま使えるレベルの設計思想を組み込んだコードを示す。
※ `Microsoft Scripting Runtime` の参照設定は不要なよう、`CreateObject` によるレイトバインディングを採用している。これにより、配布時の環境依存トラブルを最小限に抑えている。

Option Explicit

''' <summary>
''' 図面内のBOMから指定したカラムを抽出し、CSV出力するメインルーチン
''' </summary>
Public Sub ExportBOMToCSV()
    Dim swApp As SldWorks.SldWorks
    Dim swModel As SldWorks.ModelDoc2
    Dim swDraw As SldWorks.DrawingDoc
    Dim swSheet As SldWorks.Sheet
    Dim swView As SldWorks.View
    Dim swTableAnn As SldWorks.TableAnnotation
    Dim csvPath As String
    
    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

    ' 保存先パスの設定(実務ではダイアログで選択させるのが望ましい)
    csvPath = swModel.GetPathName
    If csvPath = "" Then
        MsgBox "ファイルを保存してから実行してください。", vbExclamation
        Exit Sub
    End If
    csvPath = Left(csvPath, InStrRev(csvPath, ".")) & "csv"

    ' 2. BOMテーブルの探索と抽出
    ' 図面の全ビューを走査してテーブルを探す
    Set swView = swDraw.GetFirstView ' Sheet自体が最初のView
    
    Do While Not swView Is Nothing
        ' ビュー内のテーブルを取得
        Dim swViewDoc As SldWorks.View
        Set swViewDoc = swView
        
        ' ビューに紐づくAnnotationを確認
        Dim swAnn As SldWorks.Annotation
        Set swAnn = swViewDoc.GetFirstAnnotation
        
        Do While Not swAnn Is Nothing
            If swAnn.GetType = swAnnotationTypeTable Then
                Set swTableAnn = swAnn.GetSpecificAnnotation
                
                ' テーブルがBOM(部品表)かどうかを判定
                ' 注: TableTypeによって判定。機種により異なる場合があるため注意
                If swTableAnn.Type = swTableTypeBom Then
                    ' 抽出実行
                    ProcessBOMTable swTableAnn, csvPath
                    GoTo SuccessExit ' 最初のBOMを見つけたら終了(複数ある場合はループ設計を変更)
                End If
            End If
            Set swAnn = swAnn.GetNext
        Loop
        Set swView = swView.GetNext
    Loop

    MsgBox "対象のBOMテーブルが見つかりませんでした。", vbInformation
    Exit Sub

SuccessExit:
    MsgBox "エクスポートが完了しました:" & vbCrLf & csvPath, vbInformation
    Exit Sub

ErrorHandler:
    MsgBox "致命的なエラーが発生しました。" & vbCrLf & Err.Description, vbCritical
End Sub

''' <summary>
''' 特定のテーブルからデータを抽出し、CSVに書き出す
''' </summary>
Private Sub ProcessBOMTable(swTable As SldWorks.TableAnnotation, filePath As String)
    Dim dictCols As Object
    Set dictCols = CreateObject("Scripting.Dictionary")
    
    Dim rowCount As Long, colCount As Long
    rowCount = swTable.RowCount
    colCount = swTable.ColumnCount
    
    ' --- Phase 1: Mapping (ヘッダーのスキャン) ---
    Dim c As Long
    Dim headerText As String
    For c = 0 To colCount - 1
        headerText = Trim(swTable.GetText(0, c)) ' 0行目がヘッダー
        If headerText <> "" Then
            ' 重複ヘッダー対策として、既存のキーがある場合はインデックスを保持
            If Not dictCols.Exists(headerText) Then
                dictCols.Add headerText, c
            End If
        End If
    Next c

    ' --- Phase 2: Extraction (データの走査) ---
    ' 抽出したいターゲットカラムの定義(ここを変更するだけでカスタマイズ可能)
    Dim targets As Variant
    targets = Array("部品番号", "材質", "重量", "仕入先") ' 実際のヘッダー名に合わせる
    
    Dim fileNum As Integer
    fileNum = FreeFile
    Open filePath For Output As #fileNum

    ' CSVヘッダーの書き出し
    Dim targetHeader As String
    targetHeader = Join(targets, ",")
    Print #fileNum, targetHeader

    ' データ行の書き出し
    Dim r As Long, t As Variant
    Dim cellValue As String
    Dim lineData As String
    
    For r = 1 To rowCount - 1 ' 1行目(データ開始行)から
        lineData = ""
        For Each t In targets
            cellValue = ""
            ' 辞書にターゲットが存在するか確認
            If dictCols.Exists(CStr(t)) Then
                cellValue = swTable.GetText(r, dictCols(CStr(t)))
            End If
            
            ' CSVのカンマ混入対策(ダブルクォーテーション囲い)
            lineData = lineData & """" & Replace(cellValue, """", """""") & """"
            If t <> targets(UBound(targets)) Then lineData = lineData & ","
        Next t
        Print #fileNum, lineData
    Next r

    Close #fileNum
End Sub

4. チーフアーキテクトの視点:運用における3つの注意点

このコードを実務に投入する際、君が考慮すべき「プロの作法」が3つある。

① CSVのエンコーディング問題

上記のコードは、標準的な `Print #` 文を使用しているため、システムのロケール(通常はShift-JIS)に依存する。もし、出力先のシステムが UTF-8 (BOM付き) を要求する場合は、`ADODB.Stream` オブジェクトを使用して書き出すロジックに差し替える必要がある。ここを疎かにすると、「文字化け」という名の低レベルなバグに泣かされることになる。

② エラーハンドリングの階層化

コード内に `On Error GoTo ErrorHandler` を配置したが、実務では「ファイルが書き込み禁止(Excelで開いている)場合」「BOMのヘッダー名が想定と微妙に違う場合(例:『材質』ではなく『Material』)」など、エラーのパターンを予測し、ユーザーに的確な指示を出せるようにすべきだ。

③ パフォーマンスの重み

`swTable.GetText` は、セルにアクセスするたびにAPIコールを発生させる。数千行のBOMを扱う場合、この呼び出しコストは無視できない。もし極限の速度を求めるなら、一度全てのデータを配列(Variant Array)に吸い上げ、メモリ上で処理を完結させる設計へ昇華させよ。

結論

自動化とは、単に「作業を楽にする」ことではない。「人間が介在することで発生するエラーを、設計の力で根絶すること」である。

列番号に依存したコードを書くのは、今日限りでやめろ。ヘッダーを動的に解釈し、構造の変化に耐えうる、真に「堅牢な」コードを書くのだ。幸運を祈る。

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