「DXFのWIPEOUTを読む|クリッピング境界・U/Vベクトル・表示フラグの構造」の内容を表す技術イラスト
UNIT 03CAD・3Dプリント FIG.00 · EYECATCH

DXFのWIPEOUTを読む|クリッピング境界・U/Vベクトル・表示フラグの構造

DXFのWIPEOUTを構成する挿入点、U/Vベクトル、クリッピング境界、表示フラグを整理。矩形・多角形の境界をWCSへ変換する式、Python抽出例、参照ハンドルと描画順の注意点まで実務向けに解説します。

DOC NO.EKP-03-1202
DATE
REVB
READ約 10 分

WIPEOUTは、背景色で背後の線や文字を隠すDXFエンティティです。DXF上では単純なポリゴンではなく、IMAGEと共通の座標系やクリッピング境界を持ちます。グループコード14・24の点をWCS座標として扱うと、境界がずれるため注意が必要です。

この記事では、WIPEOUTの構造をグループコードから読み解き、矩形・多角形の境界を復元する考え方とPythonによる抽出方法を整理します。

目次

WIPEOUTは「背景色で隠す領域」

WIPEOUTは、指定した境界内を現在の背景色で覆うエンティティです。塗りつぶし色を固定したSOLIDやHATCHとは異なり、背景色に追従して背後の図形を隠す用途に向いています。

AutodeskのDXFリファレンスでは、WIPEOUTにAcDbRasterImageのサブクラスマーカーが使われ、挿入点、Uベクトル、Vベクトル、画像サイズ、クリッピング境界などが定義されています。つまり、実装上はIMAGEに近い枠組みでマスク領域を表現しています。

ezdxfの公式ドキュメントも、WIPEOUTはIMAGEを基礎にしたエンティティであり、DXF R2000以降を必要とすると説明しています。一方、WIPEOUTの利用者が外部画像ファイルを用意する必要はありません。

HATCHやSOLIDは指定色で領域を描くのに対し、WIPEOUTは背景色で背後を隠します。「白いHATCH」へ置き換えると、黒背景や印刷設定で見え方が変わるため、変換先でもマスクとして扱うのが基本です。

主要グループコードを読む

WIPEOUT固有部分の中心は、配置を決めるベクトルとクリッピング境界です。

コード内容読み取り時の要点
10・20・30挿入点WCSの3次元点
11・21・31Uベクトル画像座標のU方向を表すWCSベクトル
12・22・32Vベクトル画像座標のV方向を表すWCSベクトル
13・23画像サイズU方向・V方向の大きさ
340IMAGEDEFへのハード参照実装によって0の場合がある
70表示プロパティビットフラグとして解釈
280クリッピング状態0はオフ、1はオン
281・282・283明るさ・コントラスト・フェードIMAGEと共通の値域
360IMAGEDEF_REACTORへのハード参照実装によって0の場合がある
71境界種別1は矩形、2は多角形
91後続する境界頂点数14・24の組数と照合する
14・24境界頂点2次元の画像・クリップ座標

コード70は、表示、非整列時の表示、境界の使用、透明性を表すビットの組み合わせです。値5なら、ビット1と4による「表示」と「境界を使用」の組み合わせです。

矩形と多角形で頂点の意味が変わる

コード71が1の矩形境界では、コード14・24は対角の2点です。Autodeskの仕様では、既定の矩形は左下が(-0.5, -0.5)、右上が(size.x - 0.5, size.y - 0.5)です。

コード71が2の多角形境界では、3点以上を順番に並べます。コード91は後続する頂点数なので、実際に読めた点数と一致するか検証できます。

矩形の2点は閉じた四角形の頂点列ではありません。描画や面積計算に使うときは、次の4点へ展開します。

(xmin, ymin)
(xmax, ymin)
(xmax, ymax)
(xmin, ymax)

多角形では、先頭点と末尾点が一致しているとは限りません。閉領域として処理する側で、必要に応じて先頭点を末尾へ追加します。

境界座標をWCSへ変換する

グループコード14・24は、挿入点とは別のローカルな2次元座標です。境界点を $(u_i, v_i)$、挿入点を $\mathbf{O}$、U・Vベクトルをそれぞれ $\mathbf{U}$、$\mathbf{V}$ とすると、WCS上の点は次のアフィン変換で表せます。

$$ \mathbf{P}_i = \mathbf{O} + u_i\mathbf{U} + v_i\mathbf{V} $$

各成分へ展開すると次のとおりです。

$$ \begin{aligned} x_i &= O_x + u_i U_x + v_i V_x \\ y_i &= O_y + u_i U_y + v_i V_y \\ z_i &= O_z + u_i U_z + v_i V_z \end{aligned} $$

この式は、コード10が挿入点、コード11がU方向、コード12がV方向を表すというDXF定義に基づく座標変換です。U・Vベクトルは単位ベクトルとは限りません。尺度と回転がすでに含まれているため、正規化してはいけません。

半ピクセルの境界に注意する

既定境界が-0.5から始まるのは、挿入点が境界の角ではなく画像座標の基準に対応しているためです。たとえば、挿入点が $(100, 50, 0)$、Uベクトルが $(2, 0, 0)$、Vベクトルが $(0, 2, 0)$、境界点が $(-0.5, -0.5)$ なら、WCS点は次の位置になります。

$$ \mathbf{P} = (100, 50, 0) – 0.5(2, 0, 0) – 0.5(0, 2, 0) = (99, 49, 0) $$

単純に境界点へ挿入点を足すだけでは、尺度が反映されず誤った位置になります。

PythonでWIPEOUTを抽出する

次の例は、ASCII DXFをコードと値のペアとして読み、WIPEOUTエンティティから主要項目を抽出します。完全なDXFパーサーではありませんが、変換前の監査や未知ファイルの構造確認に使えます。

from pathlib import Path

def read_pairs(path: str) -> list[tuple[int, str]]:
    lines = Path(path).read_text(encoding="utf-8", errors="replace").splitlines()
    if len(lines) % 2:
        raise ValueError("DXFの行数が奇数です")
    return [(int(lines[i].strip()), lines[i + 1].strip())
            for i in range(0, len(lines), 2)]


def entities_of_type(pairs, entity_type: str):
    current = []
    for code, value in pairs:
        if code == 0:
            if current and current[0] == (0, entity_type):
                yield current
            current = [(code, value)]
        else:
            current.append((code, value))
    if current and current[0] == (0, entity_type):
        yield current


def first(record, code: int, default="0"):
    return next((value for c, value in record if c == code), default)


def vec3(record, x_code, y_code, z_code):
    return tuple(float(first(record, code)) for code in (x_code, y_code, z_code))


def parse_wipeout(record):
    origin = vec3(record, 10, 20, 30)
    u_axis = vec3(record, 11, 21, 31)
    v_axis = vec3(record, 12, 22, 32)

    xs = [float(value) for code, value in record if code == 14]
    ys = [float(value) for code, value in record if code == 24]
    if len(xs) != len(ys):
        raise ValueError("境界のコード14と24の個数が一致しません")

    raw = list(zip(xs, ys))
    expected = int(first(record, 91, str(len(raw))))
    if expected != len(raw):
        raise ValueError(f"コード91={expected}ですが頂点は{len(raw)}点です")

    wcs = [tuple(o + x * u + y * v for o, u, v in zip(origin, u_axis, v_axis))
           for x, y in raw]
    return {
        "handle": first(record, 5, ""),
        "boundary_type": int(first(record, 71, "1")),
        "clipping": int(first(record, 280, "0")) == 1,
        "display_flags": int(first(record, 70, "0")),
        "raw_boundary": raw,
        "wcs_boundary": wcs,
    }


pairs = read_pairs("drawing.dxf")
for record in entities_of_type(pairs, "WIPEOUT"):
    print(parse_wipeout(record))

実務用ツールではセクション境界、バイナリDXF、文字コード、異常値も扱う必要があります。既存ライブラリを利用できる場合は、ezdxfのboundary_path_wcs()やmake_path()を使う方が安全です。

読み取り時に行う検証

最低限、次を検証します。

  • コード91とコード14・24の組数が一致する
  • 矩形境界は2点、多角形境界は3点以上ある
  • U・Vベクトルがゼロベクトルではない
  • コード280が0または1として解釈できる
  • コード70をビットフラグとして分解できる
  • 参照ハンドル340・360が0でも即座に破損と判定しない
  • WCSへ変換した境界が極端に大きい、または非有限値になっていない

ezdxfではIMAGEDEFとIMAGEDEF_REACTORのハンドルが常に0で、補助エンティティは不要と説明されています。参照先を必須にすると正常なWIPEOUTを誤判定します。

表示順は別問題

WIPEOUTの効果は描画順序にも依存します。DXF変換時に種類別で並べ替えると、マスク対象より先に描かれることがあります。SVGやCanvasへ変換するときも元のスタッキング順を維持します。

よくある失敗

  • コード14・24をWCS座標として描く:挿入点とU・Vベクトルが無視されます。必ずWCSへ変換します。
  • U・Vベクトルを正規化する:ベクトル長に含まれる尺度が失われ、境界サイズが変わります。
  • コード71だけで表示可否を判断する:71は境界形状です。クリッピングは280、表示関連は70を確認します。
  • 白い図形へ固定変換する:背景色や印刷スタイルで意味が変わります。変換先のマスク機能を優先します。

まとめ

WIPEOUTを正しく読む要点は、境界頂点だけを抜き出さず、挿入点とU・Vベクトルを含む座標系として扱うことです。

  • コード71で矩形と多角形を区別する
  • コード91と14・24の個数を照合する
  • 境界点を挿入点・U・VベクトルでWCSへ変換する
  • コード70はビットフラグ、コード280はクリッピング状態として読む
  • 340・360が0でも直ちに破損と決めつけない
  • 変換後も描画順序を維持する

生の境界点とWCS境界を中間データへ残せば、DXFビューア、SVG変換、図面監査、面積計算へ展開できます。

参考情報

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

この記事を書いた人

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

目次