YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

Point2Mesh β€” A Self-Prior for Deformable Meshes

Pure Python/PyTorch reimplementation of Point2Mesh (SIGGRAPH 2020) by Hanocka et al.

Input: a point cloud (.ply, .pcd, .xyz, .obj)
Output: a shrink-wrapped triangle mesh (.obj, .ply, .stl)

No training data needed β€” the method optimises a single CNN per shape at inference time, exploiting the network's architectural bias toward self-similar structure as a shape prior.


Quick Start

# Install
pip install torch numpy scipy
git clone https://huggingface.co/bdck/point2mesh
cd point2mesh

# Run
python -m point2mesh --input my_cloud.ply --output mesh.obj

# Quick test (fast, lower quality)
python -m point2mesh -i cloud.ply -o mesh.obj --n-levels 2 --iters 200 --init-faces 500

# Full quality
python -m point2mesh -i cloud.ply -o mesh.obj --n-levels 5 --iters 1500 --max-faces 40000 --device cuda

How It Works

Point Cloud ──→ Convex Hull ──→ [ CNN optimisation ] ──→ Shrink-wrapped Mesh
                  (coarse)        (coarse-to-fine)           (detailed)
  1. Initialise a coarse mesh from the convex hull of the input points
  2. Optimise a MeshCNN U-Net to deform the mesh surface toward the point cloud:
    • The CNN input is fixed random noise (not the geometry)
    • The CNN outputs per-vertex displacements
    • Losses: bidirectional Chamfer distance + beam-gap loss + normal alignment
  3. Remesh (subdivide + decimate) and repeat at finer resolution
  4. Export the final mesh

The key insight is the self-prior: the CNN architecture itself acts as a regulariser, preferring coherent, self-similar deformations over noise. No external training data is used.

CLI Reference

python -m point2mesh [OPTIONS]

Required:
  --input, -i         Input point cloud (.ply, .pcd, .xyz, .obj)
  --output, -o        Output mesh (.obj, .ply, .stl)

Optimisation:
  --n-levels          Coarse-to-fine levels (default: 4)
  --iters             Iterations per level (default: 1000)
  --lr                Learning rate (default: 0.0002)
  --samples-start     Surface samples at iter 0 (default: 15000)
  --samples-end       Surface samples at final iter (default: 50000)

Mesh resolution:
  --init-faces        Initial mesh face count (default: 2000)
  --face-growth       Face multiplier between levels (default: 1.5)
  --max-faces         Stop subdividing above this (default: 20000)

Loss weights:
  --lambda-beam       Beam-gap loss weight (default: 1.0)
  --lambda-normal     Normal alignment weight (default: 0.1)
  --beam-epsilon      Beam cylinder radius (default: 0.5)

Network:
  --in-channels       Random input features per edge (default: 6)
  --enc-channels      Encoder widths (default: 64 128 256 256)

Memory:
  --part-threshold    Use PartMesh above this face count (default: 10000)
  --n-parts           Spatial grid res for PartMesh (default: 2)

Output:
  --device            torch device (auto-detect if omitted)
  --save-intermediates  Save mesh after each level
  --output-dir        Directory for intermediates (default: .)
  --log-every         Print loss every N iters (default: 50)
  --verbose, -v       Debug logging

Python API

from point2mesh.optimize import run_point2mesh, Point2MeshConfig

cfg = Point2MeshConfig(
    n_levels=4,
    iters_per_level=1000,
    init_faces=2000,
    max_faces=20000,
    device="cuda",
)

run_point2mesh("cloud.ply", "mesh.obj", cfg)

With progress callback

def on_progress(level, iteration, loss):
    print(f"Level {level}, iter {iteration}: loss = {loss:.6f}")

run_point2mesh("cloud.ply", "mesh.obj", cfg, progress_callback=on_progress)

Architecture

point2mesh/
β”œβ”€β”€ __init__.py      # Package root
β”œβ”€β”€ __main__.py      # CLI entry point
β”œβ”€β”€ mesh.py          # Mesh data structure + edge topology + PartMesh
β”œβ”€β”€ layers.py        # MeshCNN conv / pool / unpool
β”œβ”€β”€ network.py       # Point2Mesh U-Net (encoder-decoder)
β”œβ”€β”€ losses.py        # Chamfer, beam-gap, normal alignment, surface sampling
β”œβ”€β”€ optimize.py      # Main optimisation loop
└── io_utils.py      # PCD/PLY/XYZ/OBJ loaders, mesh exporters, remeshing

Module Details

Module Description
mesh.py Half-edge-style mesh with GEMM adjacency for MeshCNN. Builds edge→4-neighbor topology. PartMesh splits large meshes into spatial sub-grids.
layers.py MeshConv: edge convolution with symmetric neighbor aggregation [e, |aβˆ’c|, a+c, |bβˆ’d|, b+d]. MeshPool: edge collapse by L2-norm priority. MeshUnpool: topology restoration from stored history.
network.py U-Net encoder-decoder on edges. Input: fixed random noise. Output: per-edge vertex displacements [N_e, 2, 3]. Output head initialised to zero (no initial displacement).
losses.py Bidirectional Chamfer distance (batched for large clouds). Beam-gap loss with Ξ΅-cylinder and mutual k-NN skip. Unoriented normal alignment 1 βˆ’ |n₁·nβ‚‚|. Differentiable area-weighted surface sampling.
optimize.py Full coarse-to-fine loop. Re-initialises network + noise each level. Linear sample-count ramp. Remeshing (subdivide β†’ smooth β†’ decimate) between levels.
io_utils.py Zero-dependency PCD/PLY/XYZ/OBJ loaders (binary + ASCII). OBJ/PLY/STL mesh writers. Convex hull initialisation. PCA-based normal estimation. Midpoint subdivision, Laplacian smoothing, greedy edge-collapse decimation.

Dependencies

Only three packages:

  • torch >= 2.0 β€” autograd, GPU acceleration
  • numpy >= 1.24 β€” array operations
  • scipy >= 1.10 β€” convex hull, KD-tree for normal estimation

No Open3D, no PyTorch3D, no trimesh, no pymeshlab.

Performance Tips

Scenario Recommendation
Quick preview --n-levels 2 --iters 200 --init-faces 500
Standard quality Default settings (4 levels, 1000 iters)
High quality --n-levels 5 --iters 1500 --max-faces 40000
Large point clouds (>100k pts) Use GPU (--device cuda)
High-res meshes (>10k faces) PartMesh auto-activates; tune --n-parts 3 if OOM
CPU only Works, but ~10Γ— slower than GPU

Differences from Original Implementation

Aspect Original This reimplementation
Remeshing RWM (Robust Watertight Manifold, external C++ binary) Midpoint subdivision + Laplacian smooth + greedy decimation
Mesh pooling Full half-edge data structure with manifold guards Simplified edge collapse with adjacency redirect
Dependencies PyTorch, Open3D, numpy, scipy, CUDA ops PyTorch, numpy, scipy only
Initial mesh (genus > 0) Alpha shape β†’ coarse RWM Convex hull (genus-0 assumption)

The main simplification is the remeshing step: the original uses the external Manifold binary for guaranteed watertight, non-self-intersecting output between levels. This reimplementation uses pure-Python subdivision + decimation which works well for most shapes but may produce self-intersections on complex topology.

Citation

@article{hanocka2020point2mesh,
  title   = {Point2Mesh: A Self-Prior for Deformable Meshes},
  author  = {Hanocka, Rana and Metzer, Gal and Giryes, Raja and Cohen-Or, Daniel},
  journal = {ACM Transactions on Graphics (TOG)},
  volume  = {39},
  number  = {4},
  year    = {2020},
  publisher = {ACM}
}
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Paper for bdck/point2mesh