Python转dxf(dwg)为Blnder三维模型

由于专业课课程需要,我需要搞个研究地块的场地现状三维模型。虽然已经给了CAD的dwg图形文件,但是,由于现状建筑过多,如果是手动拉模型那工作量将会巨大,于是,我就思考能不能使用程序来自动实现这个目标,节省我的工作时间,提升工作效率。
由于Blender有Python API接口,我有稍微懂一点Python,那就用Python实现吧。

Python读取CAD文件

经过资料检索后,我发现Python是可以利用ezdxf库读取dxf文件的,而CAD的dwg文件可以保存为dxf,那这样来看,Python获取CAD数据的问题就可以解决了。
然后就是直接面向ChatGPT编程了:

import ezdxf
import json
# 读取DXF文件
doc = ezdxf.readfile("/run/media/chocola/Rin/VM_Share_Files/test.dxf")
msp = doc.modelspace()

# 存储结果的大列表
all_polylines = []

# 读取所有LWPOLYLINE
for pline in msp.query('LWPOLYLINE'):
    polyline_data = []
    # 有些多段线的高度保存在 elevation 里
    elevation = pline.dxf.elevation if hasattr(pline.dxf, 'elevation') else 0
    if isinstance(elevation, tuple):  # 有的elevation是一个三元组 (x, y, z)
        z = elevation[2]
    else:
        z = elevation

    # 小列表,第一个元素是高度
    polyline_data.append(z)

    # 遍历顶点
    for point in pline.get_points():
        x, y = point[0], point[1]
        polyline_data.append([x, y])

    all_polylines.append(polyline_data)

# 如果模型里还有3D POLYLINE,也可以处理喵!
for pline in msp.query('POLYLINE'):
    if pline.is_3d_polyline:
        polyline_data = []
        points = pline.vertices  # 注意这里不用加括号!

        if points:
            # 取第一个点的z作为整体高度
            z = points[0].dxf.location.z
        else:
            z = 0

        polyline_data.append(z)

        for v in points:
            x, y = v.dxf.location.x, v.dxf.location.y
            polyline_data.append([x, y])

        all_polylines.append(polyline_data)


# 打印看看喵~
#for poly in all_polylines:
#    print(poly)

with open('polylines_data.json', 'w') as f:
    json.dump(all_polylines, f)

这个程序的作用是,读取dxf文件的数据,分图块整理为列表数据,列表的每一个元素又是一个列表,第一个为平面图块的高度信息,后面的列表元素则为各个顶点x坐标y坐标数据,这个数据又是一个列表。如果是完全平面的CAD,那高度就是0。然后将结果保存为json文件。记得将文件路径改为正确的文件路径。
由于Blender的Python环境比较残废,所以这个转化需要在另一个Python环境进行。

Blender利用Python导入转化的数据

这部分需要读取前面转化的数据,并利用Blender Python API建模。代码需在Blender的脚本编辑器中执行:

import bpy
import bmesh
import math
import json

with open('/run/media/chocola/6T-DATA/Projects/Get_Information/polylines_data.json', 'r') as f:
    data = json.load(f)


def create_extruded_mesh(verts2d, height, name):
    """
    根据 2D 顶点列表创建底面,并沿 Z 轴拉伸成几何体。
    直接使用输入顶点顺序,确保图形正确。

    参数:
      verts2d (list of tuple): [(x, y), ...],底面顶点,顺序正确
      height (float): 拉伸高度
      name (str): 创建的对象和 mesh 名称前缀

    返回:
      bpy.types.Object: 新创建并拉伸好的 Object
    """
    # —— 1. 创建 Mesh 和 Object ——
    mesh = bpy.data.meshes.new(name + "_Mesh")
    obj = bpy.data.objects.new(name, mesh)
    bpy.context.collection.objects.link(obj)
    bpy.context.view_layer.objects.active = obj
    obj.select_set(True)

    # —— 2. 用 BMesh 在 XY 平面创建底面 ——
    bm = bmesh.new()
    bm_verts = [bm.verts.new((x, y, 0.0)) for x, y in verts2d]  # 直接用原始数据
    bm.verts.ensure_lookup_table()
    bm.faces.new(bm_verts)
    bm.to_mesh(mesh)
    bm.free()

    # —— 3. 挤出并拉伸 ——
    bm = bmesh.new()
    bm.from_mesh(mesh)
    bm.faces.ensure_lookup_table()
    base_face = bm.faces[0]
    res = bmesh.ops.extrude_face_region(
        bm, geom=[base_face] + base_face.edges[:] + base_face.verts[:]
    )
    geom_extruded = [ele for ele in res["geom"] if isinstance(ele, bmesh.types.BMVert)]
    bmesh.ops.translate(
        bm,
        verts=geom_extruded,
        vec=(0.0, 0.0, height)
    )
    bm.to_mesh(mesh)
    mesh.update()
    bm.free()

    return obj



buildingsX = 0

for poly in data:
    buildingsX += 1
    height = poly[0]                         # 取第一个数,拉伸高度
    points_x = [tuple(item) for item in poly[1:]]  # 把后面每个 [x, y] 转成 (x, y)

    obj = create_extruded_mesh(points_x, height, f"buildings{buildingsX}")

将以上代码粘贴到Blender的脚本界面,执行,就能得到结果了。记得将文件路径改为正确的文件路径。
拉伸的高度是根据文件里面的高度数据创建的,如果没有高度的话,那就不会得到体块,需要在函数调用部分自己指定高度。例如将height = poly[0]改为height = 6
这里要注意一下,如果数据量特别大,那执行的时间会非常漫长,还会出现假死的情况。建议还是控制好区域大小,不要太大。

最终效果展示

待转化的CAD:
x_20250427_150149.jpg
转化后的效果:
x_20250427_150325.jpg
还是挺不错的,Blender牛逼!开源大法好!