GhPythonの入力をまとめてdictionaryに変換する
パラメータを管理しにくいのがGrasshopperの弱点
GhPythonやC#などスクリプトが書けるコンポーネントでプログラムを組んでいるとどうしても入力が増えがちです。
特に大規模なプログラムで大量の入力値を管理しようとすると下記のような問題がよく起こります。
- コンポーネントの入力名とプログラムの変数名が違っていてエラーが出る
- 変数名が増えたり変わったりするたびにコンポーネントの入力とコードを両方変えないといけない
この記事ではこのようなエラーをなくす1つの解決策として、どんなinput名でどんなデータ形式でもdictionaryに変換できるコードをご紹介します。
つくるもの
- GhPyhonコンポーネントの入力変数名をkey、入力値をvalueとしたdictionaryを出力するコードをつくります
- ghenvやGrasshopper.Kernelを使います
- Item, List, Treeそれぞれに対応できるようにつくります
もし自動化しなかったら?
このようなインプットをPythonのプログラムに渡すために下記のようなdictionaryにしたいときを想定します。
- 入力
Input Access Type hint toggle Item bool numbers List int points Tree Point3d - ほしい出力
{ 'toggle': True, 'numbers': [0, 1, 2, 3, 4], 'points': [ [<Rhino.Geometry.Point3d object at 0x00000000000002D9 [0,0,0]>, <Rhino.Geometry.Point3d object at 0x00000000000002DA [0,0,0]>], [<Rhino.Geometry.Point3d object at 0x00000000000002DB [1,0,0]>, <Rhino.Geometry.Point3d object at 0x00000000000002DC [0,1,0]>], [<Rhino.Geometry.Point3d object at 0x00000000000002DD [2,0,0]>, <Rhino.Geometry.Point3d object at 0x00000000000002DE [0,2,0]>], [<Rhino.Geometry.Point3d object at 0x00000000000002DF [3,0,0]>, <Rhino.Geometry.Point3d object at 0x00000000000002E0 [0,3,0]>], [<Rhino.Geometry.Point3d object at 0x00000000000002E1 [4,0,0]>, <Rhino.Geometry.Point3d object at 0x00000000000002E2 [0,4,0]>] ] }
- コード
今回のようにパラメータが少なければこう書きます。
ツリーは別途リストに変換する処理が必要です。※ちなみに、Pythonのdictionaryを出力から別のコンポーネントに送りたい場合は# -*- coding: utf-8 -*- from ghpythonlib.treehelpers import tree_to_list py_dict = { "toggle": toggle, "numbers": numbers, "points": tree_to_list(points,0) } output_ = [py_dict]
[dictionary]
のようにする必要があります。
もしパラメータが10個あるコンポーネントを10個作る必要があったら?
100パラメータ分この処理を書くのは大変ですし、パラメータに変更が起きたらコードも直さないといけません。
どれだけ気を付けていても間違えるリスクが高いです。
ということで、どんな入力でもdictionaryに変えてくれるコードを書いていきます。
テスト用のコンポーネントを作成
先ほどの入力値をサンプルとして使います。
結果がわかりやすいようにprint
してout
から見られるようにします。
入力値をdictionaryに変換する
先にコードの全文を記載しておきます。
以下主要なところを解説します。
# -*- coding: utf-8 -*-
import Grasshopper.Kernel as kernel
gh_inputs = ghenv.Component.Params.Input
py_dict= {}
access_list = kernel.GH_ParamAccess.list
access_item = kernel.GH_ParamAccess.item
access_tree = kernel.GH_ParamAccess.tree
for gh_input in gh_inputs:
if gh_input.VolatileData.PathCount > 0:
name = gh_input.Name
access = gh_input.Access
if access == access_item:
value = gh_input.VolatileData.get_Branch(gh_input.VolatileData.Paths[0])[0].Value
elif access == access_list:
value = []
for data in gh_input.VolatileData.get_Branch(gh_input.VolatileData.Paths[0]):
value.append(data.Value)
elif access == access_tree:
value = []
for path in gh_input.VolatileData.Paths:
value.append([data.Value for data in gh_input.VolatileData.get_Branch(path)])
else:
raise TypeError("Access type error detected.")
py_dict[name] = value
# print(py_dict)
output_ = [py_dict]
①Inputを取得
コンポーネントの入力パラメータを取得しています。
ghenv
はimport
せずに使えます。
gh_inputs = ghenv.Component.Params.Input
中身は下記のようになっています。
このリストをfor文でひとつずつ処理していきます。
List[IGH_Param]([
<Grasshopper.Kernel.Parameters.Param_ScriptVariable object at 0x000000000000030B [Grasshopper.Kernel.Parameters.Param_ScriptVariable]>,
<Grasshopper.Kernel.Parameters.Param_ScriptVariable object at 0x000000000000030C [Grasshopper.Kernel.Parameters.Param_ScriptVariable]>,
<Grasshopper.Kernel.Parameters.Param_ScriptVariable object at 0x000000000000030D [Grasshopper.Kernel.Parameters.Param_ScriptVariable]>
])
②データのある入力のみ取得する
入力値のデータはVolatileData
何に入っています。
PathCount
でパス数を取得して、データが入っているパラメータのみdictionaryにするようフィルターをかけます。
if gh_input.VolatileData.PathCount > 0:
③パラメータの名前、Accessを取得する
パラメータの情報はすべてgh_input
という変数に代入したGrasshopper.Kernel.Parameters.Param_ScriptVariable
オブジェクトの中に入っています。
詳しくはAPIリファレンスをご参照ください。
name = gh_input.Name
access = gh_input.Access
④Accessによって処理を分ける
Item, List, Treeそれぞれで処理の仕方を分けていきます。
念のためどれにも当てはまらない時はエラーを出しておきます。
import Grasshopper.Kernel as kernel
access_list = kernel.GH_ParamAccess.list
access_item = kernel.GH_ParamAccess.item
access_tree = kernel.GH_ParamAccess.tree
for gh_input in gh_inputs:
if gh_input.VolatileData.PathCount > 0:
name = gh_input.Name
access = gh_input.Access
if access == access_item:
....
elif access == access_list:
....
elif access == access_tree:
....
else:
raise TypeError("Access type error detected.")
⑤Itemのとき
続いてデータの中身を取り出していきます。
if access == access_item:
value = gh_input.VolatileData.get_Branch(gh_input.VolatileData.Paths[0])[0].Value
ここが少しややこしいのですが、VolatileData.get_Branch
は引数にPathそのものもしくはindexをとるので、0番目のPathを入れています。
すると下のようなList[IGH_Goo]
のリストが返ってくるので、そのリストの0番目をとってきて、.Value
で値のみを取り出します。
List[IGH_Goo]([<Grasshopper.Kernel.Types.GH_Boolean object at 0x0000000000000073 [True]>])
⑥Listのとき
先ほどとやり方は同じです。
List[IGH_GOO]
のままだと扱いにくいので、改めてPythonのリストにValueを格納します。
elif access == access_list:
value = []
for data in gh_input.VolatileData.get_Branch(gh_input.VolatileData.Paths[0]):
value.append(data.Value)
⑦Treeのとき
TreeはPathが複数になるので、Pathごとにデータを取り出してリストの中に各Treeのリストを格納していきます。
その後の処理によってはPath名をkey、リストをvalueとするDictionaryにしてもいいと思います。
elif access == access_tree:
value = []
for path in gh_input.VolatileData.Paths:
value.append([data.Value for data in gh_input.VolatileData.get_Branch(path)])
⑧dictionaryに格納して完成
input名がkeyの場所に先ほどのvalueを格納していきます。
これでどんなinput名でどんなAccessでもすべてdictionaryに変換できるコードができました。
name = gh_input.Name
....
py_dict[name] = value
さいごに
Grasshopperのコンポーネント自体をハックできると楽しいですよね。
応用するとInputやOutputを動的に変更することもできるので、いずれ記事にしようと思います。
Discussion