{"id":51671423,"url":"https://github.com/morningfrog/vvcm-rs","last_synced_at":"2026-07-15T01:00:52.611Z","repository":{"id":363718932,"uuid":"1262835472","full_name":"MorningFrog/vvcm-rs","owner":"MorningFrog","description":"Rust implementation for kinematics of multi-robot transporting systems with a deformable sheet using the Virtual Variable Cables Model (VVCM).","archived":false,"fork":false,"pushed_at":"2026-06-20T09:11:18.000Z","size":248,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T11:10:23.103Z","etag":null,"topics":["cpp","crates-io","deformable-sheet","javascript","kinematics","multi-robot","npmjs","pypi","python","robotics","rust","typescript","vcpkg","vvcm"],"latest_commit_sha":null,"homepage":"https://morningfrog.github.io/vvcm-web/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MorningFrog.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-08T11:17:10.000Z","updated_at":"2026-06-20T09:11:22.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/MorningFrog/vvcm-rs","commit_stats":null,"previous_names":["morningfrog/vvcm-rs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/MorningFrog/vvcm-rs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MorningFrog%2Fvvcm-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MorningFrog%2Fvvcm-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MorningFrog%2Fvvcm-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MorningFrog%2Fvvcm-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MorningFrog","download_url":"https://codeload.github.com/MorningFrog/vvcm-rs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MorningFrog%2Fvvcm-rs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35485406,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-14T02:00:06.603Z","response_time":114,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","crates-io","deformable-sheet","javascript","kinematics","multi-robot","npmjs","pypi","python","robotics","rust","typescript","vcpkg","vvcm"],"created_at":"2026-07-15T01:00:31.690Z","updated_at":"2026-07-15T01:00:52.605Z","avatar_url":"https://github.com/MorningFrog.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vvcm-rs\n\nRust implementation for kinematics of multi-robot transporting systems with a deformable sheet using the Virtual Variable Cables Model (VVCM).\n\n`vvcm-rs` is implemented in Rust, but it is not limited to Rust projects. The same VVCM forward-kinematics and simulation library is available to **Rust**, **Python**, **C++**, **C**, and **JavaScript/TypeScript** users through the native Rust API, Python bindings, C++17 wrapper headers, C ABI, and WebAssembly npm packages.\n\nIf you plan to modify the codebase, read [CONTRIBUTING.md](CONTRIBUTING.md) first for workflow, structure, and release expectations.\n\n**Open the visual test bench at [vvcm-web](https://morningfrog.github.io/vvcm-web/).**\n\n[![VVCM Visual Test Bench live preview](https://api.microlink.io/?url=https%3A%2F%2Fmorningfrog.github.io%2Fvvcm-web%2F\u0026screenshot=true\u0026viewport.width=1920\u0026viewport.height=1080\u0026embed=screenshot.url)](https://morningfrog.github.io/vvcm-web/)\n\n## Citation\n\nIf you use the forward kinematics algorithm, please cite:\n\n```bibtex\n@article{ma2026stable,\n  title = {Stable Kinematics for Multi-Robot Collaborative Transporting System with a Deformable Sheet},\n  author = {Ma, Wenyao and Hu, Jiawei and Li, Jiamao and Yi, Jingang and Xiong, Zhenhua},\n  year = 2026,\n  journal = {IEEE Transactions on Robotics},\n  volume = {42},\n  pages = {837-853},\n  doi = {10.1109/TRO.2026.3653870}\n}\n```\n\nFor the original VVCM model, please cite:\n\n```bibtex\n@article{hu2022multirobot,\n  title = {Multi-Robot Object Transport Motion Planning With a Deformable Sheet},\n  author = {Hu, Jiawei and Liu, Wenhang and Zhang, Heng and Yi, Jingang and Xiong, Zhenhua},\n  year = 2022,\n  journal = {IEEE Robotics and Automation Letters},\n  volume = {7},\n  number = {4},\n  pages = {9350--9357}\n}\n```\n\n## Features\n\nThis package includes `vvcm-rs` for Rust, Python, C++, C, and JavaScript/TypeScript users with:\n\n- A Rust VVCM forward-kinematics API that exposes `nalgebra` `Point2`/`Point3` aliases directly and accepts slice-based formation inputs.\n- Python bindings published as `vvcm-rs` / `vvcm_rs` with NumPy-first typed package metadata.\n- C++17 wrapper headers and a C ABI for native consumers using row-major matrix views.\n- WebAssembly bindings published to npm as `@morningfrog/vvcm-rs` and the unscoped mirror `vvcm-rs`, using `Float32Array` inputs and Rust-like solution objects.\n- Stable-solution search with taut-cable enumeration, candidate solving, and stable-branch filtering.\n- Velocity-driven and manual simulation wrappers.\n- Distribution through crates.io, npm, PyPI, GitHub Releases, and vcpkg overlays.\n\n## Module Overview\n\n- `fk`: forward kinematics engine state and stable-solution entry point.\n- `simulation`: velocity-driven simulation wrapper.\n- `manual_simulation`: wrapper for querying a new stable solution from an externally provided robot formation.\n- `types`: public `nalgebra` aliases, conversion helpers, and FK solution collections.\n- `ffi`: C++ wrapper and C ABI implementation behind the native headers.\n- `wasm`: WebAssembly bindings compiled with the `wasm` feature for npm packages.\n- `error`: crate error type.\n\n## Installation\n\nFor source-based installation or local development, read [CONTRIBUTING.md](CONTRIBUTING.md) first.\n\n### Rust\n\nUse the crate from crates.io:\n\n```shell\ncargo add vvcm-rs\n```\n\n### Python\n\nInstall the package from PyPI:\n\n```shell\npython -m pip install vvcm-rs\n```\n\nPrebuilt PyPI wheels are published for CPython 3.10 through 3.14 on Windows x64, Linux x64, and macOS arm64. The Python API depends on NumPy and uses C-contiguous `float32` arrays for formation, sheet, velocity, and result buffers. Python 3.9 and other platforms may fall back to building from the source distribution, which requires a local Rust toolchain and Python build tooling.\n\n### C++ and C\n\nInstall the prebuilt package from the GitHub release archive:\n\n```shell\nvcpkg install vvcm-rs --overlay-ports=\u003cpath-to-unzipped-release\u003e/ports --triplet \u003cplatform-triplet\u003e\n```\n\nThe prebuilt overlay ships native packages for Windows x64, Linux x64, and macOS arm64. It does not require Rust. Use the triplet that matches your platform, such as `x64-windows`, `x64-linux`, or `arm64-osx`.\n\nIf you want to build from the repository source instead, use the repo-local overlay port. That overlay builds the native Rust library with Cargo, so Rust must be installed on the machine running vcpkg. Python is only needed when you build the Python extension feature:\n\n```shell\nvcpkg install vvcm-rs --overlay-ports=\u003cpath-to-vvcm-rs\u003e/vcpkg/ports\n```\n\nThen consume the installed CMake package:\n\n```cmake\nfind_package(vvcm-rs CONFIG REQUIRED)\ntarget_link_libraries(app PRIVATE vvcm_rs::vvcm_rs)\n```\n\n### JavaScript and TypeScript\n\nInstall the WebAssembly package from npm:\n\n```shell\nnpm install @morningfrog/vvcm-rs\n```\n\nThe unscoped mirror package is also published for users who prefer the shorter install name:\n\n```shell\nnpm install vvcm-rs\n```\n\nThe npm packages target modern bundlers such as Vite, Webpack, and Rollup. They include `index.d.ts` TypeScript declarations and expose ready-to-use named exports from the package entry point.\n\n## Usage\n\nThe language-specific snippets below assume installation is already complete. Choose the section that matches your project.\n\nThe sample outputs below round floating-point values to three decimals; small platform differences are normal.\n\n### Rust Usage\n\nAfter adding `vvcm-rs` from crates.io, the Rust API looks like this:\n\n```rust\nuse vvcm_rs::{Point2, VvcmError, VvcmFk};\n\nfn main() -\u003e Result\u003c(), VvcmError\u003e {\n    // Robot formation: each Point2 is a robot node position on the\n    // world-coordinate XY plane, in millimeters.\n    let formation = vec![\n        Point2::new(213.7, 122.7),\n        Point2::new(804.6, 37.2),\n        Point2::new(904.0, 550.0),\n        Point2::new(439.3, 715.9),\n    ];\n\n    // Unfolded sheet: each Point2 is a vertex in the sheet's local\n    // coordinate frame, in millimeters.\n    let sheet = vec![\n        Point2::new(-316.1, -421.9),\n        Point2::new(803.4, -384.1),\n        Point2::new(746.1, 712.8),\n        Point2::new(-367.3, 664.2),\n    ];\n\n    // Create the FK solver with a 1000 mm hold height.\n    // The robot count is inferred from sheet.len().\n    let mut fk = VvcmFk::new(1000.0, sheet)?;\n\n    // Ask the solver to enumerate every candidate equilibrium for this formation.\n    let solutions = fk.update_stable_solutions(\u0026formation)?;\n\n    // Report the total branch count and the subset that is stable.\n    println!(\"all solutions: {}\", solutions.all_count());\n    println!(\"stable solutions: {}\", solutions.stable_count());\n\n    // Print each stable branch. lambda_values[i] belongs to taut_cables[i]\n    // on the same solution.\n    for (index, solution) in solutions.stable().enumerate() {\n        let lambda_values = solution\n            .lambda_values\n            .iter()\n            .map(|value| format!(\"{value:.3}\"))\n            .collect::\u003cVec\u003c_\u003e\u003e()\n            .join(\", \");\n        println!(\n            \"#{index}: Po=({:.3}, {:.3}, {:.3}), Vo=({:.3}, {:.3}), taut={:?}, lambda=[{}]\",\n            solution.po.x,\n            solution.po.y,\n            solution.po.z,\n            solution.vo.x,\n            solution.vo.y,\n            solution.taut_cables,\n            lambda_values,\n        );\n    }\n\n    Ok(())\n}\n```\n\nExpected output:\n\n```text\nall solutions: 3\nstable solutions: 2\n#0: Po=(568.841, 324.728, 336.736), Vo=(238.633, 125.028), taut=[0, 1, 2], lambda=[0.480, 0.039, 0.481]\n#1: Po=(557.919, 341.232, 337.247), Vo=(208.794, 152.532), taut=[0, 2, 3], lambda=[0.493, 0.495, 0.012]\n```\n\n### Python Usage\n\nAfter installing `vvcm-rs` from PyPI, import it as `vvcm_rs` and pass C-contiguous NumPy `float32` arrays. Formation and sheet arrays use shape `(n, 2)`, while object reference points use shape `(3,)`.\n\n```python\nimport numpy as np\nfrom vvcm_rs import VvcmFk\n\n# Robot formation: each row is a robot node position on the world-coordinate\n# XY plane, in millimeters.\n# Keep dtype=np.float32 and a C-contiguous shape of (n, 2) for zero-copy binding input.\nformation = np.array(\n    [\n        [213.7, 122.7],\n        [804.6, 37.2],\n        [904.0, 550.0],\n        [439.3, 715.9],\n    ],\n    dtype=np.float32,\n)\n# Unfolded sheet: each row is a vertex in the sheet's local coordinate\n# frame, in millimeters.\n# The row order must match the robot/cable order used by formation.\nsheet = np.array(\n    [\n        [-316.1, -421.9],\n        [803.4, -384.1],\n        [746.1, 712.8],\n        [-367.3, 664.2],\n    ],\n    dtype=np.float32,\n)\n\n# Create the solver with a 1000 mm hold height.\n# The robot count is inferred from sheet.shape[0].\nfk = VvcmFk(1000.0, sheet)\n\n# Solve all candidate equilibria for the current formation.\nsolutions = fk.update_stable_solutions(formation)\n\n# Report the total branch count and the subset that is stable.\nprint(f\"all solutions: {solutions.all_count()}\")\nprint(f\"stable solutions: {solutions.stable_count()}\")\n\n# Print each stable branch.\n# po, vo, taut_cables, and lambda_values are per-solution arrays.\nfor index, solution in enumerate(solutions.stable()):\n    po = solution.po\n    vo = solution.vo\n    # lambda_values[i] belongs to taut_cables[i] on the same solution.\n    lambda_values = \", \".join(f\"{value:.3f}\" for value in solution.lambda_values)\n    print(\n        f\"#{index}: Po=({po[0]:.3f}, {po[1]:.3f}, {po[2]:.3f}), \"\n        f\"Vo=({vo[0]:.3f}, {vo[1]:.3f}), taut={solution.taut_cables.tolist()}, \"\n        f\"lambda=[{lambda_values}]\"\n    )\n```\n\nExpected output:\n\n```text\nall solutions: 3\nstable solutions: 2\n#0: Po=(568.841, 324.728, 336.736), Vo=(238.633, 125.028), taut=[0, 1, 2], lambda=[0.480, 0.039, 0.481]\n#1: Po=(557.919, 341.232, 337.247), Vo=(208.794, 152.532), taut=[0, 2, 3], lambda=[0.493, 0.495, 0.012]\n```\n\n### C++ Usage\n\nAfter installing the vcpkg package or a release archive, consume the installed CMake package and headers directly. The package exports the C++17 RAII wrapper in `vvcm_rs.hpp` and the raw C ABI in `vvcm_rs.h`.\n\n```cmake\nfind_package(vvcm-rs CONFIG REQUIRED)\ntarget_link_libraries(app PRIVATE vvcm_rs::vvcm_rs)\n```\n\n```cpp\n#include \u003cvvcm_rs.hpp\u003e\n\n#include \u003ccstddef\u003e\n#include \u003ciomanip\u003e\n#include \u003ciostream\u003e\n#include \u003cvector\u003e\n\nint main() {\n    using namespace vvcm_rs;\n\n    // Robot formation in row-major [x0, y0, x1, y1, ...] order, in millimeters.\n    const std::vector\u003cfloat\u003e formation = {\n        213.7f, 122.7f,\n        804.6f, 37.2f,\n        904.0f, 550.0f,\n        439.3f, 715.9f,\n    };\n\n    // Unfolded sheet vertices in the sheet's local frame, using the same\n    // row-major layout.\n    const std::vector\u003cfloat\u003e sheet = {\n        -316.1f, -421.9f,\n        803.4f, -384.1f,\n        746.1f, 712.8f,\n        -367.3f, 664.2f,\n    };\n\n    // The RAII wrapper owns the raw C handle and releases it automatically.\n    VvcmFk fk(1000.0f, matrix_view(sheet));\n\n    // matrix_view borrows the vectors and exposes them as VvcmRsMat2f without copying.\n    FkSolutions solutions = fk.update_stable_solutions(matrix_view(formation));\n\n    // Report the total branch count and the subset that is stable.\n    std::cout \u003c\u003c \"all solutions: \" \u003c\u003c solutions.all_count() \u003c\u003c \"\\n\";\n    std::cout \u003c\u003c \"stable solutions: \" \u003c\u003c solutions.stable_count() \u003c\u003c \"\\n\";\n\n    // stable() returns owning FkSolution objects; each one carries its\n    // taut cables and lambda values.\n    std::cout \u003c\u003c std::fixed \u003c\u003c std::setprecision(3);\n    const std::vector\u003cFkSolution\u003e stable = solutions.stable();\n    for (std::size_t index = 0; index \u003c stable.size(); ++index) {\n        const FkSolution \u0026solution = stable[index];\n        const Vec3f \u0026po = solution.po;\n        const Vec2f \u0026vo = solution.vo;\n        const std::vector\u003cstd::size_t\u003e \u0026taut_cables = solution.taut_cables;\n        const std::vector\u003cfloat\u003e \u0026lambda_values = solution.lambda_values;\n\n        std::cout \u003c\u003c \"#\" \u003c\u003c index \u003c\u003c \": Po=(\"\n                  \u003c\u003c po.x \u003c\u003c \", \" \u003c\u003c po.y \u003c\u003c \", \" \u003c\u003c po.z \u003c\u003c \"), Vo=(\"\n                  \u003c\u003c vo.x \u003c\u003c \", \" \u003c\u003c vo.y \u003c\u003c \"), taut=[\";\n        for (std::size_t taut_index = 0; taut_index \u003c taut_cables.size(); ++taut_index) {\n            if (taut_index \u003e 0) {\n                std::cout \u003c\u003c \", \";\n            }\n            std::cout \u003c\u003c taut_cables[taut_index];\n        }\n        std::cout \u003c\u003c \"], lambda=[\";\n        for (std::size_t lambda_index = 0; lambda_index \u003c lambda_values.size(); ++lambda_index) {\n            if (lambda_index \u003e 0) {\n                std::cout \u003c\u003c \", \";\n            }\n            std::cout \u003c\u003c lambda_values[lambda_index];\n        }\n        std::cout \u003c\u003c \"]\\n\";\n    }\n}\n```\n\nExpected output:\n\n```text\nall solutions: 3\nstable solutions: 2\n#0: Po=(568.841, 324.728, 336.736), Vo=(238.633, 125.028), taut=[0, 1, 2], lambda=[0.480, 0.039, 0.481]\n#1: Po=(557.919, 341.232, 337.247), Vo=(208.794, 152.532), taut=[0, 2, 3], lambda=[0.493, 0.495, 0.012]\n```\n\n### C Usage\n\nThe raw C ABI uses explicit handles and matrix views. A `VvcmRsMat2f` is a borrowed row-major view over `[x0, y0, x1, y1, ...]` data; the library never takes ownership of the input arrays.\n\n```c\n#include \u003cvvcm_rs.h\u003e\n\n#include \u003cstdio.h\u003e\n#include \u003cstdlib.h\u003e\n\nstatic int check(VvcmRsErrorCode code, const char *context) {\n    if (code == VVCM_RS_ERROR_OK) {\n        return 1;\n    }\n\n    fprintf(stderr, \"%s failed: %s\\n\", context, vvcm_rs_last_error_message());\n    return 0;\n}\n\nint main(void) {\n    // Robot formation in row-major [x0, y0, x1, y1, ...] order, in millimeters.\n    const float formation_data[] = {\n        213.7f, 122.7f,\n        804.6f, 37.2f,\n        904.0f, 550.0f,\n        439.3f, 715.9f,\n    };\n\n    // Unfolded sheet vertices in the sheet's local frame, using the same\n    // row-major layout.\n    const float sheet_data[] = {\n        -316.1f, -421.9f,\n        803.4f, -384.1f,\n        746.1f, 712.8f,\n        -367.3f, 664.2f,\n    };\n\n    // rows is the point count; stride is the number of floats between adjacent rows.\n    const VvcmRsMat2f formation = {formation_data, 4, 2};\n    const VvcmRsMat2f sheet = {sheet_data, 4, 2};\n\n    VvcmRsFk *fk = NULL;\n    int status = 1;\n\n    // Create the FK solver with a 1000 mm hold height. Release it with vvcm_rs_fk_free.\n    if (!check(vvcm_rs_fk_new(1000.0f, sheet, \u0026fk), \"vvcm_rs_fk_new\")) {\n        goto cleanup;\n    }\n\n    // Solve all candidate equilibria for the current formation.\n    if (!check(vvcm_rs_fk_update_stable_solutions(fk, formation), \"vvcm_rs_fk_update_stable_solutions\")) {\n        goto cleanup;\n    }\n\n    // Query aggregate counts before reading individual solution objects.\n    size_t all_count = 0;\n    size_t stable_count = 0;\n    if (!check(vvcm_rs_fk_solution_count(fk, \u0026all_count), \"vvcm_rs_fk_solution_count\")) {\n        goto cleanup;\n    }\n    if (!check(vvcm_rs_fk_stable_solution_count(fk, \u0026stable_count), \"vvcm_rs_fk_stable_solution_count\")) {\n        goto cleanup;\n    }\n\n    printf(\"all solutions: %zu\\n\", all_count);\n    printf(\"stable solutions: %zu\\n\", stable_count);\n\n    // Read each stable solution. The fixed-size fields are in VvcmRsFkSolution.\n    size_t stable_index = 0;\n    for (size_t index = 0; index \u003c all_count; ++index) {\n        VvcmRsFkSolution solution = {0};\n        if (!check(vvcm_rs_fk_solution_at(fk, index, \u0026solution), \"vvcm_rs_fk_solution_at\")) {\n            goto cleanup;\n        }\n        if (!solution.stable) {\n            continue;\n        }\n\n        // Pass NULL to query the required variable-array length, then pass an\n        // allocated buffer.\n        size_t taut_count = 0;\n        if (!check(vvcm_rs_fk_solution_taut_cables(fk, index, NULL, \u0026taut_count), \"vvcm_rs_fk_solution_taut_cables\")) {\n            goto cleanup;\n        }\n        size_t *taut_cables = taut_count == 0 ? NULL : (size_t *)malloc(taut_count * sizeof(*taut_cables));\n        if (taut_count != 0 \u0026\u0026 taut_cables == NULL) {\n            fprintf(stderr, \"failed to allocate taut cable buffer\\n\");\n            goto cleanup;\n        }\n        size_t taut_capacity = taut_count;\n        if (!check(vvcm_rs_fk_solution_taut_cables(fk, index, taut_cables, \u0026taut_capacity), \"vvcm_rs_fk_solution_taut_cables\")) {\n            free(taut_cables);\n            goto cleanup;\n        }\n\n        // lambda_values[i] belongs to taut_cables[i] on the same solution.\n        size_t lambda_count = 0;\n        if (!check(vvcm_rs_fk_solution_lambda_values(fk, index, NULL, \u0026lambda_count), \"vvcm_rs_fk_solution_lambda_values\")) {\n            free(taut_cables);\n            goto cleanup;\n        }\n        float *lambda_values = lambda_count == 0 ? NULL : (float *)malloc(lambda_count * sizeof(*lambda_values));\n        if (lambda_count != 0 \u0026\u0026 lambda_values == NULL) {\n            fprintf(stderr, \"failed to allocate lambda value buffer\\n\");\n            free(taut_cables);\n            goto cleanup;\n        }\n        size_t lambda_capacity = lambda_count;\n        if (!check(vvcm_rs_fk_solution_lambda_values(fk, index, lambda_values, \u0026lambda_capacity), \"vvcm_rs_fk_solution_lambda_values\")) {\n            free(lambda_values);\n            free(taut_cables);\n            goto cleanup;\n        }\n\n        printf(\n            \"#%zu: Po=(%.3f, %.3f, %.3f), Vo=(%.3f, %.3f), taut=[\",\n            stable_index++,\n            solution.po.x,\n            solution.po.y,\n            solution.po.z,\n            solution.vo.x,\n            solution.vo.y);\n        for (size_t taut_index = 0; taut_index \u003c taut_capacity; ++taut_index) {\n            printf(\"%s%zu\", taut_index == 0 ? \"\" : \", \", taut_cables[taut_index]);\n        }\n        printf(\"], lambda=[\");\n        for (size_t lambda_index = 0; lambda_index \u003c lambda_capacity; ++lambda_index) {\n            printf(\"%s%.3f\", lambda_index == 0 ? \"\" : \", \", lambda_values[lambda_index]);\n        }\n        printf(\"]\\n\");\n\n        free(lambda_values);\n        free(taut_cables);\n    }\n\n    status = 0;\n\ncleanup:\n    vvcm_rs_fk_free(fk);\n    return status;\n}\n```\n\nExpected output:\n\n```text\nall solutions: 3\nstable solutions: 2\n#0: Po=(568.841, 324.728, 336.736), Vo=(238.633, 125.028), taut=[0, 1, 2], lambda=[0.480, 0.039, 0.481]\n#1: Po=(557.919, 341.232, 337.247), Vo=(208.794, 152.532), taut=[0, 2, 3], lambda=[0.493, 0.495, 0.012]\n```\n\n### JavaScript and TypeScript Usage\n\nAfter installing `@morningfrog/vvcm-rs` or `vvcm-rs` from npm, import the WebAssembly module and pass row-major `Float32Array` buffers. Formation and sheet arrays are laid out as `[x0, y0, x1, y1, ...]`.\n\n```ts\nimport { VvcmFk } from \"@morningfrog/vvcm-rs\";\n\n// Robot formation in row-major [x0, y0, x1, y1, ...] order, in millimeters.\nconst formation = new Float32Array([\n  213.7, 122.7,\n  804.6, 37.2,\n  904.0, 550.0,\n  439.3, 715.9,\n]);\n\n// Unfolded sheet vertices in the sheet's local frame, using the same row-major layout.\nconst sheet = new Float32Array([\n  -316.1, -421.9,\n  803.4, -384.1,\n  746.1, 712.8,\n  -367.3, 664.2,\n]);\n\n// Create the FK solver with a 1000 mm hold height.\n// The robot count is inferred from sheet.length / 2.\nconst fk = new VvcmFk(1000, sheet);\n\n// Solve all candidate equilibria. Each returned solution object owns its\n// own tautCables and lambdaValues arrays.\nconst solutions = fk.updateStableSolutions(formation);\n\n// Report the total branch count and the subset that is stable.\nconsole.log(`all solutions: ${solutions.allCount}`);\nconsole.log(`stable solutions: ${solutions.stableCount}`);\n\n// Print stable branches only. lambdaValues[i] belongs to tautCables[i]\n// on the same solution.\nfor (const [index, solution] of solutions.solutions.entries()) {\n  if (!solution.stable) {\n    continue;\n  }\n\n  const lambdaValues = solution.lambdaValues.map((value) =\u003e value.toFixed(3)).join(\", \");\n  console.log(\n    `#${index}: Po=(${solution.po.x.toFixed(3)}, ${solution.po.y.toFixed(3)}, ${solution.po.z.toFixed(3)}), ` +\n      `Vo=(${solution.vo.x.toFixed(3)}, ${solution.vo.y.toFixed(3)}), taut=${JSON.stringify(solution.tautCables)}, ` +\n      `lambda=[${lambdaValues}]`,\n  );\n}\n\n// Free the underlying WASM allocation when the solver is no longer needed.\nfk.free();\n```\n\nExpected output:\n\n```text\nall solutions: 3\nstable solutions: 2\n#0: Po=(568.841, 324.728, 336.736), Vo=(238.633, 125.028), taut=[0,1,2], lambda=[0.480, 0.039, 0.481]\n#1: Po=(557.919, 341.232, 337.247), Vo=(208.794, 152.532), taut=[0,2,3], lambda=[0.493, 0.495, 0.012]\n```\n\nAcross bindings, lambda values are taut-only: each `lambda_values` or `lambdaValues` entry corresponds to the matching taut cable entry on the same solution object. Slack cables are omitted instead of represented by zero-valued placeholders.\n\nLength units are not encoded in the API. Use one consistent unit for formation coordinates, sheet coordinates, and hold height; `VvcmFk` normalizes coordinates internally for numerical stability and maps returned object positions and virtual object points back to the original coordinate frames.\n\n## Error Handling\n\nForward-kinematics and simulation solves report failures through each language's normal error channel. Error messages are intended for human diagnostics; branch on Rust enum variants, Python exception classes, C++ and C error codes, or JavaScript error codes when program logic needs to distinguish failure modes.\n\nThe snippets below show one simple handling pattern for each language: catch the failure, print the message, and branch on the typed error when you need a specific recovery path.\n\n### Rust\n\n```rust\nmatch fk.update_stable_solutions(\u0026formation) {\n    Ok(solutions) =\u003e {\n        // The successful result has the same Rust-like per-solution shape\n        // as the normal example above.\n        println!(\"stable solutions: {}\", solutions.stable_count());\n    }\n    Err(vvcm_rs::VvcmError::InfeasibleFormation) =\u003e {\n        // Branch on a specific enum variant when program logic can recover\n        // from that case.\n        eprintln!(\"formation is infeasible\");\n    }\n    Err(error) =\u003e {\n        eprintln!(\"vvcm-rs solve failed: {error}\");\n    }\n}\n```\n\nThe main solve errors in Rust are:\n\n- `VvcmError::DimensionMismatch` for input size mismatches during construction or solve setup.\n- `VvcmError::InfeasibleFormation` when the robot formation cannot be realized by the sheet geometry.\n- `VvcmError::NoSolution` when no candidate branch can be constructed.\n- `VvcmError::NoStableSolution` when candidate branches exist but none are stable.\n- `VvcmError` remains the common error type, so `Err(error)` still catches any of them and `Err(vvcm_rs::VvcmError::InfeasibleFormation)` can catch one case specifically.\n\n### Python\n\n```python\nfrom vvcm_rs import InfeasibleFormationError, VvcmError\n\ntry:\n    solutions = fk.update_stable_solutions(formation)\nexcept InfeasibleFormationError as error:\n    # Catch a specific subclass when this failure has a dedicated recovery path.\n    print(f\"formation is infeasible: {error}\")\nexcept VvcmError as error:\n    # VvcmError is the common base class for all binding-level solve failures.\n    print(f\"vvcm-rs solve failed: {error}\")\nelse:\n    # The success value is an FkSolutions object with per-solution FkSolution entries.\n    print(f\"stable solutions: {solutions.stable_count()}\")\n```\n\nThe main solve errors in Python are:\n\n- `DimensionMismatchError` for input size mismatches during construction or solve setup.\n- `InfeasibleFormationError` when the robot formation cannot be realized by the sheet geometry.\n- `NoSolutionError` when no candidate branch can be constructed.\n- `NoStableSolutionError` when candidate branches exist but none are stable.\n- `VvcmError` remains the common base class, so `except VvcmError as error` still catches any of them and `except InfeasibleFormationError as error` can catch one case specifically.\n\n### C++\n\n```cpp\ntry {\n    // The C++ wrapper throws vvcm_rs::Error instead of returning VvcmRsErrorCode.\n    auto solutions = fk.update_stable_solutions(vvcm_rs::matrix_view(formation));\n    std::cout \u003c\u003c \"stable solutions: \" \u003c\u003c solutions.stable_count() \u003c\u003c \"\\n\";\n} catch (const vvcm_rs::Error \u0026error) {\n    std::cerr \u003c\u003c \"vvcm-rs failed: \" \u003c\u003c error.what()\n              \u003c\u003c \" (code \" \u003c\u003c error.code() \u003c\u003c \")\\n\";\n    if (error.code() == VVCM_RS_ERROR_INFEASIBLE_FORMATION) {\n        std::cerr \u003c\u003c \"formation is infeasible\\n\";\n    }\n}\n```\n\nThe main solve errors in C++ are:\n\n- `VVCM_RS_ERROR_DIMENSION_MISMATCH` for input size mismatches during construction or solve setup.\n- `VVCM_RS_ERROR_INFEASIBLE_FORMATION` when the robot formation cannot be realized by the sheet geometry.\n- `VVCM_RS_ERROR_NO_SOLUTION` when no candidate branch can be constructed.\n- `VVCM_RS_ERROR_NO_STABLE_SOLUTION` when candidate branches exist but none are stable.\n- `vvcm_rs::Error` keeps the originating code, so `catch (const vvcm_rs::Error \u0026error)` still handles all failures and `error.code()` lets you branch on one case specifically.\n\n### C\n\n```c\nVvcmRsMat2f formation = {formation_data, formation_rows, 2};\n\n// Run the solve and inspect the typed C error code.\n// last_error_message gives the detailed thread-local message.\nVvcmRsErrorCode code = vvcm_rs_fk_update_stable_solutions(fk, formation);\nif (code != VVCM_RS_ERROR_OK) {\n    fprintf(stderr, \"vvcm-rs failed: %s\\n\", vvcm_rs_last_error_message());\n    if (code == VVCM_RS_ERROR_INFEASIBLE_FORMATION) {\n        fprintf(stderr, \"formation is infeasible\\n\");\n    }\n} else {\n    // After a successful solve, query aggregate counts directly from the FK handle.\n    size_t solution_count = 0;\n    code = vvcm_rs_fk_solution_count(fk, \u0026solution_count);\n    if (code == VVCM_RS_ERROR_OK) {\n        fprintf(stdout, \"all solutions: %zu\\n\", solution_count);\n    }\n\n    // solution_at returns the fixed-size fields; variable arrays are copied\n    // through separate functions.\n    VvcmRsFkSolution solution = {0};\n    code = vvcm_rs_fk_solution_at(fk, 0, \u0026solution);\n    if (code == VVCM_RS_ERROR_OK) {\n        // Pass NULL to query the required taut-cable count before allocating a buffer.\n        size_t taut_count = 0;\n        code = vvcm_rs_fk_solution_taut_cables(fk, 0, NULL, \u0026taut_count);\n        if (code == VVCM_RS_ERROR_OK) {\n            fprintf(stdout, \"first solution taut cables: %zu\\n\", taut_count);\n        }\n    }\n}\n```\n\nThe main solve errors in C are:\n\n- `VVCM_RS_ERROR_DIMENSION_MISMATCH` for input size mismatches during construction or solve setup.\n- `VVCM_RS_ERROR_INFEASIBLE_FORMATION` when the robot formation cannot be realized by the sheet geometry.\n- `VVCM_RS_ERROR_NO_SOLUTION` when no candidate branch can be constructed.\n- `VVCM_RS_ERROR_NO_STABLE_SOLUTION` when candidate branches exist but none are stable.\n- `vvcm_rs_last_error_message()` returns the human-readable message for the most recent failure on the current thread, while `vvcm_rs_error_message(code)` returns the generic message for a given code.\n\n### JavaScript and TypeScript\n\n```ts\nimport { VvcmFk, type VvcmError } from \"@morningfrog/vvcm-rs\";\n\nfunction isVvcmError(error: unknown): error is VvcmError {\n  // The WASM wrapper throws Error objects with a stable vvcm-rs code field.\n  return error instanceof Error \u0026\u0026 error.name === \"VvcmError\" \u0026\u0026 \"code\" in error;\n}\n\ntry {\n  const solutions = fk.updateStableSolutions(formation);\n  console.log(`stable solutions: ${solutions.stableCount}`);\n} catch (error) {\n  if (isVvcmError(error) \u0026\u0026 error.code === \"INFEASIBLE_FORMATION\") {\n    console.error(\"formation is infeasible\");\n  } else {\n    console.error(\"vvcm-rs solve failed:\", error);\n  }\n}\n```\n\nThe main solve errors in JavaScript and TypeScript are:\n\n- `DIMENSION_MISMATCH` for input size mismatches during construction or solve setup.\n- `INFEASIBLE_FORMATION` when the robot formation cannot be realized by the sheet geometry.\n- `NO_SOLUTION` when no candidate branch can be constructed.\n- `NO_STABLE_SOLUTION` when candidate branches exist but none are stable.\n- `INVALID_ARGUMENT` when a JavaScript typed array has an invalid length for the expected point, formation, sheet, or velocity shape.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorningfrog%2Fvvcm-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorningfrog%2Fvvcm-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorningfrog%2Fvvcm-rs/lists"}