Skip to content

Axis System Architecture

This document provides a technical deep dive into FiberPath's axis mapping system, which separates planning logic from G-code output format.

Design Philosophy

FiberPath uses a logical-to-physical axis mapping approach:

  1. Planner operates on logical axes: Carriage, Mandrel, Delivery Head
  2. Dialect converts to physical axes: X/A/B or X/Y/Z depending on configuration
  3. Planning logic remains unchanged: Same algorithms work for all output formats

This design allows:

  • Supporting multiple machine configurations without code duplication
  • Easy addition of new dialects (FANUC, GRBL, custom controllers)
  • Machine-independent layer strategies and calculations

Logical Axes

The core planner (fiberpath.planning) uses three logical axes:

Carriage Axis

  • Type: Linear
  • Units: Millimeters (mm)
  • Description: Motion along the mandrel's longitudinal axis
  • Usage: All axial positioning, lead-in/lead-out moves

Mandrel Axis

  • Type: Rotational
  • Units: Degrees
  • Description: Mandrel rotation for winding
  • Usage: Hoop layer wraps, helical layer rotation component

Delivery Head Axis

  • Type: Rotational
  • Units: Degrees
  • Description: Delivery head rotation for tow feed
  • Usage: Pattern synchronization, tow angle control

Physical Mapping

The AxisMapping class (in fiberpath/gcode/dialects.py) maps logical to physical:

class AxisMapping:
    carriage: str          # Physical axis name (e.g., "X")
    mandrel: str           # Physical axis name (e.g., "A" or "Y")
    delivery: str          # Physical axis name (e.g., "B" or "Z")

    is_rotational_mandrel: bool
    is_rotational_delivery: bool

XAB Mapping (Standard)

MARLIN_XAB_STANDARD = AxisMapping(
    carriage="X",
    mandrel="A",
    delivery="B",
    is_rotational_mandrel=True,
    is_rotational_delivery=True
)

Maps logical axes to Marlin's native rotational axes:

  • Carriage → X (linear)
  • Mandrel → A (rotational)
  • Delivery → B (rotational)

XYZ Mapping (Legacy)

MARLIN_XYZ_LEGACY = AxisMapping(
    carriage="X",
    mandrel="Y",
    delivery="Z",
    is_rotational_mandrel=False,
    is_rotational_delivery=False
)

Maps rotational logical axes to linear physical axes:

  • Carriage → X (linear)
  • Mandrel → Y (linear, but represents degrees)
  • Delivery → Z (linear, but represents degrees)

MarlinDialect Class

The MarlinDialect class wraps an AxisMapping with Marlin-specific features:

class MarlinDialect:
    name: str
    mapping: AxisMapping

    def prologue(self) -> List[str]:
        """G-code commands at file start (G21, G94)"""

    def format_move(self, carriage, mandrel, delivery, feed) -> str:
        """Convert logical coordinates to G-code command"""

Prologue Generation

All Marlin dialects emit standard setup commands:

G21  ; Set units to millimeters
G94  ; Set feed rate mode to units/min

Move Formatting

The format_move() method converts logical coordinates to G-code:

Input (logical):

carriage=50.0, mandrel=180.0, delivery=90.0, feed=2000

Output XAB:

G1 X50.0 A180.0 B90.0 F2000

Output XYZ:

G1 X50.0 Y180.0 Z90.0 F2000

Planning Flow

1. Wind Definition Parsed

fiberpath.config loads and validates the .wind file.

2. Planner Creates Logical Commands

Layer strategies (fiberpath.planning.layer_strategies) generate commands using logical axes:

# Hoop layer: rotate mandrel, small carriage steps
commands = [
    (carriage=0.0, mandrel=0.0, delivery=0.0),
    (carriage=0.5, mandrel=360.0, delivery=0.0),
    (carriage=1.0, mandrel=720.0, delivery=0.0),
    ...
]

3. Dialect Converts to G-code

The G-code writer (fiberpath.gcode.generator) uses the selected dialect:

dialect = MARLIN_XAB_STANDARD  # or MARLIN_XYZ_LEGACY
gcode = dialect.format_move(carriage, mandrel, delivery, feed)

4. G-code Written

Final G-code file contains physical axis names:

; FiberPath v0.5.0 - XAB Format
G21
G94
G1 X0.0 A0.0 B0.0 F2000
G1 X0.5 A360.0 B0.0 F2000
...

Extension Points

Adding New Dialects

To support a new controller (e.g., FANUC):

  1. Create AxisMapping:

    FANUC_STANDARD = AxisMapping(
        carriage="X",
        mandrel="C",  # FANUC uses C for rotation
        delivery="B",
        is_rotational_mandrel=True,
        is_rotational_delivery=True
    )
    
  2. Create Dialect Class:

    class FanucDialect(Dialect):
        def prologue(self) -> List[str]:
            return [
                "G90",  # Absolute positioning
                "G21",  # Metric units
            ]
    
  3. Register in CLI/API:

    Add to fiberpath_cli/plan.py and fiberpath_api/routes/plan.py:

    if axis_format == "fanuc":
        dialect = FANUC_STANDARD
    

Custom Axis Configurations

For non-standard setups, create custom mappings:

CUSTOM_MAPPING = AxisMapping(
    carriage="Z",     # Vertical machine
    mandrel="A",
    delivery="C",     # Different delivery axis
    is_rotational_mandrel=True,
    is_rotational_delivery=True
)

Rotational Flag Implications

The is_rotational_* flags affect:

Simulation

  • Rotational axes use angular velocity calculations
  • Linear axes use standard distance/time calculations

Validation

  • Rotational axes checked for 0-360° wrapping
  • Linear axes checked for machine limits in mm

Future Features

  • Could affect acceleration profiles
  • May influence homing sequences
  • Potential for axis-specific optimization

Implementation Details

Code Locations

  • Axis mapping: fiberpath/gcode/dialects.py
  • G-code generation: fiberpath/gcode/generator.py
  • CLI integration: fiberpath_cli/plan.py
  • API integration: fiberpath_api/routes/plan.py

Type Definitions

# Logical coordinates (used in planning)
LogicalCoordinates = tuple[float, float, float]  # (carriage, mandrel, delivery)

# Physical coordinates (used in G-code)
PhysicalCoordinates = dict[str, float]  # {"X": 50.0, "A": 180.0, ...}

Coordinate Transformations

The planner never deals with physical axis names. All transformations happen at the G-code generation boundary:

# Planning layer (logical)
carriage_pos = calculate_carriage_position(...)
mandrel_angle = calculate_mandrel_rotation(...)

# G-code layer (physical)
gcode = dialect.format_move(carriage_pos, mandrel_angle, delivery_angle, feed)

Design Tradeoffs

Advantages

  • ✅ Single planning codebase supports multiple output formats
  • ✅ Easy to add new dialects without changing core logic
  • ✅ Clear separation of concerns
  • ✅ Machine-independent layer strategies

Limitations

  • ⚠️ Adds abstraction layer (slight complexity)
  • ⚠️ Dialect-specific optimizations require care
  • ⚠️ Must ensure all code paths use dialect correctly

Future Enhancements

Potential improvements to the axis system:

  • Dynamic dialect selection: Auto-detect from Marlin firmware capabilities
  • Dialect validation: Check firmware configuration matches expected axes
  • Non-Marlin dialects: FANUC, GRBL, custom controllers
  • Axis limits per dialect: Different physical constraints per machine type
  • Coordinate frame transformations: Support non-cartesian systems

See Also