フリーソフトで2D断面作成

iPhone LiDAR(Scaniverse)とCloudCompareで作る!手軽で高精度な3D断面➡2D断面図の作成手順

iPhoneアプリ「Scaniverse」で取得した点群データから、CADで使える2次元の横断図(距離と標高のプロット)をサクッと作成する一連のワークフローをご紹介します!

現地での調査からCAD作業に入るまでの時間を劇的に短縮できるので、ぜひ試してみてください。

STEP 1:Scaniverseでデータ取得

まずは現場でiPhoneを使い、対象となる地形や水路などをScaniverseでスキャンします。取得した3Dモデルは、点群データとして「LAS形式」でエクスポートし、PCに取り込みます。

STEP 2:CloudCompareへの取り込みとノイズ除去

PCで無料の点群処理ソフト「CloudCompare」を立ち上げ、LASデータを読み込みます。
自然地形をスキャンすると、雑草や落ち葉、スキャン時のブレによる空中のノイズが含まれるため、以下の方法で事前に綺麗にしておきます。

  • SORフィルターで自動除去: Tools > Clean > SOR filter を使い、空中に浮いた細かい孤立点を一掃します。
  • ハサミツールで手動カット: 明らかに邪魔な草などは、画面上部のハサミアイコン(Segmentツール)で囲って切り取ります。これが一番確実です。

STEP 3:3D断面(DXF)の抽出

綺麗になった点群から断面を切り出します。

  1. メニューの「Cross Section(断面ツール)」を起動。
  2. 枠(クリッピングボックス)のサイズを調整します。ここで重要なのが「スライスの厚み(Z軸など奥行き方向)を限界まで薄くする(デォでok)」こと!分厚いと手前と奥のノイズを拾ってしまい、断面がギザギザになってしまいます。
  3. パネルの「点群の抽出ボタン(緑の下矢印)」を押し、切り出された点群をDXF形式で保存します。

(注意:この時、出力された3DのDXFをLibreCADなどの2D専用CADで開いて上書き保存しないでください。高さデータが消えてしまいます)

STEP 4:Pythonで2D DXFへ一括変換

CloudCompareから出力したDXFは「3Dデータ」のため、真上から見るとただの直線になってしまいます。CADで横断面として扱うために「X軸=水平距離、Y軸=最低地点からの比高」に計算し直した2Dデータへ変換します。

今回は、指定したフォルダ内にあるすべてのDXFファイルを一括で2次元化するPythonスクリプトを作成しました。以下のコードをコピーして実行するだけで、一瞬で変換が完了します!

import os
import glob
import math
import re
import tkinter as tk
from tkinter import filedialog

def batch_convert_to_2d_dxf():
    # 画面を隠す
    root = tk.Tk()
    root.withdraw()

    # フォルダ選択ダイアログ
    dir_path = filedialog.askdirectory(title="DXFファイルが入っているフォルダを選択してください")

    if not dir_path:
        print("フォルダ選択がキャンセルされました。")
        return

    print(f"選択されたフォルダ: {dir_path}")

    # フォルダ内のdxfファイルを検索(すでに変換済みの _2D.dxf は除外)
    search_pattern = os.path.join(dir_path, "*.dxf")
    all_dxf_files = glob.glob(search_pattern)
    target_files = [f for f in all_dxf_files if not f.lower().endswith("_2d.dxf")]

    if not target_files:
        print("変換対象のDXFファイルが見つかりませんでした。")
        return

    print(f"\n{len(target_files)}個のファイルを一括処理します...\n")
    success_count = 0

    for file_path in target_files:
        print(f"処理中: {os.path.basename(file_path)}")
        try:
            with open(file_path, 'r', encoding='shift_jis', errors='ignore') as f:
                dxf_text = f.read()
                
            # X, Y, Z座標を抽出
            pattern = r"10\n\s*([-\d\.]+)\n\s*20\n\s*([-\d\.]+)\n\s*30\n\s*([-\d\.]+)"
            matches = re.findall(pattern, dxf_text)

            vertices = [(float(m[0]), float(m[1]), float(m[2])) for m in matches]
            
            # ダミー座標(0,0,0)を除外
            vertices = [v for v in vertices if v != (0.0, 0.0, 0.0)]

            if not vertices:
                print(f"  -> スキップ: 有効な頂点データがありませんでした。")
                continue

            # 地盤の最低標高を取得し、それをY=0の基準とする
            min_z = min(v[2] for v in vertices)

            p2d_vertices = []
            accum_dist = 0.0
            prev_x, prev_y, prev_z = vertices[0]
            
            p2d_vertices.append((0.0, prev_z - min_z, 0.0))
            
            for i in range(1, len(vertices)):
                cx, cy, cz = vertices[i]
                # ピタゴラスの定理で水平距離を計算
                dist_step = math.sqrt((cx - prev_x)**2 + (cy - prev_y)**2)
                accum_dist += dist_step
                
                # 最低地点からの比高をY座標にセット
                relative_y = cz - min_z
                p2d_vertices.append((accum_dist, relative_y, 0.0))
                
                prev_x, prev_y, prev_z = cx, cy, cz

            # シンプルな2D DXFを組み立て
            output_dxf = [
                "  0", "SECTION",
                "  2", "ENTITIES",
                "  0", "POLYLINE",
                "  8", "0",
                " 66", "1",
                " 70", "0",
                " 10", "0.0", " 20", "0.0", " 30", "0.0"
            ]
            
            for vx, vy, vz in p2d_vertices:
                output_dxf.extend([
                    "  0", "VERTEX",
                    "  8", "0",
                    " 10", f"{vx:.6f}",
                    " 20", f"{vy:.6f}",
                    " 30", f"{vz:.6f}"
                ])
                
            output_dxf.extend(["  0", "SEQEND", "  8", "0", "  0", "ENDSEC", "  0", "EOF"])

            # 保存
            base_name = os.path.splitext(os.path.basename(file_path))[0]
            out_file_path = os.path.join(dir_path, f"{base_name}_2D.dxf")

            with open(out_file_path, 'w', encoding='shift_jis') as f:
                f.write("\n".join(output_dxf))

            print(f"  -> 成功: {len(p2d_vertices)}頂点 (最低標高 {min_z:.2f}m基準)")
            success_count += 1

        except Exception as e:
            print(f"  -> エラー: {e}")

    print(f"\n完了! {len(target_files)}ファイル中、{success_count}ファイルの変換に成功しました。")

if __name__ == "__main__":
    batch_convert_to_2d_dxf()
    input("Enterキーを押して終了...")

STEP 5:CADで読み込んで仕上げ

出力された _2D.dxf をAutoCADなどのCADソフトで開きます。
すでに平面上のグラフとして展開されているので、視点変更の必要はありません。

高価な専用システムがなくても、このワークフローなら高精度かつスピーディーに現況断面を取得できます。ぜひお試しください!

コメント

  1. もちろん、QGISでも標高断面図から2D-dxf出力できるが断面幅が広く、加工が必要。

    返信削除

コメントを投稿

このブログの人気の投稿

IJCAD(AutoCAD互換CAD) コマンド早見表

設計定数を求めるための代表N値について

日本でよく使う EPSG コード 一覧