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
goto(device, station) Dispatch a vehicle; await with device_done
crouch(robot, depth, lean=) Lower a parked walker's body, feet planted; crouch(robot, 0) stands it up

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, ramp, policy or crouch 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,
    check: bool = False,
) -> 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). A ramp is not planned: with check, its straight joint line is checked against the cell as it stands when the ramp starts — as a planned motion's is — and a contact fails the bake by name. A walk that has just stopped still sways for a moment: each point is then held where the body will be when the arm gets there. Leave it off for a gripper closing on its part.

policy

policy(
    name: str,
    robot: Optional[str] = None,
    group: Optional[str] = None,
    hz: float = 20.0,
    max_duration: float = 10.0,
) -> Dict[str, Any]

Hand the robot (or the arm group) to the registered policy name for this step: a learned or scripted controller passed to simulate_sequence(policies={name: ...}) (see botrail.rl.Policy), asked for a joint target hz times a second until it declares itself done or max_duration seconds pass. Its targets move the joints under the model's velocity limits; a collision with the scenery while it drives fails the bake. Await it with done().

crouch

crouch(
    robot: Optional[str],
    depth: float,
    *,
    lean: float = 0.0,
    duration: Optional[float] = None,
) -> Dict[str, Any]

Lower a parked walker's body while it stands: over duration s its body (the root link) goes from the crouch it is in to depth metres below its standing height, pitched nose-down by lean rad about its own lateral axis — the feet stay where they stand, each leg re-solved every scan tick by the walk's own leg IK. crouch(robot, 0) stands it back up. Await it with done().

robot names the mounted walker (None: the scene's sole robot). duration=None takes a pace: 0.3 m/s of depth and 0.6 rad/s of lean at the mean (the smoothstep peaks at 1.5 × that), at least 0.6 s.

The bake prices the whole crouch when the step fires — a leg that cannot keep its foot planted fails it by name — and checks the robot and what it holds against the cell every tick on the way down. Fired while the walk before it is still settling, it begins when the last foot lands. While the body is lowered the vehicle does not drive (a goto is refused until it stands up), and a planned motion waits for the crouch to finish; ramps may run alongside it (with check, each point is held where the body will be).

attach

attach(
    obj: str,
    link: Optional[str] = None,
    touch_links: Union[str, Iterable[str], NoneType] = None,
    robot: Optional[str] = None,
    group: 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); group names the arm of a dual-arm robot (link then defaults to that arm's tip). 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,
    group: 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. group names the arm that follows on a dual-arm robot: only its joints are spent, so the other arm may drive on — or carry the very part this one follows (a two-handed hold is one hand grasping and the other tracking).

untrack

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

Stop following the tracked part; the robot holds where it stands. group names the arm whose track to release when several follow.

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, group: Optional[str] = None
) -> Dict[str, Any]

The named robot has no motion/ramp in flight — whichever step started it. The idle test interlocks are built from. group narrows it to one arm of a dual-arm robot: nothing drives any of its joints.

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).