仕様ツリーの構成をExcelファイルに出力するマクロ|CATIAマクロの作成方法
今回の記事は「マクロ案」よりいただいた内容です。
送って頂いた内容は以下のようなマクロです。
ワークベンチ: Part Design
マクロ案: 仕様ツリー上の各要素の名称をExcelに読み込み、
Excel上で名称を変更したものを仕様ツリーに反映するマクロ
(CATIAのマクロというよりExcelのマクロかも)
今回のマクロは下記の2つのマクロで構成していきます。
①「仕様ツリーのオブジェクト名をExcelファイルとして出力するマクロ」
②「編集したExcelファイルを読み込み仕様ツリーに反映するマクロ」
本ページでは①のマクロについて紹介していこうと思います。
(①のマクロだけの内容ですが結構長めなので頑張ってください)
CATIA VBAのオブジェクト構造を理解している方ならある程度予想ができると思いますが、仕様ツリーの階層を考慮して上から順にオブジェクトを取得することは実はかなり難しいです。そこで今回は「パートデザイン」「ジェネレーティブ・シェイプ・デザイン」の基本的なコマンドであれば実行可能なマクロを”簡単なコード”で作成しました。
簡単なコードの分、エラー処理や一部オブジェクトには対応していないという状態ですが、どのようにしてツリー構成を取得しているのかは理解しやすい内容になっていると思います。
もし本格的に取得したい場合は下記サイトのマクロを参考にしてみて下さい。
BodyのTreeを各種フォーマットでエクスポートする – C#ATIA
ハードルは少し上がりますがクラスモジュールなども使いしっかりとしたコードで作成されています。
マクロの機能
今回作成したのは仕様ツリーの構成をExcelファイルに出力するマクロです。
オブジェクト名を編集する際にわかりやすいよう、上画像のように仕様ツリーの階層をExcel上で再現して出力するようになっています。
具体的な機能は以下のとおりです。
・[パートデザイン]だけでなく [GSD]ワークベンチでも実行可能
・一部オブジェクトは出力されない(座標系、スケッチ内要素、ナレッジ関連のオブジェクト等)
→ 非対応オブジェクトを対応させるにはコードにそれぞれの処理を書く必要あり
※すべてのオブジェクトで試したわけではないので、場合によってエラーが発生します。
その場合はそのオブジェクトを除外するか専用の処理を追記する必要があります
(詳しくはコード解説部分で説明しています)
サンプルコード
マクロのコードは以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
Option Explicit Sub CATMain() 'アクティブドキュメント等の定義 If TypeName(CATIA.ActiveDocument) <> "PartDocument" Then MsgBox "このマクロはPartDocument専用です。" & vbLf & _ "CATPartに切り替えて実行してください。" Exit Sub End If Dim doc As PartDocument: Set doc = CATIA.ActiveDocument Dim pt As Part: Set pt = doc.Part Dim sel As Selection: Set sel = doc.Selection Dim objs As Collection: Set objs = New Collection Dim obj As AnyObject Dim i As Long 'Excelに出力するオブジェクトの選別 sel.Clear sel.Search ("タイプ=*,all") 'English ver -> sel.Search ("type=*,all") For i = 1 To sel.Count Set obj = sel.Item(i).Value If InStr(obj.Name, "\") = 0 And InStr(TypeName(obj), "2D") = 0 Then '"\"が入っている=パラメータ, "2D"が入っている=Sketch要素 If Not (TypeName(obj) = "Item" Or _ TypeName(obj) = "Formula" Or _ TypeName(obj) = "AxisSystem" Or _ TypeName(obj) = "Rule" Or _ TypeName(obj) = "KnowledgeObject" Or _ TypeName(obj) = "Constraint" Or _ TypeName(obj) = "AnyObject") Then objs.Add obj End If End If Next i sel.Clear 'Excel定義 Dim appExcel As Excel.Application Set appExcel = CreateObject("Excel.Application") Dim wb As Workbook: Set wb = appExcel.Workbooks.Add Dim ws As Worksheet: Set ws = wb.Sheets(1) Dim hierarchy As Long Dim cnt As Long: cnt = 1 Dim max_r As Long Dim max_c As Long: max_c = 1 'オブジェクトの階層を取得しExcelにオブジェクト名を出力する appExcel.ScreenUpdating = False CATIA.StatusBar = "ツリー構成をExcelに出力中..." For Each obj In objs hierarchy = get_hierarchy(obj) Debug.Print obj.Name & " => " & hierarchy If cnt <> 1 Then ws.Cells(cnt, hierarchy - 1).Value = "∟" End If If max_c < hierarchy Then max_c = hierarchy End If ws.Cells(cnt, hierarchy).Value = obj.Name cnt = cnt + 1 Next obj 'Excelファイルの体裁を整える For i = 1 To max_c ws.Columns(i).ColumnWidth = 2 Next i Dim r As Long Dim c As Long Dim last_r As Long Dim last_c As Long: last_c = max_c Dim cell_val As String For c = 1 To last_c last_r = ws.Cells(Rows.Count, c).End(xlUp).Row '以下をコメントアウトでツリーの枝部分を再現しない(処理速度の向上)------ For r = last_r To 1 Step -1 cell_val = ws.Cells(r, c).Value If cell_val = "∟" Or cell_val = "|" Then If ws.Cells(r - 1, c).Value = "" Then ws.Cells(r - 1, c).Value = "|" End If End If Next r '------------------------------------------------------------ If max_r < last_r Then max_r = last_r End If Next c For c = 1 To max_c ws.Cells(max_r + 1, c).Value = "―" Next c appExcel.ScreenUpdating = True CATIA.StatusBar = "ツリー構成をExcelに出力しました" MsgBox "現行の仕様ツリーの構成をExcelファイルとして出力しました" appExcel.Visible = True End Sub '-------------------------------------------------------------------------------------- ' 関数名 get_hierarchy ' ' 機能 引数objの階層を求めLong型で返す ' Partの場合「1」XY平面,パーツボディーの場合「2」が返される ' ' ※引数objのタイプによっては正常に求められないものあり ' -> そのオブジェクトは除外して考える or そのオブジェクト専用の処理を追加する必要あり '-------------------------------------------------------------------------------------- Function get_hierarchy(ByVal obj As AnyObject) As Long Dim tmp_obj As AnyObject Dim cnt As Long If InStr(TypeName(obj), "Explicit") = 0 Then Set tmp_obj = obj Else Set tmp_obj = obj.Thickness End If Do Until TypeName(tmp_obj) = "PartDocument" If TypeName(tmp_obj) <> "AnyObject" Then cnt = cnt + 1 End If Set tmp_obj = tmp_obj.Parent If cnt > 50 Then Exit Do '無限ループ強制終了用 Loop get_hierarchy = cnt End Function |
処理の流れとしては下記のような流れになっています。ツリーの階層は考慮せずに1行でExcelに出力する場合は、”階層の取得”が不要になるため②の処理がもっとシンプルになります。
② 取得したオブジェクトをループ処理
②-1 オブジェクトの階層を取得
②-2 オブジェクトの階層に応じてExcelの行を変化させながらオブジェクト名を出力
③ Excelを表示
コード解説
アクティブドキュメント等の定義
1 2 3 4 5 6 7 8 9 10 11 12 13 |
'アクティブドキュメント等の定義 If TypeName(CATIA.ActiveDocument) <> "PartDocument" Then MsgBox "このマクロはPartDocument専用です。" & vbLf & _ "CATPartに切り替えて実行してください。" Exit Sub End If Dim doc As PartDocument: Set doc = CATIA.ActiveDocument Dim pt As Part: Set pt = doc.Part Dim sel As Selection: Set sel = doc.Selection Dim objs As Collection: Set objs = New Collection Dim obj As AnyObject Dim i As Long |
まずはじめにアクティブドキュメントを定義をします。
今回のマクロはCATPartのみ有効なものなので、アクティブドキュメントがCATPart以外の場合はTypeName関数を使った条件分岐でマクロを終了するようにしています。つまり、アクティブドキュメントがCATPartの場合のみ変数「doc」にアクティブドキュメントを代入し、マクロの処理を続けます。
アクティブドキュメントが定義できたら、以降で使うためのオブジェクト/変数をまとめて定義しておきます。ここでは下記の用途で各オブジェクト/変数を定義しています。
Partオブジェクト :ツリー第1階層指定用
Selectionオブジェクト :ドキュメント内のオブジェクトを”上から順”に取得する用
objs (As Collection) :Excelに出力するオブジェクトを全て格納する用
obj (As AnyObject) :objs内ループ(For Each ~ In ~)用変数
i (As Long) :Forループ用カウント変数
Excelに出力するオブジェクトの選別
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
'Excelに出力するオブジェクトの選別 sel.Clear sel.Search ("タイプ=*,all") 'English ver -> sel.Search ("type=*,all") For i = 1 To sel.Count Set obj = sel.Item(i).Value If InStr(obj.Name, "\") = 0 And InStr(TypeName(obj), "2D") = 0 Then '"\"が入っている=パラメータ, "2D"が入っている=Sketch要素 If Not (TypeName(obj) = "Item" Or _ TypeName(obj) = "Formula" Or _ TypeName(obj) = "AxisSystem" Or _ TypeName(obj) = "Rule" Or _ TypeName(obj) = "KnowledgeObject" Or _ TypeName(obj) = "Constraint" Or _ TypeName(obj) = "AnyObject") Then objs.Add obj 'objsにExcelに出力するオブジェクトのみを追加していく End If End If Next i sel.Clear |
つぎにExcelに出力するオブジェクトをすべて取得します。
このとき、必要のないオブジェクトは除外します。この部分は自身の出力したい内容に合わせて書き換えて下さい。
オブジェクトの選別する方法は簡単で一度オブジェクトをすべて選択状態にし、条件を絞りながら選択しているオブジェクトを順に「objsコレクション」に格納していくだけです。この考え方は「選択しているオブジェクトを一時保管する方法」を参照下さい。
上記コードでいう「TypeName(obj)=”XXXX”」のXXXXのオブジェクトは除外するようになっています。除外する理由は2つあります。
1つ目はそこまで細かく必要ないためです。
あくまで個人の主観ですが、「長さや角度といったパラメータ」「スケッチ内の形状や拘束」などはあえて出力する必要はないと感じたため、ここでは除外しています。コードでいうと一番初めの条件分岐「If InStr(obj.Name, “\”) = 0 And InStr(TypeName(obj), “2D”) = 0 Then」と次の条件分岐内の「TypeName(obj) = “Constraint”」の部分です。
2つ目は除外しないとコードがうまく実行できないためです。
今回のコードはのちに出てくる関数「get_hierarchy」を使ってオブジェクトの階層を調べます。
この関数では単純に「obj.Parent.Parent…」のように親オブジェクトをさかのぼっていくものなのですが、一部オブジェクトではこの方法が通用しません。例えば「式」や「座標系」などのオブジェクトはVBAオブジェクトの関係上、通常のオブジェクトと同じ方法では階層を調べることが出来ません。
そのため、必要であれば関数「get_hierarchy」に各オブジェクト別の処理を追加すればここで除外する必要は無くなります。ただ、それをすべてのオブジェクトで対応するには膨大なコードになるため、ここでは個人的にやっていてうまく階層が取得できなかったオブジェクトのみを全て除外しています。(おそらく他にもうまくできないオブジェクトあるので、適宜ここで除外するか関数内に専用の処理を追加してください)
Excel定義 + 変数定義
1 2 3 4 5 6 7 8 9 10 11 |
'Excel定義 Dim appExcel As Excel.Application Set appExcel = CreateObject("Excel.Application") Dim wb As Workbook: Set wb = appExcel.Workbooks.Add Dim ws As Worksheet: Set ws = wb.Sheets(1) Dim hierarchy As Long Dim cnt As Long: cnt = 1 Dim max_r As Long Dim max_c As Long: max_c = 1 |
つぎに出力先となるExcelのブックを新規作成し、シートを定義します。
Excelの定義方法については「CATIAマクロでExcelを操作する方法」を参照下さい。
上記ページにも書かれている通り、参照設定をしていないと実行できないので注意して下さい。
Excelが定義できたら、以降で使うための変数をまとめて定義しておきます。ここでは下記の用途で各変数を定義しています。
hierarchy (As Long):オブジェクトのの階層格納用
cnt (As Long):カウント変数
max_r (As Long):Excel出力時に値を持つセルの最終行格納用
max_c (As Long):Excel出力時に値を持つセルの最終列格納用
オブジェクトの階層を取得しExcelにオブジェクト名を出力する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
'オブジェクトの階層を取得しExcelにオブジェクト名を出力する appExcel.ScreenUpdating = False CATIA.StatusBar = "ツリー構成をExcelに出力中..." For Each obj In objs hierarchy = get_hierarchy(obj) Debug.Print obj.Name & " => " & hierarchy If cnt <> 1 Then ws.Cells(cnt, hierarchy - 1).Value = "∟" End If If max_c < hierarchy Then max_c = hierarchy '値の入ったセルの中で最も右側にあるセルの列を取得 End If ws.Cells(cnt, hierarchy).Value = obj.Name cnt = cnt + 1 Next obj |
つぎに本マクロの核となるExcel出力処理です。
objsコレクション内に入っているオブジェクトの名称を順にExcelに出力していきます。
その際に関数「get_hierarchy」を使ってオブジェクトの階層を取得します。
この取得した階層を使って「Cells(cnt, hierarchy).Value = obj.Name」と書くことで、1行目から順にオブジェクトの名前を出力するだけでなく、階層別に列もそろえることが可能になります。
あわせて「Cells(cnt, hierarchy – 1).Value = “∟”」と書いておけば、オブジェクト名を出力したセルの左側のセルにツリーの枝部分を書き出すことができます。
これでオブジェクトの書き出しの処理自体は完了です。
以降ではより見やすくなるようセルの幅を揃えたり、ツリーの枝部分を作成したりします。
また、初めに書いている2行はそれぞれ下記のような役割です。
書かなくても処理自体に大きな影響は与えません。
CATIA.StatusBar = “ツリー構成をExcelに出力中…” CATIAステータズバーにメッセージ表示
Excelファイルの体裁を整える
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
'Excelファイルの体裁を整える '①ツリー枝部分の列幅を調整 For i = 1 To max_c ws.Columns(i).ColumnWidth = 2 Next i Dim r As Long Dim c As Long Dim last_r As Long Dim last_c As Long: last_c = max_c Dim cell_val As String '②ツリー枝部分の作成 For c = 1 To last_c last_r = ws.Cells(Rows.Count, c).End(xlUp).Row '以下をコメントアウトでツリーの枝部分を再現しない(処理速度の向上)------ For r = last_r To 1 Step -1 cell_val = ws.Cells(r, c).Value If cell_val = "∟" Or cell_val = "|" Then If ws.Cells(r - 1, c).Value = "" Then ws.Cells(r - 1, c).Value = "|" End If End If Next r '------------------------------------------------------------ If max_r < last_r Then max_r = last_r End If Next c '③ツリー最終位置に"――"を入力 For c = 1 To max_c ws.Cells(max_r + 1, c).Value = "―" Next c |
つぎに出力先したExcelファイルを見やすくするために軽く編集していきます。
やっていることとしては下記の3点です。
①「ツリー枝部分の列幅を調整」
②「ツリー枝部分の作成」
③「ツリー最終位置に”――”を入力」
このうち②「ツリー枝部分の作成」はオブジェクトの数によっては(オブジェクト数が5000を超えたあたりから)処理が重くなるため、コメントアウトして処理速度を優先にすることもできます。場合によって使い分けるか、もっと効率の良い方法があればそちらに書き換えてみて下さい。
③「ツリー最終位置に”――”を入力」は、Cells(max_r+1 , 1)からCells(max_r+1 , max_c)の間に”―”を入力しています。つまりは、その”―”部分を読み取れば、ツリーのサイズ(最終行と最終列)を取得することができます。
これは「編集したExcelファイルを読み込み仕様ツリーに反映するマクロ」を使う際に使用するためのものです。そちらのマクロも合わせて使う場合はこの処理は残しておいて下さい
最後に出力したExcelブックや完了メッセージを表示させれば完了です。
関数「get_hierarchy」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Function get_hierarchy(ByVal obj As AnyObject) As Long Dim tmp_obj As AnyObject Dim cnt As Long If InStr(TypeName(obj), "Explicit") = 0 Then Set tmp_obj = obj Else Set tmp_obj = obj.Thickness End If Do Until TypeName(tmp_obj) = "PartDocument" If TypeName(tmp_obj) <> "AnyObject" Then cnt = cnt + 1 End If Set tmp_obj = tmp_obj.Parent If cnt > 50 Then Exit Do '無限ループ強制終了用 Loop get_hierarchy = cnt End Function |
最後にこれまでにも何度か出てきた関数「get_hierarchy」についてです。
この関数は入力されたオブジェクトの階層を取得し、Long型の数値として返す機能を持っています。
ここで注意してほしいのは、「ここで出力される階層≠CATIAのツリーの階層」という点です。
この関数はExcel出力用のためPartが「1」、パーツボディーやXY平面などが「2」として出力されます。
つまりは「ここで出力される階層 – 1=CATIAのツリーの階層」と思ってもらえれば大丈夫です。
階層の取得方法は、入力されたオブジェクトの親オブジェクトをたどっていき「PartDocument」にたどり着くまでの回数を数えているだけです。
ここで注意しないといけないのが一部オブジェクトではこの方法で対応できないという点です。
たとえばデータム化した点や曲線の場合、ツリーの深くの階層にいたとしても下記のような流れで「PartDocument」にたどり着くため、階層がおかしなことになってしまいます。
この場合は「Thickness」を挟めば問題が解決するとわかったため、データム化されたオブジェクト(つまりは「Explicit」と含まれるオブジェクト)には「Thickness」を挟む専用の処理を追加しています。
これと同じで親をさかのぼっていくことがうまくできないオブジェクトがいくつか存在しています。
そもそも「PartDocument」にたどり着かないもの、たどり着くまでの回数がおかしいものなどの要因があります。こういった場合はデータム化されたオブジェクトのように専用の処理を追加するか、コード冒頭のオブジェクトの選別の時点で除外するかのどちらかが必要になってきます。
本マクロを使う場合はエラーが出るたびにそのオブジェクトをどうするか(除外or専用コード追加)を決め、徐々に最適化していくことになると思います。ただ、これは一部のオブジェクトに対していえることなだけであって、基本的なオブジェクトには対応しているはずです。
まとめ
今回は仕様ツリーの構成をExcelファイルに出力するマクロについての内容でした。
やっていることは自体は簡単なコードにしましたが、どうしてもその分対応しきれないオブジェクトが出てきてしまっています。ある程度は対応しているので、これで良しとするのであれば問題ないですが、もっとしかっりとしたコードにしたいという場合は冒頭でも紹介した下記サイトを参考にしてみて下さい。
BodyのTreeを各種フォーマットでエクスポートする – C#ATIA