SRMP is a motion planning software for robotic manipulation, leveraging state-of-the-art search-based algorithms. It ensures consistent and predictable motions, backed by rigorous theoretical guarantees. Additionally, SRMP can efficiently plan for up to dozens of manipulators while guaranteeing collision-free execution—both between robots and with the environment—while maintaining motion consistency and predictability.
Why SRMP?
Existing motion planning frameworks often struggle with the demands of high-stakes applications, where predictability and repeatability are critical. SRMP addresses these challenges by leveraging search-based planning methods, ensuring motions that are both efficient and reliable. Whether you're working on robotic manipulation, industrial automation, or large-scale multi-robot coordination, SRMP provides a powerful solution tailored to your needs.
Key Features
- Multi-Robot Motion Planning: First-of-its-kind support for planning coordinated motions in multi-manipulator systems.
- Reliable and Consistent Trajectories: Generates predictable and repeatable motions, making it ideal for high-precision and safety-critical applications.
- Seamless Integration: Compatible with major simulators, including MuJoCo, Sapien, Genesis, PyBullet and Isaac.
- Multi-Lingual: Available in both Python and C++ for easy integration into research and industrial workflows.
- MoveIt! Plugin: Enables deployment on real-world robotic systems with minimal setup.
Getting Started
To get started, check our documentation.
- Saving and Loading — scenes, agent chats, and plans
Build and Develop within the Package
The default editable install builds the VAMP SIMD validity-checking backend
alongside the coal backend. VAMP is vendored as a git submodule at vamp/
(pinned to the commit this integration expects), exactly like search/ and
third_party/pybind11, so the default build needs no install step, no
download, and no vamp_DIR — it compiles the in-tree VAMP as a CMake source
subproject and links the stock vamp::vamp target:
git submodule update --init --recursive
pip install -e .
VAMP requires CMake 3.16+, a C++17 compiler, and Eigen 3.4+. On x86-64 the result requires AVX2; ARM builds use NEON.
A source build targets the host CPU (-march=native). The PyPI wheels are
built for a fixed baseline instead: x86-64-v3 (AVX2/FMA/BMI2, Intel Haswell
2013+ and AMD Excavator/Zen+) on Linux, and Apple M1+ on macOS. import srmp
checks for those features on Linux x86-64 and raises an ImportError naming
what is missing rather than crashing later with an illegal instruction. On an
older CPU, build from source with the coal-only option below, or set
SRMP_SKIP_CPU_CHECK=1 to bypass the check. To pin a different baseline for
your own wheel, pass
--config-settings=cmake.define.IMS_MANIP_VAMP_ARCH_FLAGS=-march=<target>.
Coal-only build
To build without VAMP (for example on an architecture VAMP's SIMD backend does
not support, or a checkout where the vamp/ submodule was not initialized),
turn the backend off explicitly:
python -m pip install -e . \
--config-settings=cmake.define.IMS_MANIP_WITH_VAMP=OFF
This uses coal mesh collision checking only; get_available_validity_backends()
then reports ['coal'].
VAMP-enabled build details
Restart all running Python and Viser processes after rebuilding, then verify the loaded extension:
python -c "import srmp; p=srmp.PlannerInterface(); print(p.get_available_validity_backends())"
# ['coal', 'vamp']
VAMP resolution order at configure time, first match wins:
- An explicitly installed package (
find_package(vamp CONFIG)), so pointing at an install with-Dvamp_DIR=<prefix>/share/cmake/vampstill behaves as before. - The in-tree submodule at
vamp/(built as a subproject) — the default. - A download of the pinned fork into
ext/vampwhen-DIMS_MANIP_FETCH_VAMP=ONand the submodule is absent (fallback for checkouts without submodules).
If none resolve, the configure fails with instructions; the quickest fix is
git submodule update --init --recursive vamp.
SRMP uses stock VAMP headers and the stock vamp::vamp target. The
vamp-planner Python package is not a substitute for the C++ source or CMake
target used here.
Include .[agent] instead of . if the Gemini-backed CLI/Viser agent should
be installed at the same time.
A VAMP-enabled extension still defaults to coal, so the same installation supports controlled A/B runs:
from srmp import PlannerInterface
planner = PlannerInterface()
planner.add_robot("panda")
print(planner.get_available_validity_backends()) # ["coal", "vamp"]
print(planner.get_vamp_supported_robots()) # ["fetch", "panda", "ur5"]
print(planner.is_robot_vamp_supported("panda")) # True
context = {
"planner_id": "wAstar",
"heuristic": "bfs",
"weight": "10",
"validity_backend": "auto", # or "coal" / "vamp" explicitly
}
planner.make_planner(["panda"], context)
# "auto" picks VAMP when this build and robot support it, coal otherwise;
# an explicit "vamp" raises instead of falling back.
print(planner.get_active_validity_backend()) # "vamp"
Planners
Single-agent planner_id values: Astar, wAstar, ARAstar, MHAstar, wPASE (parallel), MGS,
vamp_rrtc; multi-agent (more than one articulation): ECBS, xECBS. All context values are strings.
vamp_rrtc is VAMP's RRT-Connect run on the VAMP backend's SIMD environment (Halton sampling,
deterministic): joint goals only, validity_backend vamp or auto, typically well under a
millisecond on uncluttered scenes. Keys: rrtc_range (2.0 rad), rrtc_max_iterations (100000),
rrtc_max_samples (100000), rrtc_simplify (true: VAMP's shortcut/B-spline pass). The
simplified path goes through the usual shortcutting and time parameterization; stats report
rrtc_iterations, rrtc_raw_waypoints, rrtc_waypoints, rrtc_solve_time, rrtc_simplify_time.
MGS (multi-graph search) grows a start graph and, for joint goals, a goal graph, and adds
further graphs rooted at IK solutions of the BFS heuristic's workspace attractors when the
search stalls, bridging graphs with straight joint-space edges:
context = {
"planner_id": "MGS",
"g_num": "10", # maximum number of graphs (start, goal, roots)
"weight": "100", # suboptimality bound of the anchor (start) graph search
"heuristic": "joint_steps", # root-to-root heuristic (joint distance in discretization steps)
"anchor_heuristic": "bfs", # goal-distance heuristic for the start graph; "joint_steps" (aimed at the
# goal root for pose goals) is 10x faster through narrow passages, slower on easy scenes
"graph_growth": "on_stall", # or "upfront": all g_num - 2 roots before searching
"stall_window": "50", # expansions without progress before a root is added
"root_selection": "frontier_nearest", # or "sequential"
"nonblocking_roots": "false", # "true": never wait for root IK; inject roots as they become ready
"goal_roots": "1", # pose goals: goal graphs rooted at IK solutions of the goal pose
"bridge_nn": "kd_tree", # bridge partner lookup: k-d tree per graph, or "heuristic" (linear scan)
"anchor_switch_on_stall": "true", # at the first stall switch the start graph's anchor to
"anchor_fallback_heuristic": "joint_steps", # this heuristic (BFS in the open, joint-space in depressions)
"root_clearance": "0.05", # roots retreat from obstacles until the EE has this clearance (m)
"short_prims_near_start": "false", # "true": use the short primitives within short_dist of the start too
"anchor_search": "true", # continue after the first connection to bound the cost
"expansion_threads": "12", # parallel edge checks per expansion (coal backend)
"snap_ik_seeds": "1", # IK seeds per snap attempt on pose goals (>1 runs them on the worker pool)
"ik_dt": "1.0", # CLIK step scale (full step); "ik_max_iter": "50", "ik_eps": "1e-5"
"snap_ik_backoff": "0", # opt-in: skip 1, 2, 4.. snap IKs after a failure (see design notes)
"snap_cached_goals": "3", # goal configurations kept as snap targets
"vamp_packed_edges": "true", # VAMP: validate all primitive edges of an expansion in shared SIMD blocks
"root_ik_threads": "6", # background root IK threads
"anchor_orientation_weight": "0", # cost/rad of EE orientation error in the anchor heuristic
"time_limit": "10",
}
Mobile manipulators and primitive-file units
A mobile base modelled as ordinary joints of the URDF (x, y prismatic and theta revolute, as
in data/ridgeback_ur10e) is part of the move group of its end effector, and the planners treat
the joints by kind: the articulation reports get_move_group_joint_types(), prismatic
coordinates are never wrapped by 2*pi, and the lattice uses resolution degrees for revolute
joints and resolution_prismatic metres (default 0.05) for prismatic ones. A primitive file may
declare its units and cost model at the top level:
units: steps # degrees (default) | radians | meters | steps -- steps: multiples of the joint's discretization
cost: time # file (default): the listed transition costs | time: duration at the joint velocity limits
With units: steps one file serves any mix of prismatic and revolute joints; with cost: time
base, torso and arm motions -- and MGS bridge and snap edges -- are priced in seconds, so the
search compares them on one scale.
A composite file assembles the move group from per-group primitive files, each in its own units
and magnitudes, padded into the full state (config/ridgeback_ur10e_mprim.yaml):
cost: time
groups:
- name: base
joints: [ x, y, theta ] # move-group joint names (or `dims: [0, 1, 2]`)
file: omni_base_mprim.yaml # 0.4 m / 10 deg long steps, 0.1 m / 2 deg short steps, diagonals
- name: arm
file: manip_6dof_mprim.yaml # no joints/dims: the remaining coordinates, in order
Group files are looked up next to the composite. The composite's cost: applies to every group.
A mobile manipulator's end effector roams far beyond the default 2 m occupancy grid, so construct
the PlannerInterface with a GridConfig that covers the base's workspace (the BFS heuristic
reports the goal position and the grid bounds otherwise).
On a mobile manipulator the planners' bfs heuristic is base-aware: besides the end-effector
wavefront it runs a 2D wavefront over the floor (obstacles up to base_height, inflated by
base_footprint_radius) from the docking region within arm_reach of the goal, and takes the
larger of the two, so a knee-high wall the end effector passes over still sends the base around.
Under cost: time the cells are priced in seconds (ee_speed, default 1 m/s; base_speed,
default the slower base velocity limit) and joint_steps heuristics become joint_time, the
straight-move duration at the velocity limits:
context = {
"planner_id": "wAstar", "heuristic": "bfs", "weight": "50",
"mprim_path": "config/ridgeback_ur10e_mprim.yaml",
"resolution": "1", # degrees, revolute joints
"resolution_prismatic": "0.05", # metres, prismatic joints (base x/y, torso)
"base_footprint_radius": "0.5", "base_height": "0.5", "arm_reach": "1.0",
}
Roots are cached across plans with the same start, goal pose and scene, and get_planning_stats()
reports MGS counters (bridge_attempts, graph_merges, num_roots, num_graphs, timings) under
bonus_stats. Design notes and measurements: docs/plans/2026-09-03-mgs-followups.md,
docs/plans/2026-09-03-mgs-incremental-graphs-design.md.
The occupancy grid (the BFS heuristics' world) defaults to 2 x 2 x 2 m at 2 cm around the
origin. Adding a mobile manipulator grows it to the base's x/y limits plus 1.5 m at 5 cm
automatically; set_grid(GridConfig) rebuilds it at any time (obstacles are voxelized again,
the planner is rebuilt on the next plan) and get_grid_config() reports it. The Viser panel's
Occupancy grid folder shows the bounds as an outline in the scene and edits them; the
Planner context field takes extra key=value pairs for the next Plan.
A mobile manipulator needs no context at all: with no mprim_path the factories compose
omni_base_mprim.yaml with the arm's file (config/mobile_6dof_mprim.yaml,
mobile_7dof_mprim.yaml), and bfs is the base-aware heuristic. In the Viser Plan panel tick
Goal as EE pose to send the gizmo pose itself and let the planner place the base (off, the
panel plans to the IK solution shown as the ghost); to tune, fill viz.plan_context_defaults
(e.g. {"weight": "50", "arm_reach": "1.2"}), which every Plan click merges into its
make_planner call, the panel's own planner and backend choices winning.
In the Viser Plan panel, select one robot and leave Collision backend
on Auto (VAMP when supported) (or pick VAMP (SIMD spheres) explicitly --
the option is offered only when the selected robot has a Robot_Module),
position the end-effector goal, and click
Plan to goal. The status line reports backend: vamp when VAMP is actually
attached to the planner. If the VAMP option is absent, the running process
loaded a coal-only extension; repeat the backend verification above and restart
Viser. VAMP selection is intentionally limited to single-agent planners until
Phase 2 multi-robot verification is complete. A coal-only build advertises only
coal and gives a reinstall message if Python requests VAMP directly.
Agentic mode
The default CLI backend is Gemini and uses Google's native google-genai SDK.
For a new editable installation, install the agent extra and configure a Google
AI Studio API key:
pip install -e ".[agent]"
export GEMINI_API_KEY=...
python -m srmp.agent.cli
The default build already includes VAMP, so no extra CMake flags are needed —
just add the [agent] extra:
git submodule update --init --recursive
pip install -e ".[agent]"
You can create a free key at https://aistudio.google.com/app/apikey. To add Gemini to an existing installation without rebuilding SRMP, install only the SDK with Python-compatible pins:
python -m pip install \
'google-genai==1.47.0; python_version < "3.10"' \
'google-genai==2.17.0; python_version >= "3.10"'
Other backends have separate dependencies; run
python -m srmp.agent.cli --help or select a backend in the GUI for their
setup instructions.
Launch the Viser agent interface with:
python -m srmp.agent.gui
Metadata
Release files for srmp 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| srmp-0.1.5-cp314-cp314-manylinux_2_35_x86_64.whl | CPython 3.14 | CPython 3.14 | Linux glibc 2.35+ x86-64 | Details |
| srmp-0.1.5-cp314-cp314-macosx_11_0_arm64.whl | CPython 3.14 | CPython 3.14 | macOS 11.0+ ARM64 | Details |
| srmp-0.1.5-cp313-cp313-manylinux_2_35_x86_64.whl | CPython 3.13 | CPython 3.13 | Linux glibc 2.35+ x86-64 | Details |
| srmp-0.1.5-cp313-cp313-macosx_11_0_arm64.whl | CPython 3.13 | CPython 3.13 | macOS 11.0+ ARM64 | Details |
| srmp-0.1.5-cp312-cp312-manylinux_2_35_x86_64.whl | CPython 3.12 | CPython 3.12 | Linux glibc 2.35+ x86-64 | Details |
| srmp-0.1.5-cp312-cp312-macosx_11_0_arm64.whl | CPython 3.12 | CPython 3.12 | macOS 11.0+ ARM64 | Details |
| srmp-0.1.5-cp311-cp311-manylinux_2_35_x86_64.whl | CPython 3.11 | CPython 3.11 | Linux glibc 2.35+ x86-64 | Details |
| srmp-0.1.5-cp311-cp311-macosx_11_0_arm64.whl | CPython 3.11 | CPython 3.11 | macOS 11.0+ ARM64 | Details |
| srmp-0.1.5-cp310-cp310-manylinux_2_35_x86_64.whl | CPython 3.10 | CPython 3.10 | Linux glibc 2.35+ x86-64 | Details |
| srmp-0.1.5-cp310-cp310-macosx_11_0_arm64.whl | CPython 3.10 | CPython 3.10 | macOS 11.0+ ARM64 | Details |
| srmp-0.1.5-cp39-cp39-manylinux_2_35_x86_64.whl | CPython 3.9 | CPython 3.9 | Linux glibc 2.35+ x86-64 | Details |
| srmp-0.1.5-cp39-cp39-macosx_11_0_arm64.whl | CPython 3.9 | CPython 3.9 | macOS 11.0+ ARM64 | Details |
Total release size: 143.0 MB
Release files / srmp-0.1.5-cp314-cp314-manylinux_2_35_x86_64.whl
| Download URL | srmp-0.1.5-cp314-cp314-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.14 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
9a025ca6630bb9ac93972568b54a85f891c80d80f8fe341327e7e48290751203
|
|
BLAKE2b-256 checksum How to use checksums |
77719a853afae0d4b138a6517d9f836bdd790575eb370bf40a4d90a1e08ff7b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp314-cp314-macosx_11_0_arm64.whl
| Download URL | srmp-0.1.5-cp314-cp314-macosx_11_0_arm64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | CPython 3.14 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8d38b8bcaf5429b18d7e014f05e77b0ebb9f888c259792683b0aa6fbab736cdb
|
|
BLAKE2b-256 checksum How to use checksums |
057f9c5c34641cbc12c3c88621a77ac8cf688f8c8da4960e385bd64c28b60e18
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp313-cp313-manylinux_2_35_x86_64.whl
| Download URL | srmp-0.1.5-cp313-cp313-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.13 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
12f9767ec2db33b7be5145ef6093b23a9bef3ac352ef46f78a6c0c85594a8894
|
|
BLAKE2b-256 checksum How to use checksums |
9c424e8ba6be10dd24d29146f8e376790f7f130e08de011cf42274088c574b63
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | srmp-0.1.5-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
0666616d26381f200281feaa9498c187928848a4e8554ef5eb6e237ce1bbc90d
|
|
BLAKE2b-256 checksum How to use checksums |
5f6d9be5f58963a76cdbac2eff79e5cfdf4ed1de6904c059a3bca1613e29d41c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp312-cp312-manylinux_2_35_x86_64.whl
| Download URL | srmp-0.1.5-cp312-cp312-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.12 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
e102a844584a5d5b844097f28f0f1666d31228ae5c66b07e945e261462c4bd08
|
|
BLAKE2b-256 checksum How to use checksums |
580a11d9e5ed4815dfe984851c1c5343c735c524b37d51015d0f73a7a5dfd14d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | srmp-0.1.5-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
fff19be1e19808e982f788c931335126b8df28c8668c577b1b8f595460db234b
|
|
BLAKE2b-256 checksum How to use checksums |
51b61d62939df9d7b3605d5b37061a0e5b1c754cbaf1c6890a23cf029559cc15
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp311-cp311-manylinux_2_35_x86_64.whl
| Download URL | srmp-0.1.5-cp311-cp311-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.11 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
1d99bbece98d153df4c17ea4ab5e5984f8480a2f32c0d294966aab054bb71158
|
|
BLAKE2b-256 checksum How to use checksums |
57c3f40edcfe1d19c81df5dac21810e33be2ce33b5f94cb04ded9c47f3471478
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp311-cp311-macosx_11_0_arm64.whl
| Download URL | srmp-0.1.5-cp311-cp311-macosx_11_0_arm64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | CPython 3.11 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
370bc31d0ac440a791160eb1a631b7ec58b3d102684e3a8f1c1873f31957a17e
|
|
BLAKE2b-256 checksum How to use checksums |
49e40e05cff6f4f5b2de894164fdda5191d97b3750c5f48c667e4fa67138436a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp310-cp310-manylinux_2_35_x86_64.whl
| Download URL | srmp-0.1.5-cp310-cp310-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.10 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
7cd6e0f9c55dc58ab52006bb65eac716416f067119ad85a1984f4590a31b84ae
|
|
BLAKE2b-256 checksum How to use checksums |
bfc43dd8b0008e00ff658403148e77ce391463aa2ff63cb4c7b2859dfecac757
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp310-cp310-macosx_11_0_arm64.whl
| Download URL | srmp-0.1.5-cp310-cp310-macosx_11_0_arm64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | CPython 3.10 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
282d12503772ef21ccfda15ff1aa559d682748d341ab07d2f8cd505b7f014f30
|
|
BLAKE2b-256 checksum How to use checksums |
3d2fd10f4a76e9770056787f507270b4c0df196748061e8da44a340dca55a0bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp39-cp39-manylinux_2_35_x86_64.whl
| Download URL | srmp-0.1.5-cp39-cp39-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 12.3 MB |
| Tags | CPython 3.9 Linux glibc 2.35+ x86-64 |
|
SHA-256 checksum How to use checksums |
a21a1db916e5ba9f9cccc3240c3ef182973ef900f81e0d1ae9731c5e60412ebf
|
|
BLAKE2b-256 checksum How to use checksums |
f00ab761a06276934b0852b974eef9a927b3e32ce01adecc09afd1e0783d6278
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / srmp-0.1.5-cp39-cp39-macosx_11_0_arm64.whl
| Download URL | srmp-0.1.5-cp39-cp39-macosx_11_0_arm64.whl |
|---|---|
| Size | 11.0 MB |
| Tags | CPython 3.9 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9c6e06ffc547041090dd50509533c0f7796944b37ed9f6f0419654fe747975a4
|
|
BLAKE2b-256 checksum How to use checksums |
dde6119e61d40fec7f15ce0d8226f61daaa7b221d5d5d110facbf1e70a07ea63
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|