📔
NotionAPIを使ってGrasshopperからNotionにアクセスする
ついにNotion APIがPublic Betaに!
ずっとprivate betaだったNotionの公式APIが5/14ついにpublic betaになりました!
すでにいろんなチュートリアル記事が公開されてますが、今回は建築界隈向けにGrasshopperからNotionにアクセスするというニッチな記事を書いていきます。
今回作るもの
Rhinoにあるオブジェクトの情報をNotionのデータベースに書き込んでいくプログラムを作っていきます。
下記のようにオブジェクトのGUID、Layer、Typeがリストアップされる予定です。
環境
- Windows10
- Rhino7
- GhPython
- Notion API : 2021-05-13
Notion APIを導入
まずはNotion APIを使えるようにセットアップしていきます。
公式ドキュメントをもとに作っていきます。
ワークスペースを準備
Admin権限のあるワークスペースを準備してください。
不安な方はテスト用に無料プランで新しくワークスペースを作っておくといいと思います。
インテグレーションの作成
公式ドキュメントの「Create a new integration」に書かれているようにintegrationを作成します。
- your integrationsにアクセス
- 「+ New Integration」をクリック
- 「Name」と「Associated workspace」を入力→「Submit」
- 「Secrets -> Internal Integration Token」の「Show」を押してトークンを確認
※トークンはセキュアに管理してください。 - 「Integration type」は今回は「Internal Integration」のまま
データベースを作成
次にデータを書き込むテーブルを作っていきます。
テーブルを作成
- 適当なところに新規ページを作成
ページ名は適当に入力してください。 - 「Table」を選択してテーブルを作成
- カラムを設定
今回は下記のようにすべてテキストのカラムにしておきます。- GUID: Text
- Type: Text
- Layer: Text
APIからデータベースにアクセスできるようにする
データベースをIntegrationと共有して、クライアントから書き込む先のデータベースを指定するためにIDを取得します。
- 「Share」→「Add people...」→先ほど作成したIntegrationを選択→「Invite」
これでIntegrationが作成したデータベースにアクセスできるようになります。 - 「Copy Link」でURLをコピーしDatabase ID部分を取り出す
下記の部分がDatabbase IDになるので、どこかにコピーしておきます。https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=... |--------- Database ID --------|
Grasshopperでクライアントコードを作成
Rhinoファイルの準備
- 適当にオブジェクトを配置していきます
- 適当にレイヤー分けしておきます
Grasshopper
Grasshopperのコードは下記のとおりです。
- Geo: 「Set Multiple Geometries」でRhinoのオブジェクトを読込んでおきます。
- ID: GeoをGUIDに変換しています。
- Button: ボタンを押したときにリクエストを送るようにします。
- Python:
Input Access Type hint run Item bool geometries List ghdoc
Pythonコード
最終的なコードは最後にまとめて記載します。
- Rhino上のオブジェクトを扱えるようにする
import scriptcontext as sc import Rhino sc.doc = Rhino.RhinoDoc.ActiveDoc
- Rhinoのオブジェクト情報をリストに格納するそれぞれ下記のようなdictionaryがリストに格納されます。
# オブジェクト情報を整理 objects = [] for geo in geometries: attr = {} obj = sc.doc.Objects.Find(geo) attr["GUID"] = str(obj.Attributes.ObjectId) attr["Layer"] = sc.doc.Layers.FindIndex(obj.Attributes.LayerIndex).FullPath attr["Type"] = str(obj.GetType()) objects.append(attr)
[ {'GUID': 'de47bfba-5792-4571-8f1e-527e3d6eee90', 'Layer': 'geometry::floor', 'Type': 'Rhino.DocObjects.BrepObject'}, {'GUID': '883101d0-d384-4536-8b61-b253ee8b71f9', 'Layer': 'geometry::wall', 'Type': 'Rhino.DocObjects.BrepObject'} ]
- HTTPリクエストを送る関数を作成
IronPythonではurllib
などを使うことが多いのですが、今回なぜかurllib
だと403エラーでNotionAPIにアクセスできなかったのでこちらのリポジトリを参考に.NETの関数を使いました。
理由わかる方いたら教えてください。- ライブラリのインポート
from System.Net import WebRequest from System.Text import ASCIIEncoding from System.IO import StreamReader from System.Net import ServicePointManager from System.Net import SecurityProtocolType from System.Net import WebException import time, json
- HTTPリクエストの関数
少しだけ変更しています。def http(method, url, data_string=None, header={}, retry=3): ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 request = WebRequest.Create(url) request.UseDefaultCredentials = True request.Method = method request.ContentLength = 0 for k,v in header.items(): request.Headers.Add(k,v) if data_string: request.ContentType = "application/json" encoding = ASCIIEncoding() data = encoding.GetBytes(data_string) request.ContentLength = data.Length stream = request.GetRequestStream() stream.Write(data, 0, data.Length) stream.Close() try: response = request.GetResponse() print (response.StatusDescription) dataStream = response.GetResponseStream() reader = StreamReader(dataStream) responseFromServer = reader.ReadToEnd() reader.Close() dataStream.Close() response.Close() return responseFromServer except WebException as e : if e.Response.StatusDescription == "Not Found" and retry>0: time.sleep(10) return http(method, url, data_string, header, retry-1) print (e.Response.StatusDescription) dataStream = e.Response.GetResponseStream() reader = StreamReader(dataStream) responseFromServer = reader.ReadToEnd() print (responseFromServer) reader.Close() dataStream.Close()
- ライブラリのインポート
- URL、Token、Headersを設定
{{YOUR_TOKEN}}
の部分に先ほど取得したTokenを入力してください。# URL url = "https://api.notion.com/v1/pages" # Token token = "Bearer {{YOUR_TOKEN}}}" # headers headers = { 'Authorization': token, 'Notion-Version': "2021-05-13", }
- Bodyを設定してリクエストを送る
{{DATEBASE_ID}}
の部分に先ほどコピーしたDatabase IDを入力してください。
効率悪いですが今回はfor文でオブジェクト毎にリクエストを送っていきます。
Notionの各プロパティごとにいろいろ設定できるようになっているようで、細かく設定したい場合はこちらをご覧ください。for obj in objects: values = { "parent": {"database_id": "{{DATEBASE_ID}}"}, "properties": { "GUID": { "title": [ { "text": { "content": obj["GUID"] } } ] }, "Layer": { "rich_text": [ { "text": { "content": obj["Layer"] } } ] }, "Type": { "rich_text": [ { "text": { "content": obj["Type"] } } ] }, } } # bodyを文字列化 data = json.dumps(values) # POST http("POST",url,data,headers)
- 最終的なコード
# coding: -*- utf-8 -*- import scriptcontext as sc import Rhino sc.doc = Rhino.RhinoDoc.ActiveDoc # APIリクエスト用のimport from System.Net import WebRequest from System.Text import ASCIIEncoding from System.IO import StreamReader from System.Net import ServicePointManager from System.Net import SecurityProtocolType from System.Net import WebException import time, json # APIリクエストを送る関数 def http(method, url, data_string=None, header={}, retry=3): ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 request = WebRequest.Create(url) request.UseDefaultCredentials = True request.Method = method request.ContentLength = 0 for k,v in header.items(): request.Headers.Add(k,v) if data_string: request.ContentType = "application/json" encoding = ASCIIEncoding() data = encoding.GetBytes(data_string) request.ContentLength = data.Length stream = request.GetRequestStream() stream.Write(data, 0, data.Length) stream.Close() try: response = request.GetResponse() print (response.StatusDescription) dataStream = response.GetResponseStream() reader = StreamReader(dataStream) responseFromServer = reader.ReadToEnd() reader.Close() dataStream.Close() response.Close() return responseFromServer except WebException as e : if e.Response.StatusDescription == "Not Found" and retry>0: time.sleep(10) return http(method, url, data_string, header, retry-1) print (e.Response.StatusDescription) dataStream = e.Response.GetResponseStream() reader = StreamReader(dataStream) responseFromServer = reader.ReadToEnd() print (responseFromServer) reader.Close() dataStream.Close() if __name__ == "__main__": # オブジェクト情報を整理 objects = [] for geo in geometries: attr = {} obj = sc.doc.Objects.Find(geo) attr["GUID"] = str(obj.Attributes.ObjectId) attr["Layer"] = sc.doc.Layers.FindIndex(obj.Attributes.LayerIndex).FullPath attr["Type"] = str(obj.GetType()) objects.append(attr) # HTTPリクエストを作成 # URL url = "https://api.notion.com/v1/pages" # Token token = "Bearer {{YOUR_TOKEN}}" # headers headers = { 'Authorization': token, 'Notion-Version': "2021-05-13", } # オブジェクトごとにbodyを作ってPOST if run: for obj in objects: values = { "parent": {"database_id": "{{DATEBASE_ID}}"}, "properties": { "GUID": { "title": [ { "text": { "content": obj["GUID"] } } ] }, "Layer": { "rich_text": [ { "text": { "content": obj["Layer"] } } ] }, "Type": { "rich_text": [ { "text": { "content": obj["Type"] } } ] }, } } # bodyを文字列化 data = json.dumps(values) # POST http("POST",url,data,headers)
リクエストを送ってみる
コードができたのでButtonを押してリクエストを送ってみます。
リクエストの関数の中にsleep
が入っているので少し時間かかると思います。
Notionを見てみると無事データベースにアイテムが追加されているのがわかると思います。
夢が広がりますね!
今回サクッとやるために簡単なデータしか送ってませんが、いろんなことに応用できそうです。
また思いついたら記事化していこうと思います。
- NotionのデータベースもとにGHのコードを動かす
- シミュレーションや最適化の結果をNotionでレポート化する
- BIMデータを集計する
Discussion