【集中講座】Grasshopper: リストの順番を崩さず条件分岐させる

はじめに

Grasshopperでアルゴリズムを作成する際、条件によって異なる処理を行いたい場面がよくあります。またジオメトリに関して処理を行うとき、単にデータを削除するだけでなく、条件に応じて処理を分け、最後に元通りのリストに統合したい場合、データ構造(インデックスや階層)を維持することが非常に重要になります。

本記事では、データの順番を崩さずに条件分岐を行うための複数のアプローチを紹介します。これらの方法はそれぞれ異なる用途に適しているため、状況に応じて使い分けることが重要となります。

[ Cull Pattern ] や [ Dispatch ] を使った分岐の基本と注意点

条件に基づいてデータを分ける代表的なコンポーネントとして、 [ Cull Pattern ] コンポーネントと [ Dispatch ] コンポーネントがあります。

[ Cull Pattern ] コンポーネントは、True、Falseなどのパターンを使用し、Falseに該当する不要なデータのみ取り除きます。特定のパターンでデータを削除したいだけであれば、一番わかりやすい方法です。

また、以降のコンポーネントでも同様ですが、パターンに入れたデータ数が少ない時は、パターンごとに最後まで繰り返すという変わった特徴があります(True、False と入れた場合、入力したアイテム数だけTrue、Falseが繰り返されます)。

[ Dispatch ] コンポーネントも同様に、TrueとFalseをパターンに入力します。ただし[ Cull Pattern ] のように削除するだけでなく、それぞれの端子からデータを分割して出力します。

しかし、[ Dispatch ] などでデータを分割し、それぞれのデータに処理を行った後、 [ Merge ] コンポーネントなどで再度統合すると、元のデータの順番が壊れてしまいます(A,B,C,D,E,Fと並んでいたデータが、統合後はA,C,E,B_Test,D_Test,F_Testの順となります)。

曲線の制御点を修正する時、点の順番が変わると曲線の形も変わってしまい問題となります。

本セクション以降では、様々なアプローチでデータの順番を守りつつ、条件に分けて処理する例をご紹介します。

補足: [ Dispatch ] や後述の [ Sift Pattern ] コンポーネントでパターンごとにデータを分けた場合、どちらの出力端子から何のジオメトリが出力されているか、確認ができません。出力を受けるコンポーネントを別途配置して、データの中身を確認してください。

アプローチ①: [ Sift Pattern ] と [ Combine Data ] の組み合わせ

元のインデックスを変更せずにリストの順番を維持したまま処理を行うためには、 [ Sift Pattern ] コンポーネント(Siftは「ふるい」という意味)と [ Combine Data ] コンポーネントの組み合わせが有効です。

[ Sift Pattern ] コンポーネントは、条件に合わないデータを削除するのではなく、<null> データを代わりに配置して出力します(null はデータが存在しないという意味です)。処理を行った後、 [ Combine Data ] コンポーネントで結合することで、元の順番通りにデータを統合できます。下図は端子1から出力されたA,C,E の末尾に _Test という文字を追加し統合した図です。

 [ Dispatch ] コンポーネントにはない機能として、ズームする際に表示される「+」アイコンから出力端子を増やすことができます。 [ Sift Pattern ] のパターンはTrue・Falseのブール型ではなく整数(int)型であるため、3つ以上の数値を用意してデータをそれぞれの出力端子に振り分けることも可能です(整数型にBooleanを繋いだ場合Trueは 1 、Falseは 0 に変換されて処理されます)。

注意点として、 [ Sift Pattern ] コンポーネントで作った <null> を含んだリストに対して一括で四則演算などを行うと、Grasshopperの仕様により <null> が別の値で埋められてしまうことがあります。

<null> がなくなってしまうと、 [ Combine Data ] コンポーネントで結合する際にデータを正しく受け渡すことができず、統合ができなくなります(下図の例では、 <null> の箇所が自動的に計算され、3.33が入って <null> が無くなった状態です)。

これを防ぐには、以下のような方法があります。

  • 片方のみ処理する: 2つに分岐させる場合、処理したい方の番号が大きいデータ(ここでは端子 1)のみ処理を行い、もう片方の番号が小さいデータ(ここでは端子 0)はそのまま [ Combine Data ] コンポーネントに接続します。
  • 再度Siftで抜き直す: 処理によって <null> が別の値で埋まってしまった後、最初に使用したのと同じパターンの [ Sift Pattern ] コンポーネントをもう一度通すことで、再度Nullを生成し結合します。

[ Combine Data ] コンポーネントの後は、パネル等を使用して意図した通りに正しく統合できているか必ず確認ください。

なお、綴りが近い [ Shift List ] コンポーネントは、データリストを指定した数だけずらす機能を持つため、使用方法が全く異なります。混同しないよう注意が必要です。

アプローチ②: [ Sift Pattern ] と [ Replace Nulls ] を使った値の置き換え

[ Sift Pattern ] コンポーネントで条件に合わなかったデータを <null> として出力する特徴を生かし、 [ Replace Nulls ] コンポーネントと併用して<null>部分に代入するパターンも非常に有効です。

[ Replace Nulls ] コンポーネントは、リスト内の <null> の箇所を、入力R端子で指定した値(Replacements)で置き換えます。例えば、条件に合わなかった <null> 部分に「0」や「”—-“」といった特定の値や文字で一括置換したい場合に使用できます。処理後には、出力N端子から置き換えた総数も確認できます。

下図は、距離に応じて処理を変えるアトラクターと併せて使用した例です。距離に応じてRの値を指定する際、最小値以下の箇所を置き換えた例です。
直感的にアルゴリズムを修正しつつ、製造上の制約条件を守りたい場合に有効な方法です。

アプローチ③: [ Dispatch ] と [ Weave ] の組み合わせ

また[ Dispatch ] コンポーネントでデータをパターンごとに分割した場合でも、 [ Weave ] コンポーネントと併せて使用することで元の順番に復元することが可能です。 [ Weave ] コンポーネントのP端子に [ Dispatch ] で分岐に使用したパターンを入力して元のリストを復元できます。

ただし、この統合方法には分割する際に使用したパターンと、[ Weave ] で組み合わせる時に使用するパターンが同じである必要があります。また [ Dispatch ] はTrueとFalseの2つしか受け付けません。そのため条件が複雑で複数回分割する場合は、分割した回数と同じだけ [ Weave ] コンポーネントを使用する必要があり、複雑になる傾向があります。

条件分岐が1度だけであれば、<null> のことを気にしなくてよい分、[ Sift Pattern ] と [ Combine Data ] の組み合わせよりも分かりやすいかもしれません。

アプローチ④: 数値範囲(Domain)と [ Pick’n’Choose ] の組み合わせ

条件が2つでなく複数かつ、分岐する条件が「数値」の場合は、あらかじめ複数の範囲を作り、どの範囲に含まれるかを分岐の条件として使用する方法もあります。

範囲の元となる値群を [ Consecutive Domains ] コンポーネントに入力して複数の範囲を作成し、 [ Find Domain ] コンポーネントでどの範囲に含まれるかを求めて、それをインデックスとして使用できます。

またその際、求めたインデックスを利用して、 [ Pick’n’Choose ] コンポーネントを使用して後から統合できます。 [ Pick’n’Choose ] は [ Weave ] と似た働きのコンポーネントですが、[ Weave ] と異なり、入力したパターン数で処理が終了します。

下図は、ある範囲の値のみ100倍、別の範囲の値を-7.7倍、それ以外はそのまま出力した例です。

この時、下記の点に注意が必要です。

  • [ Consecutive Domains ] コンポーネントで複数の範囲を作成するとき、AdditiveオプションがTrueだと範囲値が足し算されてしまいます。
  • 用意した範囲から外れるものはインデックスが「-1」となってしまうため、1000000000のような最小値や最大値を入力し、範囲から外れないように設定します。
  • 必要な値のみで構成した場合、より小さい値も、より大きい値も、ともに結果が -1となり、適切に範囲としてインデックスを指定できません。

アプローチ⑤: スクリプトと [ Pick’n’Choose ] の組み合わせ

極端に扱うデータ数が多い場合や、より複雑な条件分岐を行う場合は、C#やPythonなどのスクリプトを使用し、スクリプト内で条件をまとめた方が効率的です。

例えば、一般的なプログラミングの例でよく用いられるFizzBuzz問題(3の倍数ならFizz、5の倍数ならBuzz、15の倍数ならFizzBuzzを出力する処理)のような複数の条件を持つ問題の場合、スクリプトコンポーネント内で if、elif、else などを利用して、条件に応じたインデックス(0, 1, 2…)を出力させます。

以下は、FizzBuzz問題のインデックスを生成した例です。

  • 15で割った余りが0(15で割り切れる)の時は0
  • 5で割った余りが0(5で割り切れる)の時は1
  • 3で割った余りが0(3で割り切れる)の時は2
  • それ以外は3

を出力します。出力したインデックスを使って [ Pick’n’Choose ]コンポーネントで合成することで、シンプルにデータを取得できます。

[ Pick’n’Choose ]に入力するデータは事前に [ Repeat Data ] コンポーネントを使用して、パターンの数と同じだけデータを用意して接続しています。こういった形で条件ごとにスクリプトでインデックスを求めることで、Grasshopperの標準コンポーネントと組み合わせて使用することもできます。

アプローチ⑥: スクリプトですべて処理する

もちろん⑤で行ったような比較的簡単な例であれば、後で使用するためのインデックスを出力するのではなく、直接結果となる文字を出力するのが最も簡単です。

ただし、スクリプト内でジオメトリの処理を行う場合は、RhinoCommonなどのAPIを使用する必要があります。この詳細については本記事の範囲を超えるため説明を割愛しますが、ある程度汎用的な内容までカスタマイズできれば、Grasshopperのプラグインとして作成したり、Grasshopperを使わずにRhinoから直接実行したり、Rhinoの単体プラグインとして作成したりすることも可能です。

また、極端にデータ数が多い場合は、<null> データで順番を維持したまま作業するよりも、スクリプト内で処理する方が計算時間も短縮できます。興味があればぜひお試しください。

RhinoのAPI全般へのリンクは、下記を参照ください。

まとめ

条件によって処理を分けたい場合、状況に応じて様々なアプローチが存在します。

  • 単にデータを削除するだけの場合: [ Cull Pattern ] コンポーネント
  • シンプルな2分岐の場合: [ Dispatch ] コンポーネントと [ Weave ] コンポーネントの組み合わせ
  • インデックスを保ちつつ複数分岐させたい場合: [ Sift Pattern ] コンポーネントと [ Combine Data ] コンポーネントの組み合わせ
  • Nullデータを別の値で置き換えたい場合: [ Replace Nulls ] コンポーネントの併用
  • 数値の範囲により処理を変える場合: [ Find Domain ] と [ Pick’n’Choose ] の併用
  • さらに複雑な場合や大量のデータを扱う場合: スクリプトやプログラムの活用

これらの方法を目的に合わせて使い分けることで、日々のモデリング作業の効率化につながります。ここで挙げた以外にも多くの方法があるため、ぜひご自身で使いやすい方法を見つけて試してみてください。