🐍

GhPythonの入力をまとめてdictionaryに変換する

2021/05/22に公開

パラメータを管理しにくいのが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]>]
        ]
    }
    
    
  • コード
    今回のようにパラメータが少なければこう書きます。
    ツリーは別途リストに変換する処理が必要です。
    # -*- 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]
    
    ※ちなみに、Pythonのdictionaryを出力から別のコンポーネントに送りたい場合は[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を取得

コンポーネントの入力パラメータを取得しています。
ghenvimportせずに使えます。

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を動的に変更することもできるので、いずれ記事にしようと思います。

参考

C#: How to update all sliders and run the simulation once

Discussion