DXFのCLASSESセクションを読む|CLASSレコード・proxy flags・カスタムオブジェクト

「DXFのCLASSESセクションを読む|CLASSレコード・proxy flags・カスタムオブジェクト」の内容を表す技術イラスト

DXFファイルでは、LINEやCIRCLEのような標準図形だけでなく、追加アプリケーションが定義した独自エンティティや非図形オブジェクトを扱うことがあります。

そのクラス情報を記録するのがCLASSESセクションです。

CLASSESは図形の座標や形状を保存する場所ではありません。独自クラスのDXF名、C++クラス名、作成元アプリケーション、アプリケーションがない環境で許可する操作などを登録する場所です。

この記事では、CLASSレコードの識別情報、proxy capabilities flags、未知クラスを壊さない解析方法を整理します。

目次

CLASSESセクションの役割

AutodeskのDXF仕様では、CLASSESセクションは、BLOCKS、ENTITIES、OBJECTSの各セクションにインスタンスが現れるアプリケーション定義クラスの情報を保持します。

基本的な関係は次のとおりです。

CLASSES
    CLASSレコード
        ├── DXFレコード名
        ├── C++クラス名
        ├── アプリケーション名
        └── プロキシ操作フラグ

ENTITIES / BLOCKS / OBJECTS
    └── そのクラスのインスタンス

独自CADが標準DXFにはない図形を保存した場合、図形本体はENTITIESなどに現れ、登録情報はCLASSESに記録されます。ただし、独自オブジェクトの完全な処理ロジックが埋め込まれるわけではありません。

セクション全体の構造

CLASSESセクションは、通常のDXFセクションと同じくSECTIONとENDSECで囲まれます。

0
SECTION
2
CLASSES

0
CLASS
1
MY_CUSTOM_ENTITY
2
MyCustomEntity
3
MY_CUSTOM_APP
90
131
91
2
280
0
281
1

0
ENDSEC

コード0と値CLASSの組が1件のクラスレコードの開始です。次のコード0、またはENDSECまでがそのレコードに属します。

CLASSは描画エンティティではなく、クラス登録用のメタデータです。

CLASSレコードの主要グループコード

Autodesk仕様で定義される主要コードは次のとおりです。

コード意味
0レコード種類CLASS
1クラスのDXFレコード名
2C++クラス名
3アプリケーション名
90プロキシ機能フラグ
91カスタムクラスのインスタンス数
280was-a-proxyフラグ
281is-an-entityフラグ

Autodeskは各フィールドを必須としています。欠損値を推測で補完せず、元データを保持したまま不完全なレコードとして報告します。

コード1・2・3でクラスを識別する

コード1は、DXF上でクラスを識別するレコード名です。

1
MY_CUSTOM_ENTITY

独自エンティティの実体では、コード0にこのDXF名が現れることがあります。

コード2は、ソフトウェア側のC++クラス名です。

2
MyCustomEntity

ObjectARXでもCLASS_NAMEとDXF_NAMEは別の引数であり、両者は同じ文字列である必要がありません。

値主な用途
DXFレコード名DXFファイル内のレコード識別
C++クラス名実装クラスとの結合・識別

コード3には、そのクラスを定義したアプリケーション名が入ります。

3
MY_CUSTOM_APP

クラス定義がロードされていない場合、この名称は警告情報に使われます。単純な実行ファイル名とは限らないため原文を保持します。

仕様上、コード1は一意です。一方、ezdxfは、実在ファイルで同じDXF名に異なるC++名が対応した例を報告しています。読み取り側ではコード1だけをキーにして上書きせず、コード1・2の組と順序付きレコードを保持すると安全です。

コード90はプロキシ機能フラグ

クラスを定義したアプリケーションがない環境では、独自オブジェクトがプロキシエンティティまたはプロキシオブジェクトとして扱われる場合があります。

コード90は、そのプロキシに許可する操作をビットの組み合わせで表します。

値許可される操作
1削除
2変換
4色変更
8レイヤー変更
16線種変更
32線種尺度変更
64表示状態変更
128クローン
256線の太さ変更
512印刷スタイル名変更
1024プロキシ警告ダイアログを無効化
32768R13形式プロキシ

複数の操作を許可する場合は、各ビットの論理和として保存されます。

先ほどの例にある131は、

131 = 1 + 2 + 128

なので、削除、変換、クローンを許可する組み合わせです。

コード90を単なる列挙値として131専用の分岐へ入れるのではなく、各ビットを個別に判定します。また、将来または他製品固有の未知ビットを捨てないよう、元の整数も保存します。

コード91はインスタンス数

コード91は、カスタムクラスのインスタンス数です。

91
2

コード91だけを根拠に読み取り回数を決めず、各セクションを境界に従って解析し、実際の検出数と照合します。差があれば、宣言数、検出数、検索対象、使用した識別子を診断情報として残します。

コード280はwas-a-proxyフラグ

コード280は、DXFが作成された時点でクラスがロードされていなかったかを示します。

値意味
0クラスがロードされていた
1クラスがロードされておらず、プロキシだった

コード90は許可操作、コード280は保存時の状態です。同じproxy真偽値へまとめると情報が失われます。

コード281はentityかobjectかを示す

コード281は、クラスのインスタンスが図形エンティティ側に存在できるかを示します。

値主な配置先
1BLOCKSまたはENTITIES
0OBJECTSのみ

値が1ならAcDbEntity派生、0ならOBJECTS側の非図形オブジェクトです。ただし、1でも独自実装やプロキシグラフィックスがなければ形状を完全に復元できない場合があります。

CLASSレコードには通常ハンドルがない

ezdxfの内部資料では、CLASSはハンドルを持たず、エンティティデータベースにも格納されません。全レコードへコード5を要求せず、ハンドル索引とは別のクラス登録一覧で管理します。

PythonでCLASSレコードを解析する

順序付きタグ列からCLASSレコードを抽出し、コード90のビットを解析する例です。

from dataclasses import dataclass
from typing import Any


Tag = tuple[int, Any]


PROXY_CAPABILITIES = {
    1: "erase",
    2: "transform",
    4: "color_change",
    8: "layer_change",
    16: "linetype_change",
    32: "linetype_scale_change",
    64: "visibility_change",
    128: "clone",
    256: "lineweight_change",
    512: "plot_style_change",
    1024: "disable_proxy_warning",
    32768: "r13_proxy",
}


@dataclass
class DxfClass:
    dxf_name: str
    cpp_name: str
    app_name: str
    proxy_flags: int
    instance_count: int
    was_proxy: bool
    is_entity: bool
    raw_tags: list[Tag]


def first_value(tags: list[Tag], code: int) -> Any:
    for tag_code, value in tags:
        if tag_code == code:
            return value
    raise ValueError(f"required group code is missing: {code}")


def decode_proxy_flags(value: int) -> list[str]:
    return [
        name
        for bit, name in PROXY_CAPABILITIES.items()
        if value & bit
    ]


def parse_class(tags: list[Tag]) -> DxfClass:
    if not tags or tags[0] != (0, "CLASS"):
        raise ValueError("CLASS record must start with (0, 'CLASS')")

    return DxfClass(
        dxf_name=str(first_value(tags, 1)),
        cpp_name=str(first_value(tags, 2)),
        app_name=str(first_value(tags, 3)),
        proxy_flags=int(first_value(tags, 90)),
        instance_count=int(first_value(tags, 91)),
        was_proxy=bool(int(first_value(tags, 280))),
        is_entity=bool(int(first_value(tags, 281))),
        raw_tags=tags.copy(),
    )


sample_tags: list[Tag] = [
    (0, "CLASS"),
    (1, "MY_CUSTOM_ENTITY"),
    (2, "MyCustomEntity"),
    (3, "MY_CUSTOM_APP"),
    (90, 131),
    (91, 2),
    (280, 0),
    (281, 1),
]

dxf_class = parse_class(sample_tags)

print(dxf_class)
print(decode_proxy_flags(dxf_class.proxy_flags))

コード90が131なら、フラグ解析結果は次のようになります。

['erase', 'transform', 'clone']

実装では、解析済み属性だけでなくraw_tagsも保持します。未知コードや将来追加された値を理解できなくても、再出力時に破壊しにくくなるためです。

図面全体との対応を検査する

解析後は、必須コードの欠損、識別子の重複、コード281と配置先、コード91と実検出数を照合します。未知のDXF名と未解釈タグはRawデータに残し、不一致があっても自動削除せず診断と修復を分けます。

実装で起こりやすい失敗

  • CLASSESを実装コードと考える:保存されるのは登録情報です。
  • コード1と2を同一視する:DXF名とC++名は別の識別子です。
  • コード90を列挙値として扱う:複数操作を組み合わせるビットフラグです。
  • コード90と280を統合する:許可操作と保存時状態は別情報です。
  • CLASSにコード5を要求する:通常のハンドル付きオブジェクトとは異なります。
  • 未知クラスを削除する:理解できなくてもRawデータを保持します。

まとめ

DXFのCLASSESセクションは、アプリケーション定義クラスの登録情報を保持します。

コード1   → DXFレコード名
コード2   → C++クラス名
コード3   → アプリケーション名
コード90  → プロキシとして許可される操作
コード91  → インスタンス数
コード280 → 保存時にプロキシだったか
コード281 → entityかobjectか

CLASSESだけで独自オブジェクトの意味を完全には再現できません。識別情報とフラグを解析し、ENTITIES・BLOCKS・OBJECTSと照合しながら、未知データをRaw形式で残します。これにより、カスタムオブジェクトやプロキシを含む図面を安全に検査・中継できます。

参考情報

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

この記事を書いた人

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

目次