Matplotlib绘制3D凸多面体:消除共面单纯形冗余边的方法咨询
我之前也踩过这个坑!用凸包生成的3D多面体满是细碎的三角面,冗余线条看得人头疼,尤其是想复现Matlab plotregion那种干净的外表面效果时。这里有几个实用的解决方案,按省心程度排序:
1. 最省心:用专门的凸多面体库直接生成外表面
既然你的多面体是由不等式定义的,完全不用先求顶点再做凸包——直接用pycddlib(对应Matlab的cdd工具箱)从不等式组生成完整的外表面面,一步到位拿到干净的多边形面,没有冗余三角面。
示例代码:
import pycddlib as cdd import numpy as np from mpl_toolkits.mplot3d import Axes3D, Poly3DCollection import matplotlib.pyplot as plt # 把你的不等式转换为 Ax ≤ b 的形式 # 示例:x≥0, y≥0, z≥0, x+y+z ≤1 A = np.array([[-1, 0, 0], [0, -1, 0], [0, 0, -1], [1, 1, 1]]) b = np.array([0, 0, 0, 1]) # 转换为cdd要求的格式:每行是 [b_i, -A_i](齐次坐标形式) mat = np.hstack((b.reshape(-1, 1), -A)) poly = cdd.Polyhedron(mat) # 提取顶点和外表面面 vertices = np.array(poly.get_generators())[:, 1:] # 去掉齐次坐标的首元素 faces = [] for face_idx in poly.get_faces(): face_vertices = vertices[face_idx] faces.append(face_vertices) # 绘制干净的多面体 fig = plt.figure() ax = fig.add_subplot(111, projection='3d') poly3d = Poly3DCollection(faces, alpha=0.5, edgecolor='k') ax.add_collection3d(poly3d) # 适配坐标轴范围 ax.set_xlim(vertices[:,0].min(), vertices[:,0].max()) ax.set_ylim(vertices[:,1].min(), vertices[:,1].max()) ax.set_zlim(vertices[:,2].min(), vertices[:,2].max()) plt.show()
2. 次省心:用trimesh合并共面三角面
如果已经有了凸包的三角面数据,用trimesh库可以一键合并共面的细碎三角面,它封装了所有复杂的判断和合并逻辑,代码量极小。
示例代码:
import trimesh import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D, Poly3DCollection from scipy.spatial import ConvexHull # 替换成你的顶点数据 points = np.random.rand(10, 3) hull = ConvexHull(points) # 用trimesh加载凸包网格,合并共面 mesh = trimesh.Trimesh(vertices=points, faces=hull.simplices) mesh = mesh.merge_coplanar(tol=1e-6) # 合并共面,tol是浮点误差阈值 # 转换为Matplotlib可绘制的格式 faces = mesh.faces vertices = mesh.vertices # 绘图 fig = plt.figure() ax = fig.add_subplot(111, projection='3d') poly3d = Poly3DCollection([vertices[face] for face in faces], alpha=0.5, edgecolor='k') ax.add_collection3d(poly3d) ax.set_xlim(vertices[:,0].min(), vertices[:,0].max()) ax.set_ylim(vertices[:,1].min(), vertices[:,1].max()) ax.set_zlim(vertices[:,2].min(), vertices[:,2].max()) plt.show()
3. 纯Matplotlib实现:手动合并共面三角面
如果不想引入第三方库,就手动识别共面的三角面,把它们合并成一个完整的多边形:
- 计算每个三角面的归一化法向量,法向量几乎相同的面就是共面的
- 提取共面组内的所有唯一顶点,投影到垂直于法向量的平面后按角度排序,得到有序的多边形顶点
- 用
Poly3DCollection绘制合并后的多边形
示例代码:
import numpy as np from mpl_toolkits.mplot3d import Axes3D, Poly3DCollection import matplotlib.pyplot as plt from scipy.spatial import ConvexHull # 替换成你的顶点数据 points = np.random.rand(10, 3) hull = ConvexHull(points) # 计算每个面的归一化法向量 normals = hull.equations[:, :3] normals = normals / np.linalg.norm(normals, axis=1, keepdims=True) # 按法向量分组共面的面(考虑浮点误差) tol = 1e-6 groups = [] used = np.zeros(len(hull.simplices), dtype=bool) for i in range(len(hull.simplices)): if used[i]: continue same_normal = np.all(np.abs(normals - normals[i]) < tol, axis=1) group = hull.simplices[same_normal] used[same_normal] = True groups.append(group) # 合并每组共面的三角面为多边形 faces = [] for group in groups: # 提取组内唯一顶点 vertex_indices = np.unique(group.flatten()) face_points = points[vertex_indices] # 获取当前面的法向量 normal = normals[np.where(same_normal)[0][0]] # 投影到垂直于法向量的平面,按角度排序顶点 u = np.array([1,0,0]) if np.abs(np.dot(normal, [1,0,0])) < 0.9 else np.array([0,1,0]) u = u - np.dot(u, normal)*normal u /= np.linalg.norm(u) v = np.cross(normal, u) proj = np.dot(face_points, np.vstack((u, v)).T) angles = np.arctan2(proj[:,1], proj[:,0]) sorted_indices = np.argsort(angles) sorted_face = face_points[sorted_indices] faces.append(sorted_face) # 绘图 fig = plt.figure() ax = fig.add_subplot(111, projection='3d') poly3d = Poly3DCollection(faces, alpha=0.5, edgecolor='k') ax.add_collection3d(poly3d) ax.set_xlim(points[:,0].min(), points[:,0].max()) ax.set_ylim(points[:,1].min(), points[:,1].max()) ax.set_zlim(points[:,2].min(), points[:,2].max()) plt.show()
内容的提问来源于stack exchange,提问作者nikferrari
相关产品推荐
相关产品推荐

