「DXFのMLINEを読む|参照線・MLINESTYLE・頂点パラメータの構造」の内容を表す技術イラスト
UNIT 03CAD・3Dプリント FIG.00 · EYECATCH

DXFのMLINEを読む|参照線・MLINESTYLE・頂点パラメータの構造

DXFのMLINEを、参照線、MLINESTYLE、WCS頂点、miter方向、線・塗りつぶしパラメータから解析する方法を解説。Pythonによる構造検査と変換時の注意点も紹介します。

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

MLINEは「複数の平行線」だけではない

DXFのMLINEは、壁芯、配管、道路、ケーブル経路などを複数の線要素で表現するエンティティです。画面上では平行線の集まりに見えますが、DXF内部には完成した線分だけが並んでいるわけではありません。

形状を再現するには、次の三つを関連付けて読む必要があります。

  • MLINEが持つ参照線、尺度、位置合わせ、頂点データ
  • MLINESTYLEが持つ各線要素のオフセット、色、線種、端部形状
  • 各頂点に保存される方向ベクトル、miterベクトル、線・塗りつぶしパラメータ

単純なポリラインとして頂点だけを取り出すと、線間隔、交差部の切断、端部キャップ、塗りつぶしが失われます。CAD変換や図面比較では、参照線の棚卸しと表示形状の展開を別工程にすることが重要です。

MLINEとMLINESTYLEの役割を分ける

MLINEはENTITIESセクションに置かれる図形エンティティです。一方、MLINESTYLEはOBJECTSセクションのACAD_MLINESTYLE辞書から参照される設定オブジェクトです。

たとえば、中心線から左右へ同じ距離だけ離れた2本線を描く場合、MLINESTYLEには正負のオフセットを持つ二つの線要素を登録します。MLINE側は、そのスタイルを参照し、参照線の頂点列や尺度を保持します。

この分離により、一つのスタイルを複数のMLINEで共有できます。ただし、スタイル要素を変更しても、各MLINEに保存済みの頂点パラメータが自動的に整合するとは限りません。ライブラリで編集するときは、スタイルの差し替えだけでなく形状再生成処理が必要です。

MLINEの主要グループコード

AutodeskのDXFリファレンスで示される主要コードは次のとおりです。

コード内容
2MLINESTYLE名。読み取り用の冗長情報
340MLINESTYLEオブジェクトへのポインタハンドル
40MLINEの尺度係数
70位置合わせ。0=Top、1=Zero、2=Bottom
71状態フラグ
72頂点数
73スタイル内の線要素数
10参照線の開始点(WCS)
210エンティティ平面の法線ベクトル
11各頂点の座標(WCS)
12各頂点から始まるセグメントの方向ベクトル
13各頂点のmiter方向ベクトル
74 / 41線要素ごとのパラメータ数/値
75 / 42塗りつぶしパラメータ数/値

コード71はビット値です。値1は頂点あり、2は閉じたMLINE、4は始端キャップ抑制、8は終端キャップ抑制を表します。複数条件が同時に有効なら値を加算して保存します。

スタイル名よりハンドル参照を優先する

コード2のスタイル名とコード340のハンドルは、どちらもスタイルを示します。ただしAutodeskは、コード2を冗長フィールドとして読み取りにだけ使い、関連付けを変更するときはコード340を扱うよう説明しています。

名前だけをキーに関連付けると、名称変更、重複、不整合のあるファイルで誤ったスタイルを選ぶ可能性があります。検査結果には名前とハンドルの両方を残し、実体参照はハンドルで確認します。

MLINESTYLEが線間隔と見た目を決める

MLINESTYLEにはスタイル全体の塗りつぶし、miter表示、始終端キャップに加え、各線要素の定義が並びます。

コード内容
70塗りつぶし・miter・始終端キャップのフラグ
62スタイル全体の塗りつぶし色
51始端角度
52終端角度
71線要素数
49各線要素の参照線からのオフセット
62各線要素の色
6各線要素の線種

線要素はコード49、62、6の組で繰り返されます。同じコード62がスタイル全体の塗りつぶし色と線要素の色に使われるため、コード番号だけを辞書のキーにして最後の値を残す実装では解析できません。出現位置とコード71の要素数を保って読みます。

スタイル要素のオフセットを$o_i$、MLINEの尺度係数を$s$とすると、最外側の線要素間の幅は、単純な平行区間では次のように求められます。

$$ W = |s|\left(\max_i o_i-\min_i o_i\right) $$

位置合わせは参照線がTop、Zero、Bottomのどこを通るかを変えますが、線要素どうしの相対間隔を変えるものではありません。屈曲部ではmiter方向による交点処理が加わるため、各線を参照線から一定方向へ平行移動するだけでは完全再現になりません。

頂点ブロックは入れ子構造になっている

各頂点には、座標11、セグメント方向12、miter方向13が保存され、その後へスタイル要素ごとのパラメータが続きます。

コード74は、直後に続くコード41の個数です。コード75は、直後に続くコード42の個数です。この組はスタイル要素ごと、さらに頂点ごとに繰り返されます。したがって、41と42をファイル全体から一つの配列へ集めても、どの頂点・どの線要素に属する値か復元できません。

コード41の先頭値は、頂点からmiter方向へ進み、対象線要素と交わる位置までの距離です。次の値は、その交点から線方向へ進んだ実際の線要素開始位置を表します。後続値は、交差編集などで生じた線の表示区間と切断区間を表します。

コード42は塗りつぶし領域の表示・非表示区間を記録します。たとえば、塗りつぶされたMLINE同士の交差部を編集した場合、塗りつぶしを一時的に切る範囲が頂点パラメータへ保存されます。

頂点はWCS座標として読む

MLINEにはコード210の法線ベクトルがありますが、頂点をOCS座標として変換してはいけません。Autodeskのリファレンスは開始点と頂点をWCSとして定義し、ezdxfもMLINEはOCSエンティティではないと説明しています。

三次元頂点を持つMLINEも作成できますが、すべての頂点は同一平面上になければなりません。コード210はその平面の法線です。検査では、頂点の座標系を勝手に変換するより、WCS座標と法線をそのまま記録し、必要なら平面からの距離を別途計算します。

PythonでMLINEとスタイルを棚卸しする

次のコードはezdxfでDXFを開き、MLINEの参照情報、スタイル要素、頂点パラメータの件数をJSONへ出力します。元ファイルは変更しません。

from __future__ import annotations

from pathlib import Path
import json

import ezdxf


def vec3_list(vector) -> list[float]:
    return [float(vector.x), float(vector.y), float(vector.z)]


def style_report(mline) -> dict | None:
    style = mline.style
    if style is None:
        return None

    return {
        "handle": style.dxf.get("handle"),
        "name": style.dxf.get("name"),
        "description": style.dxf.get("description"),
        "flags": style.dxf.get("flags"),
        "fill_color": style.dxf.get("fill_color"),
        "start_angle_deg": style.dxf.get("start_angle"),
        "end_angle_deg": style.dxf.get("end_angle"),
        "elements": [
            {
                "offset": float(element.offset),
                "color": int(element.color),
                "linetype": element.linetype,
            }
            for element in style.elements
        ],
    }


def mline_report(mline) -> dict:
    vertices = []
    for vertex_no, vertex in enumerate(mline.vertices):
        vertices.append(
            {
                "index": vertex_no,
                "location_wcs": vec3_list(vertex.location),
                "line_direction": vec3_list(vertex.line_direction),
                "miter_direction": vec3_list(vertex.miter_direction),
                "line_params": [
                    [float(value) for value in params]
                    for params in vertex.line_params
                ],
                "fill_params": [
                    [float(value) for value in params]
                    for params in vertex.fill_params
                ],
            }
        )

    declared_elements = int(mline.dxf.get("style_element_count", 0))
    parameter_mismatches = [
        vertex["index"]
        for vertex in vertices
        if len(vertex["line_params"]) != declared_elements
        or len(vertex["fill_params"]) != declared_elements
    ]

    return {
        "handle": mline.dxf.get("handle"),
        "layer": mline.dxf.get("layer"),
        "style_name": mline.dxf.get("style_name"),
        "style_handle": mline.dxf.get("style_handle"),
        "scale_factor": mline.dxf.get("scale_factor"),
        "justification": mline.dxf.get("justification"),
        "flags": mline.dxf.get("flags"),
        "is_closed": mline.is_closed,
        "start_caps": mline.start_caps,
        "end_caps": mline.end_caps,
        "start_location_wcs": vec3_list(mline.dxf.start_location),
        "extrusion": vec3_list(mline.dxf.extrusion),
        "declared_vertex_count": int(mline.dxf.get("count", 0)),
        "actual_vertex_count": len(vertices),
        "declared_style_element_count": declared_elements,
        "parameter_mismatch_vertices": parameter_mismatches,
        "style": style_report(mline),
        "vertices": vertices,
    }


def inspect_dxf(source: Path) -> dict:
    document = ezdxf.readfile(source)
    reports = [
        mline_report(entity)
        for entity in document.modelspace().query("MLINE")
    ]
    return {
        "file": str(source),
        "dxf_version": document.dxfversion,
        "mline_count": len(reports),
        "mlines": reports,
    }


if __name__ == "__main__":
    result = inspect_dxf(Path("model.dxf"))
    Path("mline-inventory.json").write_text(
        json.dumps(result, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )

mline.styleがNoneなら、参照先のスタイルを解決できていません。ファイル破損だけでなく、未対応のスタイル、ハンドル不整合、変換過程でのOBJECTSセクション欠落を疑います。

検査結果で確認する項目

宣言数と実データ数

コード72の頂点数と実際の頂点数、コード73の線要素数と頂点ごとのパラメータ群数を比較します。不一致があれば、途中でタグが欠落した、要素数を変えた後に形状を更新していない、独自変換処理が階層を崩した可能性があります。

スタイル参照の解決

コード340の参照先が実在し、MLINESTYLEとして読めることを確認します。スタイル名が同じでもハンドルが異なる場合や、名前は残っているのに参照先がない場合は、名称だけで自動修復しない方が安全です。

頂点と法線の整合

頂点が同一平面上にあるか、法線ベクトルがゼロでないかを確認します。二次元図面に見えても、Z座標に微小な差が混入していることがあります。許容差は用途と図面単位に合わせて決めます。

表示形状が必要なら仮想エンティティへ展開する

ezdxfのvirtual_entities()は、MLINEをLINE、ARC、HATCHなどの仮想図形へ展開します。これらは元DXFへ登録されず、ハンドルも持ちません。プレビュー、境界ボックス、他形式への変換に便利ですが、参照線やスタイル構造を失うため、原構造の棚卸し結果と分けて扱います。

explode()は展開した図形をレイアウトへ登録し、元のMLINEを破棄します。検査だけの処理で使用すると原本を書き換えるため、読み取り工程では避けます。

よくある失敗

MLINEを中心線だけのポリラインとして変換する

コード11の頂点列だけでは、複数線のオフセット、端部、交差部、塗りつぶしを再現できません。中心線が必要なのか、見た目を保持したいのか、編集可能なMLINEを維持したいのかを先に決めます。

コード2のスタイル名だけを書き換える

実際の参照はコード340です。名前だけ変更すると、表示名と参照先が食い違います。高水準APIのset_style()など、参照更新と形状再生成をまとめて行う方法を使います。

コード41と42を平坦な配列にする

パラメータは「頂点→スタイル要素→値」の階層です。コード74と75が示す個数を使い、出現順を保って格納します。

extrusionがあるのでOCS変換する

MLINEの頂点はWCSです。法線が存在することだけを理由にOCSからWCSへ再変換すると、座標を二重変換してしまいます。

スタイル変更後に形状を更新しない

線要素数やオフセットを変えると、既存の頂点パラメータとの対応が変わります。スタイル定義だけを編集せず、関連MLINEの形状更新と再検査を行います。

変換ログに残すべき情報

MLINEを別形式へ変換するときは、次の情報を残すと追跡しやすくなります。

  • MLINEのハンドル、画層、尺度、位置合わせ、フラグ
  • スタイル名とスタイルハンドル
  • スタイル要素ごとのオフセット、色、線種
  • 頂点数とWCS座標、法線
  • 頂点ごとの線・塗りつぶしパラメータ数
  • 閉鎖状態と始終端キャップ状態
  • 参照先不明、宣言数不一致、非平面頂点の件数
  • 仮想図形へ展開したか、元のMLINE構造を保持したか
  • 使用ライブラリとバージョン

見た目だけを保持する変換では仮想図形への展開が有効ですが、元へ戻せるとは限りません。再編集や差分追跡が必要なら、原MLINEの構造化データも同時に保存します。

まとめ

DXFのMLINEは、参照線、共有MLINESTYLE、頂点ごとの派生パラメータを組み合わせて複線形状を表現します。コード11の頂点列だけでは表示形状を再現できず、コード340のスタイル参照と、コード74・41、75・42の入れ子構造が重要です。

解析では、最初にMLINEとMLINESTYLEの関連、宣言数、WCS頂点、パラメータ階層を保存します。表示用のLINE、ARC、HATCHへの展開は、その後に派生処理として行います。この順序なら、CAD間変換で見た目を保つ処理と、編集可能な構造を保つ処理を明確に分けられます。

参考情報

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

この記事を書いた人

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

目次