Motion Planning¶
Motion Planner¶
MotionPlanner(robot_name: str, config: PlannerConfig | None = None, pointcloud: np.ndarray | None = None, base_config: np.ndarray | None = None)
¶
Motion planner using OMPL + VAMP C++ backend.
All internals are private. The public API accepts and returns only numpy arrays.
For subgroup planners the helper methods :meth:extract_config,
:meth:embed_config, and :meth:embed_path convert between the
reduced DOF space used by the planner and the full 6-DOF body
configuration.
Source code in drone_planning/planning/motion_planner.py
bounds: tuple[np.ndarray, np.ndarray]
property
¶
(lower, upper) joint limits of the active DOF.
joint_limits: dict[str, tuple[float, float]]
property
¶
{joint name: (lower, upper)} the planner samples in, for every
joint of the full body. :attr:bounds gives the active joints'
ranges as arrays.
joint_names: list[str]
property
¶
Joint names controlled by this planner, in DOF order.
is_subgroup: bool
property
¶
True if this planner controls a subset of the full body.
subgroup_indices: np.ndarray | None
property
¶
Indices of this planner's joints in the full 6-DOF config.
base_config: np.ndarray
property
¶
The 6-DOF stance frozen for joints outside this subgroup.
has_pointcloud: bool
property
¶
True if a pointcloud is currently registered.
set_joint_limits(limits: Mapping[str, tuple[float, float]] | None = None) -> None
¶
Set the joint ranges the planner samples in, by joint name.
Joints left out of limits take the range compiled into the
robot model, so None restores every compiled range. The
ranges cover the full body and are kept across
:meth:set_subgroup. Collision checking does not read them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limits
|
Mapping[str, tuple[float, float]] | None
|
|
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unknown joint name, or bounds that are not
finite with |
Source code in drone_planning/planning/motion_planner.py
add_pointcloud(pointcloud: np.ndarray) -> None
¶
Set the scene pointcloud, replacing any previously-registered cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pointcloud
|
ndarray
|
|
required |
Source code in drone_planning/planning/motion_planner.py
remove_pointcloud() -> bool
¶
Drop the currently-registered pointcloud.
Returns False if there was no cloud to remove.
filter_pointcloud(pointcloud: np.ndarray, min_dist: float, max_range: float, origin: np.ndarray | list[float], workspace_min: np.ndarray | list[float], workspace_max: np.ndarray | list[float], cull: bool = True) -> np.ndarray
¶
Spatially downsample a point cloud via Morton-curve sorting.
Keeps one representative point per min_dist neighbourhood and
discards points farther than max_range from origin or
outside the [workspace_min, workspace_max] bounding box.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pointcloud
|
ndarray
|
|
required |
min_dist
|
float
|
Minimum distance between two retained points. |
required |
max_range
|
float
|
Maximum distance from |
required |
origin
|
ndarray | list[float]
|
|
required |
workspace_min
|
ndarray | list[float]
|
|
required |
workspace_max
|
ndarray | list[float]
|
|
required |
cull
|
bool
|
If |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in drone_planning/planning/motion_planner.py
filter_self_from_pointcloud(pointcloud: np.ndarray, point_radius: float, config: np.ndarray) -> np.ndarray
¶
Remove points that collide with the robot body or environment.
Computes forward kinematics at config, then drops every
point whose inflated sphere (radius point_radius) overlaps
any robot collision sphere or any registered obstacle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pointcloud
|
ndarray
|
|
required |
point_radius
|
float
|
Inflation radius for each point. |
required |
config
|
ndarray
|
Active-DOF configuration (same space as |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in drone_planning/planning/motion_planner.py
set_subgroup(robot_name: str, base_config: np.ndarray | None = None) -> None
¶
Switch active joints without rebuilding the collision environment.
The pointcloud and the joint limits are preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
robot_name
|
str
|
Subgroup name from |
required |
base_config
|
ndarray | None
|
6-DOF frozen config for inactive joints. Defaults to the previously stored base config. |
None
|
Source code in drone_planning/planning/motion_planner.py
extract_config(full_config: np.ndarray) -> np.ndarray
¶
Extract this planner's joints from a full 6-DOF configuration.
Source code in drone_planning/planning/motion_planner.py
embed_config(config: np.ndarray, base_config: np.ndarray | None = None) -> np.ndarray
¶
Embed a subgroup config into a full 6-DOF configuration.
base_config defaults to the planner's stored base — the same
6-DOF values the C++ collision checker injects for inactive
joints — so the embedded config matches what was validated.
Source code in drone_planning/planning/motion_planner.py
embed_path(path: np.ndarray, base_config: np.ndarray | None = None) -> np.ndarray
¶
Convert a subgroup path (N, sub_dof) to (N, 6).
base_config defaults to the planner's stored base — the same
6-DOF values the C++ collision checker injects for inactive
joints — so the embedded path matches what was validated.
Source code in drone_planning/planning/motion_planner.py
plan(start: np.ndarray, goal: np.ndarray, time_limit: float | None = None) -> PlanningResult
¶
Plan a collision-free path from start to goal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
ndarray
|
Start configuration (active DOF). |
required |
goal
|
ndarray
|
Goal configuration (active DOF). |
required |
time_limit
|
float | None
|
Optional per-call override for the solver time
limit. Defaults to |
None
|
Source code in drone_planning/planning/motion_planner.py
plan_kinodynamic(start: np.ndarray, goal: np.ndarray, start_velocity: np.ndarray | None = None, config: KinodynamicConfig | None = None) -> KinodynamicResult
¶
Plan a dynamically feasible, time-parameterised trajectory (FLASK).
Kinodynamic RRT-Connect in the differentially flat output space
(Duong et al., T-RO 2026): every tree edge is the closed-form
minimum-effort cubic between two (position, velocity) states,
validated with SIMD-batched collision checks and the velocity /
acceleration limits from :mod:drone_planning.x500 — the norm of
the horizontal velocity and acceleration, the climb and descent
rates, the yaw rate and the gimbal rates. The output is the
velocity setpoint stream a PX4 offboard controller tracks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
ndarray
|
Start configuration (active DOF). |
required |
goal
|
ndarray
|
Goal configuration (active DOF). Reached at rest. |
required |
start_velocity
|
ndarray | None
|
Optional active-DOF velocity at |
None
|
config
|
KinodynamicConfig | None
|
Kinodynamic planner parameters. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
KinodynamicResult
|
class: |
KinodynamicResult
|
|
|
KinodynamicResult
|
class: |
Source code in drone_planning/planning/motion_planner.py
simplify_path(path: np.ndarray, time_limit: float = 1.0) -> np.ndarray
¶
Run OMPL's shortcut-based path simplifier on path.
Same pipeline plan(..., simplify=True) uses internally
(reduceVertices + collapseCloseVertices + shortcutPath
+ B-spline smoothing), but detached so you can apply it to any
path you already have — e.g. replay an old plan with a
different collision environment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
ndarray
|
|
required |
time_limit
|
float
|
Wall-clock budget for the simplifier, seconds. |
1.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in drone_planning/planning/motion_planner.py
interpolate_path(path: np.ndarray, count: int = 0, resolution: float = 64.0) -> np.ndarray
¶
Densify path with uniform waypoints along every edge.
Three modes (pick one; the other must be zero):
* ``count > 0`` — exactly that many total waypoints
distributed proportionally to edge length.
* ``resolution > 0.0`` — ``ceil(edge_length * resolution)``
waypoints per edge (uniform density in state-space
distance — the default).
* both ``0`` — OMPL's default longest-valid-segment
fraction.
No collision check is performed — the densification only lifts points along the existing (straight) edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
ndarray
|
|
required |
count
|
int
|
Exact total waypoint count if |
0
|
resolution
|
float
|
Waypoints per unit state-space distance if
|
64.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in drone_planning/planning/motion_planner.py
validate(configuration: np.ndarray) -> bool
¶
validate_batch(configurations: np.ndarray) -> np.ndarray
¶
Batched collision check — one SIMD block per rake configs.
Packs rake distinct configurations into a single VAMP
ConfigurationBlock<rake> and runs one fkcc<rake> call
per block, so N queries cost ceil(N / rake) SIMD
sweeps in the common case. When a packed block fails, that
block's configurations are re-checked one by one, so the
returned array is always exactly one bool per input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
configurations
|
ndarray
|
|
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
ndarray
|
|
Source code in drone_planning/planning/motion_planner.py
sample_valid(max_samples: int = 100000) -> np.ndarray
¶
Sample a random collision-free configuration.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If none of |
Source code in drone_planning/planning/motion_planner.py
Factory Function¶
create_planner(robot_name: str = 'x500', config: PlannerConfig | None = None, pointcloud: np.ndarray | None = None, base_config: np.ndarray | None = None) -> MotionPlanner
¶
Create a motion planner for any robot or subgroup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
robot_name
|
str
|
Robot or subgroup name. Use :func: |
'x500'
|
config
|
PlannerConfig | None
|
Planner configuration (uses defaults if None). |
None
|
pointcloud
|
ndarray | None
|
|
None
|
base_config
|
ndarray | None
|
6-DOF values to inject for joints not controlled
by this planner (i.e. the frozen joints of a subgroup — the
gimbal for |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
MotionPlanner
|
class: |
Source code in drone_planning/planning/motion_planner.py
Available Robots¶
available_robots() -> list[str]
¶
Return all available robot names for planning.
Low-level C++ binding¶
OmplVampPlanner(active_indices: Sequence[int] = ..., frozen_config: Sequence[float] = ...)
¶
OMPL planner with VAMP SIMD-accelerated collision checking.
Two construction modes:
OmplVampPlanner()— full body, 6 DOF (x, y, z, yaw, gimbal pan, gimbal tilt).OmplVampPlanner(active_indices, frozen_config)— subgroup over the listed joints; the C++ collision checker injectsfrozen_configfor every other joint before every query.
Source code in drone_planning/_ompl_vamp.pyi
add_pointcloud(points: NDArray[np.float32], point_radius: float) -> None
¶
Set the scene point cloud, replacing any previous one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
NDArray[float32]
|
|
required |
point_radius
|
float
|
Inflation radius of every point (0 checks them as points: the robot's spheres already cover its meshes). |
required |
Source code in drone_planning/_ompl_vamp.pyi
remove_pointcloud() -> bool
¶
has_pointcloud() -> bool
¶
add_sphere(center: Sequence[float], radius: float) -> None
¶
clear_environment() -> None
¶
plan(start: NDArray[np.float64], goal: NDArray[np.float64], planner_name: str = 'rrtc', time_limit: float = 10.0, simplify: bool = True, interpolate: bool = True, resolution: float = 64.0) -> PlanResult
¶
Plan a collision-free path from start to goal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
NDArray[float64]
|
Active-DOF start configuration (length :meth: |
required |
goal
|
NDArray[float64]
|
Active-DOF goal configuration (length :meth: |
required |
planner_name
|
str
|
OMPL planner identifier (e.g. |
'rrtc'
|
time_limit
|
float
|
Solver time limit in seconds. |
10.0
|
simplify
|
bool
|
If true, run |
True
|
interpolate
|
bool
|
If true, densify the simplified path:
|
True
|
resolution
|
float
|
Waypoints per unit of state-space distance. |
64.0
|
Source code in drone_planning/_ompl_vamp.pyi
plan_kinodynamic(start: NDArray[np.float64], start_velocity: NDArray[np.float64], goal: NDArray[np.float64], time_limit: float, settings: KinodynamicSettings) -> KinodynamicResult
¶
FLASK kinodynamic planning in the flat output space.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
NDArray[float64]
|
Active-DOF start configuration. |
required |
start_velocity
|
NDArray[float64]
|
Active-DOF start velocity, or empty for rest. |
required |
goal
|
NDArray[float64]
|
Active-DOF goal configuration, reached at rest. |
required |
time_limit
|
float
|
Solver time limit in seconds. |
required |
settings
|
KinodynamicSettings
|
Limits and planner parameters. |
required |
Source code in drone_planning/_ompl_vamp.pyi
simplify_path(path: NDArray[np.float64], time_limit: float = 1.0) -> NDArray[np.float64]
¶
Run OMPL's shortcut simplifier on an (N, dimension()) path
against the current collision environment.
interpolate_path(path: NDArray[np.float64], count: int = 0, resolution: float = 64.0) -> NDArray[np.float64]
¶
Densify an (N, dimension()) path along its straight edges
(count total waypoints, or resolution per unit distance);
no collision check.
Source code in drone_planning/_ompl_vamp.pyi
validate(config: NDArray[np.float64]) -> bool
¶
validate_batch(configs: NDArray[np.float64]) -> NDArray[np.bool_]
¶
Per-configuration collision check of (N, dimension())
configurations, packed rake at a time into one SIMD sweep.
dimension() -> int
¶
lower_bounds() -> NDArray[np.float64]
¶
upper_bounds() -> NDArray[np.float64]
¶
default_joint_limits() -> tuple[NDArray[np.float64], NDArray[np.float64]]
staticmethod
¶
(lower, upper) limits compiled into the robot model, full-body order.
joint_limits() -> tuple[NDArray[np.float64], NDArray[np.float64]]
¶
set_joint_limits(lower: NDArray[np.float64], upper: NDArray[np.float64]) -> None
¶
Replace the limits of every joint (full-body order; finite,
lower < upper). They survive :meth:set_subgroup.
filter_pointcloud(points: NDArray[np.float32], min_dist: float, max_range: float, origin: Sequence[float], workspace_min: Sequence[float], workspace_max: Sequence[float], cull: bool = True) -> NDArray[np.float32]
¶
Morton-curve spatial down-sampling of a point cloud.
Source code in drone_planning/_ompl_vamp.pyi
filter_self_from_pointcloud(points: NDArray[np.float32], point_radius: float, config: NDArray[np.float64]) -> NDArray[np.float32]
¶
Remove points that collide with the robot at config or with
the registered environment.
Source code in drone_planning/_ompl_vamp.pyi
set_subgroup(active_indices: Sequence[int], frozen_config: Sequence[float]) -> None
¶
Switch active joints without rebuilding the collision environment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
active_indices
|
Sequence[int]
|
Positions in the full-body config that this planner plans over, in DOF order. |
required |
frozen_config
|
Sequence[float]
|
Full-body stance (6 values) for every joint
not in |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
For an index outside |
Source code in drone_planning/_ompl_vamp.pyi
KinodynamicSettings()
¶
Parameters of OmplVampPlanner.plan_kinodynamic (FLASK).
See ext/ompl_vamp/plan_kinodynamic.hpp for field semantics;
:class:drone_planning.types.KinodynamicConfig fills them from the
robot limits in :mod:drone_planning.x500. min_* / max_*
hold one value per active DOF; the horizontal pair (base_x_joint,
base_y_joint) is also bounded in norm when both are active.