IU Tips

Pythonでプロパティを一括入力する【Archicad】【Python】

2024.03.18

こんにちは。
IU BIM STUDIO原田です。

今回はPythonを使ってゾーンのプロパティを一括で入力する方法について説明したいと思います。
今回のモデルはArchicadのサンプルモデルを少し変えたものを使用します。

モデルはこのようになっています。

プロパティを作成する

まずArchicadで設定したいプロパティを作成します。今回はゾーンの面積を集計していきたいため、面積に関する情報を追加していきます。例として、専有部分/共用部分、延床面積対象かどうか、容積計算時の取り扱い情報のプロパティを作成します。

未定義時の取り扱いなどは運用方法によって方法はさまざまだと思いますので運用方法に応じて微調整する必要はあるかと思います。
詳細は画像の通りです。

Excelにテンプレートを作成する

次に、Excelに一括入力するためのテンプレートを作成します。

ゾーンカテゴリごとに面積計算上の情報を入力していきます。
Archicad Python APIではゾーンカテゴリ名は取得できないようなのでゾーンカテゴリコードが必要になります。

バルコニーなど一概に延床面積対象外とならない部分があるのがややこしいところですが、今回は延床面積対象外として扱います。

また、ゾーンカテゴリコードはArchicad上では重複可能ですが、プログラムで処理する以上重複しているとうまく処理できません。重複しないように気を付けてください。

今回の例ではプロパティグループ名とプロパティ名をアンダースコアで連結しています。このあたりの運用方法もいろいろあります。実際は汎用化と使いやすさのバランスを考え試行錯誤していくことになるだろうと思います。最初から正解はわからないものです。

コードを書く

それではコードを書いていきます。全体は最後に載せますので抜粋しながら書いていきます。
まず、Excelファイルを読み込み、情報を入力したワークシートを取得します。

import openpyxl
    
# Excelファイルを読み込み
wb = openpyxl.load_workbook("面積設定シート.xlsx")
ws = wb["設定"]

openpyxlはExcelファイルを処理するのに便利なライブラリです。

次にExcelのデータを処理して、プロパティを入力する準備をしていきます。
今回は長くなるため関数を作っていきます。

まず、Excelから処理するプロパティの列名を取得する関数を作ります

def get_column_names(ws) -> list[str]:
    """
    Excelの見出し行からプロパティ処理に関するもの(3列目以降)を取得
    """
    column_names = []
    start_column = 3

    for i in range(start_column, ws.max_column + 1):
        cell_value = ws.cell(1, i).value
        column_names.append(cell_value)

    return column_names

次に列名を分割して、プロパティのIDを取得する関数を作ります。
ユーザー定義プロパティを取得するためにはGetUserDefinedPropertyId関数を使います。

def get_property_ids(column_names: list[str]) -> list[act.PropertyId]:
    """
    プロパティグループとプロパティ名からプロパティIDのリストを返す
    """
    property_names = []
    for column_name in column_names:
        property_names.append(column_name.split("_"))

    property_ids = [acu.GetUserDefinedPropertyId(i[0], i[1]) for i in property_names]
    return property_ids

それらから列名をキーにプロパティIDを得るための辞書を作成できるようにします。
これは後でExcelのデータを取得する際に使います。

def create_property_id_dict(
    column_names: list[str], property_ids: list[act.PropertyId]
) -> dict:
    """
    Excelの列名からプロパティIDを取得する辞書を作成
    """
    property_id_dict = {i: str(j.guid) for i, j in zip(column_names, property_ids)}

    return property_id_dict

そこからExcelのデータを格納する辞書を作成します。
この辞書を使ってゾーンカテゴリに対応したプロパティ値を一括で入力します。

def create_property_value_dict(ws, property_id_dict: dict) -> dict:
    """
    ゾーンカテゴリとプロパティに対してExcelで設定した値を反映する辞書を作成する
    辞書はproperty_values_dict[ゾーンカテゴリ][プロパティID]の形式とする
    """

    property_values_dict = {}

    for i in range(2, ws.max_row + 1):
        zone_category_code = ws.cell(i, 2).value

        for j in range(3, ws.max_column + 1):
            column_name = ws.cell(1, j).value
            cell_value = ws.cell(i, j).value

            if zone_category_code not in property_values_dict.keys():
                property_values_dict[zone_category_code] = {}

            property_values_dict[zone_category_code][
                property_id_dict[column_name]
            ] = cell_value

    return property_values_dict

関数の準備ができたらmain関数でこれらを実行します。正確なコードは全体を確認してください。

column_names = get_column_names(ws)
property_ids = get_property_ids(column_names)
property_id_dict = create_property_id_dict(column_names, property_ids)

property_values_dict = create_property_value_dict(ws, property_id_dict)

これで、Excelデータ側の準備は完了です。

次にゾーン側の準備をします。
各ゾーンに対してゾーンカテゴリに対応したプロパティを入力したいので、ゾーンのElementIdとゾーンカテゴリコードをセットにした配列を作っておきます。

これも長くなるので関数化しておきます。

def get_zones_and_categories() -> list[act.ElementId, str]:
    """
    ゾーンとゾーンカテゴリコードをセットにしたリストを返す
    [[zone_id, zone_category],[zone_id, zone_category]...]
    """

    zones = acc.GetElementsByType("Zone")

    property_id = acu.GetBuiltInPropertyId("Zone_ZoneCategoryCode")
    values = acc.GetPropertyValuesOfElements(zones, [property_id])
    zone_category_codes = [i.propertyValues[0].propertyValue.value for i in values]

    zone_info = [[i.elementId, j] for i, j in zip(zones, zone_category_codes)]
    return zone_info

これもmain関数で実行しておきます。

zone_info = get_zones_and_categories()

材料がそろったので、プロパティ値を作成していきます。

import itertools

# プロパティ値を作成
new_property_values = []

for zone, property_id in itertools.product(zone_info, property_ids):
    zone_id = zone[0]
    zone_category_code = zone[1]

    property_value = create_property_value(
        zone_id, zone_category_code, property_id, property_values_dict
    )
    new_property_values.append(property_value)

itertoolsは組み合わせ、順列などの繰り返し作業に便利な機能が使えるライブラリです。今回はゾーンとプロパティの全組み合わせの処理をしたいのでitertools.productを使います。

各組合せに対して関数create_property_valueを実行し、結果をリストに入れていきます。 create_property_valueの中身は次のようになっています。

def create_property_value(
    zone_id: act.ElementId,
    zone_category_code: str,
    property_id: act.PropertyId,
    property_value_dict:dict,
) -> act.ElementPropertyValue:
    """
    Excelの設定から、ゾーンカテゴリ、プロパティで検索したものをゾーンのプロパティ値として返す
    """

    value = property_value_dict[zone_category_code][str(property_id.guid)]
    enum_value = act.NormalSingleEnumPropertyValue(act.DisplayValueEnumId(value))
    property_value = act.ElementPropertyValue(zone_id, property_id, enum_value)

    return property_value

property_value_dictからゾーンカテゴリ、プロパティIDで入力したい値を取得し、今回はプロパティのデータ型がすべてオプションセットなのでNormalSingleEnumPropertyValueにして、リストに入れていきます。

最後に作成したプロパティ値をセットして終了です。

# プロパティを変更
acc.SetPropertyValuesOfElements(new_property_values)

実行しゾーンを確認

一覧表を作成し、必要な情報を見れるようにしておきましょう。
スクリプトを実行すると画像のようになります

実行前

実行後

プロパティを一括で入力できました。

今回のまとめ

今回はプロパティをゾーンカテゴリーに応じて一括で入力する方法を紹介しました。入力する内容が決まっているものはPythonで一括入力することができます。

BIMでの作業は建物の膨大な量の情報を扱うため、人力で入力しようとすると設定し忘れやミスが多発します。その膨大な情報を最小の手間で入力し、最大の効果を得ることが業務効率化につながります。ただ、それをどうやってやればいいのかが分かりにくいし、ケースバイケースであることがBIM運用の難しさだと思います。

次回は少し趣向を変えて、この情報を活用する方法について考えたいと思います。

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

※2024/4/2 エレベーターの昇降路が延床面積対象外になっていたので修正しました。
正しくは延床面積対象で、容積対象外でした。

from archicad import ACConnection
import itertools
import openpyxl

conn = ACConnection.connect()
assert conn

acc = conn.commands
act = conn.types
acu = conn.utilities


def get_column_names(ws) -> list[str]:
    """
    Excelの見出し行からプロパティ処理に関するもの(3列目以降)を取得
    """
    column_names = []
    start_column = 3

    for i in range(start_column, ws.max_column + 1):
        cell_value = ws.cell(1, i).value
        column_names.append(cell_value)

    return column_names


def get_property_ids(column_names: list[str]) -> list[act.PropertyId]:
    """
    プロパティグループとプロパティ名からプロパティIDのリストを返す
    """
    property_names = []
    for column_name in column_names:
        property_names.append(column_name.split("_"))

    property_ids = [acu.GetUserDefinedPropertyId(i[0], i[1]) for i in property_names]
    return property_ids


def create_property_id_dict(
    column_names: list[str], property_ids: list[act.PropertyId]
) -> dict:
    """
    Excelの列名からプロパティIDを取得する辞書を作成
    """
    property_id_dict = {i: str(j.guid) for i, j in zip(column_names, property_ids)}

    return property_id_dict


def create_property_value_dict(ws, property_id_dict: dict) -> dict:
    """
    ゾーンカテゴリとプロパティに対してExcelで設定した値を反映する辞書を作成する
    辞書はproperty_values_dict[ゾーンカテゴリ][プロパティID]の形式とする
    """
    property_values_dict = {}

    for i in range(2, ws.max_row + 1):
        zone_category_code = ws.cell(i, 2).value

        for j in range(3, ws.max_column + 1):
            column_name = ws.cell(1, j).value
            cell_value = ws.cell(i, j).value

            if zone_category_code not in property_values_dict.keys():
                property_values_dict[zone_category_code] = {}

            property_values_dict[zone_category_code][
                property_id_dict[column_name]
            ] = cell_value

    return property_values_dict


def get_zones_and_categories() -> list[act.ElementId, str]:
    """
    ゾーンとゾーンカテゴリコードをセットにしたリストを返す
    [[zone_id, zone_category],[zone_id, zone_category]...]
    """

    zones = acc.GetElementsByType("Zone")

    property_id = acu.GetBuiltInPropertyId("Zone_ZoneCategoryCode")
    values = acc.GetPropertyValuesOfElements(zones, [property_id])
    zone_category_codes = [i.propertyValues[0].propertyValue.value for i in values]

    zone_info = [[i.elementId, j] for i, j in zip(zones, zone_category_codes)]
    return zone_info


def create_property_value(
    zone_id: act.ElementId,
    zone_category_code: str,
    property_id: act.PropertyId,
    property_value_dict: dict,
) -> act.ElementPropertyValue:
    """
    Excelの設定から、ゾーンカテゴリ、プロパティで検索したものをゾーンのプロパティ値として返す
    """

    value = property_value_dict[zone_category_code][str(property_id.guid)]
    enum_value = act.NormalSingleEnumPropertyValue(act.DisplayValueEnumId(value))
    property_value = act.ElementPropertyValue(zone_id, property_id, enum_value)

    return property_value


def main():
    # Excelファイルを読み込み
    wb = openpyxl.load_workbook("面積設定シート.xlsx")
    ws = wb["設定"]

    # Excelのデータから辞書を作成
    column_names = get_column_names(ws)
    property_ids = get_property_ids(column_names)
    property_id_dict = create_property_id_dict(column_names, property_ids)

    property_values_dict = create_property_value_dict(ws, property_id_dict)

    # ゾーンとゾーンカテゴリを取得
    zone_info = get_zones_and_categories()

    # プロパティ値を作成
    new_property_values = []

    for zone, property_id in itertools.product(zone_info, property_ids):
        zone_id = zone[0]
        zone_category_code = zone[1]

        property_value = create_property_value(
            zone_id, zone_category_code, property_id, property_values_dict
        )
        new_property_values.append(property_value)

    # プロパティを変更
    acc.SetPropertyValuesOfElements(new_property_values)


if __name__ == "__main__":
    main()