MeshLib SDK Documentation

repository·master·Indexed 21 days ago

https://github.com/meshinspector/meshlib

A high-performance, open-source SDK for advanced 3D data processing, including mesh manipulation, repair, simplification, and boolean operations. MeshLib supports C++, C, C#, Python, and WebAssembly, with GPU acceleration via CUDA. It provides tools for 3D printing, robotics, and medical imaging, featuring a half-edge data structure for manifold compliance and extensive support for meshes, point clouds, and volumes.

Tokens
15.7K
Snippets
46
Records
73
Agent score
71%

What's inside MeshLib

  1. Overview of MeshLib capabilities

    master

    MeshLib is a high-performance SDK for 3D data processing, supporting meshes, point clouds, and volumes. It is designed for high-speed execution (up to 10x faster in certain operations like booleans and simplification) and supports GPU acceleration via CUDA.

    Key features include:

    • Multi-Language Support: C++ core with bindings for C, C#, and Python.
    • Cross-Platform: Runs on Windows, macOS, Linux, and WebAssembly.
    • Data Structures: Uses a powerful half-edge data structure to ensure manifold compliance.
    • Hardware Acceleration: Supports GPU/CUDA for high-performance computing.
  2. Manage localization with gettext helper scripts

    master

    The scripts/gettext directory provides helper scripts to manage localization (i18n) for the project. You can use these scripts to extract translatable strings, update translation files, and compile them for distribution.

    Available Scripts

    • update_translations.py: Extracts translatable strings from source files into a .pot file and synchronizes existing .po files by adding new records or removing obsolete ones.
    • compile_translations.py: Converts .po files into .mo files. The .mo files are the binary format required by applications for runtime translation lookup.
    • fetch_language_names.py: Generates a list of names for known locales, providing language, country, and script names where available.
  3. Use matrix-builder to create GitHub Actions matrices

    master

    The matrix-builder action allows you to create complex GitHub Actions matrices using declarative, ordered include, extend, and exclude rules. Unlike native GitHub matrix logic where exclude runs before include, matrix-builder evaluates rules in the exact order they are written, allowing you to exclude a row and then immediately include a modified version of it.

    - uses: ./scripts/actions/matrix-builder
      id: m
      with:
        matrix: |
          platform:
            - x86_64
            - aarch64
        rules: |
          - extend:
              platform: x86_64
              runner: ubuntu-latest
          - extend:
              platform: aarch64
              runner: ubuntu-24.04-arm
    
    # Use the output in a subsequent job
    job_id:
      matrix: ${{ fromJSON(needs.setup_job.outputs.m.matrix) }}
  4. Manage WebAssembly memory with explicit disposal

    master

    API values (meshes, bit sets, settings, etc.) hold WebAssembly memory that is not reclaimed by the JavaScript garbage collector. You must free these objects explicitly to avoid memory leaks.

    For Node.js 24+ or modern browsers, use the using keyword. This automatically calls the disposal logic when the variable goes out of scope.

    using mesh = ml.Mesh.fromTriangles(coords, tris);
    // mesh is automatically freed at the end of this scope

    Handling dynamic numbers of objects (DisposableStack)

    If you are creating many objects in a loop, use a DisposableStack to manage them collectively:

    using stack = new DisposableStack();
    for (const path of inputPaths) {
      const cloud = stack.use(ml.PointsLoad.fromAnySupportedFormat(path));
      // ... use cloud
    }
    // All handles in the stack are freed here in reverse order

    Legacy: Manual .delete()

    On older runtimes, you must manually call the .delete() method on each object when finished:

    const mesh = ml.Mesh.fromTriangles(coords, tris);
    // ... use mesh
    mesh.delete();
  5. Requirements for C++ code to be binding-compatible

    master

    To ensure C++ code is correctly parsed and exported to bindings, follow these rules:

    1. Use Export Macros

    Every function in a header within a parsed project must have its corresponding export macro (e.g., MRMESH_API for MRMesh). Failure to do this results in undefined reference errors in the bindings. For internal functions, move them to namespace MR::detail instead of omitting the macro.

    2. Avoid Incomplete Types

    Bindings require types to be "complete" (the full definition must be visible). Unlike standard C++, you cannot use a forward declaration alone if the type is required for the function signature.

    • Fix: Add the necessary #include that defines the type.
    • Exception: If you cannot include the header, wrap the code in #if MR_PARSING_FOR_PB11_BINDINGS.

    3. Pointer Semantics

    All pointer types (function parameters, return values, or class fields) are assumed to point to single objects, not arrays. To pass arrays, use std::vector or std::array instead of raw pointers or std::span (until supported).

  6. Define the base matrix using Axis maps or Entry lists

    master

    The base matrix can be defined in two ways:

    1. Axis Map (Cartesian Product)

    Provide a map where each key is an axis and the value is a list of options. This generates all possible combinations.

    matrix:
      platform:
        - x86_64
        - aarch64
      config:
        - Debug
        - Release

    Result: 4 entries (x86_64/Debug, x86_64/Release, aarch64/Debug, aarch64/Release).

    2. Entry List

    Provide a list of objects. This is used when you have a specific set of combinations and do not want a Cartesian product.

    matrix:
      - { distro: ubuntu22, arch: x64 }
      - { distro: ubuntu24, arch: arm64 }
  7. Manage object lifetimes with lifetime annotations

    master

    To prevent dangling pointers or references in C# and Python, MeshLib uses a 'keep-alive' mechanism. Each managed object maintains a list of other objects it must keep alive. To ensure these relationships are correctly tracked, you must annotate C++ functions that store references or return class references using specific macros from <MRMesh/MRMacros.h>.

    When to annotate manually:

    • When a function returns a class reference (unless it's a global).
    • When a function takes a class reference (or raw pointer) and stores it (e.g., in this or in a member variable).

    Annotation Logic

    Annotations are stored as pairs (a, b), performing a._KeepAlive.push_back(b).

    • a can be a function parameter, this, or the return value.
    • b can be a function parameter or this (but not the return value).

    Macro Types

    1. MR_LIFETIMEBOUND: Used when the return value depends on a parameter or this.
    2. MR_LIFETIME_CAPTURE_BY(param_name): Used when a parameter is stored in this or another parameter.
    3. MR_THIS_LIFETIME_CAPTURE_BY(param_name): Used when a parameter is stored in this (specifically for this as the target).
    4. ..._NESTED variants: Used when you are copying an object rather than storing a reference, but want to copy its internal keep-alive list into the target object (e.g., in container push_back methods).
    // Example: Returning a reference (Return value depends on parameter)
    MR::Mesh& foo( MR::Mesh& m MR_LIFETIMEBOUND )
    {
        return m;
    }
    
    // Example: Storing a reference in 'this'
    struct B
    {
        std::vector<A> vec;
        void add( MR::Mesh& mesh MR_LIFETIME_CAPTURE_BY(this) )
        {
            vec.add( A{mesh} );
        }
    };
  8. Manage WebAssembly memory in MeshLib

    master

    Objects returned by the MeshLib API (such as meshes, bit sets, and result objects) hold WebAssembly memory that is not managed by the JavaScript garbage collector. You must explicitly free these objects to prevent memory leaks.

    If you are using Node.js 24+ or a modern browser, use the using keyword. This automatically calls the cleanup logic when the variable goes out of scope.

    using mesh = ml.Mesh.fromTriangles(coords, tris);
    // mesh is automatically freed here

    For dynamic numbers of handles (e.g., inside a loop), use a DisposableStack:

    using stack = new DisposableStack();
    for (const path of inputPaths) {
      const cloud = stack.use(ml.PointsLoad.fromAnySupportedFormat(path));
      // ... use cloud
    }
    // All handles in the stack are freed here in reverse order

    Option 2: Manual Deletion (Legacy/Older Runtimes)

    On older runtimes that do not support using or DisposableStack, you must manually call the .delete() method on every object when you are finished with it.

    // Manual cleanup for older runtimes
    const mesh = ml.Mesh.fromTriangles(coords, tris);
    // ... use mesh
    mesh.delete();
  9. Write binding-compatible C++ templates

    master

    Templates require specific handling because the bindings parser attempts to instantiate every template it encounters.

    Use requires for constrained members

    If a member function is only valid for certain template arguments, you must annotate it with requires. To maintain compatibility across platforms that may not support C++20, use the MR_REQUIRES_IF_SUPPORTED macro from MRMesh/MRMacros.h.

    template <typename T>
    struct Pair
    {
        T first, second;
    
        T sum() const MR_REQUIRES_IF_SUPPORTED( std::is_arithmetic_v<T> )
        {
            return first + second;
        }
    };

    Manually instantiate templates

    Non-member template functions and class templates must be manually instantiated for the desired types using the MR_BIND_TEMPLATE macro, unless an extern template already exists.

    template <typename T> T foo(T t) {...}
    MR_BIND_TEMPLATE( int foo(int t) )
    MR_BIND_TEMPLATE( float foo(float t) )

    Prefer friend definitions

    Instead of free functions, use friend definitions inside classes (e.g., for overloaded operators or begin()/end()). The parser automatically instantiates friend functions, whereas free functions require manual MR_BIND_TEMPLATE calls.

    template <typename T>
    struct Pair
    {
        T first, second;
    
        T sum() const MR_REQUIRES_IF_SUPPORTED( std::is_arithmetic_v<T> )
        {
            return first + second;
        }
    };
    
    // Manual instantiation for free functions
    template <typename T> T foo(T t) {...}
    MR_BIND_TEMPLATE( int foo(int t) )
    MR_BIND_TEMPLATE( float foo(float t) )
    
    // Preferred approach for operators
    template <typename T>
    struct A
    {
        friend A operator+(A, A) {...}
    };
  10. Understand MeshLib measurement unit conventions

    master

    MeshLib stores all quantities as scalars (typically float). It does not use custom types to encode units. The following conventions are used for internal storage:

    • Angles: Stored in radians. The GUI typically converts these to degrees for display.
    • Lengths, Area, Volume, and Speed: Stored as-is (e.g., mm or inch). The GUI appends the appropriate suffix based on settings without converting the underlying value.
    • Percentages: Stored as numbers between 0 and 1 (e.g., 0.5 for 50%).
  11. Apply rules with include, extend, and exclude

    master

    Rules are applied in the order they appear in the rules input. Each rule can optionally be guarded by an if: condition.

    include — Extend matching rows or add new ones

    1. Splits rule keys into axis keys (existing in base) and extra keys.
    2. Merges extra keys into all existing entries that match the axis-key criteria.
    3. If no entry matches and there is at least one axis key, it appends the rule as a new entry.
    4. If there are no axis keys, it appends the rule as a new entry unconditionally.

    extend — Extend matching rows only (never adds)

    1. Splits rule keys into axis keys and extra keys.
    2. Merges extra keys into existing entries matching the axis-key criteria.
    3. If no entry matches, it is a silent no-op. It never appends new entries.
    4. If there are no axis keys, it merges the extra keys into every entry (useful for setting defaults).

    exclude — Drop matching rows

    Removes every entry where every key/value pair in the rule matches the entry. Keys not present in an entry do not match.

  12. Use conditional logic in rules with `if:`

    master

    Each rule can be optionally guarded by an if: key. The value is evaluated by GitHub before the action runs.

    Truthiness Rules:

    • YAML true is truthy; false, null, or missing is falsy.
    • Strings: Trimmed and lowercased. "", "false", "0", "no", and "off" are falsy. Everything else is truthy.
    • Numbers: Standard JavaScript truthiness.

    If the if: condition is falsy, the entire rule is skipped.