「DXFのATTDEFとATTRIBを読む|ブロック属性のタグ・値・SEQEND」の内容を表す技術イラスト
UNIT 03CAD・3Dプリント FIG.00 · EYECATCH

DXFのATTDEFとATTRIBを読む|ブロック属性のタグ・値・SEQEND

DXFのATTDEFとATTRIBを、コード1・2・3・66・70、SEQEND、定数属性の扱いから解説。Pythonとezdxfでブロック属性を構造化し、部品表や外部DBへつなぐ方法も整理します。

DOC NO.EKP-03-1097
DATE
REVB
READ約 13 分
目次

ブロック属性は「定義」と「実値」に分かれる

CADのブロックには、形状だけでなく部品番号、機器名、定格、材質、改訂番号などの文字情報を持たせられます。

DXFでは、このブロック属性を一つの要素だけで表しません。中心となるのは次の三つです。

  • ATTDEF:ブロック定義内に置く属性のテンプレート
  • INSERT:ブロック定義を図面へ配置する参照
  • ATTRIB:個々のINSERTへ付属する属性の実値

同じブロックを10回配置した場合、ATTDEFは共通のひな型ですが、ATTRIBは各INSERTごとに異なる値を持てます。

flowchart TD
  A[BLOCK定義] --> B[ATTDEFテンプレート]
  C[INSERT参照] --> D[ATTRIB実値]
  B --> D
  D --> E[部品表や外部DBへ抽出]

この記事では、AutodeskのDXF仕様を基準に、ATTDEFとATTRIBの役割、タグと値の読み分け、INSERTとの並び、フラグの扱い、Pythonでの構造化まで整理します。

ATTDEFは属性のテンプレート

ATTDEFはattribute definition、つまり属性定義です。通常はBLOCKとENDBLKの間に置かれ、属性の名前、既定値、入力時の案内、表示位置、文字高さなどを定義します。

主なグループコードは次のとおりです。

コードATTDEFでの意味
1既定値
2属性タグ。スペース不可
3入力時のプロンプト文字列
10・20・30第1位置合わせ点
40文字高さ
50回転角度
70属性フラグ
73フィールド長。現在は未使用
74垂直方向の位置合わせ

たとえば、機器ブロックへ機器番号を持たせるATTDEFは次のように表せます。

0
ATTDEF
100
AcDbText
10
5.0
20
-2.0
30
0.0
40
2.5
1
UNASSIGNED
100
AcDbAttributeDefinition
3
機器番号を入力
2
EQUIPMENT_ID
70
0

この例では、コード2のEQUIPMENT_IDがデータを識別するタグ、コード1のUNASSIGNEDが既定値、コード3が対話入力時の案内です。

タグは画面に表示する日本語ラベルではなく、データ項目を識別するキーとして設計すると再利用しやすくなります。

表示名:機器番号
タグ:EQUIPMENT_ID
値:P-101

ATTRIBはINSERTごとの実値

ATTRIBはattribute entityです。ブロック定義内のテンプレートではなく、図面へ配置された各INSERTに付属する値を表します。

ATTDEFと共通するコードが多い一方、意味の中心は次の二つです。

コードATTRIBでの意味
1そのINSERTが持つ属性値
2属性タグ
10・20・30文字の始点
40文字高さ
50回転角度
70属性フラグ

同じポンプ記号を2台配置した場合を考えます。

INSERT #1
  EQUIPMENT_ID = P-101
  SERVICE      = COOLING_WATER

INSERT #2
  EQUIPMENT_ID = P-102
  SERVICE      = WASHING_WATER

ブロック形状とATTDEFは共通でも、ATTRIBのコード1は配置ごとに変わります。

部品表や外部データベースへ渡したいのは、多くの場合ATTDEFの既定値ではなく、各INSERTに付属するATTRIBの実値です。

INSERT・ATTRIB・SEQENDの並びを読む

属性を伴うINSERTでは、コード66が重要です。AutodeskのINSERT仕様では、コード66が1の場合、後ろにATTRIBの列が続き、SEQENDで終わる構造になります。

0
INSERT
2
PUMP_SYMBOL
66
1
10
100.0
20
50.0
30
0.0

0
ATTRIB
1
P-101
2
EQUIPMENT_ID
70
0

0
ATTRIB
1
COOLING_WATER
2
SERVICE
70
0

0
SEQEND

解析単位は、次のまとまりです。

INSERT
├─ ATTRIB: EQUIPMENT_ID = P-101
├─ ATTRIB: SERVICE = COOLING_WATER
└─ SEQEND

ATTRIBを図面全体から単独で集めるだけでは、どのブロック参照に属する値か分からなくなります。INSERTを読んだ時点で、後続するATTRIBをSEQENDまで同じレコードへ取り込みます。

コード70はビットフラグ

ATTDEFとATTRIBのコード70は、複数の状態を一つの整数へ保存するビットフラグです。

値意味
1非表示
2定数属性
4入力時に確認が必要
8プリセット。挿入時にプロンプトを出さない

複数の状態を組み合わせると、値は加算されます。たとえば非表示かつプリセットなら、コード70は1 + 8 = 9です。

フラグ判定は値の一致ではなく、ビット演算で行います。

def decode_attribute_flags(flags: int) -> dict[str, bool]:
    return {
        "invisible": bool(flags & 1),
        "constant": bool(flags & 2),
        "verify": bool(flags & 4),
        "preset": bool(flags & 8),
    }


print(decode_attribute_flags(9))

出力は次のとおりです。

{
  'invisible': True,
  'constant': False,
  'verify': False,
  'preset': True
}

flags == 1のように比較すると、値9の非表示属性を見落とします。

定数属性はATTRIBが存在しない場合がある

コード70のビット2が立つ定数属性は、すべてのブロック参照で同じ値を使うための定義です。

実装上の注意点は、一部のアプリケーションが定数属性に対応するATTRIBをINSERTへ付けず、ATTDEFだけを保持することです。ezdxfのInsert.get_attrib_text()にも、ATTRIBが見つからないときに関連する定数ATTDEFまで検索するsearch_constがあります。

したがって属性値の解決は、次の優先順位にします。

  1. INSERTに対応するATTRIBがあればコード1を使う
  2. なければ、同じタグの定数ATTDEFを探す
  3. どちらもなければ欠損として扱う

ATTDEFの既定値を無条件に全INSERTへコピーすると、ATTRIBが欠けた異常と、意図された定数属性を区別できません。コード70も併せて確認します。

タグをデータ項目として設計する

コード2のタグは、属性を検索・更新・集計するためのキーです。運用では次のルールを決めておくと安定します。

  • 大文字・小文字の比較方法を統一する
  • 表示名ではなく機械処理しやすい識別子を使う
  • 同じブロック内でタグを重複させない
  • 単位をタグまたは別スキーマで明確にする
  • 元の表記と比較用の正規化値を分けて保存する
{
  "block_name": "PUMP_SYMBOL",
  "insert_handle": "4A2",
  "attributes": {
    "EQUIPMENT_ID": "P-101",
    "SERVICE": "COOLING_WATER",
    "RATED_FLOW_L_MIN": "120"
  }
}

属性値はDXF上では文字列です。120を数値へ変換する場合は、単位、空文字、桁区切り、不正値を検証する層を別に設けます。

Pythonで属性付きINSERTを構造化する

すでにDXFをエンティティ単位のタグ列へ分割できている前提で、INSERTと後続ATTRIBをまとめる例です。

from dataclasses import dataclass, field
from typing import Any


Tag = tuple[int, Any]


@dataclass
class DxfEntity:
    entity_type: str
    tags: list[Tag]

    def first(self, code: int, default=None):
        for tag_code, value in self.tags:
            if tag_code == code:
                return value
        return default


@dataclass
class BlockReference:
    name: str
    handle: str | None
    attributes: dict[str, str] = field(default_factory=dict)


def collect_block_references(
    entities: list[DxfEntity],
) -> list[BlockReference]:
    result: list[BlockReference] = []
    index = 0

    while index < len(entities):
        entity = entities[index]
        if entity.entity_type != "INSERT":
            index += 1
            continue

        block = BlockReference(
            name=str(entity.first(2, "")),
            handle=entity.first(5),
        )
        attributes_follow = int(entity.first(66, 0)) == 1
        index += 1

        if attributes_follow:
            while index < len(entities):
                child = entities[index]
                if child.entity_type == "SEQEND":
                    index += 1
                    break
                if child.entity_type != "ATTRIB":
                    raise ValueError(
                        "unexpected entity after INSERT: "
                        f"{child.entity_type}"
                    )

                raw_tag = str(child.first(2, ""))
                normalized_tag = raw_tag.strip().upper()
                if not normalized_tag:
                    raise ValueError("ATTRIB tag is empty")
                if normalized_tag in block.attributes:
                    raise ValueError(
                        f"duplicate ATTRIB tag: {normalized_tag}"
                    )

                block.attributes[normalized_tag] = str(
                    child.first(1, "")
                )
                index += 1

        result.append(block)

    return result

この処理は、所属関係を壊さずにATTRIBを辞書へ変換します。ただし、定数ATTDEFのフォールバック、座標変換、マルチライン属性は別段階で処理します。

ezdxfで読み書きする

ezdxfでは、ブロック定義へadd_attdef()でテンプレートを追加し、INSERTへadd_auto_attribs()で値を設定できます。

import ezdxf


doc = ezdxf.new("R2010")
block = doc.blocks.new(name="PUMP_SYMBOL")
block.add_circle((0, 0), radius=5)
block.add_attdef(
    "EQUIPMENT_ID",
    insert=(6, 2),
    text="UNASSIGNED",
    dxfattribs={"height": 2.5},
)
block.add_attdef(
    "SERVICE",
    insert=(6, -2),
    text="UNKNOWN",
    dxfattribs={"height": 2.0},
)

msp = doc.modelspace()
insert = msp.add_blockref("PUMP_SYMBOL", (100, 50))
insert.add_auto_attribs({
    "EQUIPMENT_ID": "P-101",
    "SERVICE": "COOLING_WATER",
})

doc.saveas("pump-attributes.dxf")

既存ファイルを読む場合は、INSERTからタグを指定して値を取得できます。

for insert in doc.modelspace().query("INSERT"):
    equipment_id = insert.get_attrib_text(
        "EQUIPMENT_ID",
        default="",
        search_const=True,
    )
    print(insert.dxf.name, equipment_id)

非一様尺度を使うブロックでは、属性文字の配置結果がCADアプリケーションと一致しない場合があります。値の抽出と、見た目の再現を同じ成功条件にしないことが重要です。

マルチライン属性は別構造を持つ

新しいDXFでは、ATTRIBとATTDEFが埋め込みMTEXTを持つ場合があります。Autodesk仕様では、AcDbXrecord側の追加データやMTEXT相当のタグ列が続きます。

ezdxfでは、マルチラインATTRIB・ATTDEFはDXF R2018を必要とし、has_embedded_mtext_entityやplain_mtext()で判定・取得できます。

単純なパーサーが最初のコード1だけを読んで終了すると、複数行文字列の一部や書式情報を失う可能性があります。対象ファイルのDXFバージョンとサブクラスマーカーを確認し、単一行属性とマルチライン属性を分岐します。

実装で起こりやすい失敗

ATTDEFの既定値を配置済みブロックの値だと考える。
個々のINSERTの値はATTRIBを優先します。ATTDEFはテンプレートです。

ATTRIBを図面全体で平坦化する。
所属するINSERTとの関係が失われます。コード66とSEQENDを使って同じレコードへまとめます。

コード2を表示文字列として扱う。
コード2は検索キーとなるタグです。表示値の中心はコード1です。

コード70を列挙値として比較する。
複数フラグの組み合わせを見落とします。ビット演算で判定します。

定数ATTRIBが必ず存在すると仮定する。
定数属性はATTDEFだけで表される場合があります。フラグを確認してフォールバックします。

属性値をすぐ数値へ変換する。
DXF上の値は文字列です。単位や入力規則を別スキーマで検証してから型変換します。

抽出できれば表示も再現できると考える。
属性文字には位置、位置合わせ、回転、尺度、OCS、文字スタイルが関係します。データ抽出と描画再現は分離して検証します。

まとめ

DXFのブロック属性は、定義と実値を分けて読む必要があります。

  • ATTDEFはブロック定義内の属性テンプレート
  • ATTRIBは個々のINSERTに付属する実値
  • コード1は値、コード2はタグ、ATTDEFのコード3はプロンプト
  • INSERTのコード66が1ならATTRIB列が続き、SEQENDで終了
  • コード70はビットフラグとして判定
  • 定数属性はATTDEFから値を解決する場合がある

データ化では、INSERTを親、ATTRIBを子として保持し、ブロック名、ハンドル、タグ、値、フラグを一つの構造へまとめます。

この構造を正しく扱えると、図面内の部品番号や機器情報を、部品表、検査記録、外部データベース、Web検索、設計変更チェックへ展開できます。DXFは見た目を交換するだけでなく、ブロック単位の属性データを運ぶ形式として利用できます。

参考情報

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

この記事を書いた人

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

目次