Skip to content

Architecture

This page maps the skelarm class structure: how the robot model, the controllers, the configuration glue, and the PyQt6 GUI relate to one another. It complements the per-module API pages (starting with Skeleton) and the theory chapters that derive each piece.

The codebase has five structures worth seeing together:

  • the robot model (Skeleton / Link / LinkProp);
  • the controller hierarchy (the trackers of Trajectory Tracking Control and the endpoint controllers of Reaching Control);
  • the task / joint references the controllers track (TaskReference / JointReference protocols and their realizations);
  • the scenario glue that turns a TOML config into a runnable controller;
  • the PyQt6 GUI widgets and windows.
classDiagram
    %% ── Robot model ─────────────────────────────
    class Skeleton {
        +links: list~Link~
        +q  dq  ddq  tau
        +clone()  to_dict()
    }
    class Link {
        +prop: LinkProp
        +q  dq  ddq
    }
    class LinkProp {
        <<dataclass>>
        +length m i rgx rgy qmin qmax
    }
    Skeleton "1" *-- "N" Link
    Link "1" *-- "1" LinkProp

    %% ── Controllers ─────────────────────────────
    class Controller {
        <<abstract>>
        +reset(skeleton)
        +control(t, skeleton)* NDArray
        +update(t, skeleton, dt)
        +log_channels() dict
        +__call__(t, skeleton) NDArray
    }
    class TrackingController {
        +reference: JointReference
        +kp, kd
    }
    Controller <|-- TrackingController
    Controller <|-- EndpointController
    Controller <|-- JointSpaceMPC
    TrackingController <|-- JointPD
    TrackingController <|-- InverseDynamicsFeedforwardPD
    TrackingController <|-- ComputedTorque
    EndpointController <|-- VirtualSpringDamper
    EndpointController <|-- TimeVaryingStiffness
    EndpointController <|-- OnlineReferenceShaping
    OnlineReferenceShaping <|-- PositionDependentShaping
    OnlineReferenceShaping <|-- AdaptiveReferenceShaping
    EndpointController ..> Skeleton : reads endpoint
    StateLog ..> Skeleton : records / rebuilds

    %% ── Task & joint references (structural Protocols) ──
    class TaskReference {
        <<protocol>>
        +duration
        +sample(t) tuple
    }
    class JointReference {
        <<protocol>>
        +sample(t) tuple
    }
    TaskReference <|.. Trajectory : realizes
    TaskReference <|.. SampledTaskReference : realizes
    TaskReference <|.. PeriodicTaskReference : realizes
    JointReference <|.. SampledJointReference : realizes
    TaskReference ..> SampledJointReference : ik_joint_reference()
    TrackingController ..> JointReference : tracks
    JointSpaceMPC ..> JointReference : tracks

    %% ── Scenario glue (config → runnable) ────────
    class Scenario {
        <<dataclass>>
    }
    class Task {
        <<dataclass>>
        +type  target  params
        +duration  schedule
    }
    class Simulator {
        <<dataclass>>
        +dt  enforce_limits
    }
    Scenario *-- Skeleton
    Scenario *-- Task
    Scenario *-- Simulator
    Scenario *-- Controller
    Scenario ..> JointReference : builds per task type

    %% ── PyQt6 GUI ───────────────────────────────
    class QWidget {
        <<PyQt6>>
    }
    class QMainWindow {
        <<PyQt6>>
    }
    QWidget <|-- SkelarmCanvas
    SkelarmCanvas <|-- SimulatorCanvas
    QMainWindow <|-- SkelarmViewer
    QMainWindow <|-- SkelarmSimulator
    SkelarmCanvas *-- Skeleton

Click the diagram to enlarge it; click anywhere, the × button, or press Esc to close.

Robot model

Skeleton owns the ordered list of Link objects (links[0] is the fixed base; links[1:] are the actuated joints), and each Link holds a LinkProp dataclass with its geometry, mass properties, and joint limits — treated as fixed after construction by convention (the dataclass itself is not frozen). The kinematics and dynamics functions operate on a Skeleton, and the controllers below read endpoint state from it each step.

Controllers

Every controller derives from the abstract Controller, which is callable as f(t, skeleton) -> tau and exposes the reset / control / update / log_channels hooks used by the fixed-step simulate_controlled loop. Two families sit beneath it:

  • TrackingController tracks a joint reference and logs the reference and error; JointPD, InverseDynamicsFeedforwardPD, and ComputedTorque are its concrete trackers.
  • EndpointController implements the shared task-space spring-damper law; VirtualSpringDamper, TimeVaryingStiffness, and OnlineReferenceShaping extend it, and PositionDependentShaping / AdaptiveReferenceShaping further specialize the online shaper.

JointSpaceMPC derives from Controller directly rather than from TrackingController, because it consumes the joint reference through the optimizer rather than through a PD law.

The trackers and MPC read a joint reference through the JointReference Protocol. SampledJointReference satisfies it structurally (it is not an explicit subclass), so any object with a matching sample(t) method can serve as a reference.

Task references

A task-space reference is anything satisfying the TaskReference Protocol (sample(t) plus a duration): a planned Trajectory, a SampledTaskReference (a smoothed/interpolated recorded tip path), or a PeriodicTaskReference (a closed curve). ik_joint_reference converts any of them into a SampledJointReference by samplewise inverse kinematics, so the trackers and MPC need no knowledge of where the reference came from. The Trajectory Filtering & Interpolation chapter covers the smoothing (skelarm.filtering) and resampling (skelarm.interpolation) applied to recorded references.

Scenario glue

Scenario is the runnable bundle assembled from one TOML file: a Skeleton, a Task (its required type plus the target / motion / type-specific params), a Simulator (the numerical-integration parameters dt / enforce_limits from [simulator]), and a ready-to-run Controller. The loader builds the joint reference for the trackers by dispatching on the task type (a registry opened by register_reference_builder): reaching plans a point-to-point Trajectory, periodic_curve builds a PeriodicTaskReference, and the trajectory-tracking tasks load a recorded reference — each converted via ik_joint_reference, except joint_trajectory_tracking, which yields a SampledJointReference directly. See the Control Configuration and Defining a Task guides for the config schema.

PyQt6 GUI

The interactive tools subclass PyQt6 base classes. SkelarmCanvas (a QWidget) renders a Skeleton and poses it by inverse kinematics on a left click or drag, unless its drag_to_pose attribute is False. A click on it also takes the keyboard focus from any text field, so the keys go back to the window's shortcuts. It is extended by SimulatorCanvas, whose drag applies a tip force instead; SkelarmViewer and SkelarmSimulator are the QMainWindow shells that host them.

The building blocks the tools share live in skelarm.widgets and are exported at the package top level, so a tool of your own can reuse them:

  • TransportBar — the play/pause, step, and reset buttons and their keys.
  • PlaybackClock — a timer that, while running, emits ticked with the timeline seconds to advance on each tick (its period times its speed). Start and stop it with the play button, and keep the button in step with TransportBar.set_playing.
  • SpeedSpinBox — the player's speed control: 0.1× to 10× in steps of 0.1.
  • ShortcutFriendlySpinBox — a number box that leaves the window's keyboard shortcuts working: it claims only the keys that edit a number, so Space and the letters still play, reset, or quit while it has the focus, and Enter or Escape hands the focus back. SpeedSpinBox is one; use it for any number input of a tool, since a plain QDoubleSpinBox silences the shortcuts once it is edited.

The replay player (tools/player.py) is built from these three.

Value types not shown

A few standalone value types are omitted from the diagram to keep it readable: IKResult (returned by compute_inverse_kinematics) and StateLog (the recorded run produced by simulate_controlled and by the interactive simulators).