potpourri3d

repository·master·Indexed 20 days ago

https://github.com/nmwsharp/potpourri3d

A Python library for 3D geometry processing providing a high-level interface to C++ algorithms from the geometry-central library. It offers tools for triangle meshes, polygon meshes, and point clouds, including file I/O, heat methods for signed and unsigned distances, geodesic path computation via edge flips, parallel transport, logarithmic maps, and local triangulation for point clouds. Version 1.4.0.

Tokens
8.4K
Snippets
25
Records
28
Agent score
69%

What's inside potpourri3d

  1. Overview of potpourri3d capabilities

    master

    potpourri3d is a Python library providing algorithms and utilities for 3D triangle meshes, polygon meshes, and point clouds. It primarily provides Python bindings to C++ tools from geometry-central.

    Key features include:

    • File I/O: Reading and writing meshes and point clouds in various formats.
    • Heat Methods: Computing unsigned and signed distances, parallel transport, logarithmic maps, and more.
    • Geodesics: Computing geodesic polylines along surfaces via edge flips.
    • Distance Fields: Various methods for computing mesh distances (Signed, Fast Marching, etc.).
  2. Compile potpourri3d with Suitesparse for better performance

    master

    By default, precompiled binaries use Eigen's sparse linear solvers. For significantly improved performance and robustness, you can compile the package locally with Suitesparse installed. To do this, use the --no-binary flag to force a local build:

    Prerequisite: Ensure Suitesparse is installed on your system following the geometry-central dependency guide.

    python -m pip install potpourri3d --no-binary potpourri3d
  3. Install potpourri3d via pip

    master

    You can install potpourri3d using pip. Precompiled binaries are available on PyPI for most configurations. If a matching binary is not found, pip will attempt to compile the library from source, which requires cmake and a working C++ compiler toolchain.

    pip install potpourri3d
  4. Read and write meshes and point clouds

    master

    Use the following functions to handle I/O for meshes and point clouds. The library supports file types compatible with geometry-central, with types inferred from file extensions.

    Mesh I/O

    • read_mesh(filename): Returns vertices V (Nx3 real numpy array) and faces F (Mx3 or Mx4 integer numpy array).
    • read_polygon_mesh(filename): Returns vertices V (Nx3 real numpy array) and polygons (a nested list of integers representing polygon face indices).
    • write_mesh(V, F, filename, UV_coords=None, UV_type=None): Writes a mesh.
      • UV_coords (optional): Ux2 numpy array.
      • UV_type (optional): 'per-vertex', 'per-face', or 'per-corner'.
      • Warning: Shared UV indices are not preserved during writing; each coordinate is independent.

    Point Cloud I/O

    • read_point_cloud(filename): Returns a Nx3 real numpy array of vertices V. It reads a mesh file but ignores face entries.
    • write_point_cloud(V, filename): Writes a mesh file with no face entries.
    # Example mesh reading
    V, F = pp3d.read_mesh('model.obj')
    
    # Example mesh writing with UVs
    pp3d.write_mesh(V, F, 'output.obj', UV_coords=uvs, UV_type='per-vertex')
  5. Compute distance using Mesh Fast Marching

    master

    The MeshFastMarchingDistanceSolver provides an alternative distance computation method.

    Signed Distance to Curves

    signed_dist = solver.compute_distance(curves, sign=True)

    Unsigned Distance to Points

    unsigned_dist = solver.compute_distance(points, sign=False)

    Parameters

    • curves: A list of lists of source points (barycentric coordinates).
    • distances: A list of lists of initial distances (default is 0).
    • sign: (bool) If True, computes signed distance. If False, computes unsigned distance.
    import potpourri3d as pp3d
    
    V, F = # your mesh
    solver = pp3d.MeshFastMarchingDistanceSolver(V, F)
    
    # Specify each curve as a sequence of barycentric points
    curves = [
               [
                 (61, [0.3, 0.3]),
                 (7, []),
                 (16, [0.3, 0.3, 0.4]),
                 (11, [0.4]),
                 (71, []),
                 (20, [0.3, 0.3, 0.4]),
                 (13, []),
                 (58, [])
                 ]
             ]
    
    # Compute a signed distance field to a set of closed curves.
    signed_dist = solver.compute_distance(curves, sign=True) 
    
    # Compute unsigned to a set of points.
    points = [
              [
                (71, []),
                (18, [0.5])
              ]
             ]
    unsigned_dist = solver.compute_distance(points, sign=False) 
  6. Mesh basic utilities

    master

    Perform common geometric computations on triangular meshes:

    • face_areas(V, F): Returns a length-F real numpy array of face areas.
    • vertex_areas(V, F): Returns a length-V real numpy array of vertex areas (1/3 the sum of incident face areas).
    • cotan_laplacian(V, F, denom_eps=0.): Computes the cotan-Laplace matrix as a VxV real sparse CSR scipy matrix. Use denom_eps (e.g., 1e-6) for stability with degenerate faces.
    • edges(V, F): Returns an Ex2 integer matrix where each row contains the indices of the two endpoint vertices of an edge.

    Barycentric Point Representation

    Algorithms often use barycentric points specified as tuples:

    • Vertices: (vertex_index, )
    • Edges: (edge_index, [t]) where $t \in [0,1]$ is the parameter along the edge.
    • Faces: (face_index, [tA, tB]). If $tC$ is omitted, it is inferred as $1 - tA - tB$.
  7. Construct a local triangulation for a point cloud

    master

    The PointCloudLocalTriangulation class constructs a local triangulation of a point cloud. This is not a watertight mesh, but rather a "triangle soup" consisting of sets of triangles computed independently around each point. This structure is suitable for approximating surface properties, evaluating geometric energies, or building Laplace matrices.

    Usage

    1. Initialize: triangulation = pp3d.PointCloudLocalTriangulation(P, with_degeneracy_heuristic=True)
    2. Retrieve triangles: tri_indices = triangulation.get_local_triangulation()

    Output Format

    get_local_triangulation() returns a [V, M, 3] integer numpy array:

    • V: Number of vertices in the point cloud.
    • M: Maximum number of neighbors in any local triangulation.
    • 3: The three vertex indices forming a triangle.
    • For vertices with fewer than M neighbors, the trailing rows are filled with -1.
    # Example local triangulation
    P = # a Nx3 numpy array of points
    triangulation = pp3d.PointCloudLocalTriangulation(P, with_degeneracy_heuristic=True)
    tri_indices = triangulation.get_local_triangulation()
    # tri_indices is a [V, M, 3] integer numpy array
  8. Trace geodesics from points using GeodesicTracer

    master

    The GeodesicTracer traces out a geodesic path along a mesh surface given an initial point and direction/length. It returns the path as a polyline.

    Methods

    • trace_geodesic_from_vertex(start_vert, direction_xyz, max_iterations=None): Traces from a vertex. direction_xyz is a 3D vector; its magnitude determines the distance walked. The vector is projected onto the vertex's tangent space.
    • trace_geodesic_from_face(start_face, bary_coords, direction_xyz, max_iterations=None): Traces from a point within a face using barycentric coordinates.

    Parameters

    • max_iterations: (int) Terminates tracing after a certain number of faces/edges.
    import potpourri3d as pp3d
    
    V, F = # your mesh
    tracer = pp3d.GeodesicTracer(V,F) # shares precomputation for repeated traces
    
    trace_pts = tracer.trace_geodesic_from_vertex(22, np.array((0.3, 0.5, 0.4)))
    # trace_pts is a Vx3 numpy array of points forming the path
  9. Compute geodesic distance on meshes using the Heat Method

    master

    The Heat Method computes geodesic distances on surfaces. For repeated distance queries from different sources on the same mesh, use the stateful MeshHeatMethodDistanceSolver for significantly better performance.

    1. Initialize: solver = pp3d.MeshHeatMethodDistanceSolver(V, F, t_coef=1., use_robust=True)
    2. Compute from single vertex: dist = solver.compute_distance(v_ind)
    3. Compute from multiple vertices: dist = solver.compute_distance_multisource(v_ind_list)

    One-off Functions

    • pp3d.compute_distance(V, F, v_ind)
    • pp3d.compute_distance_multisource(V, F, v_ind_list)

    Note: On very coarse meshes, results may be inaccurate. Increasing mesh density improves accuracy.

    import potpourri3d as pp3d
    
    # Stateful solves (much faster if computing distance many times)
    solver = pp3d.MeshHeatMethodDistanceSolver(V, F)
    dist = solver.compute_distance(7)
    dist = solver.compute_distance_multisource([1, 2, 3])
    
    # One-off versions
    dist = pp3d.compute_distance(V, F, 7)
    dist = pp3d.compute_distance_multisource(V, F, [1, 3, 4])
  10. Point Cloud Distance and Vector Heat

    master

    The PointCloudHeatSolver provides heat-based geometric algorithms for point clouds.

    Key Operations

    • Unsigned Distance: compute_distance(p_ind) computes distance to a point.
    • Signed Distance: compute_signed_distance(curves, cloud_normals, ...) computes signed distance to oriented curves.
    • Scalar Interpolation: extend_scalar(p_inds, values) interpolates values from source points.
    • Tangent Data: get_tangent_frames() returns (basisX, basisY, basisN) for each point.
    • Parallel Transport: transport_tangent_vector(p_ind, vector) transports a single 2D tangent vector.
    • Logarithmic Map: compute_log_map(p_ind) computes the log map centered at a point.

    Parameters

    • P: Nx3 real numpy array of points.
    • curves: A list of lists of source point indices.
    • cloud_normals: A list of 3D normal vectors, one for each point in the cloud.
    • level_set_constraint: 'ZeroSet', 'None', or 'Multiple'.
    import potpourri3d as pp3d
    
    # = Stateful solves
    P = # a Nx3 numpy array of points
    solver = pp3d.PointCloudHeatSolver(P)
    
    # Compute the geodesic distance to point 4
    dists = solver.compute_distance(4)
    
    # Extend the value `0.` from point 12 and `1.` from point 17.
    ext = solver.extend_scalar([12, 17], [0.,1.])
    
    # Get the tangent frames
    basisX, basisY, basisN = solver.get_tangent_frames()
    
    # Parallel transport a vector along the surface
    sourceP = 22
    ext = solver.transport_tangent_vector(sourceP, [6., 6.])
    ext3D = ext[:,0,np.newaxis] * basisX +  ext[:,1,np.newaxis] * basisY
    
    # Compute the logarithmic map
    logmap = solver.compute_log_map(sourceP)
    
    # Signed distance to the oriented curve(s) denoted by a point sequence.
    curves = [
               [9, 10, 12, 13, 51, 48], 
               [79, 93, 12, 30, 78, 18, 92], 
               [90, 84, 19, 91, 82, 81, 83]
             ]
    signed_dist = solver.compute_signed_distance(curves, basisN)
  11. Mesh Vector Heat and Tangent Data

    master

    The MeshVectorHeatSolver uses vector and affine heat methods to compute interpolation and vector-based quantities on meshes. It is recommended to use the stateful solver for repeated operations.

    Key Operations

    • Scalar Interpolation: extend_scalar(v_inds, values) interpolates values from source vertices to the rest of the mesh based on geodesic proximity.
    • Tangent Frames: get_tangent_frames() returns (basisX, basisY, basisN) as Nx3 arrays, representing the local coordinate system at each vertex.
    • Parallel Transport:
      • transport_tangent_vector(v_ind, vector): Transports a single 2D tangent vector.
      • transport_tangent_vectors(v_inds, vectors): Transports a collection of 2D tangent vectors.
    • Logarithmic Map: compute_log_map(v_ind, strategy='AffineLocal') computes the log map centered at a vertex. Strategies include 'VectorHeat', 'AffineLocal', and 'AffineAdaptive'.

    Parameters

    • V, F: Vertices and faces (triangle meshes, should be manifold).
    • t_coef: Time for short-time heat flow (default 1.0).
    • useIntrinsicDelaunay: (bool) Uses intrinsic triangulation for robustness (default True).
    import potpourri3d as pp3d
    
    # = Stateful solves
    V, F = # a Nx3 numpy array of points and Mx3 array of triangle face indices
    solver = pp3d.MeshVectorHeatSolver(V,F)
    
    # Extend the value `0.` from vertex 12 and `1.` from vertex 17.
    ext = solver.extend_scalar([12, 17], [0.,1.])
    
    # Get the tangent frames
    basisX, basisY, basisN = solver.get_tangent_frames()
    
    # Parallel transport a vector along the surface
    sourceV = 22
    ext = solver.transport_tangent_vector(sourceV, [6., 6.])
    ext3D = ext[:,0,np.newaxis] * basisX +  ext[:,1,np.newaxis] * basisY
    
    # Compute the logarithmic map
    logmap = solver.compute_log_map(sourceV)
  12. Find geodesic paths and loops using Edge Flips

    master

    The EdgeFlipGeodesicSolver uses an iterative edge-flip strategy to find and straighten paths into geodesics. This is useful when you need the path itself rather than just the distance. Note that it is not guaranteed to find the globally shortest geodesic (it may find a local minimum).

    Methods

    • find_geodesic_path(v_start, v_end, max_iterations=None, max_relative_length_decrease=None): Computes a geodesic between two vertices. Returns an Nx3 numpy array of positions.
    • find_geodesic_path_poly(v_list, ...): Finds a path through a sequence of vertices [v_start, v_a, v_b, ..., v_end]. The path is constructed via piecewise-Dijkstra and must not cross itself.
    • find_geodesic_loop(v_list, ...): Similar to find_geodesic_path_poly, but connects the last point back to the first to form a closed loop.

    Parameters

    • max_iterations: (int) Maximum number of shortening iterations.
    • max_relative_length_decrease: (float) Limits how much the path can shorten (e.g., 0.5 means the result is at least half the original length).
    import potpourri3d as pp3d
    
    V, F = # your mesh
    path_solver = pp3d.EdgeFlipGeodesicSolver(V,F) # shares precomputation for repeated solves
    path_pts = path_solver.find_geodesic_path(v_start=14, v_end=22)
    # path_pts is a Vx3 numpy array of points forming the path