step.motion.drive_angle

Classes

DriveAngle

Drive at an arbitrary angle with distance or condition-based termination.

DriveAngleLeft

Drive at an angle to the left with distance or condition-based termination.

DriveAngleRight

Drive at an angle to the right with distance or condition-based termination.

Module Contents

class step.motion.drive_angle.DriveAngle(angle_deg: float, cm: float | None = None, speed: float = 1.0, until: step.condition.StopCondition = None, heading: float | None = None)

Bases: step.motion.motion_step.MotionStep

Drive at an arbitrary angle with distance or condition-based termination.

Decomposes the desired heading into forward and lateral velocity components, then runs a profiled PID controller in a rotated coordinate frame with heading maintenance and cross-track correction.

Requires a mecanum or omni-wheel drivetrain.

Angle convention (robot-centric): 0 = forward, 90 = right, -90 = left, 180 = backward.

The angle_deg travel direction is always robot-centric. The held heading, however, is decoupled: when heading is given the controller holds that absolute heading (degrees from the heading reference) via HeadingReferenceService — the same frame turn_to_heading and the heading= argument of drive_forward/strafe_* use — instead of the heading captured at start. This prevents drift accumulation across consecutive diagonal drives.

Requires mark_heading_reference() when using heading.

Parameters:
  • angle_deg – Travel angle in degrees.

  • cm – Distance to travel in centimeters. Omit to use condition-only mode.

  • speed – Fraction of max speed, 0.0 to 1.0.

  • until – Stop condition for early termination.

  • heading – Absolute heading in degrees from the heading reference to hold during the drive. None (default) holds the heading at the start of the drive (relative mode).

Example:

from raccoon.step.motion import drive_angle

# Drive diagonally forward-right at 45 degrees
drive_angle(45, cm=30)

# Drive pure right (same as strafe_right)
drive_angle(90, cm=20)

# Push diagonally while holding an absolute 0° heading
drive_angle(-120, heading=0).until(on_black(s))
on_start(robot: raccoon.robot.api.GenericRobot) None
on_update(robot: raccoon.robot.api.GenericRobot, dt: float) bool
lower_to_segments() list

Lower a drive-angle into a single segment.

Axis-aligned angles (0 / ±180 / ±90) lower to a linear segment; true diagonals lower to a diagonal segment (see below).

DriveAngle holds the robot’s heading fixed while translating the chassis along a vector at angle_deg relative to that heading. The path IR’s linear segment can only express travel along the Forward or Lateral axis (travel direction == held heading, or 90° off it) — it has no field to decouple the travel direction from the held heading. So only the axis-aligned angles map cleanly:

  • 0 → forward (Forward axis, +sign)

  • 180/-180 → backward (Forward axis, -sign)

  • 90 → right (Lateral axis, +sign)

  • -90 → left (Lateral axis, -sign)

Any other angle is a true diagonal (simultaneous vx+vy at a held heading). A single Forward/Lateral linear axis can’t express it, so it lowers to a "diagonal" segment carrying the known body-frame displacement (forward_m / left_m). That segment runs via an opaque adapter (this step’s own lifecycle) and to_absolute integrates the body-frame displacement into a waypoint.

angle_deg (the robot-centric travel direction) is never stamped onto heading_deg — it is not an absolute heading-reference angle, so emitting it there would mis-bind it through HeadingReferenceService. The optional heading argument, on the other hand, is an absolute heading-reference angle, so it is threaded through as heading_deg (linear case) / honoured via the opaque step’s on_start (diagonal case). With no heading the segment holds the current world heading at execution — matching on_start’s relative-mode fallback.

class step.motion.drive_angle.DriveAngleLeft(angle_deg: float, cm: float | None = None, speed: float = 1.0, until: step.condition.StopCondition = None, heading: float | None = None)

Bases: DriveAngle

Drive at an angle to the left with distance or condition-based termination.

Convenience wrapper around DriveAngle that negates the angle so that the angle_deg parameter is always positive (pointing left).

The angle is measured as degrees to the left of forward: 0 = forward, 45 = forward-left diagonal, 90 = pure left.

Parameters:
  • angle_deg – Degrees to the left of forward (0 = forward, 90 = pure left).

  • cm – Distance to travel in centimeters. Omit to use condition-only mode.

  • speed – Fraction of max speed, 0.0 to 1.0.

  • until – Stop condition for early termination.

  • heading – Absolute heading in degrees from the heading reference to hold during the drive. None (default) holds the heading at the start of the drive (relative mode).

Example:

from raccoon.step.motion import drive_angle_left

# Drive diagonally forward-left at 45 degrees
drive_angle_left(45, cm=30)

# Drive pure left until sensor
drive_angle_left(90, speed=0.6).until(on_black(s))
class step.motion.drive_angle.DriveAngleRight(angle_deg: float, cm: float | None = None, speed: float = 1.0, until: step.condition.StopCondition = None, heading: float | None = None)

Bases: DriveAngle

Drive at an angle to the right with distance or condition-based termination.

Convenience wrapper around DriveAngle that passes the angle directly (positive = right in the robot frame).

The angle is measured as degrees to the right of forward: 0 = forward, 45 = forward-right diagonal, 90 = pure right.

Parameters:
  • angle_deg – Degrees to the right of forward (0 = forward, 90 = pure right).

  • cm – Distance to travel in centimeters. Omit to use condition-only mode.

  • speed – Fraction of max speed, 0.0 to 1.0.

  • until – Stop condition for early termination.

  • heading – Absolute heading in degrees from the heading reference to hold during the drive. None (default) holds the heading at the start of the drive (relative mode).

Example:

from raccoon.step.motion import drive_angle_right

# Drive diagonally forward-right at 45 degrees
drive_angle_right(45, cm=30)

# Drive pure right until sensor
drive_angle_right(90, speed=0.6).until(on_black(s))