Skip to content

Sequences (bt.seq)

The process vocabulary: a sequence is a list of steps, and a step is entry actions plus a transition condition, evaluated on a fixed scan cycle — the PLC step-advance model.

sq = scene.sequence("cycle")
sq.step("feed", actions=[bt.seq.start("belt")], transition=bt.seq.signal("eye"))
sq.step("stop", actions=[bt.seq.stop("belt")])
sq.step("pick", actions=[bt.seq.motion("approach"), bt.seq.attach("crate")])

Omitting transition supplies the obvious default: a step that starts a motion waits for it (done()), a step that starts nothing falls through (immediately()).

Actions at a glance

Action Effect
motion(name) Start a named motion; await it with done()
ramp(targets, duration) Drive joints to targets over a fixed duration — gripper open/close
attach(obj) / detach(obj) Grasp, and release where the robot holds it
track(obj) / untrack() Latch taught poses onto a moving part, and let go
set_signal(name, value) Write an internal signal (declared with scene.define_signal)
start(device) / stop(device) Run or halt a device — a conveyor or a source
set_speed(device, speed) Rescale a conveyor's velocity, direction kept
move_to(device, position) Command a linear axis; await with device_done

Conditions at a glance

Condition Advances when
immediately() Always, on the next scan
done() This step's motion has finished
robot_done(robot) A named robot is idle — the inter-robot handshake
elapsed(seconds) A timer expires
signal(name, value) A signal, sensor, or device state matches
device_done(device) A device reached its commanded target
all_of(...) / any_of(...) Composed conditions hold

Reference

seq

PLC-style sequence authoring: action / transition-condition helpers and the step builder returned by scene.sequence(name).

A sequence is a list of steps (工程). Each step fires its entry actions and completes when its transition condition holds — the SFC / step-ladder mental model. When transition is omitted, a step that starts a motion or ramp waits for it (done()); anything else moves on immediately().

sq = scene.sequence("pick_place")
sq.step("approach", actions=[bt.seq.motion("approach")])
sq.step("close",    actions=[bt.seq.ramp({"finger": 0.008}, 0.4)])
sq.step("grasp",    actions=[bt.seq.attach("/World/Conveyor/Box_A")])
sq.step("carry",    actions=[bt.seq.motion("to_pallet")])
sq.step("release",  actions=[bt.seq.detach("/World/Conveyor/Box_A")])
timeline = sq.simulate()

SelectBuilder

Arms of one branching step (see _Steps.select).

when

when(condition: Dict[str, Any]) -> ArmBuilder

Appends an arm guarded by condition. Arms are tried in the order added (SFC's left-to-right priority); the first whose condition holds runs. An arm left empty skips straight to the rejoin.

ArmBuilder

Bases: botrail.seq._Steps

One arm's step list — the same step/select API as the sequence itself, so arms nest.

SequenceBuilder

Bases: botrail.seq._Steps

Accumulates steps for one sequence, mirroring every edit into the scene (and any connected studio). Creating a builder for an existing sequence name starts it over from zero steps.

simulate

simulate(
    dt: float = 0.01,
    max_duration: float = 120.0,
    scenario: Optional[str] = None,
)

Rolls the sequence out (see Scene.simulate_sequence). scenario runs it under a named initial-state delta (scene.add_scenario).

motion

motion(name: str) -> Dict[str, Any]

Start a named motion; await it with done().

toolpath

toolpath(
    name: str, robot: Optional[str] = None
) -> Dict[str, Any]

Start a toolpath (continuous Cartesian process path — see bt.toolpath): an automatic approach to the path start, then the feed-floored follow. Await it with done(). robot names the instance (required when the scene has several robots).

ramp

ramp(
    targets: Mapping[str, float],
    duration: float,
    robot: Optional[str] = None,
) -> Dict[str, Any]

Ramp joints to targets over duration s (gripper open/close); await it with done(). robot names the instance (required when the scene has several robots).

attach

attach(
    obj: str,
    link: Optional[str] = None,
    touch_links: Union[str, Iterable[str], NoneType] = None,
    robot: Optional[str] = None,
) -> Dict[str, Any]

Grasp: rigidly attach an obstacle at its current relative pose. robot names the carrying instance (required with several robots). touch_links="tool" exempts the whole tool subtree — palm and fingers — which a closed gripper needs (the default exempts only the anchor link's own chain).

detach

detach(obj: str) -> Dict[str, Any]

Release: the obstacle's pose freezes where the robot holds it.

track

track(
    obj: str,
    link: Optional[str] = None,
    robot: Optional[str] = None,
) -> Dict[str, Any]

Conveyor tracking: latch onto a moving part. Until :func:untrack, every commanded pose is carried by the part's motion since this step, so poses taught at the station keep meeting the part while it travels — the line never has to stop. Grasping the tracked part freezes the offset, so the lift after it goes straight up. Planned motions cannot run while tracking; ramps can.

untrack

untrack(robot: Optional[str] = None) -> Dict[str, Any]

Stop following the tracked part; the robot holds where it stands.

set_signal

set_signal(name: str, value: bool = True) -> Dict[str, Any]

Write an internal signal (declare it with scene.define_signal).

start

start(device: str) -> Dict[str, Any]

Start a conveyor.

stop

stop(device: str) -> Dict[str, Any]

Stop a conveyor.

set_speed

set_speed(device: str, speed: float) -> Dict[str, Any]

Rescale a conveyor's velocity to speed (m/s, direction kept).

move_to

move_to(
    device: str, position: float | str
) -> Dict[str, Any]

Command a linear axis to position (metres) — or a lift to a named stop (move_to("lift", "2F")); await with device_done. A lift's cargo (vehicles and loose parts in its capture zone) is fixed the moment this fires: the doors are closed.

goto

goto(device: str, station: str) -> Dict[str, Any]

Dispatch a vehicle to a named station (the AGV call); await arrival with device_done. Travel is uninterruptible: a second goto while the vehicle is still moving is a sequencing error.

advance

advance(device: str, distance: float) -> Dict[str, Any]

Indexed transfer: run a stopped conveyor for exactly distance metres along its velocity direction, then stop; await it with device_done. The final scan tick moves exactly the remainder, so the pitch is exact no matter how the scan period divides it — no more elapsed(pitch/v) plus one tick of slack.

immediately

immediately() -> Dict[str, Any]

Always true: fire the actions and move on.

done

done() -> Dict[str, Any]

Every motion/ramp started by this step has finished.

robot_done

robot_done(robot: str) -> Dict[str, Any]

The named robot has no motion/ramp in flight — whichever step started it. The idle test interlocks are built from.

elapsed

elapsed(seconds: float) -> Dict[str, Any]

On-delay timer (TON) from step entry.

signal

signal(name: str, value: bool = True) -> Dict[str, Any]

Level test of a signal (internal relay or sensor input).

rising

rising(name: str) -> Dict[str, Any]

Rising edge (-|P|-): the signal turned on since this program's previous scan — "the next part", not one already sitting on the beam. Startup state is not an edge.

falling

falling(name: str) -> Dict[str, Any]

Falling edge (-|N|-): the signal turned off since this program's previous scan.

device_done

device_done(device: str) -> Dict[str, Any]

A linear axis has reached its commanded position.

otherwise

otherwise() -> Dict[str, Any]

Always-true branch guard — the else arm of a select. Put it last: arms are tried in order, so it catches whatever the guards before it did not (and the exported script skips the wait entirely, since some arm is always ready).

all_of

all_of(*conditions: Dict[str, Any]) -> Dict[str, Any]

Series contacts (AND).

any_of

any_of(*conditions: Dict[str, Any]) -> Dict[str, Any]

Parallel contacts (OR).