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バイト |
| 1001 | APPID | XDATAグループの開始、最大31バイト |
| 1002 | 制御文字列 | {または}でリストを表現 |
| 1003 | 画層名 | XDATAに関連する画層名 |
| 1004 | バイナリ | 1チャンク最大127バイト |
| 1005 | ハンドル | 図面内エンティティへの参照 |
| 1010 | 3次元点 | 親エンティティの変換で値を変更しない |
| 1011 | WCS位置 | 親とともに移動・拡大縮小・回転・鏡像変換 |
| 1012 | WCS変位 | 移動せず、拡大縮小・回転・鏡像変換 |
| 1013 | WCS方向 | 回転・鏡像変換のみ |
| 1040 | 実数 | 一般の浮動小数点値 |
| 1041 | 距離 | 親とともに拡大縮小 |
| 1042 | 尺度 | 親とともに拡大縮小 |
| 1070 | 整数 | 16ビット整数 |
| 1071 | long整数 | 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 | 単純な点またはベクトル | 値を変更しない |
| 1011 | WCS上の位置 | 移動・拡大縮小・回転・鏡像に追従 |
| 1012 | WCS上の変位 | 拡大縮小・回転・鏡像に追従 |
| 1013 | WCS上の方向 | 回転・鏡像に追従 |
たとえば、図形上の検査位置を保存するなら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は、どちらも独自データを保存できますが、配置と管理方法が異なります。
| 項目 | XDATA | XRECORD |
|---|---|---|
| 関連付け | エンティティへ直接付加 | 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
この並びを次のように定義します。
| 順序 | コード | 意味 |
|---|---|---|
| 1 | 1000 | スキーマ名 |
| 2 | 1070 | スキーマバージョン |
| 3 | 1000 | 部品ID |
| 4 | 1070 | 判定コード |
未知のバージョンを受け取った場合は、推測で読み替えず、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を一貫して連携できます。

