使用Python把CAD高程图实体化为3D模型

因为设计课程的需要,我需要为场地建立一个直观的3D模型进行形体推敲。但是这次老师给的CAD图纸只有高程点,等高线是缺失的,所以我希望是利用这些高程点的信息进行自动化建模,快速生成场地地形模型。

转换CAD

首先,因为CAD比较凌乱,我们需要整理出一个只包含了高程点的CAD图,并且导出为DXF格式,方便后续的Python程序进行对接。
原CAD:
01.jpg
处理后的CAD:
02.jpg
处理完成后,导出DXF格式的文件,为Python程序化建模做准备。

转化程序

因为blender的Python环境不方便安装别的库,所以处理程序分为两个部分:

  • 第一部分使用系统安装的Python环境进行处理,使用json格式保存转好后的中间结果,也就是每个点的坐标.
  • 第二部分使用Blender的Python环境,读取已经处理好的地形信息json文件,进行建模。

首先是第一部分的处理dxf文件:

import ezdxf
import json
import re
import os

# --- 配置文件路径 ---
DXF_FILE_PATH = "Drawing2.dxf"
JSON_OUTPUT_PATH = "topography.json"

def dxf_to_json(dxf_path, json_path):
    print(f"正在读取 DXF 文件: {dxf_path} ...")

    if not os.path.exists(dxf_path):
        print(f"错误:找不到文件 '{dxf_path}'。")
        return

    try:
        doc = ezdxf.readfile(dxf_path)
    except Exception as e:
        print(f"读取 DXF 失败: {e}")
        return

    msp = doc.modelspace()
    points = [] # 用于存储坐标的列表

    for entity in msp.query('TEXT MTEXT'):
        try:
            text_str = entity.dxf.text
            # 提取数字(支持负数和小数)
            match = re.search(r'[-+]?\d*\.?\d+', text_str)
            if match:
                z = float(match.group())
                x = entity.dxf.insert.x
                y = entity.dxf.insert.y
                # 将坐标作为一个小列表存入总列表
                points.append([x, y, z])
        except Exception:
            continue

    if points:
        # 将数据转储为 JSON 格式
        with open(json_path, 'w', encoding='utf-8') as f:
            json.dump(points, f, indent=2) # indent=2 让 JSON 文件格式化,方便人类阅读

        print(f"✅ 成功!共提取了 {len(points)} 个坐标点。")
        print(f"数据已保存为 JSON 文件: {json_path}")
    else:
        print("❌ 提取失败:未找到有效数据。")

# 运行脚本
dxf_to_json(DXF_FILE_PATH, JSON_OUTPUT_PATH)

以上代码在系统的Python环境运行,记得安装对应的Python库ezdxf
然后是在Blender里面运行的Python代码:

import bpy
import json
import mathutils

# --- 配置参数 ---
JSON_FILE_PATH = "/run/media/chocola/3T-DATA02/设计/场地模型/topography.json"

def create_terrain_from_json(filepath):
    # 1. 读取 JSON 数据
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            points = json.load(f)
    except Exception as e:
        print(f"❌ 读取 JSON 失败: {e}")
        return

    if not points:
        print("❌ JSON 文件中没有数据!")
        return

    print(f"成功读取 {len(points)} 个坐标点,开始处理...")

    # 2. 计算边界框中心,处理坐标过大导致的浮点精度问题
    min_x = min(p[0] for p in points)
    max_x = max(p[0] for p in points)
    min_y = min(p[1] for p in points)
    max_y = max(p[1] for p in points)

    center_x = (min_x + max_x) / 2.0
    center_y = (min_y + max_y) / 2.0

    verts_3d = []
    verts_2d = [] # Delaunay 算法只需要 2D 平面坐标

    for p in points:
        # 减去中心点坐标,将整个地形移至原点 (0,0,0) 附近
        cx = p[0] - center_x
        cy = p[1] - center_y
        cz = p[2]

        verts_3d.append((cx, cy, cz))
        verts_2d.append(mathutils.Vector((cx, cy)))

    # 3. 执行 2D 德劳内三角剖分
    print("正在计算地形拓扑结构...")
    # delaunay_2d_cdt 返回值包含: (vertices, edges, faces, is_valid)
    # 我们只需要提取面 (faces) 的索引数据
    try:
        result = mathutils.geometry.delaunay_2d_cdt(verts_2d, [], [], 0, 0.0001)
        faces = result[2]
    except Exception as e:
        print(f"❌ 三角剖分计算失败: {e}")
        return

    # 4. 创建 Blender 网格并赋予数据
    mesh = bpy.data.meshes.new("Terrain_Mesh")
    # from_pydata 极其高效:直接通过顶点列表和面列表生成网格
    mesh.from_pydata(verts_3d, [], faces)

    # 开启平滑着色
    for poly in mesh.polygons:
        poly.use_smooth = True
    mesh.update()

    # 5. 生成物体并放入场景
    obj = bpy.data.objects.new("Site_Terrain", mesh)
    bpy.context.collection.objects.link(obj)

    # 选中生成的物体
    bpy.ops.object.select_all(action='DESELECT')
    bpy.context.view_layer.objects.active = obj
    obj.select_set(True)

    print(f"✅ 地形生成完毕!")
    print(f"提示: 原 CAD 坐标中心点已偏移:X={center_x:.2f}, Y={center_y:.2f}")

# 运行脚本
create_terrain_from_json(JSON_FILE_PATH)

最终效果

按顺序执行了两个程序后,你的Blender里面应该就已经出现了对应的地形文件了。当然,是单面的模型,后续可以进行实体化操作等按需调整。
x_20260304_120919.png
还有一个需要提醒的是,生成的模型是三角面的模型,并不是Blender建模推荐的四边形建模,并且布线并不规整。所以这个模型是能用,但是不方便修改。