DXFの図形を解析するとき、LINEやCIRCLEだけを読めても、図面を正しく復元できるとは限りません。
エンティティが参照するレイヤー、線種、文字スタイル、寸法スタイル、ブロック名などは、主にTABLESセクションで定義されます。
たとえばLINEに、
8
OUTLINE
6
DASHED
と記録されていても、この部分だけでOUTLINEレイヤーの色やDASHED線種のパターンまでは分かりません。参照先となるLAYERテーブルやLTYPEテーブルを解決する必要があります。
この記事では、DXFのTABLESセクションを、
TABLEとENDTABの境界- コード2・5・70・100・330
- シンボルテーブルとテーブルレコードの違い
- コード70を実件数として扱えない理由
- Pythonでの安全な抽出方法
に分けて解説します。
TABLESセクションの役割
TABLESセクションには、図面内で名前を付けて共有される設定が保存されます。
AutodeskのDXF Referenceが示す代表的なシンボルテーブルは次のとおりです。images.autodesk.com
| テーブル | 主な役割 |
|---|---|
APPID | XDATAで使う登録アプリケーション名 |
BLOCK_RECORD | ブロック定義を管理するレコード |
DIMSTYLE | 寸法スタイル |
LAYER | レイヤー |
LTYPE | 線種 |
STYLE | 文字スタイル |
UCS | ユーザー座標系 |
VIEW | 名前付きビュー |
VPORT | ビューポート設定 |
これらは画面へ直接描画される図形ではありません。複数のエンティティから名前やハンドルで参照される、図面データベース上の共有定義です。
ENTITIES
LINE ── layer name ──→ LAYER record
TEXT ── style name ──→ STYLE record
INSERT ─ block name ─→ BLOCK_RECORD
同じ定義を各エンティティへ繰り返し保存しないため、設定の一元管理と参照が可能になります。
TABLESセクション全体の構造
TABLESセクションは、通常のセクション境界で囲まれます。
0
SECTION
2
TABLES
0
TABLE
2
LTYPE
...
0
ENDTAB
0
TABLE
2
LAYER
...
0
ENDTAB
0
ENDSEC
階層として整理すると、次の構造です。
TABLES section
├── TABLE: LTYPE
│ ├── LTYPE record
│ └── LTYPE record
└── TABLE: LAYER
├── LAYER record
└── LAYER record
TABLEはテーブル本体の開始、ENDTABはそのテーブルの終了を表します。セクション全体はENDSECで終了します。
Autodesk仕様ではテーブルの並び順は変化し得ます。ただし、LTYPEテーブルはLAYERテーブルより前に出力されます。独自パーサーでは、それ以外の順序を固定せず、コード2のテーブル名で識別します。help.autodesk.com
テーブルヘッダーの主要コード
テーブル本体には、すべてのシンボルテーブルに共通するコードがあります。
| コード | 意味 |
|---|---|
| 0 | オブジェクト種類TABLE |
| 2 | テーブル名 |
| 5 | テーブル自身のハンドル |
| 330 | 所有元へのsoft-pointer参照 |
| 100 | AcDbSymbolTableサブクラスマーカー |
| 70 | テーブルに続く可能性がある最大エントリ数 |
これらの共通コードはAutodeskのシンボルテーブル仕様に定義されています。help.autodesk.com
LAYERテーブルのヘッダー例は次のようになります。
0
TABLE
2
LAYER
5
2
330
0
100
AcDbSymbolTable
70
4
ここで重要なのは、コード70の4を「必ず4件のLAYERレコードが続く」と解釈しないことです。
コード70は実際のレコード数ではない
Autodesk仕様では、テーブル内に削除済み項目が存在しても、その項目はDXFへ書き出されません。そのため、コード70が示す数より少ないレコードしか続かない場合があります。
コード70は、読み込み側が配列領域を確保するための最大数として用意された値です。help.autodesk.com
したがって、次の読み方は危険です。
for _ in range(table_header[70]): read_record()
実際の境界は、コード0と値ENDTABの組です。
コード70を読む
↓
容量確保や検査の参考にする
↓
コード0 / ENDTABまでレコードを読む
実際に読めた件数とコード70が異なる場合は、即座に破損と判定せず、診断情報として記録します。
テーブルレコードの共通構造
各テーブルの中には、そのテーブル名と同じ種類のレコードが並びます。
LAYERテーブルならLAYERレコード、STYLEテーブルならSTYLEレコードです。
シンボルテーブルレコードに共通する主なコードは次のとおりです。
| コード | 意味 |
|---|---|
| 0 | レコード種類。通常はテーブル名と同じ |
| 5 | レコード自身のハンドル |
| 105 | DIMSTYLEだけが使用する自己ハンドル |
| 330 | 所有するテーブルへの参照 |
| 100 | AcDbSymbolTableRecordサブクラスマーカー |
| 2 | レコード名 |
| 70 | レコード種類ごとに定義されるフラグ |
共通部分に加えて、各レコード固有のコードが続きます。help.autodesk.com
簡略化したLAYERレコードは次のようになります。
0
LAYER
5
10
330
2
100
AcDbSymbolTableRecord
100
AcDbLayerTableRecord
2
OUTLINE
70
0
62
7
6
CONTINUOUS
この例では、
- 自己ハンドル:
10 - 所有するテーブル:ハンドル
2 - レイヤー名:
OUTLINE - 色番号:
7 - 線種名:
CONTINUOUS
と解釈できます。
コード70はテーブルヘッダーとレコードで意味が異なります。ヘッダーでは最大エントリ数ですが、LAYERレコードではレイヤー状態を表すビットフラグです。
グループコードだけで意味を決めず、出現したオブジェクト種類とサブクラスを組み合わせて解釈します。
DIMSTYLEだけはコード105を使う
通常のシンボルテーブルレコードは、自己ハンドルにコード5を使用します。
例外はDIMSTYLEです。DIMSTYLEレコードだけはコード105が自己ハンドルになります。
0
DIMSTYLE
105
27
2
ISO-25
すべてのレコードからコード5だけを探す実装では、DIMSTYLEの識別子を取得できません。help.autodesk.com
def handle_code(record_type: str) -> int:
return 105 if record_type == "DIMSTYLE" else 5
この例外は、ハンドル索引や参照グラフを構築するときに重要です。
名前参照とハンドル参照を分ける
TABLESセクションの定義は、名前またはハンドルで参照されます。
たとえば一般的なエンティティでは、次のような名前参照が使われます。
| エンティティ側のコード | 参照対象 |
|---|---|
| 8 | レイヤー名 |
| 6 | 線種名 |
| 7 | 文字スタイル名 |
| 2 | INSERTが参照するブロック名 |
一方、コード330や340などはハンドル参照です。
パーサー内部では、次の二つの索引を分けて作ると扱いやすくなります。
records_by_name = {
("LAYER", "OUTLINE"): layer_record,
("STYLE", "STANDARD"): style_record,
}
objects_by_handle = {
"10": layer_record,
"27": dimstyle_record,
}
名前はテーブル内で解決し、ハンドルは図面全体の索引で解決します。
PythonでTABLESを分割する
次のコードは、TABLESセクションから各テーブルを抽出する最小例です。
from dataclasses import dataclass
from typing import Any
Tag = tuple[int, Any]
@dataclass
class SymbolTable:
name: str
header: list[Tag]
records: list[list[Tag]]
def get_first(tags: list[Tag], code: int) -> Any | None:
for tag_code, value in tags:
if tag_code == code:
return value
return None
def parse_tables(tags: list[Tag]) -> list[SymbolTable]:
tables: list[SymbolTable] = []
index = 0
while index < len(tags):
code, value = tags[index]
if (code, value) != (0, "TABLE"):
index += 1
continue
index += 1
header: list[Tag] = []
while index < len(tags) and tags[index][0] != 0:
header.append(tags[index])
index += 1
table_name = get_first(header, 2)
if not isinstance(table_name, str):
raise ValueError("TABLE has no valid code 2 name")
records: list[list[Tag]] = []
while index < len(tags):
code, value = tags[index]
if (code, value) == (0, "ENDTAB"):
index += 1
break
if code != 0:
raise ValueError(
f"unexpected tag before record: {(code, value)!r}"
)
record_type = str(value)
index += 1
record: list[Tag] = [(0, record_type)]
while index < len(tags) and tags[index][0] != 0:
record.append(tags[index])
index += 1
if record_type != table_name:
raise ValueError(
f"{table_name} table contains {record_type}"
)
records.append(record)
else:
raise ValueError(f"{table_name} table has no ENDTAB")
tables.append(
SymbolTable(
name=table_name,
header=header,
records=records,
)
)
return tables
この関数は、コード0を次のオブジェクト境界として扱い、ENDTABまでを一つのテーブルとして読みます。
コード70をループ回数に使用していないため、ヘッダー上の最大数と実際のレコード数が異なるDXFも解析できます。
読み取り後に検査する項目
抽出後は、構造と参照を分けて検査します。
TABLEにコード2の名前があるかENDTABで正しく閉じられているか- レコード種類がテーブル名と一致するか
- レコード名が同一テーブル内で重複していないか
- 自己ハンドルが図面全体で重複していないか
- コード330の所有先が対象テーブルか
- DIMSTYLEのコード105を取得できているか
- エンティティが参照する名前を解決できるか
- コード70と実際の件数に差があるか
コード70との差は警告候補ですが、ENDTAB欠落や異なる種類のレコード混入は、構造エラーとして扱う方が安全です。
実装で起こりやすい失敗
テーブルの出現順を固定する。
テーブル順は変化する可能性があります。コード2の名前で識別します。
コード70の回数だけレコードを読む。
コード70は実件数を保証しません。ENDTABを終端にします。
テーブルとレコードを同じオブジェクトとして扱う。TABLEはコンテナ、LAYERなどはその要素です。ハンドルと所有関係も別に保持します。
同じコードを常に同じ意味で解釈する。
コード70は出現位置によって最大数にもフラグにもなります。
DIMSTYLEのコード105を見落とす。
DIMSTYLEだけは自己ハンドルのコードが異なります。
名前だけ保存してRawタグを捨てる。
テーブルごとに追加コードが異なります。未知のタグも順序付きで保持します。
まとめ
DXFのTABLESセクションには、レイヤー、線種、文字スタイル、寸法スタイル、ブロックレコードなど、図面内で共有される名前付き定義が保存されます。
基本構造は次のとおりです。
TABLES
TABLE
table header
symbol table record
symbol table record
ENDTAB
ENDSEC
実装で特に重要なのは、コード70を実件数として読まないことです。コード70は最大エントリ数であり、実際の終端はコード0とENDTABの組です。
また、テーブル本体とレコードを分離し、
- テーブル名
- レコード名
- 自己ハンドル
- 所有先ハンドル
- テーブル固有の属性
- 未解釈のRawタグ
を保持します。
TABLESを正しく解析できれば、ENTITIESに書かれたレイヤー名や線種名を共有定義へ接続できます。
これは、DXFを単なる図形配列ではなく、参照関係を持つ図面データベースとして復元するための基盤になります。

