DXFのXDATAを読む|APPID・1000番台グループコード・拡張データ

「DXFのXDATAを読む|APPID・1000番台グループコード・拡張データ」の内容を表す技術イラスト

DXFの図形には、座標、画層、色といった標準属性だけでなく、アプリケーションが独自に定義した情報を追加できます。その代表的な仕組みがXDATA(extended data、拡張データ)です。

たとえば、LINEやINSERTへ次の情報を関連付けられます。

  • 部品IDや工程番号
  • 外部データベースのキー
  • 検査結果や判定フラグ
  • 計算条件やバージョン
  • アプリケーション固有の座標や寸法

ただし、XDATAは自由な文字列欄ではありません。APPIDの登録、1000番台グループコードの型、タグの順序、リストの対応、容量制限を守る必要があります。

この記事では、DXFファイル上のXDATAを読み取るために必要な構造と、Pythonで安全に抽出・検証する方法を整理します。

目次

XDATAはエンティティに付加される拡張データ

AutodeskのDXF仕様では、XDATAはエンティティの通常データの後ろに記録されます。XDATAを表すグループコードは1000〜1071の範囲です。

通常のLINEは概念的に次のように記録されます。

0
LINE
5
2A
8
OUTLINE
10
0.0
20
0.0
30
0.0
11
100.0
21
0.0
31
0.0

ここへXDATAを追加すると、通常データの後ろにコード1001から始まるタグ列が続きます。

1001
EKP_DATA
1000
PART-001
1070
2
1040
14.5

この例では、EKP_DATAというアプリケーション用のデータとして、文字列、整数、実数を保存しています。

重要なのは、これらの値の意味をDXF仕様が決めているわけではないことです。PART-001が部品IDであり、2が改訂番号、14.5が質量であるという解釈は、データを書き込むアプリケーション側のスキーマで定義します。

コード1001とAPPIDの関係

コード1001は、XDATAの開始と登録アプリケーション名を表します。

1001
EKP_DATA

この名前は、DXFのTABLESセクションにあるAPPIDシンボルテーブルへ登録されていなければなりません。

APPIDレコードの概念例は次のとおりです。

0
TABLE
2
APPID

0
APPID
5
2B
100
AcDbSymbolTableRecord
100
AcDbRegAppTableRecord
2
EKP_DATA
70
0

0
ENDTAB

Autodeskの説明では、拡張データをエンティティへ付加する前にアプリケーション名を登録し、その名前がAPPIDテーブルに存在する必要があります。

アプリケーション名は最大31バイトです。単なる表示名ではなくデータの名前空間として機能するため、衝突しにくく、継続して使用できる名前を決めます。

1つのエンティティには複数のAPPIDに属するXDATAを付けられます。一方、同じAPPIDについては、1エンティティあたり1つのデータグループです。

1001
APP_A
1000
data for A

1001
APP_B
1070
10

コード1001が現れるたびに、新しいアプリケーションのXDATAが始まります。したがって、1001を通常の文字列データとして扱ってはいけません。

XDATAで使う主なグループコード

XDATAは値の型ごとにコードが決まっています。

コードデータ主な注意点
1000文字列最大255バイト
1001APPIDXDATAグループの開始、最大31バイト
1002制御文字列{または}でリストを表現
1003画層名XDATAに関連する画層名
1004バイナリ1チャンク最大127バイト
1005ハンドル図面内エンティティへの参照
10103次元点親エンティティの変換で値を変更しない
1011WCS位置親とともに移動・拡大縮小・回転・鏡像変換
1012WCS変位移動せず、拡大縮小・回転・鏡像変換
1013WCS方向回転・鏡像変換のみ
1040実数一般の浮動小数点値
1041距離親とともに拡大縮小
1042尺度親とともに拡大縮小
1070整数16ビット整数
1071long整数32ビット符号付き整数

同じグループコードは繰り返して使用できます。XDATAではタグの順序に意味があるため、コードをキーとする単純な辞書へ変換すると情報を失います。

# 不適切な例:同じコードの値が上書きされる
tags = [(1000, "A"), (1000, "B")]
data = {}

for code, value in tags:
    data[code] = value

assert data == {1000: "B"}

Rawデータは、[(code, value), ...]のような順序付きタグ列として保持します。

コード1002でリスト構造を作る

コード1002の{と}を使うと、XDATA内に入れ子のリストを表現できます。

1001
EKP_DATA
1002
{
1000
MATERIAL
1000
S45C
1002
{
1000
HARDNESS
1040
220.0
1002
}
1002
}

開始と終了の波括弧は対応していなければなりません。リストは入れ子にできますが、閉じ括弧が先に現れる、または最後まで閉じられないデータは不正です。

括弧は値を整理する構造を提供するだけで、MATERIALやHARDNESSの意味までは定義しません。読み書きする双方が、タグの並びとリスト構造に関する同じスキーマを共有する必要があります。

座標コードは変換時の動作が異なる

1010〜1013は、いずれも3成分の値を保存しますが、親エンティティを移動・回転・拡大縮小したときの扱いが異なります。

コード想定する意味親エンティティの変換
1010単純な点またはベクトル値を変更しない
1011WCS上の位置移動・拡大縮小・回転・鏡像に追従
1012WCS上の変位拡大縮小・回転・鏡像に追従
1013WCS上の方向回転・鏡像に追従

たとえば、図形上の検査位置を保存するなら1011が候補になります。図形の移動に追従させたくない解析条件なら1010が適する場合があります。

単に「座標だから1010」と決めず、その値が図形操作に対してどう振る舞うべきかを先に決めます。

コード1005で別エンティティを参照する

コード1005には、図面データベース内のエンティティハンドルを保存できます。

1001
EKP_DATA
1000
RELATED_ENTITY
1005
3F

Autodeskの仕様では、INSERTやXREFのバインドなどによって図面間でデータが移されるとき、XDATAの1005も対応するエンティティハンドルと同様に変換されます。

ただし、参照先が必ず存在するとは限りません。監査で参照先と一致しない1005が検出されるとエラーとされ、修復時に0へ設定される場合があります。

パーサーでは1005を文字列として保存するだけでなく、図面全体のハンドル索引へ照合し、未解決参照を診断情報として残します。

容量制限と文字列長を意識する

AutoCADでは、1つのDXFエンティティに付加できるXDATAの合計サイズは16KBに制限されます。また、コード1000の文字列は最大255バイト、1004のバイナリは1チャンク最大127バイトです。

そのため、次のような用途にはXDATAが適しません。

  • 大きなJSON文書の埋め込み
  • 画像や大量の計測波形
  • 長大な変更履歴
  • 多数の図形に共通する重複データ

文字数ではなくバイト数で制限される項目では、マルチバイト文字を含む場合に注意が必要です。実際の保存エンコーディングと対象DXFバージョンを含めて検証します。

データ量が増える場合は、DICTIONARYとXRECORD、または外部データベースを検討します。

XDATAとXRECORDを使い分ける

XDATAとXRECORDは、どちらも独自データを保存できますが、配置と管理方法が異なります。

項目XDATAXRECORD
関連付けエンティティへ直接付加DICTIONARYから参照
識別APPID辞書エントリ名
主なコード1000番台1〜369のタグ
構造順序付きタグと1002リスト順序付きの任意タグ列
容量AutoCADでは1エンティティ合計16KB大きな構造に向く
変換追従1011〜1013などに定義あり図形変換へ自動追従しない

少量の属性を特定エンティティへ密接に関連付け、図形変換への追従も利用したい場合はXDATAが候補です。

図面全体のデータ、階層的な情報、容量が増える情報を管理するなら、DICTIONARYとXRECORDの方が扱いやすくなります。

PythonでAPPID別にXDATAを分割する

順序付きタグ列から、コード1001を境界としてAPPID別のXDATAへ分割します。

from dataclasses import dataclass, field
from typing import Any


Tag = tuple[int, Any]


@dataclass
class XDataSection:
    appid: str
    tags: list[Tag] = field(default_factory=list)


def split_xdata(tags: list[Tag]) -> list[XDataSection]:
    sections: list[XDataSection] = []
    current: XDataSection | None = None

    for code, value in tags:
        if code == 1001:
            if current is not None:
                sections.append(current)

            current = XDataSection(appid=str(value))
            continue

        if 1000 <= code <= 1071 and current is not None:
            current.tags.append((code, value))

    if current is not None:
        sections.append(current)

    return sections


def validate_xdata(section: XDataSection) -> None:
    depth = 0

    for code, value in section.tags:
        if code != 1002:
            continue

        if value == "{":
            depth += 1
        elif value == "}":
            depth -= 1
        else:
            raise ValueError(
                f"invalid 1002 value: {value!r}"
            )

        if depth < 0:
            raise ValueError("unexpected closing brace")

    if depth != 0:
        raise ValueError("unclosed XDATA list")


tags = [
    (0, "LINE"),
    (5, "2A"),
    (1001, "EKP_DATA"),
    (1000, "PART-001"),
    (1002, "{"),
    (1070, 2),
    (1040, 14.5),
    (1002, "}"),
    (1001, "CHECK_DATA"),
    (1070, 1),
]

for section in split_xdata(tags):
    validate_xdata(section)
    print(section.appid, section.tags)

出力は次のようになります。

EKP_DATA [(1000, 'PART-001'), (1002, '{'), (1070, 2), (1040, 14.5), (1002, '}')]
CHECK_DATA [(1070, 1)]

この例はタグ列の分割と波括弧の検証に対象を絞っています。実務用パーサーでは、さらに次を検査します。

  • APPIDがAPPIDテーブルへ登録されているか
  • 1000番台以外の不正コードが混入していないか
  • 文字列とバイナリチャンクが上限を超えていないか
  • 1070と1071が許容範囲内か
  • 1005の参照先ハンドルが存在するか
  • 1010〜1013が3成分を持つか
  • 同じAPPIDのグループが同一エンティティ内で重複していないか

データスキーマを明示する

XDATAのタグだけを見ても、各値の業務上の意味は分かりません。長期利用するなら、少なくとも次を仕様として残します。

  • APPID名
  • スキーマ名とバージョン
  • タグの出現順序
  • 各タグの型、単位、必須・任意
  • 1002リストの階層
  • 1005参照の対象エンティティ
  • 不明なバージョンを読んだときの処理
  • 削除、複製、図面統合時の扱い

たとえば、先頭にスキーマ名とバージョンを置けば、将来の変更を判別できます。

1001
EKP_DATA
1000
part-inspection
1070
1
1000
PART-001
1070
0

この並びを次のように定義します。

順序コード意味
11000スキーマ名
21070スキーマバージョン
31000部品ID
41070判定コード

未知のバージョンを受け取った場合は、推測で読み替えず、Rawタグを保持したまま未対応として報告します。

実装で起こりやすい失敗

APPIDを登録せずにXDATAだけを書き込む。
コード1001の名前はAPPIDテーブルに存在する必要があります。読み取り時も両者を照合します。

コード1001を通常文字列として扱う。
1001は新しいアプリケーショングループの開始です。値として使う文字列には1000を使用します。

タグを辞書へ変換して順序を失う。
同じコードは繰り返されます。Raw層では順序付きタグ列を保持します。

1002の括弧を無視する。
入れ子構造が壊れます。開始と終了の対応、途中で深さが負にならないことを検査します。

1010〜1013を同じ座標として扱う。
親エンティティの変換に対する動作が異なります。用途に対応するコードを選びます。

大きなデータをXDATAへ詰め込む。
16KB制限と各タグの上限があります。大容量データはXRECORDや外部ストレージへ分離します。

未知のAPPIDデータを削除する。
他のCADや業務アプリケーションが使用している可能性があります。理解できないXDATAもRaw層で保持します。

まとめ

DXFのXDATAは、標準エンティティへアプリケーション固有データを付加する仕組みです。

基本構造は次のとおりです。

  • コード1001で登録済みAPPIDのデータグループを開始する
  • コード1000〜1071の専用タグで値を保存する
  • 同じコードの繰り返しとタグ順序を保持する
  • コード1002の波括弧で入れ子のリストを表現する
  • 1010〜1013は図形変換への追従方法を使い分ける
  • コード1005のハンドル参照を図面全体の索引へ照合する
  • 1エンティティ合計16KBなどの制限を守る

パーサーでは、DXF原文、APPID別のタグ列、検証済みの業務データを別の層として扱うと安全です。

XDATAを単なる追加文字列ではなく、型、順序、名前空間、変換規則を持つデータ構造として扱えば、CAD図形と部品情報、検査結果、外部システムIDを一貫して連携できます。

参考情報

参考になったらシェアしてください
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

機械設計・油圧・CAD・Python・AIなど、ものづくりに関わる技術を扱っています。工学知識を整理・構造化し、設計や自動化に再利用できる形へ変えていくことを目指しています。

目次