ブロック属性は「定義」と「実値」に分かれる
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があります。
したがって属性値の解決は、次の優先順位にします。
- INSERTに対応するATTRIBがあればコード1を使う
- なければ、同じタグの定数ATTDEFを探す
- どちらもなければ欠損として扱う
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は見た目を交換するだけでなく、ブロック単位の属性データを運ぶ形式として利用できます。
