Visio図面からExcel部品表(BOM)を自動生成。その①

※2020.05.01 Visioシェイプの__セル名__ を指定してデータへアクセスする方法が分かったので追記しました。

# 要求仕様
今回やりたいこと。

* ハーネス図面、電気システム図面をVisioで書きたい
* Visio図面にある部品をマクロで集計したい
* 集計した部品データをExcelの部品表に入力したい

私は、基板設計の際は　__回路図__（KiCAD）から __部品表__（BOM）をエクスポートしています。

ハーネス図面やシステム図面はVisioを使用していましたが、
Visioは単に"画だけ"なので、部品表を手作業で作成しており不便と感じでおりました。
Visio図面からでもKiCAD同様にBOM(部品表)を自動生成できないかと考えて今回の開発に至りました。

# 開発手順
以下の開発ステップ踏んでいきます。

1. 部品情報の定義
1. 部品ライブラリ(Visioステンシル)の作成
1. 図面を書く
1. Visio図面から部品情報を抽出するマクロ作成
1. 部品情報をExcelに出力

# 開発環境
当方の使用している開発環境です。

* Windows10
* Microsoft Visio2016
* Microsoft Excel(Microsoft Office365)

# ①部品情報の定義
１つの部品に対して付加する情報を定義します。
今回は下表に示す３つの情報を付加することにしました。
(付加情報は用途に応じてアレンジして利用してください)

付加情報  |内容            |説明
---------|----------------|--------
Reference|諸元番号<br>(部品番号)        |1つの部品を特定する為の __固有の番号__ です。一つの図面内で重複する部品番号は存在しません。<br>(回路図のU1,U2みたいなヤツ)
Drowing  |図面番号<br>(型名)  |その部品を定義した図面の番号です。<br>(回路図でいうとこの部品型名になります。)
Unit     |部品名称        |部品のカテゴリを表現している名称です。<br>(コンデンサ、電源ケーブルとか部品のざっくりしたジャンルを当てはめる)

# ②部品ライブラリ(Visioステンシル)の作成
Visioの細かい操作方法については既に色々なサイトで紹介されているので割愛して、作業の流れを説明します。

1. Visioステンシル(.vssxファイル)を作成します。
2. 新しい__マスターシェイプ__ を作成して好きな名前を付けます。(今回は"PARTS_SYMBOL"という名前にします。)
3. __マスターシェイプの編集__　ウインドウを立ち上げて、以下３つのシェイプを追加します。<br> 書式等の設定については通常のVisio作画と同様です。
 
    シェイプ|説明
    ---------|--------
    テキストボックス|諸元番号(Referenceの表示用)
    四角形|図面上で部品を示すブロックの描画。中心に部品名称(Unit)を表示する。
    テキストボックス|図面番号(Drowingの表示用)

4.三つのシェイプを __グループ化__ する。<br>"開発"タブの__マスターエクスプローラー__ "にチェックをいれて、マスターエクスプローラーを見ると、グループ化したシェイプの階層が見れます。

5.シェイプを右クリックし __図形データ__ ウインドウを表示させると、”図形データなし”と表示されているので、<br>右クリックして __"図形データセット"__ を押す。

6.図形データセットウインドウの"__追加__"を押して、データセットの名前を設定する。（ここではPARTS_PROPとしておきます）
7.__定義__ を押して"図形データの定義ウインドウ"を開き、下表のように３つのプロパティを追加します。

ラベル|名前|種類|値(※)
------|----------|-----|---
諸元番号|Reference|文字列|Ref?
図面番号|Drowing|文字列|DrowingName
品名|Unit|文字列|UnitName

※”値”は任意のデフォルト表示値なので特に何でもOKです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/0bc5b73c-4214-8b1c-0dc2-ca9cfee35d87.png)

8.グループ化したシェイプを選択して、図形データセットウインドウの__チェックされた図形データセットの適用先＝"図面で選択された図形"__ にして"適用ボタン"を押す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/a686203f-cded-9c12-90d1-00485ef6dced.png)

9.図形データセットが適用されると、下図のようにグループ化したシェイプオブジェクト(Sheet.9)の図形データウインドウにデータが追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/cd7e7491-8603-1111-26b5-7aa9ba3b907d.png)

10.__表示文字に図形データをリンクする__ <br>グループ化したパーツのうちの一つだけを選択し、"挿入"タブの__"フィールド"__ を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/721a7a6c-635f-a013-2cd3-a7b472cf4d34.png)

11.挿入するフィールドの__"カテゴリ"__　は　__"ユーザー定義式"__ にして、以下のように入力します。(9の部分は人によって変わります)<br>

```
=Sheet.9!Prop.Reference
```

この式の意味としては、"Sheet.9"というオブジェクトのプロパティのReferenceを表示しろという意味です。<br>
（正確にはSheet.9クラスのサブクラスPropのReferenceというプロパティなんでしょうか？？）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/e259cefd-fb3f-471f-0b17-ceff1398cbbf.png)

同様に、図形本体に"=Sheet.9!Prop.Unit" 、下のテキストボックスに"=Sheet.9!Prop.Drowing"とフィールドを設定します。<br>すると表示内容が図形データと連動するようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/7ff3394b-0384-bcc3-8012-8c424aa9bb03.png)



12.ここまで出来たらマスターシェイプとステンシルを保存して終わります。

# ③図面を書く
次に図面作成の手順です。
1. Visio図面(.vsdxファイル)を作成します。
2. __ステンシルを開く__　から先ほど作成したPARTS_SYMBOL.vssxを開きます。
3. 表示されたマスターシェイプの中から、先ほど作成した"PARTS_SYMBOL"をドラッグ＆ドロップします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/1608cf1c-4a42-8e59-206a-3bcc4f1de8c8.png)


4.パーツのシェイプを選択すると、図形データが適用されています。図形データの"__値__" は編集可能なので、それぞれのパーツに具体的な名前を入力してください。<br>
今回の例では、モジュールの種類はマスターモジュールとスレーブモジュールの２種として、スレーブは３個ある構成とます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/2d31d3e1-7f15-3f6b-377e-c873d0c0c08b.png)

5.図面ファイルはこれで保存します。



# ④Visio図面から部品情報を抽出するマクロ作成
つぎにExcelVBAでVisioオブジェクトを開いて、図形データを収集するマクロを作成します。

1. VBAエディタの __"参照設定"__ にて、Visio関連のアドインを２つ有効にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204268/511280c3-4f72-bbdf-4f16-c366dc0a648b.png)

2. VBAコードの記述<br> 説明はソースコメントに書きましたのでコードを紹介します。

```VB:Visioファイルから部品ライブラリ情報を抽出
Option Explicit

Public Sub CaptureVisioLibData()
    Dim TargetFilename As Variant

    Dim VsdApp As Visio.Application     'Visioアプリケーションオブジェクト
    Dim VsdDoc As Visio.Document        'Visioドキュメントオブジェクト(１ファイル単位)
    Dim VsdPage As Visio.Page           'Visioページオブジェクト(１ページ単位)
    Dim VsdShape As Visio.Shape         'Visioシェイプオブジェクト(１つの図形)
    Dim FildName As String              '図形データで定義したデータの名前
    Dim FildText As String              '図形データで定義したデータの値

    'ファイルを選択するダイアログを利用して読み込むVisioファイルを指定する
    TargetFilename = Application.GetOpenFilename(FileFilter:="Visioファイル,*.vsdx", MultiSelect:=False)
        
    'Visioアプリケーションオブジェクトをインスタンス
    Set VsdApp = CreateObject("Visio.Application")

    'Visioアプリケーションオブジェクトで対象のVisoファイルを開く
    Call VsdApp.Documents.OpenEx(TargetFilename, visOpenRO + visOpenHidden)

    'Visioドキュメントオブジェクトで対象のVisoファイルを開く
    Set VsdDoc = VsdApp.Documents.Item(1)
    
    'Visioのすべてのページについて処理する
    For Each VsdPage In VsdDoc.Pages
        'すべてのシェイプ(図形)について処理する
        For Each VsdShape In VsdPage.Shapes
            'シェイプの名前が部品ライブラリとして命名したPARTS_SYMBOLだけ処理する
            If InStr(VsdShape.Name, "PARTS_SYMBOL") Then
            
                'グループ化したシェイプの中にあるアイテムの数だけループ処理
                For i = 1 To VsdShape.Shapes.Count
                    '図形データで設定した名前と値を取得
                    FildName = VsdShape.Shapes.Item(i).Characters.FieldFormulaU
                    FildText = VsdShape.Shapes.Item(i).Characters.Text
                    
                    If InStr(FildName, "Unit") Then
                        Debug.Print "品名:" & FildText
                    End If
                    If InStr(FildName, "Reference") Then
                        Debug.Print "諸元番号:" & FildText
                    End If
                    If InStr(FildName, "Drowing") Then
                        Debug.Print "図面番号:" & FildText
                    End If
                Next
            End If
        Next
    Next
    'ファイルを閉じる
    VsdDoc.Close
    VsdApp.Quit
    'オブジェクト破棄
    Set VsdDoc = Nothing
    Set VsdApp = Nothing
End Sub
```

イミディエイトウインドウに出力された実行結果です。

```
諸元番号:U2
図面番号:ZU0002
品名:スレーブモジュール
諸元番号:U1
図面番号:ZU0001
品名:マスターモジュール
諸元番号:U3
図面番号:ZU0002
品名:スレーブモジュール
諸元番号:U4
図面番号:ZU0002
品名:スレーブモジュール
```
ExcelVBAで、Visioファイルから部品情報の抽出ができました。

>2020.05.01 追記  Visioシェイプの__Cells(セル名)__ を使用したデータ抽出方法の方がシンプルだったので、改善版のコードを↓に紹介します。

```VB
Option Explicit

'部品シンボルから取り出す情報を構造体として定義
Type typPartsSymbol
    図番 As String
    品名 As String
    諸元番号 As String
End Type

Public Sub CountVisioShapes()
    Dim TargetFilename As Variant

    Dim VsdApp As Visio.Application     'Visioアプリケーションオブジェクト
    Dim VsdDoc As Visio.Document        'Visioドキュメントオブジェクト(１ファイル単位)
    Dim VsdPage As Visio.Page           'Visioページオブジェクト(１ページ単位)
    Dim VsdShape As Visio.Shape         'Visioシェイプオブジェクト(１つの図形)
    Dim FildName As String              '図形データで定義したデータの名前
    Dim FildText As String              '図形データで定義したデータの値
    Dim PartsSymbol As typPartsSymbol
    
    'ファイルを選択するダイアログを利用して読み込むVisioファイルを指定する
    TargetFilename = Application.GetOpenFilename(FileFilter:="Visioファイル,*.vsdx", MultiSelect:=False)
        
    'Visioアプリケーションオブジェクトをインスタンス
    Set VsdApp = CreateObject("Visio.Application")

    'Visioアプリケーションオブジェクトで対象のVisoファイルを開く
    Call VsdApp.Documents.OpenEx(TargetFilename, visOpenRO + visOpenHidden)

    'Visioドキュメントオブジェクトで対象のVisoファイルを開く
    Set VsdDoc = VsdApp.Documents.Item(1)
    
    'Visioのすべてのページについて処理する
    For Each VsdPage In VsdDoc.Pages
        'すべてのシェイプ(図形)について処理する
        For Each VsdShape In VsdPage.Shapes
            
            'シェイプの名前が部品ライブラリとして命名したPARTS_SYMBOLだけ処理する
            If InStr(VsdShape.Name, "PARTS_SYMBOL") Then
                'Visioシェイプのセル名を指定して、対象から文字を取り出す
                With PartsSymbol
                    .諸元番号 = VsdShape.Cells("Prop.Reference").Formula
                    .図番 = VsdShape.Cells("Prop.Drowing").Formula
                    .品名 = VsdShape.Cells("Prop.Unit").Formula
                
                    'デバッグ表示
                    Debug.Print .諸元番号 & ":" & .図番 & ":" & .品名
                End With
            End If
        Next
    Next
    'ファイルを閉じる
    VsdDoc.Close
    VsdApp.Quit
    'オブジェクト破棄
    Set VsdDoc = Nothing
    Set VsdApp = Nothing
End Sub
```



# ⑤部品情報をExcelに出力
これ以降はExcelの部品表フォーマットに合わせてExcelVBAの処理を実施しますが、記事が長くなってきたので別途紹介することにします。