DXFの図形だけを読みたい場合は、ENTITIESセクションのLINEやCIRCLEを処理すれば、最低限の形状は取得できます。
しかし、図面を「どのレイアウトに配置し、どの用紙へ、どの範囲と尺度で出力するか」まで再現するには、それだけでは足りません。
DXF R2000以降では、モデル空間と複数のペーパー空間を管理するためにLAYOUTオブジェクトが使われます。LAYOUTは印刷設定を表すAcDbPlotSettingsの情報も内包し、対応するBLOCK_RECORDや最後にアクティブだったVIEWPORTをハンドルで参照します。
この記事では、AutodeskのDXF Referenceを基準に、LAYOUTとPLOTSETTINGSの役割、主要グループコード、モデル空間・ペーパー空間との関係、Pythonでの解析方法を整理します。
LAYOUTは表示タブだけではない
CAD画面では、通常「Model」と複数のレイアウトタブが表示されます。利用者から見るとタブの違いですが、DXF内部では次の要素が連携しています。
ACAD_LAYOUT辞書
↓ 名前から参照
LAYOUTオブジェクト
├─ レイアウト名・タブ順・UCS
├─ 用紙・余白・尺度などの印刷設定
└─ 対応するBLOCK_RECORDへのハンドル
↓
モデル空間またはペーパー空間の図形
Autodeskの仕様では、LAYOUTはOBJECTSセクションにある非図形オブジェクトです。線や文字そのものではなく、空間と出力条件を管理します。
そのため、次の処理は分離した方が安全です。
- 図形の座標や形状を読む
- 図形が属する空間を解決する
- レイアウトの名前や表示順を読む
- 用紙、印刷範囲、尺度を読む
- ビューポートを通してモデルをどう見せるか解決する
モデル空間とペーパー空間はブロック構造で管理される
DXFでは、モデル空間とペーパー空間が内部的にブロック定義として表現されます。
AutodeskのBLOCK仕様では、*Model_Space、*Paper_Space、*Paper_Space0などの内部名が使われます。最初のペーパー空間は*Paper_Space、次が*Paper_Space0、その次が*Paper_Space1という順です。
ここで注意したいのは、利用者が見るレイアウト名と内部ブロック名が同じとは限らないことです。
たとえば利用者がレイアウトタブを「組立図」と名付けても、内部のブロック定義は*Paper_Space0である可能性があります。名前文字列だけで両者を結び付けず、LAYOUTの参照ハンドルを解決します。
図形側にはコード67が現れる場合があります。
| コード67 | 意味 |
|---|---|
| 0または省略 | モデル空間 |
| 1 | ペーパー空間 |
ただし、複数のペーパー空間を区別するには67だけでは不十分です。図形の所有先ハンドルからBLOCK_RECORDをたどり、どのLAYOUTへ接続されているか確認します。
LAYOUTのレコード構造
LAYOUTオブジェクトは、共通オブジェクト情報、印刷設定、レイアウト固有情報の順に構成されます。
0 LAYOUT
5 自身のハンドル
330 所有元への参照
100 AcDbPlotSettings
用紙・余白・尺度・印刷範囲
100 AcDbLayout
名前・タブ順・範囲・UCS・空間参照
つまり、LAYOUTを解析するときはAcDbLayout以降だけを読めばよいわけではありません。その前にあるAcDbPlotSettingsのタグも、同じLAYOUTの出力条件です。
レイアウト名とタブ順
LAYOUT固有部の基本コードは次のとおりです。
| コード | 意味 |
|---|---|
| 1 | レイアウト名 |
| 70 | PSLTSCALE・LIMCHECKに関するビットフラグ |
| 71 | タブ順 |
| 330 | 対応する空間のBLOCK_RECORDハンドル |
| 331 | 最後にアクティブだったVIEWPORTのハンドル |
コード71はタブの表示順を表します。ただしAutodesk仕様では、Modelタブはこの値にかかわらず常に最初に表示されます。
コード330は、共通オブジェクト部にも現れます。同じコード番号でも出現位置によって役割が異なるため、単純な辞書へ330: valueとして保存してはいけません。
limits・extents・挿入基点
LAYOUTは、その空間がカレントだったときのlimits、extents、挿入基点も保持します。
| コード | 意味 |
|---|---|
| 10・20 | limitsの最小値 |
| 11・21 | limitsの最大値 |
| 12・22・32 | 挿入基点 |
| 14・24・34 | extentsの最小値 |
| 15・25・35 | extentsの最大値 |
| 146 | elevation |
limitsは利用者が設定する作図範囲、extentsは図形が占める外接範囲に関係します。似ていますが同じ値とは限らないため、別フィールドとして保持します。
UCS情報
レイアウト固有のUCSは、コード13・23・33の原点、16・26・36のX軸、17・27・37のY軸で表されます。コード76は直交UCSの種類、345は名前付きUCS、346は基準UCSのハンドルです。346がなく76が0以外の場合、基準UCSはWORLDとして扱います。
PLOTSETTINGSが保持する出力条件
PLOTSETTINGSは名前付きページ設定として独立したオブジェクトになる場合があります。また、LAYOUT内のAcDbPlotSettingsサブクラスにも同じ種類のタグが現れます。
解析モデルでは、出現場所を保ちながら共通のPlotSettings型へ正規化すると扱いやすくなります。
プリンタ・用紙・余白
| コード | 意味 |
|---|---|
| 1 | ページ設定名 |
| 2 | システムプリンタ名またはプロッタ設定ファイル名 |
| 4 | 用紙名 |
| 40〜43 | 左・下・右・上の印刷不可余白(mm) |
| 44・45 | 物理用紙の幅・高さ(mm) |
| 46・47 | 印刷原点のX・Yオフセット(mm) |
| 7 | 現在の印刷スタイルシート |
実寸が必要なら用紙名ではなく44と45を使い、単位がmmであることも残します。プリンタやCTB・STBの名前は外部環境に依存するため、「参照名を読めた」ことと「同じ出力を再現できる」ことは別です。
印刷範囲
コード74は、何を印刷対象にするかを表します。
| 値 | 印刷範囲 |
|---|---|
| 0 | 最後の画面表示 |
| 1 | 図面範囲 |
| 2 | limits |
| 3 | コード6で指定したビュー |
| 4 | コード48・49・140・141で指定した窓 |
| 5 | レイアウト情報 |
窓指定では、48・49を左下、140・141を右上として組にします。コードを個別に読むだけでなく、コード74が4のときに初めて印刷窓として意味付けします。
印刷尺度
カスタム尺度はコード142と143の比で表されます。\[ s = \frac{u_p}{u_d} \]
ここで、$u_p$はコード142の用紙単位、$u_d$はコード143の作図単位です。
たとえばコード142が1、143が100なら、用紙上の1単位に対して作図側100単位を対応させる設定です。ただし、コード70のUseStandardScaleフラグが有効な場合は、コード75の標準尺度種別とコード147の尺度値も確認します。
コード75には1:1、1:2、1:10などの列挙値があります。値をそのまま倍率と解釈せず、公式の対応表または147と合わせて扱います。
回転・単位・フラグ
| コード | 意味 |
|---|---|
| 70 | 中央配置、標準尺度、線幅印刷などのビットフラグ |
| 72 | 用紙単位(inch・mm・pixel) |
| 73 | 印刷回転 |
| 75 | 標準尺度種別 |
| 76 | ShadePlotモード |
| 77 | ShadePlot解像度レベル |
| 78 | カスタムDPI |
コード70は単一の状態番号ではなくビット集合です。たとえば中央配置と標準尺度使用が同時に有効になることがあります。
PLOT_CENTERED = 4
USE_STANDARD_SCALE = 16
PLOT_PLOT_STYLES = 32
PRINT_LINEWEIGHTS = 128
def has_flag(value: int, flag: int) -> bool:
return (value & flag) != 0
Pythonでサブクラス単位に解析する
同じコード100や330が複数回現れるため、最初から1つの辞書へ変換する方法は危険です。まず順序付きタグ列をサブクラスマーカーで分割します。
from collections import defaultdict
from typing import Any
def split_subclasses(
tags: list[tuple[int, Any]],
) -> dict[str, list[tuple[int, Any]]]:
sections: dict[str, list[tuple[int, Any]]] = defaultdict(list)
current = "common"
for code, value in tags:
if code == 100:
current = str(value)
continue
sections[current].append((code, value))
return dict(sections)
AcDbLayoutから名前、タブ順、BLOCK_RECORD参照、VIEWPORT参照を抽出し、AcDbPlotSettingsは別の構造へ変換します。実運用では値の欠落や重複を診断情報として残し、未知のタグもRawデータとして保持します。
参照グラフとして整合性を検査する
LAYOUTを読み取った後は、ハンドル索引を使って参照先を解決します。
LAYOUT
├─ 330 → BLOCK_RECORD
├─ 331 → VIEWPORT
├─ 345 → UCSテーブルレコード
└─ 346 → 基準UCSテーブルレコード
ここから次の不整合を検出できます。
- レイアウト名が重複している
- 参照先BLOCK_RECORDが存在しない
- 最終アクティブVIEWPORTが存在しない
- 用紙幅または高さが0以下
- 印刷窓の右上が左下より小さい
- カスタム尺度の分母が0
- 印刷範囲に必要なビュー名や窓座標がない
参照切れを発見しても任意の空間へ付け替えず、診断と修復を別工程にします。
実装で起こりやすい失敗
レイアウト名と*Paper_Space名を同一視する。
利用者向けの名前と内部ブロック名は別物です。LAYOUTのコード330を解決します。
コード67だけで複数レイアウトを分類する。
67はモデル空間かペーパー空間かを示しますが、どのペーパー空間かまでは決まりません。所有先BLOCK_RECORDも確認します。
LAYOUTからAcDbLayout部分だけを読む。
用紙、余白、尺度、印刷範囲は同じオブジェクト内のAcDbPlotSettings部分にあります。
尺度の数値だけを読む。
標準尺度かカスタム尺度かをフラグで確認し、75・147または142・143を組として解釈します。
まとめ
DXFのLAYOUTは、単なるタブ名ではありません。レイアウト名、表示順、limits、extents、UCS、BLOCK_RECORD、VIEWPORT、印刷設定を結ぶ管理オブジェクトです。
解析の要点は次のとおりです。
- LAYOUTはOBJECTSセクションの非図形オブジェクト
AcDbPlotSettingsとAcDbLayoutを分けて読む- コード330から対応する空間のBLOCK_RECORDを解決する
- モデル空間・ペーパー空間の内部名と利用者向けレイアウト名を分離する
- 印刷範囲、尺度、用紙寸法、外部設定ファイルを別項目として保持する
- 同じコードの繰り返しを前提に、順序付きタグ列を残す
DXFタグ列
↓
サブクラス単位に分割
↓
LAYOUTとPLOTSETTINGSを正規化
↓
BLOCK_RECORD・VIEWPORT・UCSを解決
↓
レイアウト構造と印刷条件を検査
この構造を扱えるようになると、DXFパーサーを図形抽出だけで終わらせず、シート一覧の生成、図面構成の検査、印刷条件の比較、Web上のレイアウト管理、変換前後の差分確認へ発展させられます。

