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/JointReferenceprotocols 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:
TrackingControllertracks a joint reference and logs the reference and error;JointPD,InverseDynamicsFeedforwardPD, andComputedTorqueare its concrete trackers.EndpointControllerimplements the shared task-space spring-damper law;VirtualSpringDamper,TimeVaryingStiffness, andOnlineReferenceShapingextend it, andPositionDependentShaping/AdaptiveReferenceShapingfurther 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, emitstickedwith the timeline seconds to advance on each tick (its period times itsspeed). Start and stop it with the play button, and keep the button in step withTransportBar.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.SpeedSpinBoxis one; use it for any number input of a tool, since a plainQDoubleSpinBoxsilences 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).