VEX V5 PYTHON | 07.1 FIELD COORDINATES

Guide objective

Define one field coordinate system, distinguish travel bearing from final heading and describe navigation targets with explicit units and tolerances.

01 | CREATE ONE MAP FOR THE WHOLE TEAM

Navigation targets need a shared map. Choose a field origin, axis directions, units and a heading convention, then put them on a labelled diagram.

This course uses field +X right, +Y up, distances in millimetres and headings clockwise from field up. The origin can be field centre or another documented point.

The map is a project convention, not a promise that every sensor or library uses the same frame. If using GPS, align your map to its code-defined field frame or apply an explicit transform.

Field coordinates and a target bearingField X increases right and Y increases up. A robot at 200 minus 300 faces up. A target at 800 500 gives a displacement of 600 800, distance 1000 and bearing about 36.9 degrees clockwise from up.+X+Y / 0°(0, 0)TARGET (800, 500)ROBOT (200, -300), h = 0°1000 mmBearing ≈ 36.9°Illustrative coordinate map, not a current game layout
The target bearing is a direction from the current position. It is not automatically the required final orientation.

02 | FIELD AND ROBOT COORDINATES HAVE DIFFERENT JOBS

Body +X is right; body +Y is forward
FrameFixed toTypical use
FieldThe chosen field mapWaypoints, goals and estimated X/Y
Robot / bodyThe chassis and its reference pointForward/right errors and mechanism offsets

When the robot rotates, the field axes stay fixed and the body axes rotate. At heading 90°, robot forward is field right, while robot right is field down.

Do not label a body-forward distance as field Y unless the robot is facing field up.

03 | DEFINE THE ROBOT REFERENCE POINT

Targets should refer to the same point tracked by odometry, such as the documented chassis reference near the drivetrain centre.

An intake mouth or scoring tip may be offset from that point. If you want the tip at a goal, calculate where the reference should be at the desired final heading.

Record the reference on the robot diagram and measure all tracking-wheel and mechanism offsets from it. Changing the reference changes the meaning of coordinates.

04 | HEADING, BEARING AND ROTATION ARE DISTINCT

A route can use all four without treating them as interchangeable
TermMeaningExample
HeadingWhere the robot facesRobot faces right: 90°
Target bearingDirection from current point to target pointTarget is up/right: about 36.9°
Final target headingRequired orientation on arrivalFace a goal at 180°
Accumulated rotationTotal signed angular movementTwo clockwise turns: +720°

The inertial sensor distinguishes wrapped heading from accumulated rotation. The estimator maps its readings to a known starting field heading.

Setting a numerical sensor heading changes the reference value; it does not physically turn the robot or establish its field position.

05 | NORMALIZE DISPLAY ANGLES AND WRAP ERRORS

def normalize_heading(degrees):
    return degrees % 360.0

def angle_error(target, current):
    return (target - current + 180.0) % 360.0 - 180.0
The exact 180° tie chooses -180° in this formula
Input / comparisonResult
Heading -10°350°
Heading 450°90°
Target 10°, current 350°+20° clockwise error
Target 350°, current 10°-20° anticlockwise error

Keep accumulated rotation continuous where the algorithm needs it. Normalize display headings or explicit shortest-turn comparisons instead of throwing away complete turns.

Public target headings use degrees in the examples. Convert to radians before sin, cos or other trigonometric calculations.

06 | CALCULATE DISTANCE AND BEARING

For current point (200, -300) and target (800, 500), dx = 600 mm and dy = 800 mm. Distance is √(600² + 800²) = 1000 mm.

With clockwise heading from field up, bearing is atan2(dx, dy), converted to degrees and normalized. The argument order differs from the common mathematical angle measured from +X.

distance = math.sqrt(dx * dx + dy * dy)
bearing_deg = math.degrees(math.atan2(dx, dy)) % 360.0

The bearing is approximately 36.9°. atan2 handles quadrants and zero axis components without dividing dx by dy.

At the target point, no unique bearing exists. Return None rather than silently instructing a turn to zero.

07 | A POINT TARGET IS NOT A COMPLETE POSE

A point target specifies X/Y. A pose target also specifies final heading. Use an explicit None when final orientation is intentionally unspecified.

approach = make_target(800, 500)          # Position only
score = make_target(1000, 500, 90)        # Position plus final heading

Final heading can differ from travel bearing. A robot may approach a scoring point from below and then face left to operate a mechanism.

Specify meaningful position and heading tolerances. A pose controller must decide when and how to achieve final orientation; the target data alone does not solve that control problem.

08 | FORWARD AND REVERSE TRAVEL OPTIONS

To travel toward a point forward, the robot can face its target bearing. To travel toward the same point backward, it can face bearing +180° and use a negative drive request.

For a target directly field-right, forward-facing travel uses 90°. Reverse travel can use facing 270° while moving backward.

The final pose heading is still a separate requirement. Choosing reverse travel does not automatically transform the requested final orientation.

A tank-drive robot cannot directly obey an arbitrary lateral error as a strafe command. Use the error to plan a turn/path rather than pretending the drivetrain has another motion axis.

09 | EXPRESS FIELD ERROR IN THE BODY FRAME

To describe where a target lies relative to the robot, rotate the field displacement back into body right/forward components:

h = math.radians(current_heading_deg)
right_error = dx * math.cos(h) - dy * math.sin(h)
forward_error = dx * math.sin(h) + dy * math.cos(h)

At heading 90°, a target 100 mm field-right gives forward error +100 mm and right error zero. A target 100 mm field-up lies to the robot’s left.

These quantities help diagnose navigation and design controllers. They are geometrical errors, not automatically safe motor commands.

10 | POSITION A MECHANISM AT A GOAL

Suppose the scoring tip is 100 mm body-right and 200 mm body-forward of the reference. At final heading 90°, that offset becomes field (+200, -100) mm.

To place the tip at field goal (1000, 500), place the reference at (800, 600), facing 90°. Goal minus rotated offset gives the reference target.

A moving arm or changing intake configuration can change the offset. Use the geometry for the actual mechanism position, and account for clearance and robot footprint.

11 | TRANSFORM ANOTHER CONVENTION DELIBERATELY

Reflection and rotation are different transformations
Other operation / conventionConversion to this guide
Mathematical angle φ anticlockwise from +XHeading = (90° - φ) modulo 360°
Reflect route about field Y axisx becomes -x; y unchanged; heading becomes -heading
Rotate route 180° about originx becomes -x; y becomes -y; heading adds 180°
Translate map origin onlyApply the measured coordinate offset; orientation unchanged

Mirroring a route also transforms oriented mechanism offsets and any asymmetric actions. A mirrored coordinate list alone does not guarantee the same scoring behaviour.

The field’s obstacles and game objects may not match your mathematical symmetry. Verify the actual layout and current constraints before using a transformed route.

12 | COMPLETE MOTOR-FREE GEOMETRY LAB

This script runs in ordinary Python or below the generated region in VEXcode V5. It declares no devices and commands no movement.

# =============================================================================
# TEAM [MY TEAM NAME / NUMBER] | FIELD GEOMETRY LAB
# =============================================================================
# Runs in ordinary Python or below '# Begin project code' in VEXcode V5.
# No devices, motors or sensor readings are used.
# Field +X right, +Y up; body +X right, +Y forward.
# Heading: clockwise from field up, in degrees at public interfaces.
import math

def finite(*values):
    if not all(isinstance(v, (int, float)) and math.isfinite(v) for v in values):
        raise ValueError("Use finite numerical values")

def normalize_heading(degrees):
    finite(degrees)
    return degrees % 360.0

def angle_error(target, current):
    finite(target, current)
    return (target - current + 180.0) % 360.0 - 180.0

def make_target(x_mm, y_mm, heading_deg=None,
                position_tolerance_mm=20.0, heading_tolerance_deg=2.0):
    finite(x_mm, y_mm, position_tolerance_mm, heading_tolerance_deg)
    if position_tolerance_mm <= 0 or not 0 < heading_tolerance_deg < 180:
        raise ValueError("Use positive meaningful tolerances")
    return {"x_mm": x_mm, "y_mm": y_mm,
            "heading_deg": (None if heading_deg is None
                            else normalize_heading(heading_deg)),
            "position_tolerance_mm": position_tolerance_mm,
            "heading_tolerance_deg": heading_tolerance_deg}

def point_geometry(current_x, current_y, target_x, target_y):
    finite(current_x, current_y, target_x, target_y)
    dx, dy = target_x - current_x, target_y - current_y
    distance = math.sqrt(dx * dx + dy * dy)
    if distance < 1e-6:
        return distance, None  # No unique travel bearing at the point.
    return distance, normalize_heading(math.degrees(math.atan2(dx, dy)))

def field_vector_to_body(dx, dy, heading_deg):
    finite(dx, dy, heading_deg)
    h = math.radians(heading_deg)
    right = dx * math.cos(h) - dy * math.sin(h)
    forward = dx * math.sin(h) + dy * math.cos(h)
    return right, forward

def mechanism_reference_target(goal_x, goal_y, final_heading_deg,
                               mechanism_right_mm, mechanism_forward_mm):
    finite(goal_x, goal_y, final_heading_deg,
           mechanism_right_mm, mechanism_forward_mm)
    h = math.radians(final_heading_deg)
    offset_x = mechanism_right_mm * math.cos(h) + mechanism_forward_mm * math.sin(h)
    offset_y = mechanism_forward_mm * math.cos(h) - mechanism_right_mm * math.sin(h)
    return goal_x - offset_x, goal_y - offset_y

def mirror_left_right(target):
    # Reflection about the field Y axis. NOT a 180-degree rotation.
    h = target["heading_deg"]
    return make_target(-target["x_mm"], target["y_mm"],
                       None if h is None else -h,
                       target["position_tolerance_mm"],
                       target["heading_tolerance_deg"])

# =============================================================================
# NAMED TARGETS AND GEOMETRY ONLY; NO MOVEMENT COMMANDS
# =============================================================================
start_pose = (200.0, -300.0, 0.0)
approach = make_target(800.0, 500.0)          # Point target; heading unspecified
score = make_target(1000.0, 500.0, 90.0)     # Pose target; final heading matters
route = [("approach", approach), ("score", score)]

for name, target in route:
    distance, bearing = point_geometry(start_pose[0], start_pose[1],
                                       target["x_mm"], target["y_mm"])
    dx = target["x_mm"] - start_pose[0]
    dy = target["y_mm"] - start_pose[1]
    right_error, forward_error = field_vector_to_body(dx, dy, start_pose[2])
    print(name, target)
    print("From fixed start: distance", distance, "bearing", bearing)
    print("Body error right/forward:", right_error, forward_error)
    if bearing is not None:
        reverse_facing = normalize_heading(bearing + 180)
        print("Reverse travel facing option:", reverse_facing)
    # Each calculation starts from start_pose. This does not execute the route.

reference_x, reference_y = mechanism_reference_target(
    1000, 500, 90, mechanism_right_mm=100, mechanism_forward_mm=200)
print("Reference target to place mechanism at goal:", reference_x, reference_y)
print("Mirrored scoring pose:", mirror_left_right(score))

The two route calculations each use the fixed example start pose. They demonstrate target data and geometry; they do not execute the route or update the robot’s pose.

Before using targets in a movement routine, require a valid recent estimated pose, a confirmed starting frame and a verified clear path. The next guide builds the drive-to-point behaviour.

CHALLENGES | DEFINE A ROUTE WITHOUT AMBIGUITY

  1. Map: label origin, axes, units and cardinal headings.
  2. Identify: mark the odometry reference and one mechanism offset.
  3. Calculate: find bearings to targets in all four quadrants.
  4. Wrap: compare heading errors across zero.
  5. Specify: write three named targets, including one point and one final-pose target.
  6. Reverse: describe a backward travel orientation without changing final-heading intent.
  7. Offset: calculate a reference target that places a mechanism at a goal.
  8. Transform: compare a reflected route with a 180° rotated route.
Success evidence

A labelled map, target list, signed calculations and working geometry script. State the frame, reference point, units, tolerances and final-heading meaning explicitly.

OFFICIAL REFERENCES

The map, target format and geometry helpers are independently written teaching examples. VEX and VEXcode are trademarks of their respective owners.