IU Tips

Archicad JSON Interfaceを使ってみよう

2025.08.25

こんにちは。
IU BIM STUDIOの原田です。
今回はArchiadのデータをJSONでやり取りするArchicad JSON Interfaceを使ってみたいとおもいます。

Archiad JSON Interfaceとは

Archicad JSON InterfaceはArchicadの情報を別のプログラムで使う際のルールを決めたものです。

Archicadでは使用しているPCとネットワークを通して情報をやりとりすることができます。この時の情報の送受信にはwebAPIを使用しますが、この時のルールを決めたものがArchicad JSON Interfaceです。前回紹介したArchicad Python APIはこのArchicad JSON InterfaceをPython用に使いやすくしたものです。
なので、Archiad JSON Interfaceを直接使ってArchicadとやり取りすることもできます。

公式ドキュメントはこちらです。

Archaid JSON Interfaceの使い方

それでは実際に使ってみましょう。
今回は前回と同じものをJSON Interfaceを使って書いてみます。

PythonでWeb APIを扱う方法

今回はwebAPIを扱うのにPythonのrequestsライブラリを使用します。
外部ライブラリになるので、ライブラリのインストールが必要です。

pip install requests

Grasshopperで外部ライブラリを使うにはRhino8でPython3コンポーネントを使う必要があります。Grasshopper上で外部ライブラリが動作しない場合は環境による要因が考えられますので、Rhinoのフォーラムなどで検索して調べてもらえればと思います。


ではまず、RequestsでArchicadに情報を要求します。

import requests

url = "http://127.0.0.1:19723"

params = {
    "command": "API.GetNavigatorItemTree",
    "parameters": {
        "navigatorTreeId": {
            "type": "ProjectMap"
        }
    }
}

response = requests.get(url,json=params).json()
root_item = response ["result"]["navigatorTree"]["rootItem"]

urlは上記のURLになります。
ちなみに127.0.0.1は自分のPCを指すIPアドレスだそうです。


commandに操作したい処理を入力し、parameterに送信する情報を入力します。
そして、レスポンスから欲しい情報の部分を抜き出し、使用します。
これが基本的な使い方です。

現在、プロジェクトのツリーの最初の要素が取得できました。
この中から階の要素を取り出していきます。
ナビゲータツリーの情報はナビゲーターアイテムの配下に子要素が複数個あるような木構造になっているため、再帰関数を作成して階の要素だけを取りだします。

def get_story_items(nav_item, stories):
    if nav_item["type"] == "StoryItem":
        stories.append(nav_item)

    if "children" in nav_item.keys():
        for child in nav_item["children"]:
            get_story_items(child["navigatorItem"], stories)

stories = []
get_story_items(root_item,stories)

story_ids = [{"navigatorItemId":i["navigatorItemId"]} for i in stories]

params = {
    "command": "API.GetStoryNavigatorItems",
    "parameters": {
         "navigatorItemIds": story_ids
    }
}

response = requests.get(url,json=params).json()
story_infos = response["result"]["navigatorItems"]

これで階の情報だけが取り出せました。
後は、前回同様必要な階情報を取り出し整理していきます。

story_infos.sort(key=lambda x:x["storyNavigatorItem"]["floorLevel"])

names = []
prefixes = []
levels = []

for story_info in story_infos:
    names.append(story_info["storyNavigatorItem"]["name"])
    prefixes.append(story_info["storyNavigatorItem"]["prefix"][:-1])
    levels.append(story_info["storyNavigatorItem"]["floorLevel"])

heights = []

for i in range(1,len(levels)):
    height = levels[i] - levels[i-1]
    heights.append(height)

heights.append(0)

これで階情報が取り出せました。
出力データは前回と同じです。

Archicad JSON Interfaceの応用

最初に説明したとおり、Archicadとプログラムの通信はweb APIを使用しています。
そのため、JSON Interfaceを使えばArchicadとやり取りできるのはPythonに限りません。

同じような方法を使ってGrasshopperのコンポーネントを作成することができます。
C#にあまり慣れていないので書き方はもっと良い方法があるかもしれませんが、下記のような感じでコンポーネントを作成できます。


外部ライブラリとして、Newtonsoft JSONを使用しています。

Grasshopperコンポーネントの作り方については詳しく説明してある記事がありますので、webで検索してみてください。

using Grasshopper.Kernel;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace FloorInfo
{
    public class FloorInfoComponent : GH_Component
    {
        string url = "http://127.0.0.1:19723";
        HttpClient httpClient = new HttpClient();

        public FloorInfoComponent()
          : base("FloorInfoComponent", "Nickname",
            "Description",
            "MyComponent", "Archicad")
        {   
            httpClient.Timeout = TimeSpan.FromMilliseconds(3000);
        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            JObject response;
            try
            {
                Dictionary<string, object> json = new Dictionary<string, object>
                {
                    {"command","API.GetNavigatorItemTree" },
                        {
                            "parameters", new Dictionary<string, object>
                            {
                                {"navigatorTreeId",  new Dictionary<string, object>
                                    {
                                        { "type", "ProjectMap"}
                                    }
                                }
                            }
                        }
                    };

                return Task.Run(() => GetResposne(json)).Result;
                // 中略
            }
            catch
            {
                throw;
            }
       // 中略
        }

        public async Task<JObject> GetResposne(Dictionary<string, object> json)
        {
            string content = JsonConvert.SerializeObject(json, Newtonsoft.Json.Formatting.None);

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
            request.Content = new StringContent(content, Encoding.UTF8, "application/json");
            HttpResponseMessage response = await httpClient.SendAsync(request);

            string responseString = await response.Content.ReadAsStringAsync();
            return JObject.Parse(responseString);
        }
}

このようにArchicad JSON Interfaceを使ってArchicadの情報を取得できるGrasshopperのコンポーネントを作ることができます。

この例では一から作成しましたが、このArchicad JSON Interfaceを使用してArchicadを拡張しているGrasshopperコンポーネントがあります。それがtapir-archicad-automationになります。

https://github.com/ENZYME-APD/tapir-archicad-automation


tapir-archicad-automationはArchicadアドオンとGrasshopperのプラグインがセットになっています。Archicadアドオンを介して情報をやりとりするため、通常のPython APIではできない操作もできるようになっています。
そのアドオンを介して情報をやりとりするGrasshopperプラグインもあります。
現在まだ開発中なのか配布版のプラグインはありませんが、自分でコンパイルできる人は使用可能です。


使い方は、まずリポジトリをクローンします。

git clone https://github.com/ENZYME-APD/tapir-archicad-automation.git

そして、grasshopper-pluginフォルダの中のTapirGrasshopperPlugin.slnを開きます。それをそのままビルドして、TapirGrasshopperPlugin.ghaファイルをGrasshopperのComponents folderに入れれば使用できます。

使用にはArchicadのアドオンをダウンロードして、Archicadのアドオンフォルダに入れておく必要があります。

まだ開発中のためかアイコンが無いものなどがありますが、試したところ大半は使用可能かと思いますが、正式にリリースされていないようなので、変更等の可能性も含めて使用してください。

これを使えばgrasshopperでできることも増えますね。
Grasshopperプラグインを使わずに、tapirのArchicadアドオンをPythonスクリプトで使用することも可能です。

Pythonを使用したサンプルは下記に記載してあります。

https://github.com/ENZYME-APD/tapir-archicad-automation/tree/main/archicad-addon/Examples

まとめ

今回はArchicad JSON Interfaceを使って、いろいろな方法でArchicadと連携する方法を説明しました。Archicad Python APIはまだできない操作も多く、欲しい情報が取れない場合もあります。tapirなどのアドオンを活用して業務効率化をしていきましょう!

最後までお読みいただき、ありがとうございました。
以下にコード全文を載せておきます。

Python

import requests

def get_story_items(nav_item, stories):
    if nav_item["type"] == "StoryItem":
        stories.append(nav_item)
    
    if "children" in nav_item.keys():
        for child in nav_item["children"]:
            get_story_items(child["navigatorItem"], stories)

    
url = "http://127.0.0.1:19723"

params = {
    "command": "API.GetNavigatorItemTree",
    "parameters": {
        "navigatorTreeId": {
            "type": "ProjectMap"
        }
    }
}
response = requests.get(url,json=params).json()
root_item = response["result"]["navigatorTree"]["rootItem"]

stories = []
get_story_items(root_item,stories)

story_ids = [{"navigatorItemId":i["navigatorItemId"]} for i in stories]

params = {
    "command": "API.GetStoryNavigatorItems",
    "parameters": {
        "navigatorItemIds": story_ids
    }
}

response = requests.get(url,json=params).json()
story_infos = response["result"]["navigatorItems"]

story_infos.sort(key=lambda x:x["storyNavigatorItem"]["floorLevel"])

names = []
prefixes = []
levels = []
for story_info in story_infos:
    names.append(story_info["storyNavigatorItem"]["name"])
    prefixes.append(story_info["storyNavigatorItem"]["prefix"][:-1])
    levels.append(story_info["storyNavigatorItem"]["floorLevel"])

heights = []
for i in range(1,len(levels)):
    height = levels[i] - levels[i-1]
    heights.append(height)

heights.append(0)

Grasshopper Component(C#)

using Grasshopper.Kernel;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace FloorInfo
{
    public class FloorInfoComponent : GH_Component
    {
        class StoryInfo
        {
            public List<string> floorNames = new();
            public List<string> prefixes = new();
            public List<double> floorLevels = new();
            public List<double> floorHeights = new();
        }

        string url = "http://127.0.0.1:19723";
        HttpClient httpClient = new HttpClient();

        /// <summary>
        /// Each implementation of GH_Component must provide a public 
        /// constructor without any arguments.
        /// Category represents the Tab in which the component will appear, 
        /// Subcategory the panel. If you use non-existing tab or panel names, 
        /// new tabs/panels will automatically be created.
        /// </summary>
        public FloorInfoComponent()
          : base("FloorInfoComponent", "Nickname",
            "Description",
            "MyComponent", "Archicad")
        {   
            httpClient.Timeout = TimeSpan.FromMilliseconds(3000);

        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            pManager.AddTextParameter("Prefixes", "P", "prefixes of floor", GH_ParamAccess.list);
            pManager.AddTextParameter("Names", "N", "name of floor", GH_ParamAccess.list);
            pManager.AddNumberParameter("Heights", "H", "height of floor", GH_ParamAccess.list);
            pManager.AddNumberParameter("Elevations", "E", "elevation of floor", GH_ParamAccess.list);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object can be used to retrieve data from input parameters and 
        /// to store data in output parameters.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            JObject response;
            try
            {
                response = GetNavItems();

                JToken rootItem = response["result"]["navigatorTree"]["rootItem"];
                List<Dictionary<string, object>> guids = new();
                GetStoryItemIds(rootItem, guids);

                response = GetStoryInfo(guids);
            }
            catch
            {
                throw;
            }

            StoryInfo storyInfo = ExtractStoryInfo(response);

            DA.SetDataList("Names", storyInfo.floorNames);
            DA.SetDataList("Heights", storyInfo.floorHeights);
            DA.SetDataList("Elevations", storyInfo.floorLevels);
            DA.SetDataList("Prefixes", storyInfo.prefixes);
        }
        public async Task<JObject> GetResposne(Dictionary<string, object> json)
        {
            string content = JsonConvert.SerializeObject(json, Newtonsoft.Json.Formatting.None);

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
            request.Content = new StringContent(content, Encoding.UTF8, "application/json");
            HttpResponseMessage response = await httpClient.SendAsync(request);

            string responseString = await response.Content.ReadAsStringAsync();
            return JObject.Parse(responseString);
        }

        private void GetStoryItemIds(JToken rootItem, List<Dictionary<string, object>> guids)
        {

            foreach (JObject navItem in (JArray)rootItem["children"])
            {

                if (navItem.Type == JTokenType.Object)
                {
                    string type = (string)navItem["navigatorItem"]["type"];
                    if (type == "StoryItem")
                    {
                        guids.Add(new Dictionary<string, object>
                        {
                            {"navigatorItemId", new Dictionary<string, string>
                                {
                                    { "guid", (string)navItem["navigatorItem"]["navigatorItemId"]["guid"] }
                                }
                            }
                        });
                    }
                }
                JArray children = (JArray)navItem["navigatorItem"]["children"];
                if (children == null)
                {
                    continue;
                }

                if (children.Count > 0)
                {
                    GetStoryItemIds(navItem["navigatorItem"], guids);
                }
            }
        }

        private JObject GetStoryInfo(List<Dictionary<string, object>> storyIds)
        {
            Dictionary<string, object> json = new Dictionary<string, object>
            {
                {"command","API.GetStoryNavigatorItems" },
                {
                    "parameters", new Dictionary<string,object>
                    {
                        {"navigatorItemIds",  storyIds}
                    }
                }
            };

            return Task.Run(() => GetResposne(json)).Result;
        }


        private StoryInfo ExtractStoryInfo(JObject response)
        {
            StoryInfo storyInfo = new StoryInfo();
            JArray stories = (JArray)response["result"]["navigatorItems"];


            foreach (JObject story in stories)
            {
                storyInfo.floorNames.Add((string)story["storyNavigatorItem"]["name"]);
                storyInfo.floorLevels.Add((double)story["storyNavigatorItem"]["floorLevel"]);
                storyInfo.prefixes.Add(((string)story["storyNavigatorItem"]["prefix"]).Replace(".", ""));
            }

            for (int i = 1; i < storyInfo.floorLevels.Count; i++)
            {
                double height = storyInfo.floorLevels[i - 1] - storyInfo.floorLevels[i];
                storyInfo.floorHeights.Add(height);
            }
            return storyInfo;
        }
    private JObject GetNavItems()
        {
            Dictionary<string, object> json = new Dictionary<string, object>
            {
            {"command","API.GetNavigatorItemTree" },
                {
                    "parameters", new Dictionary<string, object>
                    {
                        {"navigatorTreeId",  new Dictionary<string, object>
                            {
                                { "type", "ProjectMap"}
                            }
                        }
                    }
                }
            };

            return Task.Run(() => GetResposne(json)).Result;
        }
        /// <summary>
        /// Provides an Icon for every component that will be visible in the User Interface.
        /// Icons need to be 24x24 pixels.
        /// You can add image files to your project resources and access them like this:
        /// return Resources.IconForThisComponent;
        /// </summary>
        protected override System.Drawing.Bitmap Icon => null;

        /// <summary>
        /// Each component must have a unique Guid to identify it. 
        /// It is vital this Guid doesn't change otherwise old ghx files 
        /// that use the old ID will partially fail during loading.
        /// </summary>
        public override Guid ComponentGuid => new Guid("375fdce3-a1b0-484f-b0ab-ae10220c9243");
    
    }
}