6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DXF/DWG解析の時に使えるezdxfを使った便利技

Last updated at Posted at 2025-12-08

はじめに

こんにちは!トグルホールディングスでエンジニアをしている木下隼です。
toggle Advent Calendar 2025の8日目となります!自分は業務で建築図面の解析などをしていまして、その中で便利だと感じたezdxfの使い方をTipsのような形式で紹介したいと思いあます!

目次

  • DXFとは
  • ezdxfとは
  • ezdxfとmatplotlibを使った描画
  • ODA File Converterを使ったDWGファイルへの対応
  • EdgeSmithとShapelyを使った図面の構造解析

DXFとは

図面などのCADデータを扱うデータ形式で、CADソフト大手のAutodeskが開発した形式です。
他のCADソフトとも横断的にデータを扱うことが可能な形式で、内部はテキストデータになっているためメモ帳などでも開くことができます。

AutodeskにはDWGという標準形式もあり、こちらをezdxfで解析するには後述のアドオン設定が必要になります。

ファイルの中身について

以下の項目ごとにデータが書かれています。
特に重要なのはENTITIESとBLOCKSになります。

  • HEADER(ヘッダー): 図面の設定や変数(単位、縮尺など)
  • CLASSES(クラス): アプリケーション定義のクラス情報
  • TABLES(テーブル): レイヤー、線種、文字スタイルなどの定義
  • BLOCKS(ブロック): 図面内で繰り返される要素(部品)の定義
  • ENTITIES(エンティティ): 実際の図形データ(線、円、円弧など)
  • OBJECTS(オブジェクト): 図形以外のデータ

ENTITIESにはLINE(線)やARC(円弧)、INSERT(BLOCKSを挿入しているもの)などがあり、最も参照する機会が多いセクションになります。

記事で使用するDXFファイル

こちらのサンプルデータを使用していきます。

image.png

ezdxfとは

ezdxfは、Pythonを使ってDXFファイルを作成、読み込み、操作するためのライブラリになります。
大体のプログラミング言語にはDXFファイルを上記のデータを扱いやすくするパースライブラリは存在しますが、ezdxfはそれ以上に解析で使える機能や、外部ライブラリとの連携で活用できる機能が豊富にあります。

今回はその中でも自分が使っていて便利だった機能や、設定などで詰まったところ、公式ドキュメントには書かれていないけど、コードの中で用法が解説されているような機能についてご紹介できたらと思います!

基本的なオブジェクト

大体以下の2つを基準に扱うとわかりやすいです

  • doc: Drawing
    • DXFを読み込んだ時の形式で中心的なオブジェクト
  • msp: Modelspace
    • モデルスペース、図面上のエンティティを読み込んだり書き込んだりするときにこの形式で行う
import ezdxf
from ezdxf.document import Drawing
from ezdxf.layouts import Modelspace

doc: Drawing = ezdxf.readfile("R12-A3F100.dxf") # 読み込む時の形式
msp: Modelspace = doc.modelspace()              # 色々操作できる形式

DXFをmatplotlibで描画する

読み込んだDXFを描画してみましょう。matplotlibへ描画をさせることができます。
(追加でmatplotlibを入れておく必要があります)

import matplotlib.pyplot as plt
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
ctx = RenderContext(doc)
out = MatplotlibBackend(ax)
Frontend(ctx, out).draw_layout(doc.modelspace(), finalize=True)
plt.show()

matplotlibのaxオブジェクトを指定してそこに描画することができます。
ドキュメント:matplotlib.MatplotlibBackend

image.png

デフォルトだと図面がこの配色になります。
しかし、背景が白の方が実際の図面のように見やすいので変更しましょう。Configで設定する必要があります。

import matplotlib.pyplot as plt
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
from ezdxf.addons.drawing import config

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
ctx = RenderContext(doc)
out = MatplotlibBackend(ax)
cfg = config.Configuration(background_policy=config.BackgroundPolicy.WHITE)
Frontend(ctx, out, config=cfg).draw_layout(doc.modelspace(), finalize=True)
plt.show()

image.png

よく見る感じの背景白で、黒い線で引かれた図面になりました!
ドキュメント: ezdxf.addons.drawing.config.Configuration

draw系関数の注意点

描画する上で、「エンティティのこの要素だけ抜き出したい!」ということが発生するかと思います!(自分は発生しました!)
mspから特定のエンティティを抜き出すのは以下のようなクエリを使うとすごく便利です。

msp = doc.modelspace()
entities = msp.query("LINE[linetype=='Continuous']")

これはDXFから実線のみを取得するクエリになります。しかしこのエンティティをdraw_entitiesという関数で描画しようとすると以下のように描画がうまくいきません。

image.png

自分でも描画できないかを色々試しましたが、そもそもdraw_entitiesはドキュメントには書かれておらず、自分がdeepwikiでたまたま見つけた関数でした。使い方が分からず断念...
そこでdraw_layoutをそのまま使う方法を考えました。

描画で便利なフィルター関数

draw_layoutにはfilter_funcという引数を受け取る仕組みがあります。自分で定義したfilter関数を使って表示したいエンティティだけを表示させることができます。

import matplotlib.pyplot as plt
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
from ezdxf.addons.drawing import config

def filter_func(entity: ezdxf.entities.DXFGraphic) -> bool:
    return entity.dxftype() == "LINE"

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
ctx = RenderContext(doc)
out = MatplotlibBackend(ax)
cfg = config.Configuration(background_policy=config.BackgroundPolicy.WHITE)
Frontend(ctx, out, config=cfg).draw_layout(msp, finalize=True, filter_func=filter_func)
plt.show()

image.png

こうしたエンティティタイプだけのフィルタ以外にも、90度、もしくは270度の円弧だけといったコードも使えます。

def is_90_270_degree(arc: ezdxf.entities.Arc) -> bool:
    s = arc.dxf.start_angle
    e = arc.dxf.end_angle
    angle = abs(s - e)
    eps = 10.0
    is_90_degree = abs(angle - 90) < eps
    is_270_degree = abs(angle - 270) < eps
    return is_90_degree or is_270_degree


def filter_func(entity: ezdxf.entities.DXFGraphic) -> bool:
    return entity.dxftype() == "ARC" and is_90_270_degree(entity)

ドキュメント: filter_func

DWG形式の入力に対応する

CADにはDWG形式のデータも存在しています(Autodeskの標準データ形式)。
ODAFileConverterというDWGとDXFの変換や、バージョンのダウングレードなどができるソフトウェアです。

こちらを使ってDWG形式のデータを扱うこともezdxfではできるようになっています。
ひとまずODAFileConverterをサイトからダウンロードをしてインストールしてきます。

そしたら作業中のディレクトリに ezdxf.ini ファイルを作成し、インストール先のパスを設定します。

[odafc-addon]  
unix_exec_path = "/Applications/ODAFileConverter.app/Contents/MacOS/ODAFileConverter"

以下のように読み込めば、あとは普通にDXFの時と同じく使うことができます。

from ezdxf.addons import odafc

doc = odafc.readfile("2000-A3F100.dwg")
msp = doc.modelspace()
    
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
ctx = RenderContext(doc)
out = MatplotlibBackend(ax)
cfg = config.Configuration(background_policy=config.BackgroundPolicy.WHITE)
Frontend(ctx, out, config=cfg).draw_layout(msp, finalize=True)
plt.show()

image.png

EdgeSmithとShapelyを使った図面の構造解析

図面を解析する上でShapelyなどのラインやポリゴンデータにできた方が便利だったりします。
その場合は、クエリを使ってLINEなどの要素を抽出しつつ、 edgesmith.edges_from_entities_2d でエッジエンティティに全て変換してしまうのが簡単です。

def load_multilinestring(doc, query_types=["LINE", "POLYLINE", "ARC", "INSERT"]):  
    msp = doc.modelspace()    
    query = " ".join(query_types)    
    entities = msp.query(query)    
        
    # POLYLINEを個々のLINEとARCに変換
    exploded_entities = []
    for entity in entities:
        if entity.dxftype() == "POLYLINE":
            # virtual_entities()を使用して安全に変換
            exploded_entities.extend(entity.virtual_entities())  
        elif entity.dxftype() == "INSERT":
            exploded_entities.extend(entity.explode())
        else:
            exploded_entities.append(entity)
        
    lines = [  
        LineString([edge.start, edge.end])
        for edge in edgesmith.edges_from_entities_2d(exploded_entities)  
        if edge.start != edge.end    
    ]  
    return MultiLineString(lines)

def dxf_to_polygons(path: str):
    doc = ezdxf.readfile(path)
    mls = load_multilinestring(doc)
    merged = unary_union(mls)
    return list(polygonize(merged))

Shapely関連の解説はしませんが、クエリは空白で複数の条件を並べることができます。POLYLINEやINSERTは、そのままだとエッジにできないので、内部のLINEやARCエンティティへ分解しています。
edgesmith.edges_from_entities_2d はドキュメントに載ってない関数ですが、これを使うとAPCなどのエンティティも強制的にエッジに変換してくれます。

image.png

結果ですが、ezdxfのMatplotlibアドオンと、Shapelyのplot_polygonを使っています。matplotlibを使うとこのように柔軟な可視化もできて便利かなと思います。

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])
ctx = RenderContext(doc)
out = MatplotlibBackend(ax)
cfg = config.Configuration(background_policy=config.BackgroundPolicy.WHITE)
Frontend(ctx, out, config=cfg).draw_layout(msp, finalize=True)
plot_polygon(multi_polygon, ax=ax, add_points=False)
plt.show()

図面のズレを補正

これはezdxfの機能ではないですが、DXFの図面を解析していると、線同士が綺麗にくっついていないことがあります。その場合、上記の方法でMultiLineStringを作成したあと、shapelyで補正をかけるといい感じになりました。

def msp_to_polygons(mls: MultiLineString):
    precision_mls = set_precision(mls, 0.0001)
    # ジオメトリの座標精度を 0.0001 にそろえて丸め誤差を抑える

    snap_tolerance = min(line.width for line in lines) * SNAP_TOLERANCE_RATIO
    # 与えられた線幅群の最小値からスナップ許容差を算出(lines 変数が事前に必要)

    snapped_mls = snap(mls, mls, snap_tolerance)
    # 許容差内にある節点をまとめて自己スナップし、断片のズレを吸収

    noded_mls = construct_node(mls)
    # MultiLineString をノード化して全交点を明示的に作成

    return list(polygonize(noded_mls))
    # ノード化した線分群から閉じたポリゴンを生成し、リスト化して返す

以上となります。DXFやDWGを読み込んで、エッジへ分解し、そこからShapelyで解析しやすくするという流れがこれで作成できました!
解析した結果も、matplotlibに統合して重ねて表示させればデバッグしやすくて助かっています。

ただ、ezdxf内にはもっとshapelyやGEOSに対応した機能もあるらしいですが、私の方ではそこまで試すことができなかったので、今後他の機能も触っていきたいなと思いました。


参考記事

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?