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:
转化后的效果:
还是挺不错的,Blender牛逼!开源大法好!