Python API Quick Start
Use skelarm as a library. Everything shown here is importable from the
top-level skelarm package; the per-module details live in the API Reference
(start at Architecture).
Build a robot and compute kinematics
From a config file, or directly in code:
import numpy as np
from skelarm import LinkProp, Skeleton, compute_forward_kinematics
skeleton = Skeleton.from_toml("examples/four_dof_robot.toml") # from TOML
link = LinkProp(length=1.0, m=1.0, i=0.1, rgx=0.5, rgy=0.0, qmin=-np.pi, qmax=np.pi)
skeleton = Skeleton([link]) # or programmatically
skeleton.q = np.array([0.5]) # setters clamp into the joint limits (and warn)
compute_forward_kinematics(skeleton)
tip = skeleton.links[-1]
print(tip.xe, tip.ye)
Run a scenario headlessly
The highest-level entry point mirrors the GUI tools — one config, one call:
from skelarm import load_scenario, rerun_log, run_scenario
scenario = load_scenario("examples/reach.toml")
log = run_scenario(scenario) # duration from [task], dt from [simulator]
log.save("reach.sklog.npz")
again = rerun_log(log) # deterministic re-simulation from the embedded config
The returned StateLog gives channel arrays for analysis
(log.channel("q"), log.times) and replays in tools/player.py — see
Record, Replay, and Re-simulate.
Simulate uncontrolled dynamics
simulate_robot integrates the free dynamics with adaptive solve_ivp, taking
a torque callback f(t, skeleton) -> tau:
import numpy as np
from skelarm import LinkProp, Skeleton, simulate_robot
link = LinkProp(length=1.0, m=1.0, i=0.1, rgx=0.5, rgy=0.0, qmin=-np.pi, qmax=np.pi)
skeleton = Skeleton([link])
skeleton.q = np.array([0.0])
skeleton.dq = np.array([0.0])
def control_torques(t, skel):
return np.array([0.0]) # zero torque
times, q_traj, dq_traj = simulate_robot(skeleton, (0.0, 1.0), control_torques)
For a stateful controller (anything with an update hook, MPC in particular),
use the fixed-step simulate_controlled loop instead — adaptive solve_ivp may
call the torque callback several times per output interval, which breaks
controller state.
simulate_controlled can also script a disturbance at the tip: external_force
is called as f(t, skeleton) -> (fx, fy) once per step, and its force acts on top
of the controller's torque (mapped to the joints as J^T F, like the mouse force
of the dynamics simulator). The force is recorded as the ext_force channel, so
tools/player.py replays it as an arrow at the tip:
import numpy as np
from skelarm import ComputedTorque, LinkProp, Skeleton, Trajectory, compute_jacobian, simulate_controlled
upper_arm = LinkProp(length=1.0, m=1.0, i=0.1, rgx=0.5, rgy=0.0, qmin=-np.pi, qmax=np.pi)
forearm = LinkProp(length=0.8, m=0.8, i=0.05, rgx=0.4, rgy=0.0, qmin=-np.pi, qmax=np.pi)
skeleton = Skeleton([upper_arm, forearm])
skeleton.q = np.array([0.3, 1.5])
reference = Trajectory(skeleton.q, skeleton.q + np.array([0.4, -0.3]), duration=1.0)
controller = ComputedTorque(reference, kp=100.0, kd=20.0)
def push(t, skel): # a 20 N push along +x from 0.4 s to 0.5 s
return np.array([20.0, 0.0]) if 0.4 <= t < 0.5 else np.zeros(2)
held = np.array([skeleton.links[-1].xe, skeleton.links[-1].ye]) # where the tip starts
def grip(t, skel): # a stiff hand holding the tip where it started
tip = np.array([skel.links[-1].xe, skel.links[-1].ye])
return -5000.0 * (tip - held) - 100.0 * (compute_jacobian(skel) @ skel.dq)
pushed = simulate_controlled(skeleton, controller, duration=2.0, dt=0.002, external_force=push)
gripped = simulate_controlled(skeleton, controller, duration=2.0, dt=0.002, external_force=grip)
Lower-level building blocks
from skelarm import (
compute_forward_dynamics, # tau -> ddq (mass matrix solve)
compute_inverse_dynamics, # motion -> tau (Recursive Newton-Euler)
compute_inverse_kinematics, # endpoint target -> q (check result.success)
compute_jacobian, # endpoint Jacobian
ik_joint_reference, # task path -> joint reference (warns on deviation)
simulate_controlled, # fixed-step control loop
)
Conventions to keep in mind:
- No gravity — the arm is horizontal; the
grav_vecparameter on the dynamics functions is an advanced/testing hook outside the supported model. - Joint limits — kinematic setters clamp and warn; the fixed-step
integrators apply hard stops;
simulate_robotis unconstrained (Joint Limits). - Radians internally — TOML configs use degrees, the API uses radians.
- Arrays are
numpy.typing.NDArray[np.float64]throughout.
Related
- Defining a Task / Defining a Controller — register your own types so scenario configs and saved logs can use them.
- Theory Reference — the equations behind each helper.