如何用Python Plotly可靠构造平面?含轴垂直平面解决方案
问题描述
尝试用Plotly绘制x=y平面,通过平面内一点和法向量生成了点集,但Mesh3d无法正常渲染,推测是点的顺序导致三角面构建失败,不知道如何设置i/j/k参数,需要适用于任意法向量(包括垂直于坐标轴的平面)的通用方案,同时欢迎Mesh3d的替代方案。
原代码:
import numpy as np import plotly.graph_objects as go p0 = np.array([0,0,0]) p1 = np.array([0,0,1]) p2 = np.array([1,1,0]) v1 = np.array([0,0,1]) v2 = np.array([1,1,0])/np.sqrt(2) n1, n2 = v1.dot(p1-p0), v2.dot(p2-p0) a1, a2 = np.mgrid[0:n1:10*1j,0:n2:10*1j] a1 = a1.flatten()[np.newaxis,:] a2 = a2.flatten()[np.newaxis,:] pts = p0[:,np.newaxis] + a1*v1[:,np.newaxis] + a2*v2[:,np.newaxis] go.Figure(data=[go.Mesh3d( x=pts[0], y=pts[1], z=pts[2] )])
解决方案
方案1:修复Mesh3d的三角面索引
Mesh3d需要明确的三角面索引(i/j/k)才能正确渲染。仅传入扁平化的点数组时,Plotly无法推断网格拓扑结构,可基于mgrid生成的二维网格行列数自动构建索引:
import numpy as np import plotly.graph_objects as go p0 = np.array([0,0,0]) v1 = np.array([0,0,1]) v2 = np.array([1,1,0])/np.sqrt(2) # 生成二维网格(保留二维结构,不立即扁平化) a1, a2 = np.mgrid[0:1:10*1j, 0:np.sqrt(2):10*1j] # 计算平面坐标(二维数组) x = p0[0] + a1 * v1[0] + a2 * v2[0] y = p0[1] + a1 * v1[1] + a2 * v2[1] z = p0[2] + a1 * v1[2] + a2 * v2[2] # 扁平化点数组 x_flat = x.flatten() y_flat = y.flatten() z_flat = z.flatten() # 获取网格行列数 rows, cols = x.shape # 生成三角面索引:将每个四边形拆分为两个三角形 i, j = np.meshgrid(np.arange(rows-1), np.arange(cols-1), indexing='ij') i1, j1 = i.flatten(), j.flatten() i2, j2 = i1 + 1, j1 i3, j3 = i1, j1 + 1 i4, j4 = i1 + 1, j1 + 1 tri1 = i1 * cols + j1 tri2 = i2 * cols + j2 tri3 = i3 * cols + j3 tri4 = i4 * cols + j4 # 合并两个三角形的索引 i_indices = np.concatenate([tri1, tri1]) j_indices = np.concatenate([tri2, tri3]) k_indices = np.concatenate([tri3, tri4]) # 渲染Mesh3d fig = go.Figure(data=[go.Mesh3d( x=x_flat, y=y_flat, z=z_flat, i=i_indices, j=j_indices, k=k_indices, opacity=0.5 )]) fig.show()
方案2:用Surface简化平面绘制
如果不需要Mesh3d的复杂特性,go.Surface是更优选择——它专门处理二维网格生成的曲面,无需手动构建三角面索引,代码更简洁:
import numpy as np import plotly.graph_objects as go p0 = np.array([0,0,0]) v1 = np.array([0,0,1]) v2 = np.array([1,1,0])/np.sqrt(2) # 生成二维网格 a1, a2 = np.mgrid[0:1:10*1j, 0:np.sqrt(2):10*1j] # 计算平面坐标(保留二维数组结构) x = p0[0] + a1 * v1[0] + a2 * v2[0] y = p0[1] + a1 * v1[1] + a2 * v2[1] z = p0[2] + a1 * v1[2] + a2 * v2[2] # 渲染Surface fig = go.Figure(data=[go.Surface( x=x, y=y, z=z, opacity=0.5 )]) fig.show()
方案3:通用平面生成函数(支持任意法向量)
以下函数可根据平面法向量和平面内一点,生成Plotly可渲染的平面数据,自动处理垂直于坐标轴的特殊情况(如x=5、z=2或x=y这类平面):
import numpy as np import plotly.graph_objects as go def create_plane(normal, point, size=5, num_points=10): """ 生成Plotly可用的平面数据 参数: normal: 平面法向量,如np.array([1,-1,0])对应x=y平面 point: 平面内任意一点,如np.array([0,0,0]) size: 平面的边长范围(从-size到size) num_points: 网格的点数(每个维度) 返回: x, y, z: 二维数组,可直接用于go.Surface """ normal = normal / np.linalg.norm(normal) # 归一化法向量 # 生成平面内的两个正交方向向量 if np.allclose(normal, np.array([1,0,0])): # 垂直于x轴的平面 v1 = np.array([0,1,0]) v2 = np.array([0,0,1]) elif np.allclose(normal, np.array([0,1,0])): # 垂直于y轴的平面 v1 = np.array([1,0,0]) v2 = np.array([0,0,1]) elif np.allclose(normal, np.array([0,0,1])): # 垂直于z轴的平面 v1 = np.array([1,0,0]) v2 = np.array([0,1,0]) else: # 通用情况:生成两个与法向量正交的向量 v1 = np.cross(normal, np.array([1,0,0])) if np.linalg.norm(v1) < 1e-6: v1 = np.cross(normal, np.array([0,1,0])) v1 = v1 / np.linalg.norm(v1) v2 = np.cross(normal, v1) v2 = v2 / np.linalg.norm(v2) # 生成网格参数 u = np.linspace(-size, size, num_points) v = np.linspace(-size, size, num_points) u_grid, v_grid = np.meshgrid(u, v) # 计算平面坐标 x = point[0] + u_grid * v1[0] + v_grid * v2[0] y = point[1] + u_grid * v1[1] + v_grid * v2[1] z = point[2] + u_grid * v1[2] + v_grid * v2[2] return x, y, z # 示例1:绘制x=y平面(法向量[1,-1,0],过原点) x, y, z = create_plane(np.array([1,-1,0]), np.array([0,0,0])) fig = go.Figure(data=[go.Surface(x=x, y=y, z=z, opacity=0.5)]) fig.show() # 示例2:绘制垂直于z轴的平面z=2 x, y, z = create_plane(np.array([0,0,1]), np.array([0,0,2])) fig = go.Figure(data=[go.Surface(x=x, y=y, z=z, opacity=0.5)]) fig.show()
说明
Surface比Mesh3d更适合绘制平面这类规则曲面,无需手动处理三角面索引,可靠性更高。- 通用函数自动处理垂直于坐标轴的特殊场景,避免计算异常,同时支持任意法向量的平面生成。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

