libigl Python Bindings

repository·main·Indexed 18 days ago

https://github.com/libigl/libigl-python-bindings

Python bindings for the libigl C++ geometry processing library using nanobind. It provides high-performance geometric algorithms with seamless integration for NumPy and SciPy data structures, supporting dense matrices via NumPy arrays and sparse matrices via scipy.sparse.csr_matrix. The library includes tools for mesh adjacency, ARAP energy construction, barycentric coordinates, and breadth-first search operations.

Tokens
30.4K
Snippets
130
Records
202
Agent score
62%

What's inside libigl-python-bindings

  1. Identify the libigl Python submodule structure

    main

    The Python package structure mirrors the C++ source organization. Depending on the source directory, the corresponding Python module is:

    • src/ $\rightarrow$ igl (Core, MPL2 license)
    • src/copyleft/ $\rightarrow$ igl.copyleft (GPL license)
    • src/copyleft/cgal/ $\rightarrow$ igl.copyleft.cgal
    • src/embree/ $\rightarrow$ igl.embree
    • src/triangle/ $\rightarrow$ igl.triangle
  2. Understand the libigl Python binding architecture

    main

    The libigl Python bindings are built using nanobind and scikit-build-core. The architecture maps C++ source files in src/ directly to libigl headers.

    Key technical details for users and developers:

    • Type Mapping: Core types are centralized in include/default_types.h. By default, Numeric is double, Integer is int64_t, and matrices use RowMajor layout.
    • Zero-copy Input: To achieve zero-copy performance, inputs should be C-contiguous float64 arrays, which are handled via nb::DRef<const Eigen::MatrixXN>.
    • Multiple Outputs: Functions returning multiple C++ parameters via std::make_tuple(...) are exposed as Python tuples for easy unpacking.
    • Sparse Matrix Support: Sparse outputs using Eigen::SparseMatrixN are automatically converted to scipy.sparse.csr_matrix via <nanobind/eigen/sparse.h>.
  3. Compile libigl bindings in editable mode

    main

    To perform an incremental (editable) build using scikit-build-core, follow these steps:

    1. Preinstall the dependencies listed at the top of pyproject.toml.
    2. Run the following command to initiate the build. Note that CMAKE_BUILD_PARALLEL_LEVEL=10 is used to invoke 10 parallel build threads.

    Warning: This is a long command required for correct editable installation configuration.

    CMAKE_BUILD_PARALLEL_LEVEL=10  python -m pip install --no-build-isolation --config-settings=editable.rebuild=true -Cbuild-dir=build -ve.
  4. Package a wheel from an existing build

    main

    If you have an existing compiled build directory, you can package it into a .whl file without recompiling. This allows you to install the wheel into other virtual environments on the same machine without needing a compiler or CMake in the target environment.

    Note: The --no-deps flag skips bundling numpy and scipy; these must be pre-installed in the target environment.

    python -m pip wheel --no-build-isolation --no-deps -Cbuild-dir=build -w wheelhouse .
    pip install wheelhouse/libigl-*.whl
  5. Download and upload all build artifacts

    main

    To download all .whl files from a successful GitHub Actions run and upload them to PyPI:

    1. Download artifacts using the GitHub CLI (gh):

      mkdir wheelhouse
      cd wheelhouse
      gh run download [runid]
    2. Upload to PyPI using twine:

      for f in wheelhouse/*/*.whl wheelhouse/*/*.tar.gz; do
          python3 -m twine upload --repository pypi "$f" || echo "Skipping failed upload: $f"
      done
  6. Add a new C++ binding

    main

    Adding bindings is a mechanical process involving these steps:

    1. Identify the C++ function: Locate the corresponding .h header file in the main libigl library (e.g., moments.h).
    2. Create a wrapper: Create a new .cpp file in the src/ directory (e.g., src/moments.cpp).
    3. Use Eigen types: Use Eigen::MatrixXN for numeric types and Eigen::MatrixXI for integer types within the wrapper.
    4. Register the function: Write a boilerplate void bind_function_name(... function to add the wrapper to the Python module.
    5. Verify: Simply adding the .cpp file will include it in the next build. If submitting a PR, add an execution test in tests/test_all.py to ensure the binding is callable.
  7. Test cibuildwheel locally

    main

    To test cibuildwheel locally, install a Python version from the official website and run the following sequence to create a virtual environment, install cibuildwheel, and build for a specific platform (example provided for macOS/Python 3.11):

    /Library/Frameworks/Python.framework/Versions/3.11/bin/python3.11 -m venv venv-official-3.11
    source venv-official-3.11/bin/activate
    python -m pip install cibuildwheel
    CIBW_BUILD="cp311-*" python -m cibuildwheel --output-dir wheelhouse --platform macos
  8. Compare skinning techniques

    main

    When choosing a skinning method, consider the following trade-offs:

    1. Rigid Skinning: Most basic; each vertex follows exactly one bone. Often results in significant artifacts at joints.
    2. Linear Blend Skinning (LBS): Most common; allows smooth transitions between bones. However, it suffers from volume loss (shrinkage/collapse) at joints.
    3. Direct Delta Mush (DDM): Advanced; uses Laplacian smoothing and cached deltas to preserve volume and detail, effectively cleaning up LBS artifacts.
  9. How biharmonic and polyharmonic deformation works

    main

    Biharmonic and polyharmonic deformations are based on solving partial differential equations (PDEs) on a mesh surface.

    • Biharmonic Surfaces: Solving $\Delta^2 \mathbf{x}' = 0$ directly on positions $\mathbf{x}'$ interpolates handles but tends to smooth away original surface details. This is often not intuitive for shape deformation.
    • Biharmonic Deformation Fields: By solving for the displacement $\mathbf{d}$ (where $\mathbf{x}' = \mathbf{x} + \mathbf{d}$) such that $\Delta^2 \mathbf{d} = 0$, the method ensures 'rest pose reproduction': if handles are not moved, the shape remains unchanged.
    • Polyharmonic Generalization: Increasing the order $k$ in $\Delta^k \mathbf{d} = 0$ increases the smoothness (continuity) of the deformation at the handles.
  10. Understand libigl mesh representation

    main

    libigl uses standard numpy arrays to represent triangular meshes. A mesh is defined by a pair of matrices:

    1. v (Vertices): An $N imes 3$ numpy.array where each row contains the $(x, y, z)$ coordinates of a vertex.
    2. f (Faces): An $M imes 3$ numpy.array storing triangle connectivity. Each row contains three indices pointing to the rows in the vertex matrix v.

    Note: The order of vertex indices in f determines the triangle orientation; this should be consistent across the entire surface.

    import numpy as np
    
    # Example: A simple mesh made of 2 triangles and 4 vertices
    V = np.array([
        [0., 0, 0],
        [1, 0, 0],
        [1, 1, 1],
        [2, 1, 0]
    ])
    
    F = np.array([
        [0, 1, 2],
        [1, 3, 2]
    ])