You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取平面与三角网格的交点并修复裁剪后的密闭网格?

解决方案:平面与三角网格交点获取及裁剪后网格密闭处理

一、直接获取平面与三角网格的交点

Open3D的clip_plane()没有直接返回交点的接口,你可以遍历网格的每个三角形,手动计算三角形与平面的交点:

  • 定义平面方程:通过open3d.geometry.Plane的get_plane_equation()获取平面参数 (ax + by + cz + d = 0);
  • 对每个三角形的三个顶点,计算顶点到平面的符号距离,判断顶点在平面的同侧/异侧;
  • 若线段跨平面,则用线性插值公式计算交点,最后对交点去重(处理浮点精度问题)。

示例代码片段:

import open3d as o3d
import numpy as np

def get_plane_mesh_intersections(mesh, plane):
    a, b, c, d = plane.get_plane_equation()
    intersections = []
    triangles = np.asarray(mesh.triangles)
    vertices = np.asarray(mesh.vertices)
    
    for tri in triangles:
        v0, v1, v2 = vertices[tri]
        # 计算顶点到平面的符号距离
        d0 = a*v0[0] + b*v0[1] + c*v0[2] + d
        d1 = a*v1[0] + b*v1[1] + c*v1[2] + d
        d2 = a*v2[0] + b*v2[1] + c*v2[2] + d
        
        signs = np.sign([d0, d1, d2])
        # 三个顶点同侧,无交点
        if np.all(signs == signs[0]):
            continue
        
        # 计算线段与平面的交点
        def compute_intersection(p0, p1, d0, d1):
            t = -d0 / (d1 - d0)
            return p0 + t*(p1 - p0)
        
        if signs[0] != signs[1]:
            intersections.append(compute_intersection(v0, v1, d0, d1))
        if signs[1] != signs[2]:
            intersections.append(compute_intersection(v1, v2, d1, d2))
        if signs[2] != signs[0]:
            intersections.append(compute_intersection(v2, v0, d2, d0))
    
    # 浮点精度去重
    intersections = np.unique(np.round(intersections, decimals=6), axis=0)
    return intersections

# 调用示例
mesh = o3d.io.read_triangle_mesh("your_mesh.ply")
plane = o3d.geometry.Plane([0,0,1], 0)  # z=0平面
intersections = get_plane_mesh_intersections(mesh, plane)

二、裁剪后网格重新密闭

fill_holes()无效通常是因为裁剪产生的洞为非简单多边形,或边界未被正确识别,可尝试以下两种方法:

方法1:手动生成裁剪面合并

  1. 用clip_plane()得到裁剪后的网格clipped_mesh;
  2. 提取网格边界环(mesh.get_boundary_loop()),取最大的边界环对应裁剪洞;
  3. 调整边界顶点顺序保证法向量方向正确,将边界作为新面添加到网格中。

示例代码:

def close_clipped_mesh(clipped_mesh, plane):
    boundary_loops = clipped_mesh.get_boundary_loop()
    if not boundary_loops:
        return clipped_mesh
    
    # 取最大的边界环
    boundary = max(boundary_loops, key=lambda x: len(x))
    a, b, c, _ = plane.get_plane_equation()
    normal = np.array([a, b, c])
    
    # 调整顶点顺序保证法向量正确
    center = np.mean([clipped_mesh.vertices[i] for i in boundary], axis=0)
    if np.dot(normal, center) < 0:
        boundary = boundary[::-1]
    
    # 添加新面
    clipped_mesh.triangles.append(boundary)
    clipped_mesh.triangles = o3d.utility.Vector3iVector(clipped_mesh.triangles)
    clipped_mesh.compute_vertex_normals()
    return clipped_mesh

# 调用示例
clipped_mesh = mesh.clip_plane(plane)
closed_mesh = close_clipped_mesh(clipped_mesh, plane)

方法2:用凸包补洞

若裁剪洞近似平面,可提取边界顶点生成凸包面合并到网格:

def close_with_convex_hull(clipped_mesh):
    boundary_loops = clipped_mesh.get_boundary_loop()
    if not boundary_loops:
        return clipped_mesh
    
    boundary = max(boundary_loops, key=lambda x: len(x))
    boundary_points = o3d.geometry.PointCloud()
    boundary_points.points = o3d.utility.Vector3dVector([clipped_mesh.vertices[i] for i in boundary])
    
    # 生成凸包补面
    hull, _ = boundary_points.compute_convex_hull()
    clipped_mesh += hull
    # 清理冗余元素
    clipped_mesh.remove_duplicated_vertices()
    clipped_mesh.remove_duplicated_triangles()
    clipped_mesh.compute_vertex_normals()
    return clipped_mesh

三、修改交点/裁剪区域颜色

给交点或裁剪区域上色可通过以下方式:

  • 遍历网格顶点,标记边界顶点并设置颜色;
  • 提取交点生成点云,单独设置颜色后和网格一起可视化。

示例:

# 给裁剪后网格的边界顶点设为红色
boundary_loops = closed_mesh.get_boundary_loop()
boundary_vertices = set()
for loop in boundary_loops:
    boundary_vertices.update(loop)

colors = np.asarray(closed_mesh.vertex_colors)
for i in range(len(colors)):
    if i in boundary_vertices:
        colors[i] = [1, 0, 0]
closed_mesh.vertex_colors = o3d.utility.Vector3dVector(colors)

# 可视化
o3d.visualization.draw_geometries([closed_mesh])

内容的提问来源于stack exchange,提问作者Issam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 08:47:49