Skip to content

Native (experimental)¤

A planning backend whose work happens in a compiled core (Rust) inside the Python process: no Docker, no PyBullet, no ROS. It is experimental: the API is the usual compas_fab backend API, but what it supports is limited (see below) and may change.

When to use¤

  • Cartesian paths (lines, spirals, toolpaths), fast: frame or point-axis waypoints, for 6-axis arms, redundant arms and robots on external axes.
  • Free motion between configurations, frames or point-axis targets, around obstacles.
  • Inverse kinematics for any serial robot: closed form where there is one, numerical otherwise.
  • Checking many configurations for collisions at once.
  • Obstacles with holes or concave shapes, through distance fields.
  • Trying it out and telling us what breaks.

Trade-offs¤

What you get What you give up
Cartesian paths in milliseconds, through the compas_fab API Robots without a closed form search a beam of postures along the path (8 by default, tracks): wide, but not exhaustive
Free motion in milliseconds, for any serial robot, timed under velocity and acceleration limits No jerk limits yet
Closed-form IK for UR (offset wrist) and spherical-wrist arms; numerical IK (collision-aware) for every other robot Numerical IK finds solutions from seeds: "no solution" means none was found, not that none exists
Sphere-model collision checks for thousands of configurations per millisecond, validated on exact meshes, including tools and workpieces the robot carries Large flat links (gantry carriages, tall bases) are approximated poorly by spheres (planner.poorly_fitted_links); the exact check catches what the spheres miss, at the cost of slower plans (exact_links checks them during planning too, slower still)
Mesh obstacles with holes (distance fields) Experimental: the API may change between minor versions

Setup¤

pip install compas_fab_native

Wheels are available for Linux (x86_64), macOS (Intel and Apple Silicon) and Windows (x64), for Python 3.9 and later. Elsewhere, pip builds the extension from source, which needs a Rust toolchain. From a checkout of the repository, pip install -e . builds it in place.

First example¤

Forward and inverse kinematics on a UR5:

"""01 - Forward and inverse kinematics with the native planner.

The native planner is a compas_fab backend like any other: you give it a RobotCell, then
ask it for kinematics, collision checks and paths. The difference is underneath: the work
happens in a compiled (Rust) core instead of Python, PyBullet or ROS.

Install the core once with ``pip install .`` from a compas_fab checkout.
"""

import time

from compas.geometry import Frame
from compas_fab.backends import UR5Kinematics
from compas_fab.robots import FrameTarget
from compas_fab.robots import RobotCellLibrary
from compas_fab.robots import TargetMode

from compas_fab_native import NativePlanner

# 1. A robot cell: a UR5 on its own. `state` holds everything that moves: the robot's joint
#    values, its base frame, tools and obstacles.
robot_cell, state = RobotCellLibrary.ur5()

# 2. The planner. A UR has an offset wrist, so we tell it which closed-form solver to use.
#    (Spherical-wrist arms like ABB or Staubli need nothing: the solver is derived from the
#    robot model.) `set_robot_cell` sends the robot to the core, fitting collision spheres
#    to every link mesh. That takes a moment, once.
planner = NativePlanner(UR5Kinematics())
t0 = time.perf_counter()
planner.set_robot_cell(robot_cell, state)
print("set_robot_cell: {:.0f} ms (once per cell)".format((time.perf_counter() - t0) * 1e3))

# 3. Forward kinematics: where is the flange (the robot coordinate frame, RCF) for these joints?
state.robot_configuration.joint_values = [0.0, -1.2, 1.4, -1.8, -1.57, 0.0]
flange = planner.forward_kinematics(state, TargetMode.ROBOT)
print("flange at", flange.point)

# 4. Inverse kinematics: every way the robot can reach a frame. A 6-axis arm has up to 8
#    (shoulder left/right x elbow up/down x wrist flip). Collision checking is on by default,
#    so solutions where the robot hits itself or an obstacle are dropped.
target = FrameTarget(Frame([0.4, 0.2, 0.5], [1, 0, 0], [0, -1, 0]), TargetMode.ROBOT)
solutions = list(planner.iter_inverse_kinematics(target, state))
print("{} collision-free IK solutions".format(len(solutions)))
for configuration in solutions:
    print("  ", ["{:+.2f}".format(v) for v in configuration.joint_values])

# 5. How fast? Time many IK calls, including the collision check of every solution.
n = 200
t0 = time.perf_counter()
for _ in range(n):
    list(planner.iter_inverse_kinematics(target, state))
print("IK + collision check: {:.0f} us per target (mostly Python overhead)".format((time.perf_counter() - t0) / n * 1e6))

More examples¤

Each example builds on the previous one and explains what the planner does as it goes.

  • 02_collision_checking.py: the sphere model, checking 10,000 configurations at once, and how spheres compare with exact meshes
  • 03_cartesian_line.py: a 100-waypoint line, what the planner does, and starting from the current configuration
  • 04_tool_and_free_axis.py: a spiral toolpath that only works when the tool may turn about its own axis
  • 05_mesh_obstacles.py: plunging through a ring, impossible with bounding boxes, possible with a distance field
  • 06_free_motion.py: free motion around obstacles, to configurations, frames and point-axis targets, timed under the joint limits, on a UR5 and a 7-axis Panda

How it works, in short¤

The full explanation, with diagrams, is in How it works.

  1. set_robot_cell fits spheres inside every link mesh and sends the robot, its meshes and the obstacles to the core. Sphere fits are cached (in memory and in ~/.cache/compas_fab/native_spheres; COMPAS_FAB_NATIVE_CACHE=0 turns that off), so setting up the same cell again takes milliseconds. Tools and workpieces the planning group carries get spheres on the link that carries them; everything that stands still while it plans (other robots of the cell, their tools, parked tools) becomes an obstacle.
  2. IK is closed-form where possible: ported for UR arms, derived from the model for spherical-wrist arms. Other robots (a 7-axis arm, a robot on a gantry) get numerical IK: a least-squares solve that also keeps the robot clear of collisions. options={"ik": "numeric"} uses it for any robot.
  3. A Cartesian request first fills in frames between the waypoints (every max_step, 1 cm by default, and max_step_angle, 0.1 rad), so the tool moves in straight lines. With closed-form IK it solves every frame (and every sampled rotation about the tool axis), checks all solutions for collisions in one batch, and searches for the smoothest sequence through them. With numerical IK it carries a beam of postures along the path, branching into new ones when they die out, and picks the smoothest sequence. Point-axis waypoints leave the rotation about the axis free.
  4. The chosen path is validated on the exact meshes; if a configuration collides there, the search continues without it.
  5. Free motion tries the straight joint-space motion first, then RRT-Connect from the start and from every goal at once, then shortcutting and smoothing, and checks the result on the exact meshes (if it touches them, it plans again with a safety margin). Its collision checks run 8 configurations at a time in SIMD and stop at the first contact.
  6. The path is then timed: corners are rounded off within a tolerance (if the rounded corner is collision-free), and the speed along the path is the fastest the joint velocity and acceleration limits allow, starting and ending at rest.

Design notes, measurements and open issues are in SPIKE.md.

API reference¤

See the API reference.