GH python Grasshopper

【Grasshopper】GhPythonからAPIを叩き、UserAttributesをDBで管理してみた。

今回は、node.jsでAPIを作成し、GhPythonコンポーネントから、作成したAPIを叩いてみました。4枚のパネルに対してUserAttributesを設定し、GhPythonからAPIを叩き、CRUD操作を一通り行い、DBでattributesを管理するといった内容になってます。

はじめに

初めに以下の前提があることを認識してから記事を読んでいただけると幸いです。

  • 私自身、最近はJavascriptを使用した開発が中心でPythonは”にわか”です(~_~;)。Rhinoceros / GrasshopperでAPIを叩くときに雰囲気で書いてますが、その程度なのでもしかしたら変な書き方してるかもです。そもそも今回はさほどPython書きませんが、あらかじめご理解ください。
  • Rhino / Grasshopperは業務でそこそこ使いますが、複雑な案件をやっているわけではありません。今回の記事を見て、「実務でこうやってんだな~」みたいには思わないようにおきおつけください。ただの思い付きでやってみただけです<m(__)m>。(実務でどんなふうにやっているのかはむしろ僕が知りたい・・・)
  • 今回は全体的にどんな感じでやっているかだけ解説していきます。ザックリとしか説明していないのであらかじめご了承ください。Githubにソースコードは載せてます。Grasshopperのファイルもpushしてます。⇒こちらから

では解説していきます。

概要

今回の概要は上の様になってます。

  1. Grasshopperで4枚のPanelを生成。4枚のパネルに対して、delivery(出荷日)のattributesを付与することを想定しています。
  2. node.jsを使用し、delivery attributesの保存・取得・更新・削除するAPIを作成。
  3. GhPythonから作成したAPIにリクエストを送ることで、データベースに保存したattributesをgrasshopperから保存・取得・更新・削除できるようにします。
  4. RhinoモデルにはElefrontでIdのattributeのみ付与し、deliveryのattributeはデータベースで管理。

みたいな内容になってます。

ちなみに諸々のバージョン。

  • Rhinocero7
  • API ⇒ node.js (18.12.1) / Express(4.18.2) / Typescript( 4.8.4 ) /mongoose( 6.7.2 )
  • DB ⇒ MongoDB

ちなみにサーバーサイドの言語選定理由は、ただ単に自分がやりやすいツールを使っているだけです。お好きなサーバーサイドの言語で実装していただいて全く問題ありませんし、DBに関してもRDB系のデータベースでもなんでもいいです。ブラウザで完結して楽だから今回MongoDBを選定しているだけです。mongooseはmongoDBとの接続を行ってくれるモジュールとなっております。

GhPythonからAPIを叩いてみる

とりあえずgrasshopperのGhPythonコンポーネントからAPIを叩いてみましょう。リクエスト先はJSONPlaceholderを使用します。こちらはAPIサーバーになっており、リクエストを送ると適当なダミーデータをJSONで返してくれます。今回はJSON PlaceholderのUsers(10ユーザーのダミーデータ)を取得してみます。

import urllib2
import json

def fetch_data():

  url = 'https://jsonplaceholder.typicode.com/users'

  response = json.load(urllib2.urlopen(url))
  for value in response:
    print value

fetch_data();

Pythonのモジュールurllib2を使用し、リクエストを送ってます。json形式でレスポンスが返ってくるので、パースしてから出力してます。上画像の様に、データが取得できているのがわかります。

このエンドポイントを、自分で作ったAPIのエンドポイントにして諸々の処理を施していく感じです。

API作成

上が作成したAPIとなります。詳しくは説明しませんが5つのエンドポイントを作成しています。ソースコード興味ある方はgithub参照願います。

  • (GET) : DBに保存されているすべてのattributeを取得 =>http://localhost:3000/api/panels/
  • (GET) : 指定したIdのattributeを取得 => http://localhost:3000/api/panels/[:id]
  • (POST) :attributeの登録 => http://localhost:3000/api/panels
  • (PUT) : 指定したIdのattributeを更新 => http://localhost:3000/api/panels/[:id]
  • (DELETE) : 指定したIdのattributeを削除 => http://localhost:3000/api/panels/[:id]

デプロイせずローカル環境のみで動作確認するので、それぞれのエンドポイントは上の様になってます。

MongoDBはPanelコレクションのみ、idとdeliveryのフィールドがあるだけです。RDBでいうと1テーブル(Panel tabel )の2カラム(id, delivery)です。

Grasshopperの実装:全体像

  • 上画像がGrasshopperのプログラムの全体像となっております。

Grasshopperの実装(モデリング / attributes作成)

それではGrasshopperの解説を行っていきます。

  • CreatePanelグループで三角形のパネルを4枚作成しています。この4枚パネルにattributesを与えていく予定です。(※プラグインLunch Box使用しています。インストールはこちらから)
  • PanelFrameのグループではフレームを作成しています。フレームは今回attributeを与えないので作成しなくてもOKです。
  • GenerateDateのグループで与えるパネルに与えるdelivery attributesを生成しています(2022-11-1~2022-11-4)
  • InitialAttributesのグループでパネルに与えるidとdeliveryのattributesを生成しています。idはわかりやすく(P-1, P-2, P-3, P-4)としています。

Grasshopperの実装( attributesを持たせたBake )

  • METHODのグループでmethodによってStreamFilterで処理を変えてます。POST / UPDATE / DELETEで場合分けをしている感じです。ちなみにPOSTは0, UPDATEは1, DELETEは2を返します。
  • Visible Attributesのグループでattribuesを可視化してます。現状パネルにP1~P4のIdが振られており、2022-11-1 ~ 2022-11-4までのdeliery attributesが割り当てられてます。
  • Bakeのグループで、 Elefrontを使用してattributesを持たせたBakeを行ってます。attributesはIdだけ付与しており、delivery attributesはMongoDBに保存してきます。(※Elefrontのインストールはこちらから)

Grasshopperの実装( POST methodを実行 )

  • POSTメソッドの処理をGhpythonコンポーネントに書いていきます。コードは以下になります。ElefrontのBakeボタンが押され、かつStreamFilterに接続しているValueListがPOSTの時に実行されるようにしています。以下のプログラムを実行します。
import urllib2
import json

def postPanel():

  url = 'http://localhost:3000/api/panels/'
  handler = urllib2.HTTPHandler()
  opener = urllib2.build_opener(handler)
  
  id = attributes[0]
  delivery = attributes[1]

  sendValues = {
      "_id":id,
      "delivery":delivery
  }
    
  sendValuesJson = json.dumps(sendValues).encode("utf8")
  
  request = urllib2.Request(url, sendValuesJson)
  request.add_header("Content-Type",'application/json')
  response = json.load(urllib2.urlopen(request))
  print response

if method == 0 and trigger == True:
    postPanel()
  • MongoDBを確認してみると。_id:P-1~P4までのデータに対して、delivery : 2022-11-1 ~2022-11-4のデータがしっかり保存されています。
  • Rhino側にも同時にBakeされますが、Rhino側はIdのみ付与してます。deliveryのattributesはAPIを叩いて取得すればいいので。
  • DBのすべてのデータを取得するコンポーネントも作っておきます。Bottonを押すと実行されるようにしています。
import urllib2
import json

def getAllPanels():

  url = 'http://localhost:3000/api/panels'

  response = json.load(urllib2.urlopen(url))
  for value in response:
    print value

if excute:
    getAllPanels();

Grasshopperの実装( PUT methodを実行 ①: データの取得 )

  • 次にUPDATE ( PUT )を実装していきます。まずは現状のジオメトリとattributesを取得してきます。
  • getCurrentDataのグループでは、Rhino上にBakeしたジオメトリを取得しています。先ほどElefrontでBakeした際panelというレイヤ名でBakeしましたので、ElefrontのReference by Layerでpanelレイヤ内のジオメトリを取得しています。その後、GetUserAttributesで付与したidのattributesを取得しています。
  • sortPanelsByIdのグループで、取得したジオメトリをId順に並べ替えています。
  • GhPythonコンポーネントで、Rhinoモデルから取得したIdを使用し、MongoDBに保存されている値を取得してきます。
import urllib2
import json

def getAllPanels():

  url = 'http://localhost:3000/api/panels/' + id

  response = json.load(urllib2.urlopen(url))
  print response
  
  _id = response['_id']
  delivery = response['delivery']
  
  return _id,delivery

if excute:
    attributes = getAllPanels();

Grasshopperの実装( PUT methodを実行 ②:データの更新 )

  • attributesUpdateのグループで、delivery attributesを更新します。上の例では、0番目~3番目まである4枚のパネルのうち、2番目のパネルの搬入日を 2022-11-3 ⇒ 2022-11-1に変更しています。(※item selectorコンポーネントはプラグインhumanを使用しています。インストールはこちらから)
  • METHODグループのStreamFilterに、先ほど取得してきたRhino上のジオメトリと、更新したattributesを接続し、ValueListをUPDATEに変更します。Rhino上の可視化したattributesでP-2が2022-11-1になっているのがわかりますが、まだBakeしていないのでUpdateはされていません。
  • GhPythonでMongoDB内のattributesを更新するリクエストを作成し実行してみます。POSTの時と同様で、Bakeボタンが押され、かつValueListの値がUPDATEの時に実行されるようになってます。
import urllib2
import json

def updatePanel():
  id = attributes[0]
  delivery = attributes[1]

  url = 'http://localhost:3000/api/panels/'+id
  handler = urllib2.HTTPHandler()
  opener = urllib2.build_opener(handler)
  
  sendValues = {
      "_id":id,
      "delivery":delivery
  }
    
  sendValuesJson = json.dumps(sendValues).encode("utf8")
  
  request = urllib2.Request(url, sendValuesJson)
  request.add_header("Content-Type",'application/json')
  request.get_method = lambda: 'PUT'
  response = json.load(urllib2.urlopen(request))
  print response

if method == 1 and trigger == True:
    updatePanel()
  • MongoDBが更新されているか確認します。問題なく更新されいます。↓
  • GhPythonで作成したGetAllでMongoDBに保存されている値を取得します。問題ないさそうです。Rhino上でも問題ないようです。(ジオメトリがダブるとか、idがずれてるとか起きていないですね。)

Grasshopperの実装( DELETE methodを実行 )

  • まずは、ジオメトリとattributesを最新の状態にします。Reference by LayerのUpdateを押して最新の状態にします。同時にGhPythonに実装したidごとのデータを取得するスクリプトも走りattributesが最新の状態になります。
  • DeleteGeometryのグループでいらなくなったジオメトリとattributesをリストから除外しています。上の例では0番目のパネルを削除しています。StreamFilterに0番目を削除したリストを接続し、ValueListをDELETEにすると、0番目のパネルが消えてるかと思います。まだBakeしていないの反映はしていません。
  • GhPythonコンポーネントにattributesをDELETEするリクエストを書いて実行してみます。
import urllib2
import json

def deletePanel():
    
  id = attributes[0]

  url = 'http://localhost:3000/api/panels/'+id

  handler = urllib2.HTTPHandler()
  opener = urllib2.build_opener(handler)
    
  request = urllib2.Request(url)
  request.add_header("Content-Type",'application/json')
  request.get_method = lambda: 'DELETE'
  response = json.load(urllib2.urlopen(request))
  print response
  

if method == 2 and trigger == True:
    deletePanel()
  • MongoDBも問題なく反映されておりP-0が削除されています。
  • GetAllとRhinoも確認。どちらも問題ないようです。

以上になります。かなりざっくりした説明になりましたが、ご興味ある方はGithubにGrasshopperファイルもpushしとくのでのぞいてみてください。元々は、GoogleDriveに格納済みのスプレッドシートをGrasshopper上で参照する必要があり、APIを叩いていたら今回の様なこともやってみようかなと思った次第です。(※スプシの方もそのうちブログ書くと思います。)やってることは、ただGhPythonコンポーネントからAPIを叩く、それだけです。node.jsでやってることも基本的なことばかりです。javasciptの基礎知識は必要ですが、適当にudemyでnode.jsの講座を購入してやれば、簡単に作れます。そのうち役に立ちそうだなと思い記事にしときました。是非ご参考ください。

【参考文献】

  • stackoverflow : https://stackoverflow.com/questions/6348499/making-a-post-call-instead-of-get-using-urllib2
  • mongoose docs : https://mongoosejs.com/docs/api.html

-GH python, Grasshopper
-, ,