An optimised robot representation for kinematic computations.
This struct is built from the Spark DSL at compile-time and contains:
- All physical values converted to SI base units (floats)
- Flat maps for O(1) lookup of links, joints, sensors, and actuators by name
- Pre-computed topology metadata for efficient traversal
- Bidirectional parent/child references
Structure
The robot is organised as flat maps indexed by name:
links- all links in the robot, keyed by atom namejoints- all joints in the robot, keyed by atom namesensors- all sensors (at any level), keyed by atom nameactuators- all actuators, keyed by atom name
Unit Conventions
All physical quantities are stored as native floats in SI base units:
- Length: meters
- Angle: radians
- Mass: kilograms
- Moment of inertia: kg·m²
- Force: newtons
- Torque: newton-meters
- Linear velocity: m/s
- Angular velocity: rad/s
Summary
Functions
Get the full path from root to an actuator.
Get the child joints of a link.
Get a joint by name.
Get a link by name.
Get all joints in traversal order.
Get all links in topological order (root first).
Get the parent joint of a link.
Get the path from a source link down to a target link.
Get the path from root to a given link or joint.
Get the link at the root of the kinematic tree.
Types
@type actuator_info() :: %{ name: atom(), joint: atom(), transmission: transmission() | nil }
@type sensor_info() :: %{ name: atom(), attached_to: {:link, atom()} | {:joint, atom()} | :robot, transmission: transmission() | nil }
@type t() :: %BB.Robot{ actuators: %{required(atom()) => actuator_info()}, joints: %{required(atom()) => BB.Robot.Joint.t()}, links: %{required(atom()) => BB.Robot.Link.t()}, name: atom(), param_subscriptions: %{required([atom()]) => [param_location()]}, root_link: atom(), sensors: %{required(atom()) => sensor_info()}, topology: BB.Robot.Topology.t() }
Functions
@spec actuator_path(t(), atom()) :: {:ok, [atom()]} | {:error, BB.Error.Kinematics.UnknownActuator.t()}
Get the full path from root to an actuator.
Actuator names are unique per robot, so the path is derivable: an actuator
always hangs off a joint, and the joint's own path gives the links and joints
above it. The result matches the :path the framework injects into the
actuator's :bb option, and therefore the topic its commands are published
to — [:actuator | actuator_path(robot, name)].
BB.Robot.actuator_path(robot, :pan_servo)
#=> {:ok, [:base, :pan, :pan_servo]}
@spec child_joints(t(), atom()) :: {:ok, [BB.Robot.Joint.t()]} | {:error, BB.Error.Kinematics.UnknownLink.t()}
Get the child joints of a link.
A link with no children returns {:ok, []}, which is distinct from naming a
link that doesn't exist.
@spec get_joint(t(), atom()) :: {:ok, BB.Robot.Joint.t()} | {:error, BB.Error.Kinematics.UnknownJoint.t()}
Get a joint by name.
@spec get_link(t(), atom()) :: {:ok, BB.Robot.Link.t()} | {:error, BB.Error.Kinematics.UnknownLink.t()}
Get a link by name.
@spec joints_in_order(t()) :: [BB.Robot.Joint.t()]
Get all joints in traversal order.
@spec links_in_order(t()) :: [BB.Robot.Link.t()]
Get all links in topological order (root first).
@spec parent_joint(t(), atom()) :: {:ok, BB.Robot.Joint.t()} | {:error, BB.Error.Kinematics.UnknownLink.t() | BB.Error.Kinematics.UnknownJoint.t() | BB.Error.Kinematics.NoParentJoint.t()}
Get the parent joint of a link.
The root link has no parent joint, which is reported as
{:error, %BB.Error.Kinematics.NoParentJoint{}} — a distinct type from
UnknownLink so a caller walking up the tree can match on it as a termination
signal rather than being told a valid root link doesn't exist.
@spec path_between(t(), atom(), atom()) :: {:ok, [atom()]} | {:error, BB.Error.Kinematics.UnknownLink.t() | BB.Error.Kinematics.NotAnAncestor.t()}
Get the path from a source link down to a target link.
Restricted to the case where source_link is an ancestor of target_link,
which is a prefix drop on the precomputed root-relative paths. The result
starts at source_link and ends at target_link, interleaving the joints and
links between them, so path_to/2 is the special case of a source at the root.
A source that isn't above the target reports
BB.Error.Kinematics.NotAnAncestor, carrying the nearest common ancestor so
the message names the link the caller should have passed.
BB.Robot.path_between(robot, :chassis, :sensor_head)
#=> {:ok, [:chassis, :mast, :sensor_head]}
@spec path_to(t(), atom()) :: {:ok, [atom()]} | {:error, BB.Error.Kinematics.UnknownLink.t()}
Get the path from root to a given link or joint.
Equivalent to path_between/3 from the root link, and delegates to
BB.Robot.Topology.path_to/2.
Get the link at the root of the kinematic tree.
Returns a bare atom rather than a result tuple: unlike every other lookup here
it cannot fail, because BB.Dsl.TopologyTransformer guarantees exactly one
root link exists.
Useful when a caller genuinely wants a whole-tree chain and has to say so —
BB.Motion requires :source_link with no default, precisely so that
root-to-target is recorded as a decision rather than assumed.
BB.Motion.move_to(robot, :gripper, target,
source_link: BB.Robot.root_link(robot),
solver: BB.IK.DLS
)