図面の変更箇所を見える化する!DXF差分比較ツールを作ってみました【ezdxf】

  • URLをコピーしました!

図面の修正版が送られてきたとき、「前の版から結局どこが変わったのか」を目で追って確認するのは、地味にしんどい作業です。
線が1本増えただけなのか、寸法が動いたのか、レイヤーだけ変わったのか——目視だけで見比べるのは限界があります。
dxfをプログラム作図・情報取得できるpythonのライブラリezdxfを使って、建設業共通のこのお悩みを解決するツールを研究してみたので、その内容をご紹介致します!

そこで、2つのDXFファイルを読み込んで差分を自動で色分け判定するロジックをPython(ezdxf)で組んでみました。この記事では、その判定ロジックの中身をまとめます。

図面の差分比較、実は「有償級」の機能

実はこの「図面のビフォーアフターを色分けする」という機能自体は、目新しいものではありません。むしろちゃんとお金を払って使うのが当たり前の、業務用の機能です。ざっと調べてみました。

ツール特徴価格の目安
AutoCAD
「DWG Compare」
AutoCAD 2019以降に標準搭載。グレー=共通/赤=比較先のみ/緑=現図面のみで色分け、リビジョンクラウドも自動生成AutoCAD本体のサブスクリプションが前提
ProtoTech
「DWG/DXF Compare」
AutoCAD用の比較専用プラグインフローティング $399/オートメーション $299
DWGSee CADDXF/DWG専用ビューア+比較機能を搭載$198(買い切り)
Bluebeam Revu
「Overlay Pages」
建設業の図面レビューで定番のPDF重ね合わせツール有償ソフト(サブスクリプション)
MIIDEL(ミーデル)AI画像認識でDXF/DWG/PDF等の差分を自動検出。建設業界で500件超の導入実績要問い合わせ(法人向け見積もり制)

どれも実務で十分使える、いわば「お金を払って解決する」レベルの機能です。事務所に1本しかCADライセンスが無い、協力会社側にはそもそも専用ソフトが無い、といった場面では気軽に使えません。

つかだ

「この有償級の機能を、手元のPythonだけで無料で再現できないか?」と思ったのが、このツールを作ったきっかけです!

ezdxfの基本的な使い方は「ezdxfを使ってみよう」シリーズでまとめています。
この記事は、そこで扱った図形の読み込み・属性取得の実践編にあたります。

目次

できること

  • DXF①(元図面)とDXF②(更新図面)を読み込み、差分を色分けしたDXF③を自動生成
  • ジオメトリの変更だけでなく、線色・線太・線種・レイヤーといった属性だけの変更も別扱いで検出
  • Z座標だけが変わった要素(平面上は同じ位置)も見分けて黄色ドットで表示
  • 差分結果をExcelに一覧出力(要素種別・変化区分・座標・変化内容を色分き一覧に)
つかだ

必要な準備としては、変更前図面と変更後図面をどちらもdxfに変換しておいてください。

準備サンプル図面

DXF①(元図面)

DXF②(更新図面)

実行例

テスト用のDXFで実際に比較した結果です。

意味
変更なし
削除(①にあって②にない)
追加(②にあって①にない)
形状変更(位置・サイズなどが変わった)
ピンク属性変更のみ(線色・太さ・線種・レイヤー)
黄ドットZ座標のみ変更
つかだ

目視では気づきにくい「線色だけ変わった」「太さだけ変わった」まで
拾えいるのがポイントです!

仕組み:どうやって「差分」を判定しているか

単純に「同じ場所に同じ図形があるか」を突き合わせるだけでは、実務の図面比較には足りません。実際にはこんな判定を段階的に行っています。

1. 複合エンティティをすべて分解する

比較の前処理として、LWPOLYLINE・POLYLINE・INSERT(ブロック参照)・ELLIPSE・SPLINEなどをすべてLINE / ARC / CIRCLE / TEXT / MTEXTまで分解します。ブロックの中身が1点でも変わっていれば検出したいので、複合図形のまま比較すると粒度が粗すぎるためです。

PRIMITIVES = {'LINE', 'ARC', 'CIRCLE', 'TEXT', 'MTEXT'}

def _flatten_entity(entity, out_msp, out_doc, depth=0):
    if depth > 20:
        return
    t = entity.dxftype()

    if t in PRIMITIVES:
        color = entity.dxf.get('color', 256)
        copy_entity(entity, out_msp, out_doc, color)
        return

    # LWPOLYLINE / POLYLINE / INSERT はezdxfのvirtual_entities()で分解
    if t in ('LWPOLYLINE', 'POLYLINE', 'INSERT', 'SOLID', 'TRACE', '3DFACE'):
        for ve in entity.virtual_entities():
            _flatten_entity(ve, out_msp, out_doc, depth + 1)
        return

    # ELLIPSE / SPLINE は直線近似(flattening)
    if t in ('ELLIPSE', 'SPLINE'):
        pts = list(entity.flattening(FLATTEN_TOLERANCE))
        for i in range(len(pts) - 1):
            out_msp.add_line(pts[i], pts[i + 1], dxfattribs={...})
        return

INSERT(ブロック参照)はvirtual_entities()を再帰的に呼び出すことで、ネストしたブロックも展開できます。ELLIPSEとSPLINEは正確な曲線比較が難しいため、細かい直線の連なりに近似しています。

2. 完全一致 → 白 or ピンク

各図形の座標を丸めて「ジオメトリキー」を作り、①と②で完全一致するものを最初に拾います。
ジオメトリが同じでも、色・太さ・線種・レイヤーが違えば属性変更(ピンク)として区別します。

def geo_key(entity):
    t = entity.dxftype()
    if t == 'LINE':
        s, e = entity.dxf.start, entity.dxf.end
        return ('LINE', _r(s.x), _r(s.y), _r(s.z), _r(e.x), _r(e.y), _r(e.z))
    elif t == 'ARC':
        c = entity.dxf.center
        return ('ARC', _r(c.x), _r(c.y), _r(c.z),
                _r(entity.dxf.radius), _r(entity.dxf.start_angle), _r(entity.dxf.end_angle))
    # CIRCLE / TEXT / MTEXT も同様に座標+固有値でキー化
    ...

def diff_attrs(e1, e2):
    """色・太さ・線種・レイヤーの差分を人間が読める文字列リストで返す"""
    a1, a2 = get_attrs(e1), get_attrs(e2)
    result = []
    if a1['color'] != a2['color']:
        result.append(f'線色変更 {a1["color"]}→{a2["color"]}')
    if a1['lineweight'] != a2['lineweight']:
        result.append(f'太さ変更 {_fmt_lw(a1["lineweight"])}→{_fmt_lw(a2["lineweight"])}')
    # linetype / layer も同様
    return result

3. XYだけ一致 → Z座標変更(黄ドット)

平面図だけを見ていると、Z座標(高さ方向)だけがこっそり変わっているケースを見落とします。XY座標だけで再度キー化して一致を探し、実際にZが変化していれば黄色ドット+注記で警告する仕組みにしています。

4. 残りをファジーマッチング → 紫(形状変更)

完全一致しなかった要素同士は、位置やサイズがどれだけ近いかを0〜1のスコアで比較し、一定以上似ていればペアとみなして「形状変更」として扱います。図面のスケール(数値の大小)に影響されないよう、変位を要素自身のサイズで正規化しているのがポイントです。

def similarity(e1, e2):
    """変位を要素サイズで正規化したスコア(0〜1)。スケール不問。"""
    if e1.dxftype() != e2.dxftype():
        return 0.0
    if e1.dxftype() == 'LINE':
        s1, f1 = e1.dxf.start, e1.dxf.end
        s2, f2 = e2.dxf.start, e2.dxf.end
        len1 = math.hypot(f1.x - s1.x, f1.y - s1.y)
        len2 = math.hypot(f2.x - s2.x, f2.y - s2.y)
        ref  = max((len1 + len2) / 2, 1.0)
        d = (math.hypot(s1.x - s2.x, s1.y - s2.y)
             + math.hypot(f1.x - f2.x, f1.y - f2.y)) / ref
        return 1.0 / (1.0 + d)
    # CIRCLE / ARC / TEXT / MTEXT も同様の考え方でスコア化
    ...

FUZZY_SCORE_THRESHOLD = 0.5  # これを超えたら「同じ要素が形状変更した」とみなす

TEXT・MTEXTなども文字列は、内容(文字列)が違う時点でスコアは0にし、削除と追加として別々に処理しています。文字が違う別のテキストを「移動した」と誤判定しないようにするためです。

つかだ

線の要素の起点終点のいずれかや、円の半径(中心点はそのまま)など、
エンティティ要素の一部が変更されると紫色に判定します!

5. 最後まで余った要素 → 削除・追加

ここまでの判定で誰ともマッチしなかった要素が、純粋な削除(①だけにあった)青追加(②だけにある)赤として扱われます。

Excel出力

差分結果はopenpyxlで一覧表としても出力できます。要素種別・変化区分・レイヤー・座標情報・変化内容の列に、DXF③と同じ色でセルを塗って出力します。

DXFを開かなくてもExcelだけで変更点を確認できます。集計シートで全体の件数もひと目で分かります。

wb = openpyxl.Workbook()
ws = wb.active
ws.title = '差分比較結果'

for row_idx, rec in enumerate(records, 2):
    status = rec['status']
    bg_hex, fg_hex = _EXCEL_STATUS_STYLE.get(status, ('FFFFFFFF', 'FF000000'))
    fill = PatternFill('solid', fgColor=bg_hex)
    values = [row_idx - 1, rec['type'], status, rec['layer'], rec['coord'], rec['detail']]
    for col, val in enumerate(values, 1):
        cell = ws.cell(row=row_idx, column=col, value=val)
        cell.fill = fill
つかだ

変更箇所の数量の確認に有効です!
チェック表としても活躍!

対応している図形・制限事項

現状、比較対象としているのはLINE / ARC / CIRCLE / TEXT / MTEXTの5種類(に分解した後)です。3D要素(3DFACE等)は簡易対応、HATCHなど未対応の図形は比較対象から外れます。あくまで2D図面の変更点チェック用として使ってください。

CADソフト依存の独自要素などは正常に対応しない可能性があります。
あくまでプログラミングCADの研究の一部成果としてご了承ください!

まとめ

  • 複合エンティティをプリミティブまで分解してから比較することで、ブロック内部の細かい変更も拾える
  • ジオメトリ完全一致→属性のみ変更→Z変更→ファジーマッチ→削除/追加の順で段階的に判定
  • ファジーマッチのスコアは要素サイズで正規化しているので、図面のスケールに左右されない
  • DXFの色分け結果とExcel一覧を両方出力するので、CAD操作に不慣れな人への説明にも使える

図形の読み込み・属性取得・座標の扱い方など、ここで使っているezdxfの基本は「ezdxfを使ってみよう」シリーズでまとめています。合わせてご覧ください。

つかだ

図面を2つ比較するだけなので、動作が軽い!
tkinterでUIなどをつければ現場で役立つアカウント不要ツールになります!

関連記事

よかったらシェアしてね!
  • URLをコピーしました!

コメントお待ちしてます!

コメントする

CAPTCHA


目次