Skip to content

Subgroup Planning

The wheelchair robot has 10 planning joints: the planar base (Joint_Virtual_X, Joint_Virtual_Y, Joint_Virtual_Theta) and the xArm7 (joint1 … joint7). Often only a slice of the body is under active control while the rest stays put.

Subgroups are user-defined. A subgroup is an ordered list of indices into the 10-DOF body. The OMPL planner reasons in the reduced joint space; the C++ collision pipeline injects the frozen stance you choose and still checks the full body.

Built-in aliases

Aliases ship for the common slicings, discoverable via available_robots():

Alias DOF What it controls
wheelchair_arm 7 xArm7 arm, base held fixed
wheelchair_base 3 mobile base (x, y, yaw)
wheelchair_whole_body 10 base + arm
wheelchair 10 full body

The list is a dict in wheelchair_planning.wheelchair.PLANNING_SUBGROUPS — add your own entry to extend it, or use the low-level API below for one-off slicings.

Using a built-in alias

from wheelchair_planning.wheelchair import HOME_JOINTS
from wheelchair_planning.planning import create_planner
from wheelchair_planning.types import PlannerConfig

live_config = robot.get_joint_state()          # 10-D array from your system

planner = create_planner(
    "wheelchair_arm",
    config=PlannerConfig(time_limit=1.0),
    base_config=live_config,                    # frozen joints anchored here
    pointcloud=obstacle_cloud,
)

start = planner.extract_config(live_config)     # 10-D → 7-D
goal = planner.sample_valid()
result = planner.plan(start, goal)

full_path = planner.embed_path(result.path)     # 7-D path → 10-D path

base_config defaults to HOME_JOINTS. Pass the live joint reading instead to pin every non-active joint exactly where the robot currently is — collision checks see the real stance, not a synthetic home pose. planner.set_subgroup(robot_name, base_config=None) switches an existing planner to another alias without rebuilding the collision environment; joint limits set with set_joint_limits are kept.

Defining a custom subgroup

Any ordered list of joint indices is a valid subgroup. Drop to the C++ binding when the alias list doesn't match your problem:

from wheelchair_planning._ompl_vamp import OmplVampPlanner
from wheelchair_planning.wheelchair import HOME_JOINTS, wheelchair_robot_config

# Name the joints you want to plan over; the rest stay frozen.
active_names = ["joint1", "joint2", "joint3", "joint4"]
full_names = wheelchair_robot_config.joint_names
active_indices = [full_names.index(j) for j in active_names]

planner = OmplVampPlanner(active_indices, HOME_JOINTS.tolist())
planner.add_pointcloud(cloud.tolist(), *planner.min_max_radii(), 0.012)

start = [HOME_JOINTS[i] for i in active_indices]
goal = [...]                                    # your own sampling logic
result = planner.plan(start, goal, planner_name="rrtc", time_limit=1.0)

Same planner, same collision pipeline, same point-cloud broadphase — just a different slice. You can also switch an existing planner to a different subgroup on the fly with set_subgroup(active_indices, frozen_config), which preserves the collision environment.

When it matters

Planning the whole body for a task that only needs the arm searches base configurations that do not matter. Whole-body subgroups are the right choice when the base has to move with the arm. Pick the smallest subgroup that captures the joints your task needs to move; the benchmarks give each subgroup's planning times.