DXFのGROUPは図形ではなく参照の集合
CAD上で複数の線、円、文字などをまとめて選択したいとき、グループ機能を使うことがあります。見た目は一つのまとまりとして操作できますが、DXFではグループ全体が新しい図形へ変換されるわけではありません。
各LINEやCIRCLEは独立したエンティティのまま残り、OBJECTSセクションのGROUPオブジェクトが、それらのハンドルを参照します。
GROUPオブジェクト
├── 340 → LINE
├── 340 → CIRCLE
└── 340 → TEXT
この構造を理解すると、グループ情報の復元、ブロックとの区別、参照切れの検査が可能になります。
GROUPオブジェクトはOBJECTSセクションにある
GROUPは画面へ直接描画されるエンティティではなく、非図形オブジェクトです。そのため、通常はENTITIESではなくOBJECTSセクションへ保存されます。
一方、グループのメンバーとなるLINE、ARC、TEXTなどは、それぞれのモデル空間またはペーパー空間に存在します。
| 要素 | 保存場所 | 役割 |
|---|---|---|
| GROUP | OBJECTS | メンバーの参照と設定を保持 |
| LINE・CIRCLEなど | ENTITIES | 実際の形状を保持 |
| ACAD_GROUP辞書 | OBJECTS | グループ名とGROUPを対応付ける |
ENTITIESだけを解析すると図形は取得できても、グループ所属は復元できません。
GROUPの主要グループコード
AutodeskのDXF仕様で定義されている主要コードは次のとおりです。
| グループコード | 内容 |
|---|---|
| 0 | オブジェクト型GROUP |
| 5 | GROUP自身のハンドル |
| 102 | persistent reactorグループの境界 |
| 330 | 所有辞書などへのsoft-pointer参照 |
| 100 | サブクラスマーカーAcDbGroup |
| 300 | グループの説明 |
| 70 | 匿名フラグ。1は匿名、0は名前付き |
| 71 | 選択可否。1は選択可能、0は選択不可 |
| 340 | メンバーエンティティへのhard-pointer参照 |
メンバー数を表す専用コードはありません。コード340が現れた回数と、解決できた参照先の数を分けて数えます。
GROUPのDXF例を読む
次の例では、ハンドルA1のGROUPが、3個のエンティティを参照しています。
0
GROUP
5
A1
330
2F
100
AcDbGroup
300
Pump skid outline
70
0
71
1
340
10A
340
10B
340
10C
読み取れる内容は次のとおりです。
- GROUP自身のハンドル:
A1 - 所有辞書のハンドル:
2F - 説明:
Pump skid outline - 名前付きグループ:コード70が0
- 選択可能:コード71が1
- メンバー:
10A、10B、10C
コード340は繰り返し出現します。通常の辞書型へ直接変換すると最後の値で上書きされるため、順序付き配列として保持します。
{
"handle": "A1",
"owner_dictionary": "2F",
"description": "Pump skid outline",
"unnamed": false,
"selectable": true,
"member_handles": ["10A", "10B", "10C"]
}
グループ名はGROUP本体に保存されない
GROUPオブジェクトには、グループ名を直接保存する専用コードがありません。名前は、名前付きオブジェクト辞書からたどるACAD_GROUP辞書のキーとして管理されます。
概念的な参照関係は次のようになります。
名前付きオブジェクト辞書
└── ACAD_GROUP → グループ辞書
└── PUMP_SKID → GROUP A1
├── 340 → 10A
├── 340 → 10B
└── 340 → 10C
辞書部分を簡略化すると、次のような名前とハンドルの組になります。
3
PUMP_SKID
350
A1
GROUPだけを単独で読んでも名前PUMP_SKIDは取得できません。辞書キーと参照先GROUPを対応付ける必要があります。
コード70が1の匿名グループにも、実際の辞書管理上は*A...形式の自動生成名が付くことがあります。表示名が不要に見えても、再出力や参照解決のために辞書キーを保持します。
コード340は所有ではなくhard pointer
コード340はhard-pointer handleです。GROUPはメンバーを参照しますが、LINEやCIRCLEの形状データを内包しているわけではありません。
GROUPとメンバーの削除処理は分けます。グループ解除は参照集合を取り除く操作であり、通常は図形自体を消す操作ではありません。
また、DXFのハンドル参照は、図面間コピーやINSERT、XREFなどで変換対象になります。独自のDXF結合処理では、GROUP内の340も新しいハンドルへ更新しなければ、コピー後に旧図面のエンティティを指す参照切れが発生します。
GROUPとBLOCKの違い
GROUPとBLOCKは、データ構造と用途が異なります。
| 項目 | GROUP | BLOCK |
|---|---|---|
| 役割 | 既存図形の選択集合 | 再利用する図形定義 |
| 保存 | 各レイアウトの図形を参照 | BLOCKS内に定義 |
| 配置 | 元図形を参照 | INSERTで配置 |
| 変換 | 配置変換を持たない | 尺度・回転を設定可能 |
同じ形状の反復配置にはBLOCK、既存図形の論理的な集合にはGROUPが適します。両者を自動変換すると、所有場所や編集単位が変わるため注意が必要です。
メンバーは同じレイアウトに存在する
ezdxfでは、1個のグループのメンバーは同じレイアウトに存在し、モデル空間とペーパー空間をまたがず、ブロック定義内の図形も含めないことが条件です。
独自パーサーでは、各メンバーのハンドルを解決した後、次を検査します。
- すべての参照先が存在するか
- 参照先が図形エンティティか
- 全メンバーが同じモデル空間またはペーパー空間に属するか
- ブロック定義内のエンティティが混ざっていないか
- 同じハンドルが重複登録されていないか
違反を見つけても自動移動せず、GROUP、辞書名、メンバー、所属レイアウトを診断情報として出力します。
PythonでGROUPを解析する
順序付きタグ列からGROUP固有の値を抽出する例です。
from dataclasses import dataclass
@dataclass
class GroupData:
handle: str | None
owner_handle: str | None
description: str
unnamed: bool
selectable: bool
member_handles: list[str]
def normalize_handle(value: object) -> str:
return str(value).strip().upper()
def parse_group(
tags: list[tuple[int, object]],
) -> GroupData:
handle = None
owner_handle = None
description = ""
unnamed = False
selectable = True
members: list[str] = []
for code, value in tags:
if code == 5:
handle = normalize_handle(value)
elif code == 330:
owner_handle = normalize_handle(value)
elif code == 300:
description = str(value)
elif code == 70:
unnamed = int(value) == 1
elif code == 71:
selectable = int(value) == 1
elif code == 340:
members.append(normalize_handle(value))
return GroupData(
handle=handle,
owner_handle=owner_handle,
description=description,
unnamed=unnamed,
selectable=selectable,
member_handles=members,
)
コード330はコード102のreactorグループ内にも現れ得ます。タグ位置と境界を保持し、所有参照とreactor内参照を区別します。
ハンドル索引でメンバーを解決する
図面全体のオブジェクトを読み込んでハンドル索引を作成した後、コード340を解決します。
def resolve_group_members(
group: GroupData,
handle_index: dict[str, dict],
) -> tuple[list[dict], list[str]]:
resolved = []
missing = []
for handle in group.member_handles:
entity = handle_index.get(handle)
if entity is None:
missing.append(handle)
else:
resolved.append(entity)
return resolved, missing
解決後は、グループ名、設定、元のハンドル列、解決済みエンティティ、未解決ハンドルを分けて保存します。元の列を残せば、修復や再出力を別工程で判断できます。
実装で起こりやすい失敗
ENTITIESだけを読む:GROUPとACAD_GROUP辞書を取得できません。- コード340を一つの値として保存する:複数メンバーが上書きされます。
- コード300をグループ名とみなす:300は説明であり、名前は辞書キーです。
- GROUPをBLOCKとみなす:再利用定義、挿入点、尺度、回転の構造はありません。
- 参照先を解決せず文字列のまま使う:所属レイアウトやエンティティ型を検査できません。
- グループ解除でメンバーを削除する:参照集合と図形のライフサイクルを混同しています。
- 図面結合時に340を変換しない:コピー後のハンドル対応が崩れます。
- すべての330を同じ意味で扱う:コード102の文脈やサブクラス位置を確認する必要があります。
まとめ
DXFのGROUPは、複数の図形を一つの図形へ変換する仕組みではありません。OBJECTSセクションのGROUPオブジェクトが、コード340で既存エンティティを参照する構造です。
ACAD_GROUP辞書
↓ 名前からGROUPを取得
GROUP
↓ コード340を順番に解決
LINE・CIRCLE・TEXTなど
主要項目は、コード300の説明、70の匿名フラグ、71の選択可否、繰り返し現れる340です。グループ名はGROUP本体ではなく辞書側に保存されます。
解析ではハンドル索引を作り、辞書名とGROUP、GROUPとメンバーを二段階で解決します。そのうえで参照切れ、重複、レイアウト混在を検査します。
この構造を保持できれば、CAD上の論理的な選択集合を、部品候補、注記セット、検査対象、変換単位などの設計データとして再利用できます。
